作为一名长期bug程序员(也是很无奈呀),最重要的就是日志的输出了,才能在出现bug的情况下及时找到问题原因,这篇文章对日志做一个总结。
大家应该最熟悉的就是System.out.println();
学java的小伙伴人生中第一次写的代码大概就是System.out.println("hello world!");
但是对于一个简单的main
方法来说还可以,但是真正在项目中,用System.out.println();
却比较麻烦,比如代码修改好了怎么办?当然是删除System.out.println();
。那又改错了怎么办,再添加System.out.println();
,反复来这么几次,就觉得太麻烦了,有小伙伴说,那不删除不就行了么?System.out.println();
作为调试手段,用来向控制台输出,但是多了也是会影响程序效率的,程序发布后,这些打印的信息也很可能泄露程序的一些重要信息,所以要去掉,而且打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分。所以就用到了我们今天说的SLF4J
。
想要摸透SLF4J
。首先要把它的兄弟们也要了解清楚。
一、jdk logging
从jdk1.4起,JDK开始自带一套日志系统。JDK Logger最大的优点就是不需要任何类库的支持,只要有Java的运行环境就可以使用。看代码👇
public static void main(String[] args) {
Logger logger = Logger.getGlobal();
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
按照日志级别打印,控制台输出的,只有info之前的,之后的没有打印出来,原因是jdk
的logging
默认日志为info
,info
级别以下的,不会被打印出来,使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。
十二月 08, 2020 5:07:56 下午 com.centrin.report.controller.aa main
严重: severe
十二月 08, 2020 5:07:57 下午 com.centrin.report.controller.aa main
警告: warning
十二月 08, 2020 5:07:57 下午 com.centrin.report.controller.aa main
信息: info
二、Commons Logging
commons-logging是apache提供的一个通用的日志接口。用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的logging, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。当然,common-logging内部有一个Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j来使用。使用它的好处就是,代码依赖是common-logging而非log4j, 避免了和具体的日志方案直接耦合,在有必要时,可以更改日志实现的第三方库。
默认情况下,Commons Loggin
自动搜索并使用Log4j
(Log4j是另一个流行的日志系统),如果没有找到Log4j
,再使用JDK Logging
。
先在pom.xml
中导入commons-Logging
的依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
后端进行日志打印
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Testlogging {
public static Log log = LogFactory.getLog(Testlogging.class); //commons.logging
//commons.logging
public static void main(String[] args) {
log.debug("debug()...");
log.info("info()...");
log.error("error()...");
try {
} catch (Exception e) {
e.printStackTrace();
log.error("error()...", e);
}
}
}
如果只是依赖JCL,不依赖其它日志组件的话,会打印出info,error
,debug
是不打印的
[INFO ][2020-12-10 09:39:20][Testlogging-main] info()...
[ERROR][2020-12-10 09:39:20][Testlogging-main] error()...
Process finished with exit code 0
到这里,Commons Loggin
就已经完成了!!!
三、log4j
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
所以简单的来说,Log4j可以理解为一个通过配置文件进行配置的日志操作工具
第一步:首先在pom.xml中
引入log4j
的依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
第二步:log4j.properties
这个文件就是采用Log4j
操作包完成日志部分的操作配置。
那么如何让ssm框架知道这个配置并让他其作用呢?👇
在web.xml
插入以下代码块,这里就告诉了框架我们采用了Log4j
配置,配置文件位置是classpath
路径下的log4j.properties
文件。
<!--log4j配置文件开始-->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:config/log4j.properties</param-value>
</context-param>
<!--log4j配置文件结束-->
第三步:在resources
下添加log4j.properties
文件
### 配置根 ###
log4j.rootLogger = info,console ,fileAppender,dailyRollingFile,ROLLING_FILE
### 设置输出sql的级别,其中logger后面的内容全部为jar包中所包含的包名 ###
log4j.logger.org.apache=${log.level}
log4j.logger.java.sql.Connection=${log.level}
log4j.logger.java.sql.Statement=${log.level}
log4j.logger.java.sql.PreparedStatement=${log.level}
log4j.logger.java.sql.ResultSet=${log.level}
### 配置输出到控制台 ###
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.ConversionPattern = [%-5p][%d{yyyy-MM-dd HH:mm:ss}][%c-%M] %m%n
### 配置输出到文件 ###
log4j.appender.fileAppender = org.apache.log4j.FileAppender
log4j.appender.fileAppender.File = logs/log.log
log4j.appender.fileAppender.Append = true
log4j.appender.fileAppender.Threshold = DEBUG
log4j.appender.fileAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern = [%-5p][%d{yyyy-MM-dd HH:mm:ss}][%c-%M] %m%n
### 配置输出到文件,并且每天都创建一个文件 ###
log4j.appender.dailyRollingFile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyRollingFile.File = ${log.dir}/log.log
log4j.appender.dailyRollingFile.Append = true
log4j.appender.dailyRollingFile.Threshold = DEBUG
log4j.appender.dailyRollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.dailyRollingFile.layout.ConversionPattern = [%-5p][%d{yyyy-MM-dd HH:mm:ss}][%c-%M] %m%n
### 配置输出到文件,且大小到达指定尺寸的时候产生一个新的文件 ###
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.ROLLING_FILE.MaxFileSize=10KB
log4j.appender.ROLLING_FILE.MaxBackupIndex=1
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[%-5p][%d{yyyy-MM-dd HH:mm:ss}][%c-%M] %m%n
第四步,后端就可以打印日志了
import org.apache.log4j.Logger;
public class Testlogging {
public static Logger logger = Logger.getLogger(Testlogging.class); //log4j
//log4j
public static void main(String[] args) {
//使用默认的配置信息,不需要写log4j.properties
// BasicConfigurator.configure();
//设置日志输出级别为info,这将覆盖配置文件中设置的级别
// logger.setLevel(Level.INFO);
logger.info("info()...");
logger.error("error()...");
try {
int a=0;
int b= 1/0;//0不能为除数,所以会走catch
} catch (Exception e) {
e.printStackTrace();
logger.error("0不能为除数",e);
}
}
}
控制台打印效果
[INFO ][2020-12-10 10:20:42][Testlogging-main] info()...
[ERROR][2020-12-10 10:20:42][Testlogging-main] error()...
java.lang.ArithmeticException: / by zero
at Testlogging.main(Testlogging.java:18)
[ERROR][2020-12-10 10:20:42][Testlogging-main] 0不能为除数
java.lang.ArithmeticException: / by zero
at Testlogging.main(Testlogging.java:18)
到这里,log4j的配置就已经完成,就可以随意使用日志啦!!!
五,SLF4J+logback
SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现,它提供了Java中所有日志框架的简单抽象。因此,它使用户能够使用单个依赖项处理任何日志框架,例如:Log4j,Logback和JUL(java.util.logging)。可以在运行时/部署时迁移到所需的日志记录框架。
CekiGülcü创建了SLF4J作为Jakarta commons-logging框架的替代品。
- 使用SLF4J框架,可以在部署时迁移到所需的日志记录框架。
- Slf4J提供了对所有流行的日志框架的绑定,例如log4j,JUL,Simple logging和NOP。因此可以在部署时切换到任何这些流行的框架。无论使用哪种绑定,SLF4J都支持参数化日志记录消息。
- 无论使用哪种绑定,SLF4J都支持参数化日志记录消息。由于SLF4J将应用程序和日志记录框架分离,因此可以轻松编写独立于日志记录框架的应用程序。而无需担心用于编写应用程序的日志记录框架。
- SLF4J提供了一个简单的Java工具,称为迁移器。使用此工具,可以迁移现有项目,这些项目使用日志框架(如Jakarta Commons Logging(JCL)或log4j或Java.util.logging(JUL))到SLF4J。
第一步,在pom.xml
中加入SLF4J
和logback
的依赖
<!--SLF4J start-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.20</version>
</dependency>
// 这个jar包用log4j的时候写,如果用logback的话,要删掉, 否则出现jar包冲突警告👇
// Class path contains multiple SLF4J bindings.警告
<!-- <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.20</version>
</dependency>
<!--SLF4J end-->
<!-- LOGGING end -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<!-- LOGGING end -->
第二步,在resources
下面添加logback.xml
,直接复制即可
<?xml version="1.0" encoding="UTF-8"?>
<!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志 -->
<!-- scan 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 动态日志级别 -->
<jmxConfigurator />
<!-- 定义日志文件 输出位置 -->
<property name="log_dir" value="${log.dir}" />
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="30" />
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n
</pattern>
</encoder>
</appender>
<!-- ERROR级别日志 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender -->
<appender name="ERROR"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>ERROR</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志输出位置 可相对、和绝对路径 -->
<fileNamePattern>
${log_dir}/error/%d{yyyy-MM-dd}.log
</fileNamePattern>
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件假设设置每个月滚动,且<maxHistory>是6, 则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除 -->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n
</pattern>
</encoder>
</appender>
<!-- WARN级别日志 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>WARN</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志输出位置 可相对、和绝对路径 -->
<fileNamePattern>${log_dir}/warn/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO级别日志 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/info/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- DEBUG级别日志 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/debug/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE级别日志 appender -->
<appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/trace/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- ALL级别日志 appender -->
<appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%-5level %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- root级别 DEBUG -->
<root>
<!-- 打印debug级别日志及以上级别日志 -->
<level value="${log.level}" />
<!-- 控制台输出 -->
<appender-ref ref="console" />
<!-- 文件输出 -->
<appender-ref ref="ERROR" />
<appender-ref ref="WARN" />
<appender-ref ref="INFO" />
<appender-ref ref="ALL" />
<!-- <appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
<appender-ref ref="WARN" />
<appender-ref ref="DEBUG" />
<appender-ref ref="TRACE" /> -->
</root>
</configuration>
第三步,测试
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class aa {
public static Logger logger = LoggerFactory.getLogger(aa.class);
public static void main(String[] args) {
logger.info("info()...");
logger.error("error()...");
try {
int a=0;
int b= 1/0;//0不能为除数,所以会走catch
} catch (Exception e) {
e.printStackTrace();
logger.error("0不能为除数",e);
}
}
}
控制台输出
INFO 2020-12-10 13:30:14.767 [main] aa - info()...
ERROR 2020-12-10 13:30:14.770 [main] aa - error()...
java.lang.ArithmeticException: / by zero
at aa.main(aa.java:17)
ERROR 2020-12-10 13:30:14.773 [main] aa - 0不能为除数
java.lang.ArithmeticException: / by zero
at aa.main(aa.java:17)
到这里就全部完成啦
觉得不错的点个赞,希望更多的人可以看得到!笔芯