[J2EE:中间件] LOG4J+Slf4J快速入门及日志最佳实践

1 概述

1.1 常见的Java日志框架及选择

commons-logging和slf4j(slf4j-api.jar)都是日志类库的接口,供客户端使用,而没有提供实现!
log4j,logback等等才是日志的真正实现。

  • 日志(类库)
    • 作用:可以调试程序,就像输出一样
  • Java语言的日志类库:
    • JUL(java.util.logging) :自 JDK 1.5 开始,java.util.logging(JUL) 包下就提供了内置的日志工具类,功能比较简单,一般没人使用。
    • Apache Commons-Logging : Apache Commons-logging 也被称作 Jakarta Commons-Logging(JCL) 最早提供了日志门面接口,采用 facade 模式更符合面向接口的抽象编程方式,使得用户可以自由选择不同的日志实现框架,而不必改动具体的日志语句。2014年后没再更新,JCL 的风光早已不在。
    • SLF4J : [Simple Logging Facade(外观/表面) For Java], 一个简单的日志门面,类似 Commons-logging,通过 Facade Pattern 提供一些 Java logging API。SLF4J 的 slf4j-api 包中提供了众多日志接口定义,它只服务于各种各样的日志框架而不负责具体的日志实现,只在编译时负责寻找合适的日志系统进行绑定。作者创建 SLF4J 的目的是为了替代 JCL。
      • 1个 日志标准/适配器
        • 通过调用slf4j的API统一打印日志,而可忽略其他日志的具体方法
    • Log4J : [Log For Java]
      • log4j 是Apache为java提供日志管理的工具
      • 真正实现日志功能的日志类库
    • Logback : Logback 是由 Log4j 创始人设计的又一个开源日记组件,是 Slf4j 的原生实现框架,相比 log4j,logback 拥有更快的执行速度,在 Log4j2 出来前的很长一段时间里都是 java 日志界的主流。
    • Log4J : [Log For Java 2] : Log4j 和 Log4j2 也都是 Apache 的开源日志框架,Log4j 2.0 以后的版本称为 Log4j2Log4 1.x 的升级版,Log4j 1.x 版在 2015.08.05 停止维护了。Log4j2 参考了 Logback 的一些优秀的设计,并修复了老版的问题,整体提升很大,特别是异步方面的性能提升。 Log4j2 与 Log4j 发生了很大的变化,log4j2 不兼容 Log4j。

JCLSLF4J 只是一种日志抽象门面、一种标准,不是负责具体实现的日志框架,若项目中只有slf4j的包,是没有办法实现日志功能的。
LogbackLog4j/Log4j2 是具体的日志实现框架。在选择一个日志框架时可考虑以下两点:

  • 具有日志缓冲区的框架可以减少频繁的文件 I/O 操作,对性能提升显著;
  • 支持异步日志功能的框架,不会阻塞其它应用线程,因而是首选;

LogbackLog4j2 都支持以上特性,在关注性能的地方,推荐使用: slf4j + log4j2slf4j + logback。如果不想任何额外的依赖,则使用 JUL容器框架已经提供的日志接口。

1.2 日志级别(从高到低)

  • OFF : 最高等级,用于关闭所有日志记录
  • FATAL : 最严重的日志级别/重大错误级(系统有问题),必须慎用。一般表示服务不可用,比如:程序崩溃、无法启动、OOM 等;
  • ERROR : 错误级(一个模块有问题)
  • WARN : 警告级
  • INFO : 信息级。可以查看程序执行的流程
  • DEBUG : 调试。用来调试程序的bug及显示
  • TRACE : 类似 DEBUG,但记录更详细的跟踪信息
  • ALL : 最低等级。用于打开所有日志记录

常用日志框架间的级别对应关系见下表:

SLF4J Log4j Log4j2 Logback JUL
FATAL FATAL FATAL
ERROR ERROR ERROR ERROR SEVERE
WARN WARN WARN WARN WARNING
INFO INFO INFO INFO INFO, CONFIG
DEBUG DEBUG DEBUG DEBUG FINE, FINER
TRACE TRACE TRACE TRACE FINEST

debug,info,error等日志级别的方法都可传入多个可变参数:(以debug为例)

1.3 安装/依赖 [slf4j + log4j]

方式1:手动导入JAR包(slf4j-api-1.7.5.jar、slf4j-log4j-1.7.5.jar、log4j-1.2.16.jar)或配置如下Maven依赖:

  • slf4j-api:提供接口
  • slf4j-log4j[xx]:提供Log具体的实现
<!-- Log4j 依赖包 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.9.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.9.1</version>
</dependency>
<!-- slf4j 依赖包 -->
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.2</version>
</dependency>

1.3 配置文件

log4j.properties
log4j.xml
log4j2.xml(log4j/org.apache.logging.log4j:log4j在2.0.0及以后,被拆分为 log4j-api与log4j-core,对应的配置文件为 log4j2.xml)

2 LOG4J

2.0 Log4J的配置

如果没有调用BasicConfigurator.configure(),PropertyConfigurator.configure()或DOMConfigurator.configure()方法,Log4j会自动加载CLASSPATH下名为log4j.properties的配置文件。如果把此配置文件改为其他名字,例如my.properties,程序虽然仍能运行,但会报出不能正确初始化Log4j系统的提示。这时可以在程序中加上:PropertyConfigurator.configure("classes/my.properties");

配置Log4j环境就是指配置root Logger,包括把Logger为哪个级别,为它增加哪些Appender,以及为这些Appender设置Layout,等等。因为所有其他的Logger都是root Logger的后代,所以它们都继承了root Logger的性质。这些可以通过设置系统属性的方法来隐式地完成,也可以在程序中调用XXXConfigurator.configure()方法来显式地完成。有以下几种方式来配置Log4j。

  • A:配置放在文件里,通过环境变量传递文件名等信息,利用Log4j默认的初始化过程解析并配置。
  • B:配置放在文件里,通过应用服务器配置传递文件甸等信息,利用一个特定的Servlet来完成配置。
  • C:配置放在文件里,通过命令行org.apache.log4j.PropertyConfigurator.configure(args[])解析log4j.properties文件并配置Log4j
  • D:配置放在文件里,在程序中直接调用org.apache.log4j.BasicConfigor.configure()方法,或在程序中完成相关配置(↓)
    • step1:用默认的方式创建PatternLayout对象p:
      • PatternLayout p = new PatternLayout("%-4r[%t]%-5p%c%x-%m%n");
    • step2: 用p创建ConsoleAppender对象a,目标是System.out,标准输出设备:
      • ConsoleAppender a = new CpnsoleAppender(p,ConsoleAppender.SYSTEM_OUT);
    • step3: 为root Logger增加一个ConsoleAppender p:
      • rootLogger.addAppender(p);
    • step4: 把rootLogger的log level设置为DUBUG级别
      • rootLogger.setLevel(Level.DEBUG);
  • E:org.apache.log4j.DOMConfigurator.configure(String filename);

2.1 核心概念/3大组件

Log4j有3大组件:

  • 【日志器(Logger)】:用来输出消息的类,可以输出不同级别的,比如错误消息,警告消息等
    • 创建日志器
      • Logger log = Logger.getLogger(calssNameX.class); log.info(); log.debug(); [log4J方式]
      • Logger logger = LoggerFactory.getLogger(calssNameX.class); log.info(); log.debug();[slf方式(推荐)]
    • 根日志器(rootLogger)
      • 若需要输出到多个位置的时候可用逗号隔开: log4j.rootLogger=info, A, B
在配置文件里,需要为log4j.properties配置一个根日志器:
log4j.rootLogger=DEBUG,AA
log4j.rootLogger=WARN
log4j.APPENDER.AA=org.apache.log4j.ConsoleAppender
  • 【输出源(Appender)】:日志输出的目标。日志输出到哪里去(文件、控制台)
    • org.apache.log4j.ConsoleAppender: 向控制台输出日志
    • org.apache.log4j.FileAppender: 向文件输出日志
    • org.apache.log4j.DailyRollingFileAppender
    • org.apache.log4j.RollingFileAppender
log4j.appender.AA.File=../log.txt
log4j.appender.AA.LAYOUT=org.apacskhe.log4j.SimpleLayout
log4j.appender.AA.DatePatten='yyyy-MM-dd'
  • 【格式化器(Layout)】:对输出消息进行格式化,比如添加日期
    • org.apache.log4j.PatternLayout
    • org.apache.log4j.SimpleLayout
    • org.apache.log4j.HTMLLayout
    • org.apache.log4j.TTCCLayout

2.3.1 日志器[Logger]

  • 特点
    • Logger(log4j)记录的是当前类的日志,不是每个实例的日志
      • 使用static修饰的属性
      • 只要有一个记录就可以了
    • private static final Logger logger = LoggerFactory.getLogger(Slf4jTest.class.getName());// slf4j日志记录器
    • 每个Logger都配有1个日志级别(Log Level):用来控制日志信息的输出。
  • rootLogger(根日志器)
    • root Logger(根Logger)是所有Logger的祖先
    • 它总是存在的
    • 它不可以通过名字获得
      • public static Logger Logger.getRootLogger();
      • public static Logger Logger.getLogger(Class clazz)

2.3.2 输出源[Appender]

A) Log4J提供的Appender

  • org.apache.log4j.ConsoleAppender (控制台)
Threshold=WARN:指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认值是true
Target=System.err:默认值是System.out
  • org.apache.log4j.FileAppender (文件)
Threshold=WARN:指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认true
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认true
File=D:/logs/logging.log4j:指定消息输出到logging.log4j文件
  • org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件, 常用)
Threshold=WARN:指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认true
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认true
File=D:/logs/logging.log4j:指定当前消息输出到logging.log4j文件
DatePattern='.'yyyy-MM:每月滚动一次日志文件,即每月产生一个新的日志文件。
                        当前月的日志文件名为logging.log4j,前一个月的日志文件名为logging.log4j.yyyy-MM
另外,也可以指定按周、天、时、分等来滚动日志文件,对应的格式如下:
1)'.'yyyy-MM:每月
2)'.'yyyy-ww:每周
3)'.'yyyy-MM-dd:每天
4)'.'yyyy-MM-dd-a:每天两次
5)'.'yyyy-MM-dd-HH:每小时
6)'.'yyyy-MM-dd-HH-mm:每分钟
  • org.apache.log4j.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新的文件)
Threshold=WARN:指定日志信息的最低输出级别,默认DEBUG
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认true
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认true
File=D:/logs/logging.log4j:指定消息输出到logging.log4j文件
MaxFileSize=100KB:后缀可以是KB,MB或者GB。在日志文件到达该大小时,将会自动滚动,即将原来的内容移到logging.log4j.1文件
MaxBackupIndex=2:指定可以产生的滚动文件的最大数,例如,设为2则可以产生logging.log4j.1,logging.log4j.2两个滚动文件和一个logging.log4j文件
  • org.apache.log4j.WriterAppender (将日志信息以流格式发送到任意指定的地方)

B) Logback提供的Appender

C) Apache Skywalking提供的Appender

  • org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender

[注意事项] Skywalking 的 Layout 所支持的ConversionPattern核心参数在apm-toolkit-log4j-1.x插件中是T(即 traceId, 区分大小写[T≠t]),在apm-toolkit-logback-1.x插件中是%X{tid},需打印到日志中,才能被 Skywalking OAP 服务的Collertor收集并识别请求链路中的日志信息,否则日志收集失败。
[注意事项] Skywalking for log4j 的JAR包依赖信息:

<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-log4j-1.x</artifactId>
    <version>8.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>8.5.0</version>
</dependency>
Threshold=INFO
layout=org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLayout
layout.ConversionPattern=%d %-5p | %X{tid} | %t | (%C{1}.java:%L %M) | %m%n
  • org.apache.skywalking.apm.toolkit.log.log4j.v1.x.log.GRPCLogClientAppender
Threshold=INFO
layout=org.apache.skywalking.apm.toolkit.log.log4j.v1.x.TraceIdPatternLayout
layout.ConversionPattern=%d %-5p | %T | %t | (%C{1}.java:%L %M) | %m%n

上述 log4j 的 parttern 的样例日志记录:

2023-02-08 11:07:55,501 INFO  | TID:fee46004e86942d9a13a1ca8be0b88b1.44.16758256563660001 | http-nio-8081-exec-2 | (SkywalkingController.java:83 callThridPartyService$original$MTe6LVxm) | thridPartyServiceUrl: http://127.0.0.1:8081/study-springmvc/hello.do?caller=StudySpringMVCService&token=helloToken, statusCode: <200 OK,200>, headers: [Set-Cookie:"JSESSIONID=AB568DF47926DB97BDA634E0E348FF0D; Path=/study-springmvc; HttpOnly", Content-Type:"text/html;charset=UTF-8", Content-Language:"zh-CN", Content-Length:"342", Date:"Wed, 08 Feb 2023 03:07:55 GMT", Keep-Alive:"timeout=20", Connection:"keep-alive"] 
2023-02-08 11:07:55,501 INFO  | TID:fee46004e86942d9a13a1ca8be0b88b1.44.16758256563660001 | http-nio-8081-exec-2 | (SkywalkingController.java:90 callThridPartyService$original$MTe6LVxm) | Time-consuming in total: 2155ms

2.3.3 日志格式化器[Layout]

指定logger输出内容及格式: log4j.appender.appenderName.layout=className

  • org.apache.log4j.PatternLayout(最常用,可以灵活地指定布局模式)根据指定的转换模式格式化日志输出,或若未指定任何转换模式,就用默认的转化模式格式
    • ConversionPattern=%m%n:设定以怎样的格式显示消息
  • org.apache.log4j.HTMLLayout:(以HTML表格形式布局)格式化日志输出为HTML表格形式
    • LocationInfo=true:输出java文件名称和行号,默认false
    • Title=My Logging: 默认值是Log4J Log Messages
  • org.apache.log4j.SimpleLayout:(包含日志信息的级别和信息字符串)以一种非常简单的方式格式化日志输出,它打印三项内容:级别-信息
  • org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
【patterm参数】
%p: 日志级别<从高到低>(OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL)
%c: 输出日志信息所属的类目,通常就是所在类的全名。可写为%c{num},表示取完整类名的层数,从后向前取,比如%c{2}取 "cn.qlq.exam"类为"qlq.exam"。
%r: 输出自应用启动到输出该日志耗费的毫秒数
%d: 输出日志时间点的日期或时间。默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-dd HH:mm:ss,SSS},SSS为毫秒数(也可以写为SS,只不过SSS如果不足三位会补0),输出类似:2011-10-18 22:10:28,021
%m: 输出代码中指定的消息,产生的日志具体信息
%n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行
%t: 输出当前产生日志的线程名称
%F: 输出日志消息产生时所在的文件名称 
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中 
%%: 输出1个"%"字符
%l: 输出日志事件的位置,相当于%c.%M(%F:L)的组合,包括类全名、方法、文件名以及在代码中行数。例如:cn.xm.test.PlainTest.main(PlanTest.java:12)

3 Slf4J

3.1 特点

  • 不同于其他日志类库,与其它日志类库有很大的不同
    • 不是1个真正的日志实现,而是1个抽象层
    • 它允许你在后台使用任意一个日志类库
  • 使代码独立于任一特定的日志API;
  • 无需忍受加载和维护新的日志框架的痛苦
  • 占位符(place holder):"{}"
    • 占位符非常类似于在String的format()方法中的%s
      • 因为它会在运行时被某个提供的实际字符串所替换
    • 不仅降低了代码中字符串连接次数,而且还节省了新建的String对象
    • 可以在运行时延迟字符串的建立。
      • 这意味着只有需要的String对象才被建立

4 最佳实践

1. 日志的基本格式

基本的日志格式需要输出:时间、级别、线程名称、logger 名称、日志内容。如果能拿到调用链 ID 的话,输出到日志中对问题的定位帮助很大。出现异常时,将异常堆栈输出到日志也是非常有必要的。日志产生的日期和时间非常重要,一般精确到毫秒,推荐格式 yyyy-MM-dd HH:mm:ss.SSS。通常线上日志配置了按天滚动,日志文件名带有日期,此时使用 HH:mm:ss.SSS 格式即可。

2. 使用门面模式的日志框架(比如流行的 SLF4J)而不要直接使用具体日志实现框架。

一方面面向接口编程更优雅;另一方面便于统一使用方式和日后的维护。比如,老的项目中直接使用了 log4j,但 log4j 已经停止维护不再更新,现在想升级到 log4j2 后换成 logback,因 API 的不兼容所以替换的代价就高了。

3. 一个类中通常只使用一个 Logger 对象,Logger 应该是 private static final 的。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Demo.class); 

4. 使用占位符,不要使用字符串拼接。

字符串拼接一方面不便于阅读,另一方面字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符 仅是替换动作,可以有效提升性能。

// 推荐
logger.info("用户 {} 借走图书 <<{}>> ",  userName, bookTitle);

// 不推荐
logger.info("用户 " + userName + " 借走图书 <<" + bookTitle + ">>");

5. 输出异常的全部信息,不要使用 logger.error(msg) 和 logger.error(msg,e.getMessage()),因为它们会丢失掉最重要的 StackTrace 信息。

void foo() {
    try {
        //  do something ...
    } catch ( Exception e ) {
        logger.error("Bad things : ", e); // 正确

        logger.error(e.getMessage());  // 错误
        logger.error("Bad things : ", e.getMessage()); // 错误
    }
}

6. catch 分支不要使用 System print(包括 System.out.println 和 System.error.println)和 printStackTrace 语句,代替 log 打印异常信息。

void foo() {
    try{
        // do something...
    } catch ( Exception e ) {
        logger.error("Bad things : ",e );  // 正确

        System.out.println(e.getMessage());  // 错误
        System.err.println(e.getMessage());  // 错误
        e.printStackTrace();  // 错误
    }
}

7. 出于性能的考虑,建议执行频率非常搞的核心代码中的 trace/debug 低级别的日志增加开关判断。

虽然日志框架内部有级别开关判断,比如 logger.debug(…) 在 Slf4j 内部调用的是 ExtendedLogger.logIfEnabled(String var1, Level var2 …),但是参数可能会进行字符串拼接运算,此时直接在外层进行开关判断可以省去无畏的方法调用,以及可能的字符串拼接开销。

if (logger.isDebugEnabled ()) {
    logger.debug("return content:" + content);
}

8. 可用 warn 级别日志记录不在功能范围内的操作,或错误的请求参数等场景,避免用户投诉时不知所措。但此类场景不要使用 error 级别日志,避免不必要的告警。

9. 严格控制生产日志:

  • 禁止输出 debug 日志,对于支持动态调整日志级别的,不要将打开 debug 及以下级别的开关;
  • 有选择地输出 info 日志,避免打印大的对象,应该选择性的将关键的业务信息打印出来;
  • 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。

10. Logback / Log4j / Log4j2 中的 additivity 属性设为 false

通过 additivity 属性可以控制子 Logger 是否继承父 Logger 的 appender,Logback / Log4j / Log4j2 都支持该属性。属性 additivity 默认为 true 表示子 Logger 会继承父 Logger 的 appender,子 Logger 会在父 Logger 的 appender 里输出。将 additivity 设为 false,则子 Logger 只会在自己的 appender 里输出,可避免重复日志打印。

<!--Logback 中关闭 additivity-->
<Configuration>
  ...
  <Loggers>
    <Logger name="demo.wood" level="debug" additivity="false">
      <AppenderRef ref="applog" />
    </Logger>
    ...
  </Loggers>
</Configuration>

<!--Log4j2 中关闭 additivity-->
<Configuration>
    ...
    <Loggers>
        <Logger name="org.springframework" level="INFO" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        ...
    </Loggers>
</Configuration>

11. 谨慎打印日志

  • 要明确不同日志的用途,对日志内容进行分类,比如框架日志和应用日志分离。
  • 绝不要打印没用的日志,防止无用日志淹没重要信息;日志信息要精准,努力做到仅凭日志就可以定位问题。没有限制的日志输出,会带来无畏的性能和资源浪费,严重的会导致 OOM,应用直接崩溃。
  • 查询类接口不要将查询出来的数据全部打印,存在 DB 中的数据,查询出来后再打印一遍意义不带还消耗资源。
  • 写接口需要写日志,尽可能将有用的业务信息记录下来,但也要避免 controller、service 等多层重复打印。

12. 建议提供动态日志输出功能

动态日志输出可以实时调整系统日志级别,常见的做法是配置中心提供了服务的日志级别动态下发功能,在需要时实时调整(一般都要审批)日志级别以便获取重要信息。虽然不建议在生产中打开低级别的日志,但在遇到重大疑难问题时,可临时选择一个生产实例动态调低日志级别,以便问题排查。所以动态日志在实际中还是很有用的。关键动态日志的实现,可参考下节说明。

5 动态日志输出


Log4j2 和 Logback 都支持在线动态修改日志级别,除了提供相应的 API,还提供了 JMX 支持。下面通过例子演示下两种使用方式,其中 logback 的版本为 1.2.3,log4j2 的版本为 2.13.3(不同版本的 API 可能有所不同,在使用前先去官方文档 double check 下)。

5.1. 通过 API 动态修改日志级别

Log4j2 和 Logback 都提供了修改日志级别的方法 setLevel,其中 Logback 的 setLevel 方法位于 classch.qos.logback.classic.Logger里, setLevel 方法,Log4j 的 setLevel 方法位于org.apache.logging.log4j.core.config.Configurator下。使用方式上都是先获取到需要设置的 logger,然后在对 logger 设置级别,下面的示例代码演示了两者 API 的使用方式。

// Example 1: 设置 Logback 日志级别
Logger logger = oggerContext.getLogger("需要设置级别的 logger 名");
logger.setLevel(Level.DEBUG);

// Example 2: 设置 Log4j2 日志级别
Logger logger4j = LogManager.getLogger("需要设置级别的 logger 名");
Configurator.setLevel(logger4j.getName(), Level.ERROR);

由于我们通常使用 SL4J 而不是直接使用具体的日志框架,因而在获取 logger 的过程中注意类型转换。下面通过 DynamicLogbackLevelDemo 演示下在 SLF4J + Logback 使用方式下,如何通过 API 动态修改日志级别。Log4j / Log4j2 的使用方式类似,这里不再演示。

  • Logback 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss}] -- [%-5p]: [%c] -- %m%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>
  • Demo Code
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Scanner;

public class DynamicLogbackLevelDemo {
    private static final Logger logger = LoggerFactory.getLogger(DynamicLogbackLevelDemo.class);

    private static final Map<Integer, String> LOG_LEVEL_MAP = new ImmutableMap.Builder<Integer, String>()
            .put(1, "TRACE").put(2, "DEBUG").put(3, "INFO").put(4, "WARN").put(5, "ERROR").build();

    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        String userInput = null;
        do {
            System.out.printf("\nPlease Enter Logback Logger Level (q to quit): 1 - TRACE, 2 - DEBUG, 3 - INFO, 4 - WARN, 5 - ERROR\n");

            userInput = scanner.nextLine().trim();
            if (!StringUtils.isNumeric(userInput)) {
                continue;
            }

            String logLevel = LOG_LEVEL_MAP.get(Integer.parseInt(userInput));
            if (null != logLevel) {

                // 获取 loggert
                LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
                ch.qos.logback.classic.Logger tmplogger = loggerContext.getLogger(DynamicLogbackLevelDemo.class);

                // 修改日志级别
                System.out.printf("[current logger level]: %s ---> [new logger level]: %s\n", tmplogger.getLevel(), logLevel);
                tmplogger.setLevel(Level.toLevel(logLevel));

                System.out.println("verifying new logger level:");
                logger.trace("sample trace log");
                logger.debug("sample debug log");
                logger.info("sample info log");
                logger.warn("sample warn log");
                logger.error("sample error log");
            }
        } while (!"q".equalsIgnoreCase(userInput));
        scanner.close();
    }
}

下图是运行 DynamicLogbackLevelDemo 后的截图,可以看到,随着用户输入不同的日志级别,logback 就会按级别进行日志过滤,并在控制台输出不同的日志信息。

img

5.2. 使用 JMX 动态修改日志级别

Log4j2 和 Logback 都支持 JMX 设置日志级别,其中 Log4j2 不需要配置默认就打开了 JMX 功能,而 Logback 需要在配置文件中添加 <jmxConfigurator /> 才能开启 JMX 功能。下面通过 DemoApp 演示下 Logback 的 JMX 功能,Log4j2 / Log4j 类似,这里不再赘述。

DemoApp 是在 spring initializr 网站 https://start.spring.io 上自动生成的一个应用,由于 springboot 默认使用 logback,因而 DemoApp 没有额外配置,只是增加了下面的 logback 配置文件。

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
    <!-- 开启 JMX -->
    <jmxConfigurator />

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%logger{36}]: %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/demoApp.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>demoApp-%d-%i.log</fileNamePattern>
            <MaxHistory>20</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5level] [%logger{36}]: %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <logger name="org.springframework" additivity="false">
        <level value="ERROR" />
        <appender-ref ref="STDOUT" />
        <appender-ref ref="fileAppender" />
    </logger>

    <logger name="org.apache.tomcat.util" additivity="false">
        <level value="ERROR"/>
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="fileAppender"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="fileAppender"/>
    </root>
</configuration>

启动 DemoApp 后,就可以验证 Logback JMX 的功能了。这里选择 jconsole 测试下 Logback 的动态日志输出功能。使用 jconsole 连接上 DemoApp 后,在左边的窗口找到 *ch.qos.logback.classic* 打开 operations 就可以看到 setLoggerLevel 操作,填上 logger 名称和日志级别后回车,就能及时修改日志级别。下图展示了在 jconsole 中将 org.springframework 包下的日志级别调整为 debug 后,控制台立即输出 debug 及以上级别的日志效果。

img

下面为使用 jconsole 动态调整 Log4j2 的截图,操作起来和 logback 类似。由于 Springboot 默认使用 logback,同时也提供了对 log4j2 的集成,因而在 springboot 项目中使用 log4j2 时需要在 pom 中排除spring-boot-starter-logging的依赖,同时添加spring-boot-starter-log4j2的依赖即可。

img

6 问题集

1: log4j.properties 放置在/src/log4j.properties
2: org.apache.log4j.BasicConfigurator.configure(); //自动快速地使用缺省Log4j环境

7 实际项目开发中的日志应用

案例1 完整Sample [2020-06-04]

1 一般是将捕捉到的Exception对象(e)作为日志记录的最后一个参数(会显示具体的出错信息以及出错位置),而且要放在{}可以格式化的参数之外。防止被{}转为e.toString()
2 可使用e.toString(),而尽量不使用e.getMessage()。因为有的异常不一定有message,可以使用e.toString只会显示信息,不会显示出错的位置信息(不建议这种)

//link - blog: https://www.cnblogs.com/qlqwjy/p/9275415.html
package cn.johnnyzen.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Slf4jTest {

    private static Logger log = LoggerFactory.getLogger(Slf4jTest.class);

    public static void main(String[] args) {
        openFile("xxxxxx");
    }

    public static void openFile(String filePath) {
        File file = new File(filePath);
        try {
            InputStream in = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            //Exception对象(e)作为日志记录的最后一个参数(会显示具体的出错信息以及出错位置),而且要放在{}可以格式化的参数之外
            log.error("can found file [{}]", filePath, e);
        }
    }

}
2020-06-04 00:30:03 [cn.johnnyzen.test.Slf4jTest]-[ERROR] can found file [xxxxxx]
java.io.FileNotFoundException: xxxxxx (系统找不到指定的文件。)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:146)
    at cn.xm.exam.test.Slf4jTest.openFile(Slf4jTest.java:22)
    at cn.xm.exam.test.Slf4jTest.main(Slf4jTest.java:16)

案例2 resources/log4j.properties

# +======================================================================+#
log4j.rootLogger=${log4j.log.level},${log4j.log.target}
log4j.addivity.org.apache=false # 将 logger 中的 additivity 属性配置为 false,则 这个logger不会将日志流反馈到root中,防止日志中同一记录反复打印N次(N>1)
log4j.addivity.cn.johnnyzen.bd.gatewayservice.biz=false


# +======================================================================+#
# | [target] - Console / A1
# +----------------------------------------------------------------------+#
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${log4j.log.level}
log4j.appender.CONSOLE.Encoding=${log4j.log.encoding}
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=${log4j.log.layout}
# log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=${log4j.log.layout.pattern}

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Threshold=DEBUG
log4j.appender.A1.Encoding=${log4j.log.encoding}
log4j.appender.A1.Target=System.out
log4j.appender.A1.layout=${log4j.log.layout}
log4j.appender.A1.layout.ConversionPattern=${log4j.log.layout.pattern}

# +----------------------------------------------------------------------+#
# 若想某个【包】的打印级别和别的文件不一样,则自己定义appender,比如 A1
## 方式1
# log4j.logger.cn.seres.bd.gatewayservice.biz.common.filter.gatewayfilter=DEBUG,A1
## 方式2
log4j.category.org.springframework: WARN,A1
log4j.category.org.springframework.cloud.gateway: WARN,A1
log4j.category.org.springframework.web.reactive: WARN,A1
log4j.category.com.alibaba.nacos.client.naming: ERROR,A1
log4j.category.com.alibaba.nacos.shaded.io.grpc.netty: WARN,A1
log4j.category.reactor.netty: WARN,A1
log4j.category.reactor.ipc.netty: WARN,A1
log4j.category.reactor.util.Loggers: WARN,A1
log4j.category.io.netty.buffer.AbstractByteBuf: WARN,A1
log4j.category.com.zaxxer.hikari.pool.HikariPool: WARN,A1
log4j.category.cn.johnnyzen.bd.gatewayservice.biz.common.filter.gatewayfilter=DEBUG,A1

# 若想某个【类】的打印级别和别的文件不一样,则 自己定义appender(且必须指定),比如A1
log4j.logger.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG,A1
# log4j.logger.com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.util.internal.logging.AbstractInternalLogger=WARN,A1
# log4j.logger.com.alibaba.nacos.client.naming=ERROR,A1
# log4j.logger.cn.johnnyzen.bd.gatewayservice.biz.common.filter.gatewayfilter.PrefixPathGatewayFilter=DEBUG,A1
# log4j.logger.cn.johnnyzen.bd.gatewayservice.biz.common.filter.gatewayfilter.StripPrefixGatewayFilter=DEBUG,A1

# 日志输出的地址: 可任意配置, logs/存储在当前项目中   e:/logs
log4j.log.dir=logs/
#日志的等级:
#log4j.log.level=ALL,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF
log4j.log.level=WARN
#log4j.log.target=CONSOLE,FILE,DATABASE,EMAIL,SOCKET
log4j.log.target=CONSOLE
log4j.log.encoding=UTF-8
log4j.log.layout=org.apache.log4j.PatternLayout
log4j.log.layout.pattern=[%d %r] [%-5p] [%t] [%l] [%m]%n 

案例3 resources/log4j.properties

##################### [0] 自定义配置(可灵活修改) #####################
## 日志的等级(自定义配置项)
##log4j.log.level=ALL,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF
log4j.log.level=WARN
log4j.log.threshold=${log4j.log.level}

## 2 个 target(自定义配置项)
# log4j.log.target=CONSOLE,FILE,DATABASE,EMAIL,SOCKET
log4j.log.target=CONSOLE
log4j.log.fileTarget=MyFileAppender

log4j.log.encoding=UTF-8

## 日志输出的地址(自定义配置项): 可任意配置, logs/存储在当前项目中   e:/logs
log4j.log.dir=./logs

## org.apache.log4j.HTMLLayout (以HTML表格形式布局),
## org.apache.log4j.PatternLayout (可以灵活地指定布局模式),
## org.apache.log4j.SimpleLayout (包含日志信息的级别和信息字符串),
## org.apache.log4j.TTCCLayout (包含日志产生的时间、线程、类别等等信息)
log4j.log.layout=org.apache.log4j.PatternLayout
log4j.log.layout.pattern=[%d %r] [%-5p] [%t] [%l] [%m]%n


##################### [1] 定义 Logger #####################
# ------------------- [1.1] 定义 RootLogger 等 全局性配置(不可随意修改) ------------------- #
## log4j.rootLogger = [ level ] , appenderName, appenderName, ...
#log4j.rootLogger=${log4j.log.level},${log4j.log.target},${log4j.log.fileTarget}
log4j.rootLogger=${log4j.log.level},${log4j.log.target},${log4j.log.fileTarget}
log4j.additivity.org.apache=false

# ------------------- [1.2] 指定个别 Class 的 Logger (可随意修改,建议在 nacos 上修改) ------------------- #
log4j.logger.cn.johnnyzen.bd.dataservice.common.xx.XXClass=DEBUG,MyFileAppender
log4j.logger.cn.johnnyzen.bd.dataservice.common.utils.XXUtil=DEBUG,CONSOLE,MyFileAppender

##################### [2] 定义 Appender #####################

# ------------------- [2.1] CONSOLE Appender ------------------- #
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
# Threshold: 当前 Appender 输出日志的最低日志级别。例如 rootLogger=DEBUG, 当前 Appender 的 Threshold=ERROR, 则: 当前 Appender 输出日志仅输出 ERROR 信息
#log4j.appender.CONSOLE.Threshold=${log4j.log.threshold}
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %-5p[%t] : %m%n
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.Encoding=${log4j.log.encoding}
log4j.appender.CONSOLE.layout=${log4j.log.layout}

# ------------------- [2.1] MyFileAppender Appender ------------------- #
log4j.appender.MyFileAppender=org.apache.log4j.DailyRollingFileAppender
log4j.appender.MyFileAppender.File=${log4j.log.dir}/bdp_data_service.log
log4j.appender.MyFileAppender.DatePattern='_'yyyy-MM-dd
log4j.appender.MyFileAppender.encoding=${log4j.log.encoding}
#log4j.appender.MyFileAppender.Threshold=${log4j.log.threshold}
log4j.appender.MyFileAppender.layout=${log4j.log.layout}
log4j.appender.MyFileAppender.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %-5p[%t] : %m%n

案例4 resources/log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="TRACE" monitorInterval="1800">
    <Properties>
        <!-- ==================== 公共配置 ==================== -->
        <!-- 设置日志文件的目录名称 -->
        <property name="serviceName">johnnyzen-data-service</property>

        <!-- 日志默认存放的位置,可以设置为项目根路径下,也可指定绝对路径 -->
        <property name="basePath">../logs/${serviceName}</property>

        <!-- 日志文件输出格式;%d{yyyy/MM/dd HH:mm:ss.SSS}:时间; ${serviceName}:服务名; %X{localIp}:ip; %traceId:traceId; %-5p:日志等级,5位左对齐; %t:线程名; %c{length}:类名及限定名;%M:方法名;%m:错误信息;%n:换行 -->
        <property name="log_pattern">[%d{yyyy/MM/dd HH:mm:ss.SSS}] [${serviceName}] [%X{localIp}] [%traceId] [%-5p] [%t] [%c{8}] [%M] %m%n</property>

        <!-- 日志默认切割的最小单位 -->
        <property name="every_file_size">20MB</property>
        <!-- ==================== 日志级别控制 ==================== -->
        <!-- 日志级别 -->
        <property name="log_level">${env:LOG_LEVEL:-WARN}</property>
        <property name="johnnyzen_log_level">${env:JOHNNYZEN_LOG_LEVEL:-DEBUG}</property>
        <property name="middleware_log_level">${env:MIDDLEWARE_LOG_LEVEL:-WARN}</property>

        <!-- ==================== 所有级别日志配置 ==================== -->
        <!-- 日志默认存放路径(所有级别日志) -->
        <property name="rolling_fileName">${basePath}/${serviceName}-all.log</property>
        <!-- 日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="rolling_filePattern">${basePath}/%d{yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- 日志默认同类型日志,同一文件夹下可以存放的数量,不设置此属性则默认为7个,filePattern最后要带%i才会生效 -->
        <property name="rolling_max">500</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="rolling_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="rolling_timeModulate">true</property>

        <!-- ==================== Warn ERROR级别日志 ==================== -->
        <!-- 日志默认存放路径(Warn Error级别日志) -->
        <property name="warn_fileName">${basePath}/${serviceName}-warn-error.log</property>
        <!-- 日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="warn_filePattern">${basePath}/%d{yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- 日志默认同一文件夹下可以存放的数量,不设置此属性则默认为7个 -->
        <property name="warn_max">100</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="warn_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="warn_timeModulate">true</property>

    </Properties>

    <!--定义appender -->
    <appenders>
        <!-- ==================== 用来定义输出到控制台的配置 ==================== -->
        <Console name="Console" target="SYSTEM_OUT">
            <!-- 设置输出格式,不设置默认为:%m%n -->
            <PatternLayout charset="UTF-8" pattern="${log_pattern}"/>
        </Console>

        <!--kafka的配置-->
<!--        <Kafka name="kafkaLog" topic="backend_log">-->
<!--            <PatternLayout charset="UTF-8" pattern="${log_pattern}"/>-->
<!--            &lt;!&ndash;kakfa集群的各个节点:host:port,以逗号分隔&ndash;&gt;-->
<!--            <Property name="bootstrap.servers">${env:KAFKA_LOG_SERVER:-172.20.64.13:9092}</Property>-->
<!--        </Kafka>-->

        <!-- ==================== 打印root中指定的level级别以上的日志到文件 ==================== -->
        <RollingFile name="RollingFile" fileName="${rolling_fileName}" filePattern="${rolling_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${rolling_timeInterval}" modulate="${warn_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <!-- 设置同类型日志,同一文件夹下可以存放的数量,如果不设置此属性则默认存放7个文件 -->
            <DefaultRolloverStrategy max="${rolling_max}">
                <Delete basePath="${basePath}" maxDepth="2">
                    <IfFileName glob="*.log">
                        <IfLastModified age="60d">
                            <IfAny>
                                <IfAccumulatedFileSize exceeds="10 GB" />
                            </IfAny>
                        </IfLastModified>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- ==================== 打印ERROR WARN级别的日志到文件 ==================== -->
        <RollingFile name="WarnFile" fileName="${warn_fileName}" filePattern="${warn_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${warn_timeInterval}" modulate="${warn_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <DefaultRolloverStrategy max="${warn_max}"/>
            <Filters>
                <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>
    </appenders>

    <!--定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--建立一个默认的root的logger-->
        <root level="${log_level}">
            <appender-ref ref="Console"/>
<!--            <appender-ref ref="kafkaLog"/>-->
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="WarnFile"/>
        </root>
        <logger name="cn.seres" level="${seres_log_level}"
                additivity="false">
            <appender-ref ref="Console"/>
<!--            <appender-ref ref="kafkaLog"/>-->
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="WarnFile"/>
        </logger>
        <!-- 设置spring包下的日志只打印WARN及以上级别的日志 -->
        <logger name="org.springframework" level="${middleware_log_level}" additivity="false">
            <appender-ref ref="Console"/>
<!--            <appender-ref ref="kafkaLog"/>-->
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="WarnFile"/>
        </logger>
        <!-- 设置kafka下的日志只打印WARN及以上级别的日志 -->
        <logger name="org.apache.kafka" level="${middleware_log_level}" additivity="false">
            <appender-ref ref="Console"/>
<!--            <appender-ref ref="kafkaLog"/>-->
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="WarnFile"/>
        </logger>
    </loggers>

</configuration>

案例3 resources/log4j.properties [2023-02-08]

##################### [0] 自定义配置(可灵活修改) #####################
## 日志的等级(自定义配置项)
##log4j.log.level=ALL,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF
log4j.log.level=DEBUG
log4j.log.threshold=${log4j.log.level}

## 2 个 target(自定义配置项)
# log4j.log.target=CONSOLE,FILE,DATABASE,EMAIL,SOCKET
# 当前聚焦的 Target
log4j.log.target=CONSOLE
# 文件输出的 Target
log4j.log.fileTarget=MyFileAppender
# 链路追踪客户端的 Target
log4j.log.linkTraceClientTarget=MySkyWalkingClientAppender

log4j.log.encoding=UTF-8

## 日志输出的地址(自定义配置项): 可任意配置, logs/存储在当前项目中   e:/logs
log4j.log.dir=./logs

## org.apache.log4j.HTMLLayout (以HTML表格形式布局),
## org.apache.log4j.PatternLayout (可以灵活地指定布局模式),
## org.apache.log4j.SimpleLayout (包含日志信息的级别和信息字符串),
## org.apache.log4j.TTCCLayout (包含日志产生的时间、线程、类别等等信息)
# log4j.log.layout=org.apache.log4j.PatternLayout
log4j.log.layout=org.apache.skywalking.apm.toolkit.log.log4j.v1.x.TraceIdPatternLayout
## log4j.appender.*.layout=org.apache.skywalking.apm.toolkit.log.log4j.v1.x.TraceIdPatternLayout
log4j.log.layout.pattern=%d %-5p | %T | %t | (%C{1}.java:%L %M) | %m%n
## log4j.log.layout.pattern=%d %-5p %t (%C{1}.java:%L %M) %m%n
## log4j.log.layout.pattern=[%d %r] [%-5p] [%t] [%l] [%m]%n
## log4j.log.layout.pattern=%d{yyyy-MM-dd HH\:mm\:ss} %-5p[%t] : %m%n

##################### [1] 定义 Logger #####################
# ------------------- [1.1] 定义 RootLogger 等 全局性配置(不可随意修改) ------------------- #
## log4j.rootLogger = [ level ] , appenderName, appenderName, ...
# log4j.rootLogger=${log4j.log.level},${log4j.log.target},${log4j.log.fileTarget}
log4j.rootLogger=${log4j.log.level},${log4j.log.target},${log4j.log.fileTarget},${log4j.log.linkTraceClientTarget}
log4j.additivity.org.apache=false

# ------------------- [1.2] 指定个别 Class 的 Logger (可随意修改,建议在 nacos 上修改) ------------------- #
log4j.logger.org.springframework=WARN
# log4j.logger.org.springframework.context=WARN
# log4j.logger.org.springframework.core=WARN
# log4j.logger.org.springframework.beans.factory=WARN
log4j.logger.org.mybatis.logging=WARN
log4j.logger.com.baomidou.mybatisplus=WARN
log4j.logger.com.alibaba.nacos.shaded=WARN

##################### [2] 定义 Appender #####################

# ------------------- [2.1] CONSOLE Appender ------------------- #
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
## Threshold: 当前 Appender 输出日志的最低日志级别。例如 rootLogger=DEBUG, 当前 Appender 的 Threshold=ERROR, 则: 当前 Appender 输出日志仅输出 ERROR 信息
# log4j.appender.CONSOLE.Threshold=${log4j.log.threshold}
log4j.appender.CONSOLE.layout.ConversionPattern=${log4j.log.layout.pattern}
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.Encoding=${log4j.log.encoding}
log4j.appender.CONSOLE.layout=${log4j.log.layout}

# ------------------- [2.2] MyFileAppender Appender ------------------- #
log4j.appender.MyFileAppender=org.apache.log4j.DailyRollingFileAppender
log4j.appender.MyFileAppender.file=${log4j.log.dir}/bdp_data_service.log
log4j.appender.MyFileAppender.datePattern='_'yyyy-MM-dd
log4j.appender.MyFileAppender.encoding=${log4j.log.encoding}
#log4j.appender.MyFileAppender.Threshold=${log4j.log.threshold}
log4j.appender.MyFileAppender.layout=${log4j.log.layout}
log4j.appender.MyFileAppender.layout.ConversionPattern=${log4j.log.layout.pattern}

# ------------------- [2.3] MySkyWalkingClientAppender Appender ------------------- #
## 依赖 JAR 包 : org.apache.skywalking:apm-toolkit-log4j-1.x:8.5.0 , org.apache.skywalking:apm-toolkit-trace:8.5.0
## Skywalking 的 Layout 所支持的`ConversionPattern`核心参数在`apm-toolkit-log4j-1.x`插件中是`T`(即 `traceId`, 区分大小写[T≠t]),在`apm-toolkit-logback-1.x`插件中是`%X{tid}`,需打印到日志中,才能被 Skywalking OAP 服务的`Collertor`收集并识别请求链路中的日志信息,否则日志收集失败。
log4j.appender.MySkyWalkingClientAppender=org.apache.skywalking.apm.toolkit.log.log4j.v1.x.log.GRPCLogClientAppender
log4j.appender.MySkyWalkingClientAppender.Threshold=${log4j.log.threshold}
log4j.appender.MySkyWalkingClientAppender.layout=${log4j.log.layout}
log4j.appender.MySkyWalkingClientAppender.layout.ConversionPattern=${log4j.log.layout.pattern}

6 参考文献

posted @ 2020-06-03 22:05  千千寰宇  阅读(636)  评论(0编辑  收藏  举报