JAVA日志框架
- 常见日志框架
- 日志级别
- 阿里日志规约
- 什么时候打印日志
- 配置文件
- log4j.properties
- logback-spring.xml
- logback的默认配置
 
- 导入依赖
- 日志使用方式,引入slf4j的API
- 输出用户日志
常见日志框架
日志框架:Log4j 、Logback 。
 日志门面:Slf4j。
 Logback 是 Slf4j 的原生实现框架,同样也是出自 Log4j 一个人之手,但拥有比 log4j 更多的优点、特性和更做强的性能,现在基本都用来代替 log4j 成为主流。
SLF4J只是日志标准,还是需要实际日志框架。日志框架本身未实现SLF4J API,所以需要有个前置转换。例如log4j需要slf4j-log4j2的依赖
 Logback本身就按SLF4J API标准实现,所以无需绑定模块做转换。
 
日志级别
| 日志级别 | 描述 | 
|---|---|
| OFF | 关闭:最高级别,不输出日志。 | 
| FATAL | 致命:输出非常严重的可能会导致应用程序终止的错误。 | 
| ERROR | 错误:输出错误,但应用还能继续运行。 | 
| WARN | 警告:输出可能潜在的危险状况。 | 
| INFO | 信息:输出应用运行过程的详细信息。 | 
| DEBUG | 调试:输出更细致的对调试应用有用的信息。 | 
| TRACE | 跟踪:输出更细致的程序运行轨迹。 | 
| ALL | 所有:输出所有级别信息。 | 
阿里日志规约
-  【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 
 (SLF4J、JCL–Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和
 各个类的日志处理方式统一。
 说明:日志框架(SLF4J、JCL–Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)
 使用 SLF4J:
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 private static final Logger logger = LoggerFactory.getLogger(Test.class);
 使用 JCL:
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 private static final Log log = LogFactory.getLog(Test.class);
-  【强制】所有日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。对于 
 当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下,
 过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
 说明:以 mppserver 应用为例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史日志
 名称为 mppserver.log.2016-08-01
-  【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: 
 appName_logType_logName.log。logType:日志类型,如 stats/monitor/access 等;logName:日志描
 Java 开发手册
 31/57
 述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查
 找。
 说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系
 统进行及时监控。
 正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log
-  【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。 
 说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅
 是替换动作,可以有效提升性能。
 正例:logger.debug(“Processing trade with id: {} and symbol: {}”, id, symbol);
-  【强制】对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。 
 说明:虽然在 debug(参数)的方法体内第一行代码 isDisabled(Level.DEBUG_INT)为真时(Slf4j 的常见实现
 Log4j 和 Logback),就直接 return,但是参数可能会进行字符串拼接运算。此外,如果 debug(getName())
 这种参数内有 getName()方法调用,无谓浪费方法调用的开销。
 正例:
 // 如果判断为真,那么可以输出 trace 和 debug 级别的日志
 if (logger.isDebugEnabled()) {
 logger.debug(“Current ID is: {} and name is: {}”, id, getName());
 }
-  【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。 
 正例:
-  【强制】生产环境禁止直接使用 System.out 或 System.err 输出日志或使用 
 e.printStackTrace()打印异常堆栈。
 说明:标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易
 造成文件大小超过操作系统大小限制。
 public void printStackTrace() {
 printStackTrace(System.err);
 }它其实也是利用 System.err 输出到了 tomcat 控制台。
-  【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 
 关键字 throws 往上抛出。
 正例:logger.error(各类参数或者对象 toString() + “_” + e.getMessage(), e);
-  【强制】日志打印时禁止直接用 JSON 工具将对象转换成 String。 
 说明:如果对象里某些 get 方法被重写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流
 程的执行。
 正例:打印日志时仅打印出业务相关属性值或者调用其对象的 toString()方法。
 10.【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用
 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑
 爆,并记得及时删除这些观察日志。
 Java 开发手册
 32/57
 说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些
 日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
 11.【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适
 从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
 说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息。
 12.【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用
 中文描述即可,否则容易产生歧义。
 说明:国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息
什么时候打印日志
1、当你遇到问题的时候,只能通过debug功能来确定问题,你应该考虑打日志,良好的系统,是可以通过日志进行问题定为的。
 2、当你碰到if…else 或者 switch这样的分支时,要在分支的首行打印日志,用来确定进入了哪个分支
 3、经常以功能为核心进行开发,你应该在提交代码前,可以确定通过日志可以看到整个流程
 4、不要在循环中打印日志。
 5、接口的入参,出参打印日志。
 6、禁止在生产环境中开启debug日志。
 7、error 异常或者重要的错误信息
 8、warn 日志级别来记录用户输入参数错误的情况,例如else用户名不存在,记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑
 爆,并记得及时删除这些观察日志
 9、info,记录传参数信息,进入那个条件分支
配置文件
log4j.properties
###配置日志根Logger
log4j.rootLogger=DEBUG,stdout,D,E,I,file
#ERROR 为严重错误 主要是程序的错误
#WARN 为一般警告,比如session丢失
#INFO 为一般要显示的信息,比如登录登出
#DEBUG 为程序的调试信息
log4j.additivity.org.apache=true
###配置日志信息输出目的地Appender
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
### 输出到日志文件 ###
#org.apache.log4j.ConsoleAppender(控制台)
#org.apache.log4j.FileAppender(文件)
#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
#log4j.appender.error.Target=System.out
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = /data/log/bill.debug.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.DatePattern='.'yyyy-MM-dd-HH-mm
# '.'yyyy-MM:每月
# '.'yyyy-ww:每周
# '.'yyyy-MM-dd:每天
# '.'yyyy-MM-dd-a:每天两次
# '.'yyyy-MM-dd-HH:每小时
# '.'yyyy-MM-dd-HH-mm:每分钟
#log4j.appender.file.MaxFileSize=1MB
###滚动文件的最大数
#log4j.appender.file.MaxBackupIndex=8
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-30c{1}) [TxId : %X{PtxId} , SpanId : %X{PspanId}] [ET:%X{ENV_TYPE},AN:%X{APP_NAME},SN:%X{SERVICE_NAME},CN:%X{CONTAINER_NAME},CI:%X{CONTAINER_IP}] %m%n
log4j.appender.file.Threshold=DEBUG
###将消息增加到指定文件中,false指将消息覆盖指定的文件内容
log4j.appender.file.append=true
###日志的保存位置
#log4j.appender.file.File=E:/logs/file-debug-log.log
log4j.appender.file.File=E:/logs/debug-debug.log
###每天产生一个日志文件
#log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.maxFileSize=100
#log4j.appender.file.maxBackupIndex=5
#log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-30c{1}) [TxId : %X{PtxId} , SpanId : %X{PspanId}] [ET:%X{ENV_TYPE},AN:%X{APP_NAME},SN:%X{SERVICE_NAME},CN:%X{CONTAINER_NAME},CI:%X{CONTAINER_IP}] %m%n
#log4j.appender.file.Threshold=DEBUG
#log4j.appender.file.append=true
#log4j.appender.file.File=E:/logs/debug-log.log
 
 
 
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
###配置日志信息的格式(布局)
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#org.apache.log4j.HTMLLayout(以HTML表格形式布局)
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
#org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
#org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
log4j.appender.D.layout = org.apache.log4j.PatternLayout
###配置日志打印的格式格式化日志信息
#%m   输出代码中指定的消息
#%p   输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
#%r   输出自应用启动到输出该log信息耗费的毫秒数
#%c   输出所属的类目,通常就是所在类的全名
#%t   输出产生该日志事件的线程名
#%n   输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
#%d   输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss , SSS}
#%l   输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数
 
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
 
### 输出到日志文件 ###
log4j.appender.I = org.apache.log4j.DailyRollingFileAppender
log4j.appender.I.File = /data/log/bill.info.log
log4j.appender.I.Append = true
log4j.appender.I.Threshold = INFO
log4j.appender.I.layout = org.apache.log4j.PatternLayout
log4j.appender.I.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%n
 
### 保存异常信息到单独文件 ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = /data/log/bill.error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern =%-d{yyyy-MM-dd HH:mm:ss} [%t:%r] - [%p] %m%
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
     <!--取yml文件值-->
    <springProperty scope="context" name="LOG_PATH" source="logger.file.path"/>
    <springProperty scope="context" name="LOG_FILE_SIZE" source="logger.file.size"/>
    <springProperty scope="context" name="LOG_FILE_LEVEL" source="logger.file.level"/>
       <!--取系统环境变量-->
    <property name="logName" value="${HOSTNAME}" />
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} traceLogId[%X{traceLogId}] dstTraceId[%X{dstTraceId}] %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <property name="FILE_LOG_PATTERN"
              value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} traceLogId[%X{traceLogId}] dstTraceId[%X{dstTraceId}]: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!-- Console 输出设置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                <charset>utf-8</charset>
            </pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/aipbcp_oauth_${logName}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/aipbcp_oauth_${logName}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--            <maxHistory>30</maxHistory>-->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>${LOG_FILE_SIZE}</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>
    <!-- mybatis日志打印-->
    <logger name="org.apache.ibatis" level="DEBUG"/>
    <logger name="java.sql" level="DEBUG"/>
    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>
日志配置文件概念
 appender:追加日志类,ConsoleAppender,RollingFileAppender
 rollingPolicy:appender的方法,回滚属性,可以设置日志保存时间,日志文件名,日志大小
 encoder:appender的方法,日志的编码方式,日志输出的格式
 filter:appender的方法,可以过滤不同级别的日志,日志分级别输出
root:可以用来注册appender,例如:STDOUT,error_file,info_file,可以将不同级别的日志输出到文件控制台
logback的默认配置
springboot2.x默认记录控制台日志。
logging:
  config: classpath:logback-spring.xml
  level:
  	com.fang: debug
导入依赖
一般启动器都含有spring-boot-starter. -------->. spring-boot-starter-logging. ---------> logback,因此不需要再手动的导入依赖
 在Spring Boot 2.x及以上版本中,默认使用logback作为日志记录框架。
当logback和log4j同时存在于类路径上时,Spring Boot会自动屏蔽log4j,并使用logback进行日志记录。这是因为Spring Boot会检测到logback的存在并优先选择使用它。这样可以避免潜在的冲突和兼容性问题
日志使用方式,引入slf4j的API
方式一:
private final Logger logger = LoggerFactory.getLogger(RequestInterceptor.class);
方式二:使用lombok的注解
@Slf4j
输出用户日志
1、每个类都可以配置自己的logger
private static final Logger logger = LoggerFactory.getLogger("sys-user");
或者
private static final Logger logger = LoggerFactory.getLogger(this.CLASS);
2、每个logger都可以设置自己的输出日志级别
<!--系统用户操作日志-->
   <logger name="sys-user" level="info">
       <appender-ref ref="sys-user"/>
   </logger>
   
<logger name="com.ruoyi" level="debug" />
这里制定logger的输出级别为info,即使在代码中logger.debug,也不会输出对应的日志
 3、root logger也可以设置自己的级别,root logger的输出级别优先于logger生效
<root level="info">
    <appender-ref ref="file_info" />
    <appender-ref ref="file_error" />
</root>
4、给logger绑定appender,
<!-- 用户访问日志输出  -->
   <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
   <file>${log.path}/sys-user.log</file>
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <!-- 按天回滚 daily -->
           <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
           <!-- 日志最大的历史 60天 -->
           <maxHistory>60</maxHistory>
       </rollingPolicy>
       <encoder>
           <pattern>${log.pattern}</pattern>
       </encoder>
   </appender>
5、root logger可以统一绑定appender输出不同级别日志,但是需要给appender设置filter,因为输出debug日志的同时会输出info级别的
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${log.path}/sys-error.log</file>
       <!-- 循环政策:基于时间创建日志文件 -->
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <!-- 日志文件名格式 -->
           <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 日志最大的历史 60天 -->
      <maxHistory>60</maxHistory>
       </rollingPolicy>
       <encoder>
           <pattern>${log.pattern}</pattern>
       </encoder>
       <filter class="ch.qos.logback.classic.filter.LevelFilter">
           <!-- 过滤的级别 -->
           <level>ERROR</level>
      <!-- 匹配时的操作:接收(记录) -->
           <onMatch>ACCEPT</onMatch>
      <!-- 不匹配时的操作:拒绝(不记录) -->
           <onMismatch>DENY</onMismatch>
       </filter>
   </appender>



















