日志

为什么要写日志

举个简单的例子,你把项目写完后,交给客户,然后客户运行的时候出现问题,很紧急,你要解决这个问题,怎么办?

这就是写日志的原因,在程序出现问题的时候,日志可以第一时间定位到问题的所在,方便及时排查、解决问题。

在系统开发中,日志是很重要的一个环节,日志写得好对于我们开发调试,线上问题追踪等都有很大的帮助。但记日志并不是简单的输出信息,需要考虑很多问题,比如日志输出的速度,日志输出对于系统内存,CPU的影响等,为此,出现了很多日志框架,以帮助开发者解决这些问题。

为什么项目里不让使用System.out

  1. 出错了不知道哪个类哪个方法第几行有问题
  2. System.out.println(“”) 很费性能
  3. 你写了以后过几天你就找不见写在哪了
public static void main(String[] args) {

	long start = System.currentTimeMillis();
	for(int i = 0; i < 1000000; i ++){
	    // 注释掉试试
		System.out.println("测试System.out性能");
	}
	long end = System.currentTimeMillis();
	System.out.println("共耗时"+(1.0*(end - start)/1000) + "s");
	
}

不注释System.out共耗时6.83s
注释System.out共耗时0.004s

6.83 / 0.004 = 1707.5
差距够大吧

怎么写日志

假如你开发完项目,交给其他人,他们遇到问题,找你解决,你远在千里,解决的时候需要什么信息,你写日志的时候就写什么信息。就这么简单。

写日志用什么

  1. j.u.l (即java.util.logging)
  2. log4j
  3. commons-logging
  4. logback
  5. SLF4J

这些都可以,但是推荐使用 SLF4J + logback

Java日志

SLF4J+logback

<!-- SLF4J -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>

<!-- Logback -->
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-core</artifactId>
	<version>1.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.1.3</version>
</dependency>

<!-- lombok -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.16.18</version>
</dependency>

logback.xml
放src目录下或者src/main/resources目录下就行

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="60 seconds"  debug="false">
<!--  scan="true" scanPeriod="60 seconds" debug="false" -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="./logs" />
    <property name="LOG_LEVEL" value="INFO" />
    <property name="appName" value="appName"></property>

    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] [%line] - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件 -->
    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/${appName}.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%logger{50}] [%line] - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 日志输出级别 -->
    <root level="${LOG_LEVEL}">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

常用写法

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class LogTest {
    
    private static final Logger log = LoggerFactory.getLogger(CrawlerUmetrip.class);
    
    public static void main(String[] args) {
        log.debug("调试信息");
        log.info("详细信息");
        log.warn("警告信息");
        log.error("错误信息");
    }
    
}

更简洁的写法

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class LogTest {
    
    
    public static void main(String[] args) {
        log.debug("调试信息");
        log.info("详细信息");
        log.warn("警告信息");
        log.error("错误信息");
    }
    
}

SLF4J+log4j

<!-- SLF4J -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.21</version>
</dependency>


<!-- lombok -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.16.18</version>
</dependency>
### log4j配置 ###
log4j.rootLogger = DEBUG, console, D, E
log4j.additivity.org.apache = false

### 输出信息到控制台 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
#log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout = com.custom.CustomPatternLayout
log4j.appender.console.layout.ConversionPattern =  %d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n

### 输出DEBUG 级别以上的日志到=E://logs/log.log ###
#log4j.appender.D = org.apache.log4j.RollingFileAppender
log4j.appender.D = com.custom.TestRollingFileAppender
log4j.appender.D.File = E://logs/test/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG 
log4j.appender.D.MaxFileSize = 10MB 
#log4j.appender.D.MaxBackupIndex = 10 #MaxBackupIndex在TestRollingFileAppender没用,可以不写。
log4j.appender.D.layout = com.custom.CustomPatternLayout
log4j.appender.D.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.RollingFileAppender
log4j.appender.E.File = E://logs/test/error.log 
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR 
log4j.appender.E.MaxFileSize = 10MB 
log4j.appender.E.MaxBackupIndex = 10
log4j.appender.E.layout = com.custom.CustomPatternLayout
log4j.appender.E.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n

log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' >
  
  	<!-- 输出到控制台 -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<!-- 设置布局 -->
		<layout class="com.custom.CustomPatternLayout"><!-- org.apache.log4j.PatternLayout -->
			<!-- 输出格式/转换格式 -->
		 	<param name="ConversionPattern"  value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n" />
		</layout>
		<!--过滤器设置输出的级别-->
		<filter class="org.apache.log4j.varia.LevelRangeFilter">
			<!-- 设置最低级别 -->
			<param name="levelMin" value="debug" />
			<!-- 设置最高级别 -->
			<param name="levelMax" value="fatal" />
			<param name="AcceptOnMatch" value="true" />
		</filter>
	</appender>

	<!-- 设置输出到文件  设置滚动方式 -->
	<appender name="fileD" class="com.custom.TestRollingFileAppender"> <!-- org.apache.log4j.RollingFileAppender -->
		<!-- 设置日志输出文件名 --> 
		<param name="File" value="E:/logs/test4/log.log" />
		<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
		<param name="Append" value="true" />
		<!-- 设置滚动时最多保存几个日志文件 -->
		<param name="MaxBackupIndex" value="10" />
		<!-- 设置文件大小达到多大时滚动-->
		<param name="MaxFileSize" value="10MB"/>
		<!-- 设置布局 -->
		<layout class="com.custom.CustomPatternLayout"><!-- org.apache.log4j.PatternLayout -->
			<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n" />
		</layout>
	</appender>
 
 	<!-- 设置输出到文件 设置滚动方式  -->
	<appender name="fileE" class="org.apache.log4j.RollingFileAppender">
		<!-- 设置日志输出文件名 --> 
		<param name="File" value="E:/logs/test4/error.log" />
		<!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
		<param name="Append" value="true" />
		<!-- 设置滚动时最多保存几个日志文件 -->
		<param name="MaxBackupIndex" value="10" />
		<!-- 设置文件大小达到多大时滚动-->
		<param name="MaxFileSize" value="10MB"/>
		<!-- 设置布局 -->
		<layout class="com.custom.CustomPatternLayout"><!-- org.apache.log4j.PatternLayout -->
			<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n" />
		</layout>
	</appender>
	
	<!--  -->
 	<!-- 设置输出到文件 设置滚动方式  -->
	<appender name="fileF" class="org.apache.log4j.DailyRollingFileAppender">
		<param name="File" value="E:/logs/test4/fatal.log" /> 
		<param name="DatePattern" value="'.'yyyy-MM-dd'.log'" /> 
		<layout class="org.apache.log4j.PatternLayout">
		 <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} -(%T)- [%t]   %p:  -%m%n" />
		</layout> 
	</appender>
  
	

	<!-- 设置根logger-->
	<root>
		<priority value ="debug"/>
		<!-- 设置输出到哪,和appender name相对应 -->
		<appender-ref ref="console"/>
		<appender-ref ref="fileD"/>	
		<appender-ref ref="fileE"/>
	</root>
	
</log4j:configuration>

springboot项目日志输出到控制台

在application.properties文件里添加以下两行:

logging.file=account.log
logging.config= logback-spring.xml的绝对路径

再把logback-spring.xml文件放到对应的路径下,通过slf4j+logback来管理,在配置里配置不往控制台输出日志。
默认的logback-spring.xml在 classpath/logback-spring.xml

References

[1] 【Java深入学习系列】之那些年我们用过的日志框架
[2] Java日志框架(Commons-logging,SLF4j,Log4j,Logback)
[3] 封装SLF4J/Log4j,不再处处定义logger变量
[4] 日志工具现状调研
[5] logback layoutInsteadOfEncoder
[6] Logback源码赏析-日志按时间滚动(切割)
[7] features/log
[8] mavenrepo/index
[9] boot-features-logging.html
[10] boot-features-logging
[11] spring howto-logging
[12] log4j/1.2/faq.html
[13] Spring Boot干货系列:(七)默认日志logback配置解析
[14] 面试官:Logback如何配置,才能提升TPS?
[15] logback异步日志配置