0. 概述

       在Java生态体系中,围绕着日志,有很多成熟的解决方案。关于日志输出,主要有两类工具。

    一类是日志框架,主要用来进行日志的输出的,比如输出到哪个文件,日志格式如何等。 LogBack Log4j Log4j2 java.util.logging
    另外一类是日志门面,主要一套通用的API,用来屏蔽各个日志框架之间的差异的。SLF4J commons-logging

  所以,对于Java工程师来说,关于日志工具的使用,最佳实践就是在应用中使用如Log4j + SLF4J 这样的组合来进行日志输出

1. log4j配置详解

参考地址:Springboot整合log4j2日志全解 

log4j2.xml配置想达到的效果:error输出到error的文件,info输出到info的文件。主要思想就是root的级别最低为all,然后分别在各个级别文件RollingFile中去配那些要那些不要顺序不能错,先配更高级别的,

首先要过滤更高级别的,将其NEUTRAL交给下一个高级别的过滤器处理, 不符合的日志级别,把不需要的DENY掉,然后再ACCEPT需要的日志级别,如下面的Filters

<Filters>
	   <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
	   <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
以下详细讲解log4j的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
  <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

  <!--变量配置-->
  <Properties>
    <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
    <!-- 定义日志存储的路径 -->
    <property name="FILE_PATH" value="更换为你的日志路径" />
    <property name="FILE_NAME" value="更换为你的项目名" />
    <!--这一堆property就相当于配了一堆变量,以怎样的格式输出到哪里,,等等,下面引用方便 -->
  </Properties>

  <appenders>

    <console name="Console" target="SYSTEM_OUT">
      <!--输出日志的格式-->
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
    </console>

    <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
    <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
      <PatternLayout pattern="${LOG_PATTERN}"/>
    </File>

    <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <Policies>
        <!--interval属性用来指定多久滚动一次,默认是1 hour-->
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
      <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
      <DefaultRolloverStrategy max="15"/>
    </RollingFile>

    <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <Policies>
        <!--interval属性用来指定多久滚动一次,默认是1 hour-->
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
      <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
      <DefaultRolloverStrategy max="15"/>
    </RollingFile>

    <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <Policies>
        <!--interval属性用来指定多久滚动一次,默认是1 hour-->
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
      <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
      <DefaultRolloverStrategy max="15"/>
    </RollingFile>

  </appenders>

  <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
  <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
  <loggers>
    <logger name="sysInfo" level="info" additivity="false">
      <appender-ref ref="InfoFile"/>
    </logger>
    <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
    <logger name="org.mybatis" level="info" additivity="false">
      <AppenderRef ref="Console"/>
    </logger>
    <!--监控系统信息-->
    <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger(root) 的appender里输出。-->
    <Logger name="org.springframework" level="info" additivity="false">
      <AppenderRef ref="Console"/>
    </Logger>
    <!--root这里配的appender意思是系统里面所有的info级别的日志都会打印到下面配的appender-->
    <!--如何实现分类打印日志呢:主要思想就是root的级别最低为all,然后分别在各个级别文件RollingFile中去配那些要那些不要
    顺序不能错,先配更高级别的,首先要过滤更高级别的,将其NEUTRAL交给下一个高级别的过滤器处理, 不符合的日志级别,把不需要的DENY掉,然后再ACCEPT需要的日志级别-->
    <root level="info">
      <appender-ref ref="Console"/>
      <!--<appender-ref ref="Filelog"/>
      <appender-ref ref="RollingFileInfo"/>
      <appender-ref ref="RollingFileWarn"/>
      <appender-ref ref="RollingFileError"/>-->
    </root>
  </loggers>

</configuration>

2. Spring Aop 打印日志

import java.util.ArrayList;
import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;

//pom.xml添加依赖的时候注意排除springBoot自动加载的logging
@Aspect
@Configuration
public class AspectController {

    //  private final  static Logger logger = LoggerFactory.getLogger(AspectController.class);
    //这种方法需要把log4j配置文件的root配成也打在文件,而不只是打在控制台。这种打出来的效果就是会把所有的info信息打印,有些不是我们想要的,推荐用下面的方法
    Log infoLog = LogFactory.getLog("sysInfo");
    //sysInfo是log4j里面最后配的logger,获取指定的logger,自己下面想打什么就只打什么

    @Pointcut("execution(* XXX.controller.*.*(..))")
    public void printMsg(){
        // implementation ignored
    }

    @Around("printMsg()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
//        String queryString = request.getQueryString();
        ArrayList<Object> argList = new ArrayList<>();
//        String queryString = JSONObject.toJSONString(pjp.getArgs());这种方法获取json格式的请求参数,有时会有异常,说你不是异步的请求咋的,pjp.getArgs()返回的数组中携带有Request或者Response对象,导致序列化异常。
//pjp.getArgs()是获取你的接口所有的参数,你的controller里面写了啥参数,这儿就获取到什么参数。如果controller里面需要HttpServletRequest,那这里就要过滤因为不能序列化
        for (Object arg : pjp.getArgs()) { 
            // request/response无法使用toJSON 
            if (arg instanceof HttpServletRequest) { 
                argList.add("request"); 
            } else if (arg instanceof HttpServletResponse) { 
                argList.add("response"); 
            } else { 
        argList.add(arg);
            } 
        } 
//        logger.info("request start, url: {}, method: {}, uri: {}, params: {}", url, method, uri, queryString);
        infoLog.info("url: " + url + ", " + "method: " + method + ", " + "uri: " + uri);
        infoLog.info("params: " + argList);
        Object result = pjp.proceed();
//          logger.info("response data: {}" , result);
        infoLog.info("response data: " + result);
        return result;
    }
}

 补充:

在logback.xml中做修改,先按照天进行分割,然后按照文件大小进行分割,文件的大小为50M,在按照总大小为10GB的时候进行删除

springboot 设置日志打印线程名 spring aop打印日志_sed