java中的日志框架

各日志框架配置原则: 先看官网 --> 再看源代码 --> 最后中文博客

1.java中日志概述

在开发过程中,应用系统关于log的jar包非常的混乱,而这种混乱常常会带来jar包冲突、多份日志输出等各种问题。
比如你应用采用了log4j作为日志实现,但是你又通过间接依赖的方式引入了logback的包,
这样开发者往往很难察觉,往往是出现了相应的异常现象才排查出log冲突的问题。

1.1 java日志框架的历史

>> Apache Commons Logging(Jakarta Commons Logging,JCL)
>> Simple Logging Facade for Java (SLF4J)
>> Apache Log4j(Log4j2)
>> Java Logging API(JUL)
>> Logback
>> tinylog

在这些日志组件当中,最早得到广泛应用的是log4j,
成为了Java日志的事实上的标准,现在可以看到很多应用都是依赖于log4j的日志实现。

然而当时Sun公司在jdk1.4中增加了JUL(java.util.logging),企图对抗log4j,于是造成了混乱,
当然此时也有其它的一些日志框架的出现,如simplelog等,简直是乱上加乱。

为了解决这种混乱Commons Logging出现了,他只提供日志的接口,而具体的实现则在运行过程中动态寻找。
这样在代码中全部使用Commons Logging的编程接口,而具体日志实现则在外部配置中体现。
这样还有一个好处,由于应用日志并不依赖具体的实现,那么应用日志的实现则可以轻松的切换。
所以现在也能看到很多应用基于Commons Logging+Log4j的搭配。

但是呢log4j的作者觉得Commons Loggin不够优秀,于是自己实现了一套更为优雅的,
这个就是SLF4J,并且还亲自实现了一个日志实现logback。
那么现在关于log的局面就更为混乱了。
为了让之前使用Commons Logging和JUL的能够很好的转到SLF4J的体系中来,
log4j的作者又对其他的日志工具做了桥接......
后来该作者又重写了log4j,即log4j2,同时log4j2也加进了SLF4J体系中......

1.2 主流日志工具介绍

1.2.1 Commons-logging

Commons-logging是Apache提供的一个日志抽象,他提供一组通用的日志接口。
应用自由选择第三方日志实现,像JUL、log4j等。
这样的好处是代码依赖日志抽象接口,并不是具体的日志实现,这样在更换第三方库时带来了很大便利。

工作原理:
1、查找名为org.apache.commons.logging.Log的factory属性配置
(可以是java代码配置,也可以是commons-logging.properties配置);
2、查找名为org.apache.commons.logging.Log的系统属性;
3、上述配置不存在则 classpath下是否有Log4j日志系统,如有则使用相应的包装类;
3、如果系统运行在JDK 1.4系统上,则使用Jdk1.4 Logger;
4、上述都没有则使用SimpleLog。

所以如果使用commons-logging+log4j的组合只需要在classpath中加入log4j.xml配置即可。
commons-logging的动态查找过程是在程序运行时自动完成的。
他使用ClassLoader来寻找和载入底层日志库,
所以像OSGI这样的框架无法正常工作,因为OSGI的不同插件使用自己的ClassLoader。

1.2.2 SLF4J(Simple logging facade for Java)

SLF4J类似于commons-logging,他也是日志抽象。
和commons-logging动态查找不同slf4j是静态绑定,他是在编译时就绑定真正的log实现。
同时slf4j还提供桥接器可以将基于commons-loggging、jul的日志重定向到slf4j。
比如程序中以前使用的commong-logging,那么你可以通过倒入jcl-over-slf4j包来讲日志重定向到slf4j。

SLF4J提供了统一的记录日志的接口(LoggerFactory),只要按照其提供的方法记录即可,
最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。

// SLF4J提供的桥接包:
• slfj-log4j12.jar (表示桥接 log4j)
• slf4j-jdk14.jar(表示桥接jdk Looging)
• sIf4j-jcl.jar(表示桥接 jcl)
• log4j-slf4j-impl(表示桥接log4j2)
• logback-classic(表示桥接 logback)
SLF4J与各种日志实现的使用.png
SLF4J桥接.png

1.2.3 Log4j & Log4j2

log4j是Apache的开源日志框架,其最新版本是在2012年5月更新的1.2.17版本。

log4j2在其基础之上进行了重写,其具有插件式的架构、强大的配置功能、锁的优化、java8支持等特性。

1.2.4 Logback

Logback是由log4j创始人设计的又一个开源日志组件。当前分成三个模块:
>> logback-core
>> logback- classic
>> logback-access
logback-core是其它两个模块的基础模块。
logback-classic是log4j的一个改良版本,此外logback-classic完整实现SLF4J API。
logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。
Logback是要与SLF4J结合起来用。

1.3 最佳实现

1.3.1 二方库使用

二房库中建议不要绑定任何的日志实现,统一使用日志抽象(commons-logging、slf4j)。



    org.slf4j
    slf4j-api
    1.7.21

1.3.2 slf4j+logback


    org.slf4j
    slf4j-api
    1.7.21



    ch.qos.logback
    logback-classic
    1.1.7

1.3.3 slf4j+log4j


    org.slf4j
    slf4j-api
    1.7.21



    org.slf4j
    slf4j-log4j12
    1.7.21

1.4 问题与冲突

1.4.1 老应用日志改造

老应用则没有改变日志的必要,因为会有开发成本。但是开发需要保证三点:
1、应用依赖中同一个log包不能出现多个版本;
2、日志实现框架必须唯一,可以log4j、logback等,但是不能出现既有log4j又有logback的情况;
3、日志桥接不要出现循环重定向,比如你加入了jcl-over-slf4j.jar之后又加入了slf4j-jcl.jar。

1.4.2 日志系统的冲突

// 目前日志系统的冲突主要分为两种:
>> 同一个日志系统的多个实现
>> 桥接接口与实现类

// 冲突1: 同一个日志系统的多个实现
像slf4j接口实现的冲突,如:
slf4j-log4j、logback、slf4j-jdk14、log4j2之间的冲突
这几个包都实现了slf4j的接口,同一接口只能有一个实现才能被jvm正确识别,
与传统的jar冲突相同,当jvm发现两个一模一样的实现的时候,它就不知道选择哪个或选择了一个错误的,
就会提示ClassNotFound.

// 冲突2: 桥接jar与实现包
在日志系统中,最常见的就是桥接jar包与实现包的冲突,如:
>> jul-to-slf4j 与 slf4j-jdk14
>> log4j-over-slf4j 与 slf4j-log4j
>> jcl-over-slf4j 与 jcl
因为转接的实现就是将其余的日志系统调用进行一个转发,既然要转发,
就必须要定义与原有对象相同的类名、包名,才能正确的被调用,
所以桥接jar包就必然与实现包产生冲突。

// 其他冲突
slf4j-api和实现版本最好对应,尤其是1.6.x和1.5.x不兼容,直接升级到最新版本

https://yq.aliyun.com/articles/608736?spm=a2c4e.11153940.0.0.72182110hOwgxl (日志系统总结)
https://yq.aliyun.com/articles/57769?spm=a2c4e.11153940.0.0.72182110hOwgxl (日志系统常见问题)

2. log4j2 框架

2.1 org.apache.Log4j.Layout

模式转换字符

转换字符 含义
%c 使用它为输出的日志事件分类,比如对于分类 "a.b.c",模式 %c{2} 会输出 "b.c" 。
%C 使用它输出发起记录日志请求的类的全名。比如对于类 "org.apache.xyz.SomeClass",模式 %C{1} 会输出 "SomeClass"。
%d 使用它输出记录日志的日期,比如 %d{HH:mm:ss,SSS} 或 %d{dd MMM yyyy HH:mm:ss,SSS}。
%F 在记录日志时,使用它输出文件名。
%l 用它输出生成日志的调用者的地域信息。
%L 使用它输出发起日志请求的行号。
%m 使用它输出和日志事件关联的,由应用提供的信息。
%M 使用它输出发起日志请求的方法名。
%n 输出平台相关的换行符。
%p 输出日志事件的优先级(DEBUG、INFO、WARN……)。
%r 使用它输出从构建布局到生成日志事件所花费的时间,以毫秒为单位。
%t 输出生成日志事件的线程名。
%x 输出和生成日志事件线程相关的 NDC (嵌套诊断上下文)。
%X 该字符后跟 MDC 键,比如 %X{clientIP} 会输出保存在 MDC 中键 clientIP 对应的值。
% 百分号, %% 会输出一个 %。

格式修饰符 (pattern对齐修饰)

缺省情况下,信息保持原样输出。但是借助格式修饰符的帮助,就可调整最小列宽、最大列宽以及对齐。
格式修饰符 左对齐 最小宽度 最大宽度 注释
%20c 20 如果列名少于 20 个字符,左边使用空格补齐。
%-20c 20 如果列名少于 20 个字符,右边使用空格补齐。
%.30c 不适用 30 如果列名长于 30 个字符,从开头剪除。
%20.30c 20 30 如果列名少于 20 个字符,左边使用空格补齐,如果列名长于 30 个字符,从开头剪除。
%-20.30c 20 30 如果列名少于 20 个字符,右边使用空格补齐,如果列名长于 30 个字符,从开头剪除。

有些特殊符号不能直接打印,需要使用实体名称或者编号

& —— & 或者 &
< —— <  或者 <
> —— >  或者 >
“ —— " 或者 "
‘ —— ' 或者 '

2.2 MDC机制

https://blog.csdn.net/xiaolyuh123/article/details/80560662

MDC之坑.png

https://logging.apache.org/log4j/2.x/manual/configuration.html (log4j2官网配置)
https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout (log4j2 各种 %d%m 等配置来源参考)

3. logback 框架

http://logback.qos.ch/manual/introduction.html (logback 官网配置)

logback 官网配置.png

https://blog.csdn.net/wangyonglin1123/article/details/85119724 (logback.xml 配置)

4.实际应用

4.1 spring-boot 2.1.4.RELEASE中使用 logback 作为日志框架, 实现告警日志打印 (打印成 json 格式)

pom.xml


    org.springframework.boot
    spring-boot-starter-web

application.yml

logging:
  config: classpath:logback.xml

logback.xml



    
    
    
    
        
            %d %p (%file:%line\)- %m%n
            
            UTF-8
        
    
    
    
    
    
        log/kafka_producer_log.log
        
        
        
            
            
            log/kafka_producer_log.%d.%i.log
            
            30
            
                
                1000MB
            
        
        
            
            
                
                %m%n
            
            
            UTF-8
        
    


    
    
        log/alarm.log
        
            log/alarm.%d.%i.log
            30
            
                1KB
            
        
        
            
                %m%n
            
            UTF-8
        
    
    
    
        
        512
        
        0
        
        true
        
        
    
    
        
    
    


    
    
        
    
    
    
    
    
        
    


AlarmManager

package com.zy.alarm;

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;

public class AlarmManager {

    /**
     * 这里的 alarm 对应于 logback.xml 中 
     */
    private static final Logger alarmLogger = LoggerFactory.getLogger("alarm");

    /**
     * 打印告警日志
     * @param alarmBean
     */
    public static void alarm(AlarmBean alarmBean) {
        Optional.ofNullable(alarmBean).ifPresent(alarmBean1 -> {
            alarmBean.setAlarmType(AlarmType.ALARM.getType());
            alarmBean.setAlarmBeginTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            alarmLogger.warn(JSON.toJSONString(alarmBean));
        });
    }

    /**
     * 解除告警
     * @param alarmBean
     */
    public static void fire(AlarmBean alarmBean) {
        Optional.of(alarmBean).ifPresent(alarmBean1 -> {
            alarmBean.setAlarmType(AlarmType.FIRE.getType());
            alarmBean.setAlarmEndTime(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            alarmLogger.warn(JSON.toJSONString(alarmBean));
        });
    }

    @AllArgsConstructor
    @Getter
    private enum AlarmType {
        /**
         * 告警中
         */
        ALARM("alarm"),
        /**
         * 告警解除
         */
        FIRE("fire"),
        ;
        private String type;
    }
}

AlarmBean

package com.zy.alarm;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class AlarmBean {
    private Integer id;
    private String name;
    private String alarmType;
    private String alarmBeginTime;
    private String alarmEndTime;
    public AlarmBean(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

AlarmController

package com.zy.controller;

import com.zy.alarm.AlarmBean;
import com.zy.alarm.AlarmManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AlarmController {

    @RequestMapping("alarm")
    public String alarm() {
        System.out.println("开始---------");
        try {
            AlarmManager.alarm(new AlarmBean(1, "alarmName"));
            System.out.println("结束--------");
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "failure";
    }
}

参考资料

你可能感兴趣的:(java中的日志框架)