- 日志框架浩瀚如海,种类众多,但是大致可分为门面日志(接口日志)和实现类日志(接口实现类)
- 门面日志中最为出名的有Common Logging ,SLF4J
- 日志实现框架最出名的有Log4J, 后来为了改进升级,有两个著名的日志实现替代框架Log4j2 以及Logback.
- Log4j2 和 Logback 的抉择,Spring Boot 默认使用的也是Logback日志。
- 推荐优先选logback
Apache Commons Logging(JCL) 提供了一个Log接口,旨在实现轻量级和独立的其他日志工具包的抽象
Simple Logging Facade for Java(SLF4J)用作各种日志框架(例如java.util.logging,logback,log4j)的简单外观或抽象,允许最终用户在部署时插入所需的日志记录框架。
- j.u.l (java.util.logging)
JDK1.4 之后自带的
Log4J
2015年8月5日,测井服务项目管理委员会宣布Log4j 1.x已达到使用寿命,建议用户使用Log4j 1升级到Apache Log4j 2
- Apache Log4j 2
Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些固有问题.
- LogBack
- Logback旨在作为流行的log4j项目的后续版本
- 目前,logback分为三个模块:logback-core,logback-classic和logback-access
- logback-core模块为其他两个模块奠定了基础。
- logback-classic模块可以被同化为log4j的显着改进版本。此外,logback-classic本身实现了SLF4J API,因此您可以在logback和其他日志框架(如log4j或java.util.logging(JUL))之间来回切换。
- logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。请注意,您可以在logback-core之上轻松构建自己的模块
Simple Logging Facade for Java(SLF4J) 可以作为以上四种日志的日志门面。
如果想使用SLF4J 和java.util.logging 集成,那么只需要添加如下依赖即可
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-jdk14artifactId>
<version>1.7.26version>
dependency>
注意:
- 如上配置除了会在classpath 添加
slf4j-jdk14-1.7.26.jar
外- 由于Maven传递依赖的特性,也会自动引入
slf4j-api-1.7.26.jar
- 如果手动再次添加了这个依赖,当然也不会有问题,只要需要注意兼容性和版本冲突问题
SLF4J 和 log4j 集成只需要添加如下依赖即可
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.26version>
dependency>
注意:
- 如上配置classpath除了会加入
slf4j-log4j12-1.7.26.jar
外- 由于Maven传递依赖的特性,还会引入
slf4j-api-1.7.26.jar
和log4j-1.2.17.jar
- 如果手动再次添加了这俩,当然也不会有问题,只要需要注意兼容性和版本冲突问题
SLF4J 和Logback集成只需要添加一个依赖即可
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
注意:
- 如上配置classpath 除了会添加
logback-classic-1.2.3.jar
外- 由于Maven传递依赖的特性,也会自动引入
slf4j-api-1.7.26.jar
和logback-core-1.2.3.jar
- 如果手动再次添加了这俩,当然也不会有问题,只要需要注意兼容性和版本冲突问题
Spring Boot 项目和Logback 集成
如果是Spring Boot 项目和Logback 集成,则只需要添加如下依赖即可
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
注意:
- 但是实际开发的时候,你可能会发现我们往往不需要手动添加spring-boot-starter-logging 依赖就可以直接使用。
- 那是因为我们一般默认添加了如下依赖:
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency>
- 由于Maven依赖传递性,使用的spring-boot-starter-test 传递依赖了
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-bootartifactId> <version>2.1.6.RELEASEversion> <scope>compilescope> dependency>
- 而这个依赖传递依赖了
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-loggingartifactId> <version>2.1.6.RELEASEversion> <scope>compilescope> dependency>
- 最后这个依赖又传递了如下三个依赖:
<dependency> <groupId>ch.qos.logbackgroupId> <artifactId>logback-classicartifactId> <version>1.2.3version> <scope>compilescope> dependency> <dependency> <groupId>org.apache.logging.log4jgroupId> <artifactId>log4j-to-slf4jartifactId> <version>2.11.2version> <scope>compilescope> dependency> <dependency> <groupId>org.slf4jgroupId> <artifactId>jul-to-slf4jartifactId> <version>1.7.26version> <scope>compilescope> dependency>
Logback 查找XML配置顺序
- 首先classpath 路径下寻找 logback-test.xml
- 如果没有找到,寻找logback.groovy
- 如果也没找到,寻找logback.xml
- 如果还没找到加载默认配置
<configuration debug="true" scan="true" scanPeriod="30 seconds" packagingData="false">
<property scope="context" name="APP_NAME" value="myApp"/>
<property scope="context" name="LOG_FILE_PATH" value="/opt/applog/${APP_NAME}/log" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- %cyan(%logger{36}) --- %c:%L:%n%m%npattern>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE_PATH}/${APP_NAME}.logfile>
<append>trueappend>
<immediateFlush>trueimmediateFlush>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- %logger{36} --- %c:%L:%n%m%npattern>
encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/%d{yyyy-MM-dd,aux}/${APP_NAME}-%d{yyyy-MM-dd,UTC}.%i.logfileNamePattern>
<maxFileSize>100MBmaxFileSize>
<maxHistory>30maxHistory>
<totalSizeCap>20GBtotalSizeCap>
<cleanHistoryOnStart>falsecleanHistoryOnStart>
rollingPolicy>
appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
root>
<logger name="com.xingyun" level="INFO" additivity="true"/>
<logger name="com.xingyun.dao" level="DEBUG" additivity="true"/>
configuration>
接下来讲讲这些配置的含义。
这个节点有四个属性可以配置:
- debug="true"
- 1.配置文件被找到
- 2.配置文件是格式良好的XML
如果找到配置文件但格式不正确,则logback将检测错误情况并自动在控制台上打印其内部状态
Java代码测试打印方法如下:// 假设已经添加了 SLF4J 和Logback 依赖 LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); // 打印Logback 状态信息 StatusPrinter.print(lc);
- scan=“true”: Logback-classic可以扫描其配置文件中的更改,并在配置文件更改时自动重新配置。
- scanPeriod=“30 seconds”:
默认情况下,将每分钟扫描一次配置文件以进行更改,这里我们改成了30秒检测一次。 我们可以通过设置元素的scanPeriod属性来指定不同的扫描周期。 可以以milliseconds
,seconds
,minutes
或者hours
为单位指定值- packagingData=“true”: 从1.1.4版开始,默认情况下禁用打包数据,一般不要打开.
上面的configuration节点配置debug=true 等价于如下方式配置控制台监听,两者任选其一即可
<configuration scan="true" scanPeriod="30 seconds" packagingData="false"> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /> configuration>
- 在独立的Java应用程序中,向配置文件添加指令是确保在JVM退出之前允许完成任何正在进行的压缩任务的简单方法。
在Web服务器中的应用程序中,将自动安装webShutdownHook
,使指令非常冗余且不必要。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能: Logback 日志使用测试
* 作者: 星云
*/
@RestController
public class LoggerController {
private static final Logger LOGGER=LoggerFactory.getLogger(LoggerController.class);
@GetMapping("/test1.do")
public String test1(){
for (int i = 0; i <1000 ; i++) {
LOGGER.info("Current message From main Thread");
}
return "success";
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能: Logback 日志使用测试
* 作者: 星云
*/
@Slf4j
@RestController
public class LoggerController {
@GetMapping("/test1.do")
public String test1(){
for (int i = 0; i <1000 ; i++) {
log.info("Current message From main Thread");
}
return "success";
}
}
注意: 如上操作后,日志会输出到控制台,并且输出到 /opt/applog/myApp/log/myApp.log
如果日志日期超过一天或单个文件超过100MB,那么就归档到
- /opt/applog/myApp/log/2019-08-20/myApp-2019-08-20.0.log
- /opt/applog/myApp/log/2019-08-20/myApp-2019-08-20.1.log
- /opt/applog/myApp/log/2019-08-20/myApp-2019-08-20.2.log
- /opt/applog/myApp/log/2019-08-20/myApp-2019-08-20.3.log
- /opt/applog/myApp/log/2019-08-20/myApp-2019-08-20.4.log
- /opt/applog/myApp/log/2019-08-20/myApp-2019-08-20.5.log
在实际项目中可能会有这样一种需求,需要根据线程分隔到不同的日志文件夹。
项目中原来使用的log4j , 如今我想升级为logback
那么如何操作呢?
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import ch.qos.logback.core.util.FileSize;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.charset.Charset;
/**
* 线程日志工具类
*/
public class LogbackThreadLogger extends RollingFileAppender{
/**
* 禁用构造方法
* */
private LogbackThreadLogger(){}
// App Name
public final static String APP_NAME="myApp";
//通用配置
//日志存放路径
public final static String LOG_FILE_BASE_PATH="/opt/applog/"+APP_NAME+"/log/";
//配置日志输出格式
public static final String fileLogLayout="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- %logger{36} --- %c:%L:%n%m%n";
//配置日志编码
public static final String charSetName="UTF-8";
//是否自动追加
public static final Boolean append=true;
//是否立即刷新
public static final Boolean immediateFlush=true;
//单个文件最大值
public static final String maxFileSize="100MB";
//单个文件最大值
public static final FileSize maxFileSizeValue=FileSize.valueOf(maxFileSize);
//保存30天
public static final Integer maxHistory=30;
//日志保存最大容量
public static final String totalSizeCap="20GB";
//日志保存最大容量
public static final FileSize totalSizeCapValue=FileSize.valueOf(totalSizeCap);
//启动清理
public static final Boolean cleanHistoryOnStart=false;
/**
* 打印线程日志
* @param folderName
* @param className
* @return
*/
@SuppressWarnings("unused")
public static Logger getLogger(String folderName,String className){
//创建日志实例对象
Logger logger = (Logger) LoggerFactory.getLogger(className);
//获取日志上下文
LoggerContext loggerContext = logger.getLoggerContext();
//创建文件追加器
RollingFileAppender rollingFileAppender = new RollingFileAppender();
//配置上下文
rollingFileAppender.setContext(loggerContext);
//配置文本追加 如果设置为false则覆盖
rollingFileAppender.setAppend(append);
//配置立即追加
rollingFileAppender.setImmediateFlush(immediateFlush);
//配置日志路径和名称 opt/applog/log/spring/log/test/myThread.log
rollingFileAppender.setFile(LOG_FILE_BASE_PATH+folderName+ File.separator+className+".log");
//配置输出格式和输出字符集
rollingFileAppender.setEncoder(configPatternLayoutEncoder(loggerContext));
//配置滚动策略 opt/applog/log/spring/log/2019-08-20/test/myThread.log
rollingFileAppender.setRollingPolicy(configSizeAndTimeBasedRollingPolicy(loggerContext,rollingFileAppender,folderName,className));
//启动文件追加器
rollingFileAppender.start();
//如果为false,线程日志不会重复追加到主日志文件中,如果为 true,则会输出到主日志和线程文件夹中
logger.setAdditive(false);
//添加日志追加器
logger.addAppender(rollingFileAppender);
return logger;
}
/**
* 日志输出格式和字符集
* @param loggerContext
* @return
*/
private static PatternLayoutEncoder configPatternLayoutEncoder(LoggerContext loggerContext){
PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder();
//配置日志上下文
patternLayoutEncoder.setContext(loggerContext);
//配置日志输出格式 layout
patternLayoutEncoder.setPattern(fileLogLayout);
//配置日志输出字符集
patternLayoutEncoder.setCharset(Charset.forName(charSetName));
//启动encoder
patternLayoutEncoder.start();
return patternLayoutEncoder;
}
/**
* 日志滚动策略
* @param loggerContext
* @param rollingFileAppender
* @param folderName
* @param className
* @return
*/
private static SizeAndTimeBasedRollingPolicy configSizeAndTimeBasedRollingPolicy(LoggerContext loggerContext,RollingFileAppender rollingFileAppender,String folderName,String className){
//配置大小和日期时间分割策略
SizeAndTimeBasedRollingPolicy sizeAndTimeBasedRollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
//配置上下文
sizeAndTimeBasedRollingPolicy.setContext(loggerContext);
//配置日志追加器
sizeAndTimeBasedRollingPolicy.setParent(rollingFileAppender);
//配置日志滚动策略规则 /opt/applog/myApp/log/2019-08-20/test/myThread.log
sizeAndTimeBasedRollingPolicy.setFileNamePattern(LOG_FILE_BASE_PATH+"%d{yyyy-MM-dd,aux}"+File.separator+folderName+File.separator+className+"-%d{yyyy-MM-dd,UTC}.%i.log");
//设置单个文件大小
sizeAndTimeBasedRollingPolicy.setMaxFileSize(maxFileSizeValue);
//保存多少天
sizeAndTimeBasedRollingPolicy.setMaxHistory(maxHistory);
//磁盘最大日志大小
sizeAndTimeBasedRollingPolicy.setTotalSizeCap(totalSizeCapValue);
//启动时候清理日志
sizeAndTimeBasedRollingPolicy.setCleanHistoryOnStart(cleanHistoryOnStart);
//开启滚动策略
sizeAndTimeBasedRollingPolicy.start();
return sizeAndTimeBasedRollingPolicy;
}
}
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能: Logback 日志使用测试
* 作者: 星云
*/
@RestController
public class LoggerController {
@GetMapping("/test1.do")
public String test1(){
Logger logger= LogbackThreadLogger.getLogger("test","myThread");
for (int i = 0; i <1000 ; i++) {
logger.info("current Message From myThread");
}
return "success";
}
}
注意: 如上操作后,日志会输出到控制台,并且输出到 /opt/applog/myApp/log/test/myThread.log
如果日志日期超过一天或单个文件超过100MB,那么就归档到
- /opt/applog/myApp/log/2019-08-20/test/myThread-2019-08-20.0.log
- /opt/applog/myApp/log/2019-08-20/test/myThread-2019-08-20.1.log
- /opt/applog/myApp/log/2019-08-20/test/myThread-2019-08-20.2.log
- /opt/applog/myApp/log/2019-08-20/test/myThread-2019-08-20.2.log
- /opt/applog/myApp/log/2019-08-20/test/myThread-2019-08-20.2.log
- /opt/applog/myApp/log/2019-08-20/test/myThread-2019-08-20.2.log
合并测试:
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 功能: Logback 日志使用测试
* 作者: 星云
*/
@Slf4j
@RestController
public class LoggerController {
@GetMapping("/test1.do")
public String test1(){
Logger logger= LogbackThreadLogger.getLogger("test","myThread");
for (int i = 0; i <6 ; i++) {
logger.info("current Message From myThread");
log.info("current Message From Main Thread");
}
return "success";
}
}
最终测试效果如下所示:
2019-08-20 文件夹内容如下:
test 文件夹内容如下:
- logback 官方手册
- 为什么阿里巴巴禁止工程师直接使用日志系统(Log4j、Logback)中的 API
- logback自定义logger的java代码