Java日志体系

一、相关概念

1.1、 日志门面

       日志门面是一个对外的、统一的日志接口,它不是具体的日志实现,需要和log4j、logback这样的实现框架配合使用。现在最流行的日志门面是slf4j,按照官方的说法,slf4j是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用所希望的日志系统。

问:为什么要使用日志门面?直接调用log4j等实现框架的API不行吗?
答:直接调用实现框架的API当然可以,但如果未来某一天有比log4j性能更好的实现框架,那升级替换可就是个大工程。另外,如果现有工程依赖了两个模块,两个模块分别用不同的日志实现方案,那现有工程就需要维护两套日志配置。但有了日志门面后一切都变了,我们仅需要引入一种实现,也只需要维护这一种实现的配置文件,其他的实现全部桥接到门面即可,往后如果需要升级日志实现,也只需要升级一个jar包、一个配置文件即可。

1.2、桥接器

       我们在开发中当然可以只使用一种日志实现方案,但引用的第三方依赖可就不一定了,为了统一日志配置,我们需要将第三方依赖的日志实现”引流”到我们自己的日志框架上,这个“引流”的功能就叫做桥接器。桥接器仅能路由API层面的调用,无法路由底层实现。

1.3、日志实现

日志实现就是真正打日志干活的部分,Java中日志实现太多了,常见的有log4j、log4j2、jcl、logback等。

二、各种Jar包及功能一览

jar包 类型 转换方向 描述 注意事项
jcl-over-slf4j 桥接包 jcl -> slf4j 将Jakarta Commons Logging日志框架桥接到slf4j
jul-to-slf4j 桥接包 juc -> slf4j 将java.util.logging的日志桥接到slf4j
osgi-over-slf4j 桥接包 osgi -> slf4j 将osgi环境下的日志桥接到slf4j
slf4j-android 桥接包 android -> slf4j 将android环境下的日志桥接到slf4j
log4j-over-slf4j 桥接包 log4j -> slf4j 将log4j的日志桥接到slf4j 不能和slf4j-log4j12同时用
log4j-to-slf4j 桥接包 log4j2 -> slf4j 将log4j2的日志桥接到slf4j 不能和log4j-slf4j-impl同时用
slf4j-api slf4j门面 slf4j的api接口jar包
slf4j-ext slf4j门面 slf4j扩展功能jar包
log4j-api 实现包 log4j2的api接口jar包
log4j-core 实现包 log4j2的日志输出核心jar包
log4j 实现包 log4j实现包(1.2)
slf4j-migrator 实现包 一个GUI工具,支持将代码中其他日志API转换为slf4j的写法
slf4j-jcl slf4j+jcl打包 slf4j -> jcl slf4j采用Jakarta Commons Logging日志框架实现
slf4j-jdk14 slf4j+jul打包 slf4j -> jul slf4j采用java.util.logging实现 不能和jul-to-slf4j同时用
slf4j-log4j12 slf4j+log4j12打包 slf4j -> log4j slf4j采用log4j实现(12表示1.2版本) 不能和log4j-over-slf4j同时用
log4j-slf4j-impl slf4j+log4j2打包 slf4j -> log4j2 slf4j采用log4j2实现 不能和log4j-to-slf4j同时用
slf4j-nop slf4j+无输出打包 slf4j -> null slf4j的空接口输出绑定,丢弃所有日志输出
slf4j-simple sl4j+简单实现打包 slf4j -> slf4j-simple slf4j的自带的简单日志输出实现
logback-classic slf4j+logback打包 slf4j -> logback slf4j采用logback实现
logback-core logback核心依赖
logback-access 与servlet容器集成,提供通过http访问日志功能
log4j-1.2-api log4j桥接到log4j2 log4j -> log4j2 将log4j的日志桥接到log4j2日志框架

Note:一般地,x-over-y.jar表示将x日志桥接到y上,桥接动作只能路由API层面的调用,比如org.apache.log4j.Logger#getLogger(java.lang.Class),而不能路由底层实现调用,如果代码中引用了日志的具体实现,比如appenders、filters等,桥接包将对其不起作用,因此在代码中直接依赖日志底层实现的行为都应该被鄙视。

三、常见使用搭配

1、使用slf4j日志门面

  1. slf4j + log4j(已过时):引入slf4j-log4j12即可,另外可根据需求加入桥接包,不可与log4j-over-slf4j同时使用,会引发StackOverflowError异常。
  2. slf4j + log4j2(流行):引入log4j-slf4j-impl即可,另外可根据需求加入桥接包,不可与log4j-to-slf4j同时使用,会引发StackOverflowError异常。
  3. slf4j + logback(最优):引入 logback-classic即可,另外可根据需求加入桥接包。logback与slf4j是同一人开发,兼容性很好,springboot默认就采用logback方案。

2、各实现单独使用,如log4j、log4j2等

  1. log4j:引入log4j.jar即可,上古程序员的最爱,不推荐。
  2. log4j2:引入log4j-apilog4j-core即可,可根据需要引入log4j-1.2-api将log4j 1.x桥接到log4j2。

强烈不推荐单独使用日志实现,请按阿里巴巴Java代码规范中【日志规约】编码。

四、常见问题

  1. 找到多个slf4j实现(Class path contains multiple SLF4J bindings.)

    这种情况是上面的slf4j+xxx打包这样的jar包引入了两类导致的,我们根据需要保留一类,排除其他的就可以了。

  2. jar包不能共存,或者直接报StackOverflowError异常

    这种情况大多数原因是实现包与实现包对应的桥接包同时引入了,打印日志的时候就会发生死循环方法调用,一直到栈溢出,实现比较好的包如log4j-slf4j-impl,会在启动时告诉你两者不能共存。总的来说,实现和桥接只能选一个

  3. 引入桥接包后仍提示xxxClassNotFoundxxxMethodNotFound,一般是代码中直接调用了日志底层实现,这种情况无法桥接,只能使用原始日志方案。

  4. 其他异常情况大多数是由版本不兼容导致的,请参考slf4j错误含义及解决方法

  5. log4j(也称log4j12)与log4j2的区别:Log4j与Log4j2的区别

五、常见日志配置

5.1 log4j配置

  1. 基于XML的配置

    点击展开代码 参考文档
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

    <log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/' >

    <!-- ========================== 自定义输出格式说明======================== -->
    <!-- %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL -->
    <!-- %r 输出自应用启动到输出该log信息耗费的毫秒数 -->
    <!-- %c 输出所属的类目,通常就是所在类的全名 -->
    <!-- %t 输出产生该日志事件的线程名 -->
    <!-- %n 输出一个回车换行符,Windows平台为“/r/n”,Unix平台为“/n” -->
    <!-- %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 -->
    <!-- %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlo4.main(TestLog4.java:10) -->
    <!-- ============================================================== -->

    <!-- ========================== 输出方式说明========================= -->
    <!-- Log4j提供的appender有以下几种: -->
    <!-- org.apache.log4j.ConsoleAppender(控制台), -->
    <!-- org.apache.log4j.FileAppender(文件), -->
    <!-- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件), -->
    <!-- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件), -->
    <!-- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) -->
    <!-- ================================================================== -->

    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
    <!-- <param name="Target" value="System.out"/> -->
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/>
    </layout>
    <!-- <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="LevelMin" value="DEBUG"/>
    <param name="LevelMax" value="DEBUG"/>
    </filter> -->
    </appender>
    <!-- output the debug -->
    <!-- <appender name="log4jDebug" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="File" value="log_"/>
    <param name="MaxFileSize" value="KB"/>
    <param name="MaxBackupIndex" value="2"/> -->
    <appender name="log4jDebug" class="org.apache.log4j.rolling.RollingFileAppender">
    <param name="Append" value="true"/>
    <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
    <param name="FileNamePattern" value="./log/log_%d{yyyy-MM-dd}.log" />
    </rollingPolicy>
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="LevelMin" value="DEBUG"/>
    <param name="LevelMax" value="DEBUG"/>
    </filter>
    </appender>
    <!-- <appender name="log4jInfo" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="File" value="log_"/>
    <param name="DatePattern" value="'.log'yyyy-MM-dd"/>
    <param name="Append" value="true"/>
    <param name="MaxFileSize" value="5KB"/>
    <param name="MaxBackupIndex" value="2"/> -->
    <appender name="log4jInfo" class="org.apache.log4j.rolling.RollingFileAppender">
    <param name="Append" value="true"/>
    <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
    <param name="FileNamePattern" value="./log/log_%d{yyyy-MM-dd}.log" />
    </rollingPolicy>
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="LevelMin" value="INFO"/>
    <param name="LevelMax" value="INFO"/>
    </filter>
    </appender>
    <!-- <appender name="log4jWarn" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="File" value="/log_"/>
    <param name="DatePattern" value="'.log'yyyy-MM-dd"/>
    <param name="Append" value="true"/>
    <param name="MaxFileSize" value="5KB"/>
    <param name="MaxBackupIndex" value="2"/> -->
    <appender name="log4jWarn" class="org.apache.log4j.rolling.RollingFileAppender">
    <param name="Append" value="true"/>
    <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
    <param name="FileNamePattern" value="./log/log_%d{yyyy-MM-dd}.log" />
    </rollingPolicy>
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="LevelMin" value="WARN"/>
    <param name="LevelMax" value="WARN"/>
    </filter>
    </appender>
    <!-- <appender name="log4jError" class="org.apache.log4j.DailyRollingFileAppender"> -->
    <appender name="log4jError" class="org.apache.log4j.rolling.RollingFileAppender">
    <!-- <param name="File" value="/error_"/>
    <param name="DatePattern" value="'.log'yyyy-MM-dd"/> -->
    <param name="Append" value="true"/>
    <rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
    <param name="FileNamePattern" value="./log/error_%d{yyyy-MM-dd}.log" />
    </rollingPolicy>

    <!-- <param name="MaxFileSize" value="5KB"/> -->
    <!-- <param name="MaxBackupIndex" value="2"/> -->
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c Method: %l ]%n%p:%m%n"/>
    </layout>
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
    <param name="LevelMin" value="ERROR"/>
    <param name="LevelMax" value="ERROR"/>
    </filter>
    </appender>
    <!--通过<category></category>的定义可以将各个包中的类日志输出到不同的日志文件中-->
    <!-- <category name="com.gzy">
    <priority value="debug" />
    <appender-ref ref="log4jTestLogInfo" />
    <appender-ref ref="log4jTestDebug" />
    </category> -->
    <appender name="MAIL"
    class="org.apache.log4j.net.SMTPAppender">
    <param name="threshold" value="debug" />
    <!-- 日志的错误级别
    <param name="threshold" value="error"/>
    -->
    <!-- 缓存文件大小,日志达到512K时发送Email -->
    <param name="BufferSize" value="512" /><!-- 单位K -->
    <param name="From" value="[email protected]" />
    <param name="SMTPHost" value="smtp.163.com" />
    <param name="Subject" value="juyee-log4jMessage" />
    <param name="To" value="[email protected]" />
    <param name="SMTPUsername" value="test" />
    <param name="SMTPPassword" value="test" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern"
    value="%-d{yyyy-MM-dd HH:mm:ss.SSS a} [%p]-[%c] %m%n" />
    </layout>
    </appender>

    <root>
    <priority value="debug"/>
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="log4jDebug" />
    <appender-ref ref="log4jInfo" />
    <appender-ref ref="log4jWarn" />
    <appender-ref ref="log4jError" />
    <!-- <appender-ref ref="MAIL" /> -->
    </root>
    </log4j:configuration>
  2. 基于properties文件的配置

    点击展开代码 参考文档
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    # priority  :debug<info<warn<error
    #you cannot specify every priority with different file for log4j
    log4j.rootLogger=debug,stdout,info,debug,warn,error

    #console
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
    #info log
    log4j.logger.info=info
    log4j.appender.info=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'
    log4j.appender.info.File=./src/com/hp/log/info.log
    log4j.appender.info.Append=true
    log4j.appender.info.Threshold=INFO
    log4j.appender.info.layout=org.apache.log4j.PatternLayout
    log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
    #debug log
    log4j.logger.debug=debug
    log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log'
    log4j.appender.debug.File=./src/com/hp/log/debug.log
    log4j.appender.debug.Append=true
    log4j.appender.debug.Threshold=DEBUG
    log4j.appender.debug.layout=org.apache.log4j.PatternLayout
    log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
    #warn log
    log4j.logger.warn=warn
    log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log'
    log4j.appender.warn.File=./src/com/hp/log/warn.log
    log4j.appender.warn.Append=true
    log4j.appender.warn.Threshold=WARN
    log4j.appender.warn.layout=org.apache.log4j.PatternLayout
    log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
    #error
    log4j.logger.error=error
    log4j.appender.error = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log'
    log4j.appender.error.File = ./src/com/hp/log/error.log
    log4j.appender.error.Append = true
    log4j.appender.error.Threshold = ERROR
    log4j.appender.error.layout = org.apache.log4j.PatternLayout
    log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n

5.2 log4j2配置

点击展开代码 参考文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?> 
<Configuration status="WARN">
<!-- 先定义所有的appender -->
<Appenders>
<!-- 输出控制台 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 这个都知道是输出日志的格式 -->
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>

<!-- 输出至文件,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用 -->
<!-- append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true -->
<File name="log" fileName="log/test.log" append="false">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!-- 添加过滤器ThresholdFilter,可以有选择的输出某个级别以上的类别 onMatch="ACCEPT" onMismatch="DENY"意思是匹配就接受,否则直接拒绝 -->
<File name="ERROR" fileName="logs/error.log">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
</File>

<!-- 这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="RollingFile" fileName="logs/web.log" filePattern="logs/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} %-5level %class{36} %L %M - %msg%xEx%n"/>
<SizeBasedTriggeringPolicy size="2MB"/>
</RollingFile>
</Appenders>

<!-- 然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<Loggers>
<!-- 建立一个默认的root的logger -->
<Root level="trace">
<AppenderRef ref="RollingFile"/>
<AppenderRef ref="Console"/>
<AppenderRef ref="ERROR" />
<AppenderRef ref="log"/>
</Root>
</Loggers>
</Configuration>

5.3 logback配置

点击展开代码 参考文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true" scan="true" scanPeriod="1 seconds">

<contextName>logback</contextName>
<!--定义参数,后面可以通过${app.name}使用-->
<property name="app.name" value="logback_test"/>
<!--ConsoleAppender 用于在屏幕上输出日志-->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!--定义了一个过滤器,在LEVEL之下的日志输出不会被打印出来-->
<!--这里定义了DEBUG,也就是控制台不会输出比ERROR级别小的日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<!-- encoder 默认配置为PatternLayoutEncoder -->
<!--定义控制台输出格式-->
<encoder>
<pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern>
</encoder>
</appender>

<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--定义日志输出的路径-->
<!--这里的scheduler.manager.server.home 没有在上面的配置中设定,所以会使用java启动时配置的值-->
<!--比如通过 java -Dscheduler.manager.server.home=/path/to XXXX 配置该属性-->
<file>${scheduler.manager.server.home}/logs/${app.name}.log</file>
<!--定义日志滚动的策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--定义文件滚动时的文件名的格式-->
<fileNamePattern>${scheduler.manager.server.home}/logs/${app.name}.%d{yyyy-MM-dd.HH}.log.gz
</fileNamePattern>
<!--60天的时间周期,日志量最大20GB-->
<maxHistory>60</maxHistory>
<!-- 该属性在 1.1.6版本后 才开始支持-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!--每个日志文件最大100MB-->
<maxFileSize>100MB</maxFileSize>
</triggeringPolicy>
<!--定义输出格式-->
<encoder>
<pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern>
</encoder>
</appender>

<!--root是默认的logger 这里设定输出级别是debug-->
<root level="trace">
<!--定义了两个appender,日志会通过往这两个appender里面写-->
<appender-ref ref="stdout"/>
<appender-ref ref="file"/>
</root>

<!--对于类路径以 com.example.logback 开头的Logger,输出级别设置为warn,并且只输出到控制台-->
<!--这个logger没有指定appender,它会继承root节点中定义的那些appender-->
<logger name="com.example.logback" level="warn"/>

<!--通过 LoggerFactory.getLogger("mytest") 可以获取到这个logger-->
<!--由于这个logger自动继承了root的appender,root中已经有stdout的appender了,自己这边又引入了stdout的appender-->
<!--如果没有设置 additivity="false" ,就会导致一条日志在控制台输出两次的情况-->
<!--additivity表示要不要使用rootLogger配置的appender进行输出-->
<logger name="mytest" level="info" additivity="false">
<appender-ref ref="stdout"/>
</logger>

<!--由于设置了 additivity="false" ,所以输出时不会使用rootLogger的appender-->
<!--但是这个logger本身又没有配置appender,所以使用这个logger输出日志的话就不会输出到任何地方-->
<logger name="mytest2" level="info" additivity="false"/>
</configuration>

附录:阿里巴巴Java代码规范手册

文章作者: Jack.Charles
文章链接: https://blog.zjee.me/2020/01/05/java-log-system/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 江影不沉浮