1.什么是日志?
日志是记录程序运行的轨迹,方便查找信息,快速定位问题。日志应用主要有下面三个原因:记录操作轨迹、监控系统运行状况、回溯系统故障。在开发时可以使用debug来跟踪代码,真正代码发布到了DAT 生产环境是没办法随便使用远程调试的。因此如果日志打的好,线上的问题很快便能定位,反之用不好则影响系统性能。
2.如何引入日志?
常用日志框架及区别
在使用日志前先来认识下常用的日志框架:
log4j、log4j2、Logging、commons-logging、slf4j、logback等,那么它们之间有什么关系,又该如何选择呢?
日志框架分为三大部分:日志门面、日志适配器、日志库。
- 门面设计模式是面向对象设计模式中的一种,日志框架采用的就是这种模式,类似jdbc的概念,既它只是一套设计规范,并不提供具体的实现。目的使使用者无论使用什么日志框架都无需关心其具体具体是哪个库负责打印。目前主流使用的框架有两种**:slf4j、commons-logging。**
- 日志库具体实现了日志的相关功能,主流日志库有:log4j、log4j2、log-jdk、logback。
- 日志适配器是针对没有实现门面模式接口的日志框架想使用类似于self4j这种门面模式框架时,需要单独引入一个适配器来解决接口不兼容的问题。或者在老的工程中,使用了多种日志框架,随着时间的推移,想将原来直接调用日志库的模式改成门面模式,此时可使用适配器来完成旧API到slf4j的路由。
日志级别详解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nzh8PY2g-1572703274878)(https://i.loli.net/2019/10/28/ZwWKcU7hvtxRGg2.png)]
所以,日志优先级别标准顺序为:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
使用方式
以springboot中的slf4j+logback为例
Spring boot默认支持的就是slf4j+logback的日志框架,想要定制日志策略只需要添加配置文件即可。spring推荐配置文件以-spring命名。此处命名为logback-spring.xml。
基本配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="./logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value=" %contextName %d{HH:mm:ss.SSS} [logid=%logid] [ip=%ip] [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<conversionRule conversionWord="ip" converterClass="io.renren.IPLogConfig" />
<conversionRule conversionWord="logid" converterClass="io.renren.LogIdConfig" />
<contextName>myLogBack</contextName>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%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>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<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>
<!-- 用户访问日志输出 -->
<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>
<!-- sql 输出配置 -->
<appender name="log.dao" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log-dao.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/log-dao.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 2天 -->
<maxHistory>2</maxHistory>
<!-- 日志最大文件大小10m -->
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="io.renren.config" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
<!--sql日志-->
<logger name="io.renren.dao" level="DEBUG">
<appender-ref ref="log.dao"/>
</logger>
</configuration>
- contextName:设置上下文名称,用来区分不同进程
- property 两个属性name 和value,name是变量名称,定义后可使用${}来使用。
- appender格式化日志输出节点,有俩个属性name和class,class用来指定哪种输出策略,常用就是控制台输出策略和文件输出策略。(控制台输出ConsoleAppender、输出到文件RollingFileAppender)设置文件大小限制和保留天数时冲突解决方案:https://blog.csdn.net/wujianmin577/article/details/68922545
- encoder 表示日志编码:%d{HH: mm:ss.SSS}输出时间、%thread——输出日志的进程名字、%-5level——日志级别,并且使用5个字符靠左对齐、%msg——日志消息、%n——平台的换行符
- logger 设置某一个包或者具体的某一个类的日志打印级别、以及指定。仅有一个name属性,一个可选的level和一个可选的addtivity属性。addtivity :是否向上级logger传递打印信息。默认是true
- conversionRule 可配置自定义class来输出到日志中.
3.使用时需要注意什么?
1.日志命名的规范
推荐日志的命名方式为:appName_logType_logName.log,logType是日志类型,推荐分类有stats、monitor、visit等;logName为日志描述。这种命名方式的好处是通过文件名就可以知道日志文件属于什么应用,什么类型。
2.日志的存储周期
在EasyCoding中推荐将日志文件至少保存15天,防止以周为频率发生的异常无法被发现。然后根据日志文件的重要程度,文件大小,磁盘空间来自行延长保存时间。
3.使用时注意
在使用时为了防止产生无用的字符串对象,建议使用条件判断 + 占位符形式来输出日志。例:
//使用条件判断形式
if(myLogger.isInfoEnabled()){
//使用占位符形式
myLogger.info("This logInfo id:{},and value:{}","木大木大木大","阿嘟嘟嘟嘟");
}
4.避免无效的日志输出
因为日志打印的顺序是由低到高先子级再父级,包名级别越高则logger的级别越高。在执行完子级日志后执行父级日志会重复输出,此时应在子级配置中设置additivity=“false”,例:
<!-- 系统模块日志级别控制 -->
<logger name="io.renren.config" level="info" additivity="false"/>
5.使用异步写入日志AsyncAppender
一般程序使用串行状态,要记录日志时必然会阻塞到程序的运行,这是一种损耗效率的方式,所以实际使用日志时使用异步的方式来记录,这样日志同主程序就会形成并行状态,不会影响程序运行。
AsyncAppender并不处理日志,只是将日志缓冲到一个BlockingQueue里面去,并在内部创建一个工作线程从队列头部获取日志,之后将获取的日志循环记录到附加的其他appender上去,从而达到不阻塞主线程的效果。因此AsynAppender仅仅充当事件转发器,必须引用另一个appender来写日志。
<appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold >0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref ="FILE_LOG"/>
</appender>
6.根据环境不同使用不同的日志策略
<!-- 开发环境输出到控制台 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</springProfile>
<!-- 生产环境输出到文件 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE_LOG" />
</root>
</springProfile>
在root节点外面套了一层springProfile ,指定了name属性的值,此时启用时有两种方式.
方式1:执行jar包时添加参数:
java -jar xxx.jar --spring.profiles.active=prod
方式2:在项目的application.properties配置文件中添加:
spring.profiles.active=prod
4.整合
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义一个将日志输出到控制台的appender,名称为STDOUT -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%contextName]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
</appender>
<!--定义一个将日志输出到文件的appender,名称为FILE_LOG -->
<appender name="FILE_LOG" class="ch.qos.logback.core.FileAppender">
<file>D:/test.log</file>
<append>true</append>
<encoder>
<pattern>[Eran]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
</appender>
<!-- 按时间滚动产生日志文件 -->
<appender name="ROL-FILE-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>D:/logs/test.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 只保留近七天的日志 -->
<maxHistory>7</maxHistory>
<!-- 用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[Eran]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
</appender>
<!-- 按时间和文件大小滚动产生日志文件 -->
<appender name="ROL-SIZE-FILE-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>D:/logs/test.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 单个文件的最大内存 -->
<maxFileSize>100MB</maxFileSize>
<!-- 只保留近七天的日志 -->
<maxHistory>7</maxHistory>
<!-- 用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[Eran]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
<!-- 只处理INFO级别以及之上的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<!-- 只处理INFO级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步写入日志 -->
<appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold >0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref ="FILE_LOG"/>
</appender>
<!-- 指定com.demo包下的日志打印级别为DEBUG,但是由于没有引用appender,所以该logger不会打印日志信息,日志信息向上传递 -->
<logger name="com.example" level="DEBUG"></logger>
<!-- 指定开发环境基础的日志输出级别为DEBUG,并且绑定了名为STDOUT的appender,表示将日志信息输出到控制台 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</springProfile>
<!-- 指定生产环境基础的日志输出级别为INFO,并且绑定了名为ASYNC的appender,表示将日志信息异步输出到文件 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC" />
</root>
</springProfile>
</configuration>
参考资源:
https://juejin.im/post/5c7e2445f265da2de71370f2
http://tengj.top/2017/04/05/springboot7/#%E5%A4%9A%E7%8E%AF%E5%A2%83%E6%97%A5%E5%BF%97%E8%BE%93%E5%87%BA
https://blog.csdn.net/wujianmin577/article/details/68922545
http://tengj.top/2017/02/28/springboot2/