3️⃣Logback组件
🍀(1)appender
输出源,一个日志可以后好几个输出源
🍀(2)encoder
一个appender有一个encoder,负责将一个event事件转换成一组byte数组,并将转换后的字节数据输出到文件中。
Encoder负责把事件转换为字节数组,并把字节数组写到合适的输出流。因此,encoder可以控制在什么时候、把什么样的字节数组写入到其拥有者维护的输出流中。Encoder接口有两个实现类,LayoutWrappingEncoder与PatternLayoutEncoder。
注意:在logback 0.9.19 版之前没有 encoder。
在之前的版本里,多数 appender 依靠 layout 来把事件转换成字符串并用 java.io.Writer 把字符串输出。在之前的版本里,用户需要在 FileAppender里嵌入一个 PatternLayout。
🍀(3)layout
格式化数据将event事件转化为字符串,解析的过程。
🍀(4)filter 过滤器
LevelFilter levelFilter = new LevelFilter(); levelFilter.setOnMatch(FilterReply.DENY); levelFilter.setLevel(Level.WARN); levelFilter.start(); ca.addFilter(levelFilter);
%-5level
%d{yyyy-MM-dd HH:mm:ss.SSS} 日期
%c 类的完整名称
%M 为method
%L 为行号
%thread 线程名称
%m或者%msg 为信息
%n换行
4️⃣Logback配置
🍀(1)基本配置信息
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %c [%thread] %-5level %msg%n"/> <!-- Appender: 设置日志信息的去向,常用的有以下几个 ch.qos.logback.core.ConsoleAppender (控制台) ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件) ch.qos.logback.core.FileAppender (文件) --> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.err</target> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。 <loger>仅有一个name属性,一个可选的level和一个可选的addtivity属性 name: 用来指定受此logger约束的某一个包或者具体的某一个类。 level: 用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 如果未设置此属性,那么当前logger将会继承上级的级别。 additivity: 是否向上级loger传递打印信息。默认是true。 <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个 logger --> <!-- 也是<logger>元素,但是它是根logger。默认debug level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个 logger。 --> <root level="ALL"> <appender-ref ref="console"/> </root> </configuration>
🍀(2)FileAppender配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 自定义属性 可以通过${name}进行引用--> <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/> <!-- 日志输出格式: %d{pattern}日期 %m或者%msg为信息 %M为method %L为行号 %c类的完整名称 %thread线程名称 %n换行 %-5level --> <!-- 日志文件存放目录 --> <property name="log_dir" value="d:/logs"></property> <!--控制台输出appender对象--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.err</target> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!--日志文件输出appender对象--> <appender name="file" class="ch.qos.logback.core.FileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!--日志输出路径--> <file>${log_dir}/logback.log</file> </appender> <!-- 生成html格式appender对象 --> <appender name="htmlFile" class="ch.qos.logback.core.FileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="ch.qos.logback.classic.html.HTMLLayout"> <pattern>%level%d{yyyy-MM-dd HH:mm:ss}%c%M%L%thread%m</pattern> </layout> </encoder> <!--日志输出路径--> <file>${log_dir}/logback.html</file> </appender> <!--RootLogger对象--> <root level="all"> <appender-ref ref="console"/> <appender-ref ref="file"/> <appender-ref ref="htmlFile"/> </root> </configuration>
🍀(3)RollingFileAppender配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 自定义属性 可以通过${name}进行引用--> <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/> <!-- 日志输出格式: %d{pattern}日期 %m或者%msg为信息 %M为method %L为行号 %c类的完整名称 %thread线程名称 %n换行 %-5level --> <!-- 日志文件存放目录 --> <property name="log_dir" value="d:/logs"></property> <!--控制台输出appender对象--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.err</target> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- 日志文件拆分和归档的appender对象--> <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!--日志输出路径--> <file>${log_dir}/roll_logback.log</file> <!--指定日志文件拆分和压缩规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--通过指定压缩文件名称,来确定分割文件方式--> <fileNamePattern>${log_dir}/rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern> <!--文件拆分大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> </appender> <!--RootLogger对象--> <root level="all"> <appender-ref ref="console"/> <appender-ref ref="rollFile"/> </root> </configuration>
🍀(4)Filter和异步日志配置
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 自定义属性 可以通过${name}进行引用--> <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c %M %L [%thread] %m %n"/> <!-- 日志输出格式: %d{pattern}日期 %m或者%msg为信息 %M为method %L为行号 %c类的完整名称 %thread线程名称 %n换行 %-5level --> <!-- 日志文件存放目录 --> <property name="log_dir" value="d:/logs/"></property> <!--控制台输出appender对象--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <!--输出流对象 默认 System.out 改为 System.err--> <target>System.err</target> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> </appender> <!-- 日志文件拆分和归档的appender对象--> <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--日志格式配置--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${pattern}</pattern> </encoder> <!--日志输出路径--> <file>${log_dir}roll_logback.log</file> <!--指定日志文件拆分和压缩规则--> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!--通过指定压缩文件名称,来确定分割文件方式--> <fileNamePattern>${log_dir}rolling.%d{yyyy-MMdd}.log%i.gz</fileNamePattern> <!--文件拆分大小--> <maxFileSize>1MB</maxFileSize> </rollingPolicy> <!--filter配置--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <!--设置拦截日志级别--> <level>error</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!--异步日志--> <appender name="async" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="rollFile"/> </appender> <!--RootLogger对象--> <root level="all"> <appender-ref ref="console"/> <appender-ref ref="async"/> </root> <!--自定义logger additivity表示是否从 rootLogger继承配置--> <logger name="com.ydlclass" level="debug" additivity="false"> <appender-ref ref="console"/> </logger> </configuration>
5️⃣Logback-access的使用
在server.xml里的< host>标签下加上如下所示就可以了。
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
下面咱们逐一分析各个参数:
className | 想配置访问日志?这就必须得写成这样。 |
directory | 这个东西是日志文件放置的目录,在tomcat下面有个logs文件夹,那里面是专门放置日志文件的,当然你也可以修改,我就给改成了D:\ |
prefix | 这个是日志文件的名称前缀,我的日志名称为localhost_access_log.2007-09-22.txt,前面的前缀就是这个localhost_access_log |
suffix | 这就是后缀名啦,可以改成别的 |
pattern | 这个是最主要的参数了,具体的咱们下面讲,这个参数的内容比较丰富。 |
resolveHosts | 如果这个值是true的话,tomcat会将这个服务器IP地址通过DNS转换为主机名,如果是false,就直接写服务器IP地址啦 |
logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使 用logback-access模块来替换tomcat的访问日志。
(1)将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下;
(2)修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:
<Valve className="ch.qos.logback.access.tomcat.LogbackValve"/>
(3)logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- always a good activate OnConsoleStatusListener --> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/> <property name="LOG_DIR" value="${catalina.base}/logs"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/access.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern> </rollingPolicy> <encoder> <!-- 访问日志的格式 --> <pattern>combined</pattern> </encoder> </appender> <appender-ref ref="FILE"/> </configuration>
%h %l %u %user %date "%r" %s %b
官方配置: https://logback.qos.ch/access.html#configuration
六、 Log4j2日志框架
前已经有三个门面了,其实不管是哪里都是江湖,都想写一个门面,一统江湖,所以log42出了提供日志实现以外,也拥有一套自己的独立的门面。
目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。
1️⃣Log4j2入门
🍀(1)使用log4j-api做门面
(1)添加依赖
<!-- Log4j2 门面API--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency> <!-- Log4j2 日志实现 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency>
(2)JAVA代码
public class TestLog4j2 { private static final Logger LOGGER = LogManager.getLogger(TestLog4j2.class); @Test public void testLog(){ LOGGER.fatal("fatal"); LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); } }
(3)结果
🍀(2)使用slf4j做门面
使用slf4j作为日志的门面,使用log4j2作为日志的实现。
(1)添加依赖
<!-- Log4j2 门面API--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency> <!-- Log4j2 日志实现 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency> <!--使用slf4j作为日志的门面,使用log4j2来记录日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!--为slf4j绑定日志实现 log4j2的适配器 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.12.1</version> </dependency>
(2)JAVA代码
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(TestLog4j2.class); @Test public void testSlf4j(){ LOG.error("error"); LOG.warn("warn"); LOG.debug("debug"); LOG.info("info"); LOG.trace("trace"); }
(3)结果
我们看到log4j2的默认日志级别好像是error。
2️⃣Log4j2配置
🍀默认配置
DefaultConfiguration类中提供的默认配置将设置,
通过debug可以在LoggerContext类中发现
private volatile Configuration configuration = new DefaultConfiguration();
可以看到默认的root日志的layout
我们也能看到他的日志级别:
我们能从默认配置类中看到一些默认的配置:
protected void setToDefault() { // LOG4J2-1176 facilitate memory leak investigation setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode())); final Layout<? extends Serializable> layout = PatternLayout.newBuilder() .withPattern(DefaultConfiguration.DEFAULT_PATTERN) .withConfiguration(this) .build(); final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout); appender.start(); addAppender(appender); final LoggerConfig rootLoggerConfig = getRootLogger(); rootLoggerConfig.addAppender(appender, null, null); final Level defaultLevel = Level.ERROR; final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, defaultLevel.name()); final Level level = Level.valueOf(levelName); rootLoggerConfig.setLevel(level != null ? level : defaultLevel); }
🍀自定义配置文件位置
log4j2默认在classpath下查找配置文件,可以修改配置文件的位置。在非web项目中:
public static void main(String[] args) throws IOException { File file = new File("D:/log4j2.xml"); BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); final ConfigurationSource source = new ConfigurationSource(in); Configurator.initialize(null, source); Logger logger = LogManager.getLogger("mylog"); }
如果是web项目,在web.xml中添加:
<context-param> <param-name>log4jConfiguration</param-name> <param-value>/WEB-INF/conf/log4j2.xml</param-value> </context-param> <listener> <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class> </listener>
log4j2默认加载classpath下的 log4j2.xml 文件中的配置。事实上log4j2可以通过 XML、JSON、YAML 或properties格式进行配置:https://logging.apache.org/log4j/2.x/manual/configuration.html
如果找不到配置文件,Log4j 将提供默认配置。DefaultConfiguration 类中提供的默认配置将设置:
%d{HH:mm:ss.SSS} ,表示输出到毫秒的时间
%t,输出当前线程名称
%-5level,输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
%logger,输出logger名称,因为Root Logger没有名称,所以没有输出
%msg,日志文本
%n,换行
其他常用的占位符有:
%F,输出所在的类文件名,如Client.java
%L,输出行号
%M,输出所在方法名
%l,输出语句所在的行数, 包括类名、方法名、文件名、行数
private void reconfigure(final URI configURI) { Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY); final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}", contextName, configURI, this, cl); final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); if (instance == null) { LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl); } else { setConfiguration(instance); /* * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) { * old.stop(); } */ final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource()); LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}", contextName, location, this, cl); } }
ConfigurationFactory:
for (final ConfigurationFactory factory : getFactories()) { final String[] types = factory.getSupportedTypes(); if (types != null) { for (final String type : types) { if (type.equals(ALL_TYPES)) { final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); if (config != null) { return config; } } } } }
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="warn" monitorInterval="5"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L - -- %m%n" /> </Console> <File name="file" fileName="${LOG_HOME}/myfile.log"> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" /> </File> <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log"> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" /> </RandomAccessFile> <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="D:/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyyMM-dd-HH-mm}-%i.log"> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" /> <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" /> <Policies> <OnStartupTriggeringPolicy /> <SizeBasedTriggeringPolicy size="10 MB" /> <TimeBasedTriggeringPolicy /> </Policies> <DefaultRolloverStrategy max="30" /> </RollingFile> <RollingRandomAccessFile name="MyFile" fileName="${LOG_HOME}/${FILE_NAME}.log" filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> <Policies> <TimeBasedTriggeringPolicy interval="1" /> <SizeBasedTriggeringPolicy size="10 MB" /> </Policies> <DefaultRolloverStrategy max="20" /> </RollingRandomAccessFile> </Appenders> <Loggers> <Logger name="mylog" level="trace" additivity="false"> <AppenderRef ref="MyFile" /> </Logger> <Root level="error"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
注意根节点增加了一个monitorInterval属性,含义是每隔300秒重新读取配置文件,可以不重启应用的情况下修改配置,还是很好用的功能。
RollingRandomAccessFile的属性:
fileName 指定当前日志文件的位置和文件名称
filePattern 指定当发生Rolling时,文件的转移和重命名规则
SizeBasedTriggeringPolicy 指定当文件体积大于size指定的值时,触发Rolling
DefaultRolloverStrategy 指定最多保存的文件个数
TimeBasedTriggeringPolicy 这个配置需要和filePattern结合使用,注意filePattern中配置的文件重命名规则是${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i,最小的时间粒度是mm,即分钟。
TimeBasedTriggeringPolicy指定的size是1,结合起来就是每1分钟生成一个新文件。如果改成%d{yyyy-MM-dd
HH},最小粒度为小时,则每一个小时生成一个文件。
3️⃣Log4j2异步日志
log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用 log4j2的异步日志。
🍀同步日志
🍀异步日志
Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应前面我们说的Appender组件和Logger组件。
注意:配置异步日志需要添加依赖
<!--异步日志依赖--> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.3.4</version> </dependency>
(1)AsyncAppender方式
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="warn"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <File name="file" fileName="${LOG_HOME}/myfile.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> </File> <Async name="Async"> <AppenderRef ref="file"/> </Async> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="Async"/> </Root> </Loggers> </Configuration>
(2)AsyncLogger方式
AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的 更快。你可以有两种选择:全局异步和混合异步。
全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加一个log4j2.component.properties 配置。
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <properties> <property name="LOG_HOME">D:/logs</property> </properties> <Appenders> <File name="file" fileName="${LOG_HOME}/myfile.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> </File> <Async name="Async"> <AppenderRef ref="file"/> </Async> </Appenders> <Loggers> <AsyncLogger name="com.ydlclass" level="trace" includeLocation="false" additivity="false"> <AppenderRef ref="file"/> </AsyncLogger> <Root level="info" includeLocation="true"> <AppenderRef ref="file"/> </Root> </Loggers> </Configuration>
如上配置: com.ydlclass 日志是异步的,root日志是同步的。
使用异步日志需要注意的问题:
如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和AsyncAppender一致,降至最低。
设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。
for (int i = 0; i < 1000000; i++) { LOGGER.fatal("fatal"); } long end = System.currentTimeMillis(); System.out.println(end - start); 2970
七、怎么打印日志
🍀基本格式
必须使用参数化信息的方式:
logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);
不要进行字符串拼接,那样会产生很多String对象,占用空间,影响性能。反例(不要这么做):
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
使用[]进行参数变量隔离,如有参数变量,应该写成如下写法:
logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol)
这样的格式写法,可读性更好,对于排查问题更有帮助。
不同级别的使用如下:
🍀ERROR,影响到程序正常运行、当前请求正常运行的异常情况
打开配置文件失败
所有第三方对接的异常(包括第三方返回错误码)
所有影响功能使用的异常,包括:SQLException和除了业务异常之外的所有异常(RuntimeException和Exception)
不应该出现的情况,比如要使用阿里云传图片,但是未响应
如果有Throwable信息,需要记录完成的堆栈信息
log.error("获取用户[{}]的用户信息时出错",userName,e);
说明,如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理:
反例(不要这么做):
try{ .... }catch(Exception ex){ String errorMessage=String.format("Error while reading information of user [%s]",userName); logger.error(errorMessage,ex); throw new UserServiceException(errorMessage,ex); }
🍀WARN,不应该出现但是不影响程序、当前请求正常运行的异常情况
有容错机制的时候出现的错误情况
找不到配置文件,但是系统能自动创建配置文件
即将接近临界值的时候,例如:缓存池占用达到警告线,业务异常的记录,比如:用户锁定异常
🍀INFO,系统运行信息
Service方法中对于系统/业务状态的变更
主要逻辑中的分步骤:1,初始化什么 2、加载什么
外部接口部分
客户端请求参数(REST/WS)
调用第三方时的调用参数和调用结果
对于复杂的业务逻辑,需要进行日志打点,以及埋点记录,比如电商系统中的下订单逻辑,以及OrderAction操作(业务状态变更)
调用其他第三方服务时,所有的出参和入参是必须要记录的(因为你很难追溯第三方模块发生的问题)
说明:并不是所有的service都进行出入口打点记录,单一、简单service是没有意义的(job除外,job需要记录开始和结束,)。
反例(不要这么做):
public List listByBaseType(Integer baseTypeId) { log.info("开始查询基地"); BaseExample ex=new BaseExample(); BaseExample.Criteria ctr = ex.createCriteria(); ctr.andIsDeleteEqualTo(IsDelete.USE.getValue()); Optionals.doIfPresent(baseTypeId, ctr::andBaseTypeIdEqualTo); log.info("查询基地结束"); return baseRepository.selectByExample(ex); }
🍀DEBUG,可以填写所有的想知道的相关信息(但不代表可以随便写,debug信息要有意义,最好有相关参数)
生产环境需要关闭DEBUG信息
如果在生产情况下需要开启DEBUG,需要使用开关进行管理,不能一直开启
说明:如果代码中出现以下代码,可以进行优化:
获取用户基本薪资
获取用户休假情况
计算用户应得薪资
logger.debug("开始获取员工[{}] [{}]年基本薪资",employee,year); logger.debug("获取员工[{}] [{}]年的基本薪资为[{}]",employee,year,basicSalary); logger.debug("开始获取员工[{}] [{}]年[{}]月休假情况",employee,year,month); logger.debug("员工[{}][{}]年[{}]月年假/病假/事假为[{}]/[{}]/[{}]",employee,year,month,annualLeaveDays,sickLeaveDays,noPayLeaveDays); logger.debug("开始计算员工[{}][{}]年[{}]月应得薪资",employee,year,month); logger.debug("员工[{}] [{}]年[{}]月应得薪资为[{}]",employee,year,month,actualSalary);
🍀TRACE,特别详细的系统运行完成信息,业务代码中,不要使用.(除非有特殊用意,否则请使用DEBUG级别替代)
规范示例说明:
@Override @Transactional public void createUserAndBindMobile(@NotBlank String mobile, @NotNull User user) throws CreateConflictException{ boolean debug = log.isDebugEnabled(); if(debug){ log.debug("开始创建用户并绑定手机号. args[mobile=[{}],user=[{}]]", mobile, LogObjects.toString(user)); } try { user.setCreateTime(new Date()); user.setUpdateTime(new Date()); userRepository.insertSelective(user); if(debug){ log.debug("创建用户信息成功. insertedUser=[{}]",LogObjects.toString(user)); } UserMobileRelationship relationship = new UserMobileRelationship(); relationship.setMobile(mobile); relationship.setOpenId(user.getOpenId()); relationship.setCreateTime(new Date()); relationship.setUpdateTime(new Date()); userMobileRelationshipRepository.insertOnDuplicateKey(relationship); if(debug){ log.debug("绑定手机成功. relationship=[{}]",LogObjects.toString(relationship)); } log.info("创建用户并绑定手机号. userId=[{}],openId=[{}],mobile[{}]",user.getId(),user.getOpenId(),mobile); // 如果考虑安全,手机号记得脱敏 }catch(DuplicateKeyException e){ log.info("创建用户并绑定手机号失败,已存在相同的用户. openId=[{}],mobile=[{}]",user.getOpenId(),mobile); throw new CreateConflictException("创建用户发生冲突, openid=[%s]",user.getOpenId()); } }