参考:
* [工作3年啦,你竟然连Java日志体系都没闹懂?说不过去~ ](https://mp.weixin.qq.com/s/8gTsdstUqHdyjLvRkv7nqw)
* https://www.cnblogs.com/cb0327/p/5759441.html
## logger 使用步骤
1. 引入依赖
```xml
1.7.25
1.1.11
org.slf4j
slf4j-api
${slf4j.version}
ch.qos.logback
logback-core
${logback.version}
ch.qos.logback
logback-classic
${logback.version}
ch.qos.logback
logback-access
${logback.version}
```
2. 配置 logback.xml
> 实际项目中,可修改下面模板使用
```xml
System.out
${pattern}
${LOG_DIR}/info.log
${LOG_DIR}/info_%d{yyyy-MM-dd}.log.%i.gz
${maxFileSize}
${maxHistory}
${pattern}
INFO
ACCEPT
DENY
${LOG_DIR}/error.log
${LOG_DIR}/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
```
3. 打印日志
```java
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 ...");
}
}
```
## logger 配置
logback.xml 的结构:
```
configuration
|- property
|- appender
|- logger
|- root
```
**第1种情况:只配置root**
```xml
%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n
```
结果:控制台只会打印大于等于 INFO 级别的日志信息
**第2种情况:配置一个logger**
```xml
%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n
```
logger 有3个属性:
* name 日志范围,包名或者类名。root没有name属性,默认根目录。日志范围会优先匹配最精确的包名。
比如说上面这个例子,在没有name="liu.log.guide"的logger之前,是由root记录所有类包日志的。有了logger之后,包"liu.log.guide"里的日志会交给最精确匹配的logger来记录。
* level 日志级别,默认继承上级的日志级别。用来筛选不同日志级别的日志信息。
* additivity 是否将日志信息传递给上级,默认为true
**上面的情况会发生如下过程:**
1. logger 默认继承了上级root的level,所以会记录所有日志级别level大于等于INFO的日志信息。
2. logger 将筛选后的日志信息交给appender处理。除此之外,additivity属性默认true,所以还会将日志信息传递给上级logger,即root
3. root 收到下级logger传递来的日志信息后,将其全部交给自己的appender处理
4. appender 收到日志信息后,如果设置了levelFilter日志级别过滤器,就会只将过滤后的日志信息输出到目的地。
> logger上下级关系使有name表示的包或类决定的。比如 name="liu.log" 就是 name="liu.log.guide" 的上级
所以最明显的结果就是:日志级别大于等于INFO的日志都会被记录2次,这2次分别来自于logger和 root
**扩展**
如果将上面的logback.xml改为如下这样,会发生什么呢?
```xml
%d{yyyy-MM-dd:HH:mm:ss.SSS} [%thread] %-5level %msg%n
```
## appender
参考 https://www.cnblogs.com/cb0327/archive/2004/01/13/5770794.html
### 自定义 appender
> 本身logback提供了AppenderBase和UnsynchronizedAppenderBase两个抽象类(同步和异步),所以我们自定义时,只需要看实际业务继承其中的一个即可。
1. 自定义appender类
```java
public class MyDbAppender extends UnsynchronizedAppenderBase {
Layout 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
}
```
2. logback配置
```xml
INFO
${pattern}
刘传伟(logback)
```
> 在 springboot-log-guide 中会用spring 继承logback将日志写入到数据库中
参考:https://www.cnblogs.com/zimublog/p/12923786.html
```java
@Component
public class MyLogDbAppender extends UnsynchronizedAppenderBase {
@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());
}
}
}
```
## 对比 log4j.properties
参考:https://blog.csdn.net/drift_away/article/details/7403658
```properties
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 日志规范,应有尽有,建议收藏!!](https://mp.weixin.qq.com/s/UqvMC6t39YTPDa_FqrkbaA)
## 占位符
slf4j logger 占位符非常简单,并没有类型的区别
```java
String name = "中国";
logger.info("我的名字叫{}", name);
```
## isDebugEnabled
参考:[Java日志框架中需要判断log.isDebugEnabled()吗?](https://www.cnblogs.com/leiwei/p/14674143.html)
很多代码中都用到了 isDebugEnabled ,如下:
```java
if (logger.isTraceEnabled()) {
logger.trace("Detected " + this.multipartResolver);
}
else if (logger.isDebugEnabled()) {
logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
}
```
看源码可知,isDebugEnabled() 用来判断当前日志级别是否低于debug,比如info级别高于debug,则不会打印日志。
```java
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条件内的代码
```java
//代码1
logger.debug("这是一条debug日志:" + msg);
//代码2
if(logger.isDebugEnabled()) {
logger.debug("这是一条debug日志:" + msg);
}
```
网上有部分人说,不需要加if判断,使用占位符即可。
```java
logger.debug("这是一条debug日志:{}", msg);
```
但在某些情况下,占位符依然不能避免性能消耗。
```java
logger.debug("这是一条debug日志:{}", obj.toString());
```
如上面这段代码,最终不会打印debug日志,但是会执行 obj.toString()
### 最佳实践
==**原则一:如果打印字符串常量,不需要`isDebugEnabled`**==
比较下面两段代码:
```java
//代码1
logger.debug("hello, world");
//代码2
if(logger.isDebugEnabled()){
logger.debug("hello, world");
}
```
因为打印的日志是字面常量,没有计算逻辑。两段代码的性能是几乎一样的。添加`isDebugEnabled`反而会导致额外的代码。
==**原则二:如果有参数,且参数只是字符串常量或计算简单,使用占位符**==
考虑如下代码,`debug`方法包含了参数`user.getName()`。虽然执行`debug`方法时,会计算`user.getName()`,但只是一个简单的**get**方法,没有复杂计算,这时候,也可以不添加`isDebugEnabled`。
```java
logger.debug("hello, {}", user.getName());
```
==**原则三:如果有参数,且参数计算复杂,添加`isDebugEnabled`**==
```java
logger.debug("order price: {}", calculatePrice());
```
假设`calculatePrice`方法需要经过复杂计算。那么就应该添加`isDebugEnabled`判断,使用如下的代码:
```java
if(logger.isDebugEnabled()){
logger.debug("order price: {}", calculatePrice());
}
```