问题描述
- log4j配置文件中没配置应用目录的appender,为什么会有日志输出?
- 关闭了log4j中logger节点的日志打印,为什么catalina.out文件仍然有日志输出?
- 为什么应用日志会在appender指定的目录及catalina.out文件中重复输出?
简易log4j XML 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Properties>
<Property name="var.log.level">DEBUG</Property>
<Property name="var.log.path.dir">./</Property>
<Property name="var.output.pattern">[%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%l] %m%n</Property>
</Properties>
<Appenders>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="${var.output.pattern}" />
</Console>
<Console name="stdout1" target="SYSTEM_ERR">
<PatternLayout pattern="${var.output.pattern}" />
</Console>
</Appenders>
<Loggers>
<AsyncRoot level="INFO" includeLocation="true">
<AppenderRef ref="stdout" />
</AsyncRoot>
<AsyncLogger name="com.example.log"
level="WARN"
includeLocation="true"
additivity="true">
<AppenderRef ref="stdout1"/>
</AsyncLogger>
</Loggers>
</Configuration>
解决
log框架的继承关系-解决问题1
- 只配置了com.example.log目录的logger,但是com.example.log.logTest目录也正常输出了日志,这个是由于log框架日志继承关系导致
- 在日志打印过程中会寻找对应的目录Logger进行日志输出,当前目录不存在Logger,会寻找上级目录Logger
com.example.log
com.example;
com;
- 寻找logger到应用的跟目录仍然未找到,使用rootLogger进行日志输出,rootLogger默认level为ERROR,输出方式为SYSTEM_OUT,输出到控制台
配置文件中Console节点与AsyncRoot节点-解决问题2
- Console节点是特殊的appender,无需指定目录,默认输出到控制台,tomcat会输出到catalina.out文件中
- AsyncRoot(rootLogger)节点是特殊的Logger,指定rootLogger的日志级别及输出目录,AppenderRef一般设置为Console,未配置对应的目录logger会,输出到控制台
配置文件appender中的additivity属性-解决问题3
- additivity是子Logger是否继承父Logger的输出源(appender)的标志位。
- 默认值为true,默认情况下子Logger会继承父Logger的appender,也就是说子Logger会在父Logger的appender里输出。
- 若是additivity设为false,则子Logger只会在自己的appender里输出,而不会在父Logger的appender里输出。
demo additivity=true
-
xml配置
xml配置 - 日志输出结果-自己的appender及父Logger的appender都输出
日志输出
demo additivity=false
-
xml配置
xml配置 - 日志输出结果-只在自己的appender里输出
日志输出结果
统一修改log框架日志级别
log4j方式
- 全局配置,包含rootLogger日志级别
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration configuration = context.getConfiguration();
for (Map.Entry<String, LoggerConfig> entry : configuration.getLoggers().entrySet()) {
if (entry.getValue().getLevel().intLevel() != level.intLevel()) {
entry.getValue().setLevel(Level.ERROR);
}
}
context.updateLoggers(configuration);
- 单独设置Logger日志级别
// log4j中""为rootLogger的path
Configurator.setLevel("",Level.ERROR);
Configurator.setLevel("com.example.log.logTest",Level.ERROR);
logback方式
- 全局配置,包含rootLogger日志级别
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
List<ch.qos.logback.classic.Logger> loggerList = loggerContext.getLoggerList();
loggerList.forEach(logger1 -> {
logger1.setLevel(level);
});
- 单独设置Logger日志级别
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
// log4j中 ROOT 为rootLogger的path,默认DEBUG
loggerContext.getLogger("ROOT").setLevel(Level.ERROR);
建议
- 动态调整日志级别需要调整rootLogger节点的日志基本
避免降级到rootLogger,导致日志输出调整不完整 - 合理设置设置不同目录的日志级别
比如spring/dubbo/mq的日志可以设置为ERROR,且独立设置日志文件
mybatis如果需要看sql日志,可以设置为DEBUG,生产可以ERROR
我们应用目录的日志文件,需要独立设置文件 - 合理设置日志文件生成策略
设置合理的滚动方式(时间滚动/文件大小)
设置合理的单个文件文件大小及文件个数
设置正确的日志输出格式,可参考https://www.jianshu.com/p/de1ca64f2a78 - 有目的的合理打印日志
日志需要包含明确的业务意义
打印对象需要重写toString或者json序列化
json序列化需要明确的了解序列化对象的结构,避免由于打印日志导致系统异常
打印json序列化对象,需要判断当前日志级别,避免需用的json序列化
参考Log4j配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Properties>
<Property name="var.log.level">ERROR</Property>
<Property name="var.log.path.dir">/tmp/com.abc.local</Property>
<Property name="var.output.pattern">[%-5p %d{yyyy-MM-dd HH:mm:ss.SSS}] [%t] [%l] %m%n</Property>
</Properties>
<Appenders>
<Console name="stdout" target="SYSTEM_ERR">
<PatternLayout pattern="${var.output.pattern}" />
</Console>
<RollingRandomAccessFile bufferSize="1024" name="framework-output" fileName="${var.log.path.dir}/framework-output.log" filePattern="${var.log.path.dir}/framework-output.log.%i">
<PatternLayout>
<Pattern>${var.output.pattern}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="100MB" />
</Policies>
<DefaultRolloverStrategy max="5" />
</RollingRandomAccessFile>
<RollingRandomAccessFile bufferSize="1024" name="proj-output"
fileName="${var.log.path.dir}/proj-output.log"
filePattern="${var.log.path.dir}/proj-output.%d{yyyy-MM-dd HH}.%i.log"
>
<PatternLayout>
<Pattern>${var.output.pattern}</Pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="500MB" />
<!--时间滚动-->
<TimeBasedTriggeringPolicy interval="1" modulate="false"/>
</Policies>
<DefaultRolloverStrategy max="4">
<Delete basePath="${var.log.path.dir}/" maxDepth="1">
<!--? 此处会删除目录下所有符合条件的log -->
<IfFileName glob="*.log"/>
<!--!Note: 这里的age必须和filePattern协调, 后者是精确到HH, 这里就要写成xH, xd就不起作用
另外, 数字最好>2, 否则可能造成删除的时候, 最近的文件还处于被占用状态,导致删除不成功!-->
<IfLastModified age="1H" />
</Delete>
</DefaultRolloverStrategy>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<AsyncRoot level="${var.log.level}" includeLocation="true">
<AppenderRef ref="stdout" />
</AsyncRoot>
<!-- log4spring -->
<AsyncLogger name="org.springframework" level="ERROR" includeLocation="true" additivity="false">
<AppenderRef ref="framework-output" />
</AsyncLogger>
<!-- log4jdbc -->
<AsyncLogger name="jdbc.sqltiming" level="INFO" includeLocation="true" additivity="false">
<AppenderRef ref="framework-output" />
</AsyncLogger>
<!-- zookeeper -->
<AsyncLogger name="org.apache.zookeeper" level="WARN" includeLocation="true" additivity="false">
<AppenderRef ref="framework-output" />
</AsyncLogger>
<!-- apache activemq -->
<AsyncLogger name="org.apache.activemq" level="WARN" includeLocation="true" additivity="false">
<AppenderRef ref="framework-output" />
</AsyncLogger>
<!-- project loggers begin -->
<AsyncLogger name="com.abc.web" level="${var.log.level}" includeLocation="true" additivity="false">
<AppenderRef ref="proj-output" />
</AsyncLogger>
<!-- project loggers end -->
</Loggers>
</Configuration>
网友评论