参考:
引入依赖
<project>
<properties>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.1.11</logback.version>
</properties>
<dependencies>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
</project>
<!-- Appender: 设置日志信息的去向,常用的有以下几个
ch.qos.logback.core.ConsoleAppender (控制台)
ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
ch.qos.logback.core.FileAppender (文件)
--> System.out ${pattern}
${LOG_DIR}/info.log ${LOGDIR}/info%d{yyyy-MM-dd}.log.%i.gz ${maxFileSize} ${maxHistory} ${pattern} INFO ACCEPT DENY
${LOG_DIR}/error.log ${LOGDIR}/error%d{yyyy-MM-dd}.log.%i.gz ${maxFileSize} ${maxHistory} ${pattern} ERROR ACCEPT DENY
${LOG_DIR}/debug.log ${LOG_DIR}/debug.%d{yyyy-MM-dd}.log ${pattern} DEBUG ACCEPT DENY
<!--
-->
```
打印日志
public class LogGuideApplication {
private static Logger logger = LoggerFactory.getLogger(LogGuideApplication.class);
public static void main(String[] args) {
logger.trace("trace ...");
logger.debug("debug ...");
logger.info("info ...");
logger.warn("warn ...");
logger.error("error ...");
}
}
logback.xml 的结构: ```
configuration
|- property
|- appender
|- logger
|- root
**第1种情况:只配置root**
```xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
</encoder>
</appender>
<!-- 将 >= INFO 级别的日志信息,交给 STDOUT 处理-->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
结果:控制台只会打印大于等于 INFO 级别的日志信息
第2种情况:配置一个logger
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
</encoder>
</appender>
<logger name="liu.log.guide">
<appender-ref ref="STDOUT"/>
</logger>
<!-- 将 >= INFO 级别的日志信息,交给 STDOUT 处理-->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
logger 有3个属性:
上面的情况会发生如下过程:
所以最明显的结果就是:日志级别大于等于INFO的日志都会被记录2次,这2次分别来自于logger和 root
扩展 如果将上面的logback.xml改为如下这样,会发生什么呢?
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n</pattern>
</encoder>
</appender>
<logger name="liu.log.guide" level="INFO">
<appender-ref ref="STDOUT"/>
</logger>
<root level="WARN">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
参考 https://www.cnblogs.com/cb0327/archive/2004/01/13/5770794.html
本身logback提供了AppenderBase和UnsynchronizedAppenderBase两个抽象类(同步和异步),所以我们自定义时,只需要看实际业务继承其中的一个即可。
自定义appender类
public class MyDbAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
Layout<ILoggingEvent> layout;
//自定义配置
String printString;
@Override
public void start(){
//这里可以做些初始化判断 比如layout不能为null ,
if(layout == null) {
addWarn("Layout was not defined");
}
//或者写入数据库 或者redis时 初始化连接等等
super.start();
}
@Override
public void stop() {
//释放相关资源,如数据库连接,redis线程池等等
System.out.println("logback-stop方法被调用");
if(!isStarted()) {
return;
}
super.stop();
}
@Override
public void append(ILoggingEvent event) {
if (event == null || !isStarted()){
return;
}
// 此处自定义实现输出
// 获取输出值:event.getFormattedMessage()
// System.out.print(event.getFormattedMessage());
// 格式化输出
System.out.print(printString + ":" + layout.doLayout(event));
}
// 省略 setter 和 getter
}
<appender-ref ref="MyDbAppender"/>
```在 springboot-log-guide 中会用spring 继承logback将日志写入到数据库中
参考:https://www.cnblogs.com/zimublog/p/12923786.html
@Component
public class MyLogDbAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
@Autowired
private BusinessLogService businessLogService;
@PostConstruct
public void init() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
ThresholdFilter filter = new ThresholdFilter();
filter.setLevel("ERROR");
filter.setContext(context);
filter.start();
this.addFilter(filter);
this.setContext(context);
// 定义 logger (和在 logback.xml 中的配置是对应的)
Logger logger = context.getLogger("liu.log.guide");
logger.setLevel(Level.DEBUG);
logger.setAdditive(false);
logger.addAppender(MyLogDbAppender.this);
// 定义要有下面这句
super.start();
}
@Override
public void append(ILoggingEvent event) {
if (event == null || !isStarted()) {
return;
}
// TODO 根据实际情况替换
BusinessLog businessLog = new BusinessLog();
businessLog.setLevel(event.getLevel().levelStr);
businessLog.setClassPath(event.getLoggerName());
businessLog.setMessage(event.getMessage());
businessLog.setCreateBy(1L);
businessLog.setCreateDate(new Date());
try {
businessLogService.add(businessLog);
} catch (Exception e) {
this.addError("上报错误日志失败:" + e.getMessage());
}
}
}
参考:https://blog.csdn.net/drift_away/article/details/7403658
log4j.rootLogger=DEBUG,CONSOLE,A1,im
#DEBUG,CONSOLE,FILE,ROLLING_FILE,MAIL,DATABASE
###################
# Console Appender
###################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n
#####################
# File Appender
#####################
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=file.log
log4j.appender.FILE.Append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# Use this layout for LogFactor 5 analysis
参考:别再乱打日志了,这份 Java 日志规范,应有尽有,建议收藏!!
slf4j logger 占位符非常简单,并没有类型的区别
String name = "中国";
logger.info("我的名字叫{}", name);
参考:Java日志框架中需要判断log.isDebugEnabled()吗?
很多代码中都用到了 isDebugEnabled ,如下:
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
看源码可知,isDebugEnabled() 用来判断当前日志级别是否低于debug,比如info级别高于debug,则不会打印日志。
public boolean isDebugEnabled(Marker marker) {
final FilterReply decision = callTurboFilters(marker, Level.DEBUG);
if (decision == FilterReply.NEUTRAL) {
return effectiveLevelInt <= Level.DEBUG_INT;
} else {
throw new IllegalStateException("Unknown FilterReply value: " + decision);
}
}
为什么要加if判断? 如果不加if判断,即使最终不需要debug日志输出,编译时也会自动执行字符串拼接,多少会消耗一部分性能。 而加上if判断之后,就会跳过if条件内的代码
//代码1
logger.debug("这是一条debug日志:" + msg);
//代码2
if(logger.isDebugEnabled()) {
logger.debug("这是一条debug日志:" + msg);
}
网上有部分人说,不需要加if判断,使用占位符即可。
logger.debug("这是一条debug日志:{}", msg);
但在某些情况下,占位符依然不能避免性能消耗。
logger.debug("这是一条debug日志:{}", obj.toString());
如上面这段代码,最终不会打印debug日志,但是会执行 obj.toString()
==原则一:如果打印字符串常量,不需要isDebugEnabled
==
比较下面两段代码:
//代码1
logger.debug("hello, world");
//代码2
if(logger.isDebugEnabled()){
logger.debug("hello, world");
}
因为打印的日志是字面常量,没有计算逻辑。两段代码的性能是几乎一样的。添加isDebugEnabled
反而会导致额外的代码。
==原则二:如果有参数,且参数只是字符串常量或计算简单,使用占位符==
考虑如下代码,debug
方法包含了参数user.getName()
。虽然执行debug
方法时,会计算user.getName()
,但只是一个简单的get方法,没有复杂计算,这时候,也可以不添加isDebugEnabled
。
logger.debug("hello, {}", user.getName());
==原则三:如果有参数,且参数计算复杂,添加isDebugEnabled
==
logger.debug("order price: {}", calculatePrice());
假设calculatePrice
方法需要经过复杂计算。那么就应该添加isDebugEnabled
判断,使用如下的代码:
if(logger.isDebugEnabled()){
logger.debug("order price: {}", calculatePrice());
}