项目中日志的xml文件结构很复杂,这里逐步解析记录下,还有相应的参考教程。
<?xml version='1.0' encoding="UTF-8" ?>
<Configuration>
<Properties>
<!-- 定义日志文件的存储地址 -->
<Property name="log-dir">/export/Logs/jx-search-web</Property>
<!--
%p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r:输出自应用启动到输出该日志讯息所耗费的毫秒数
%t:输出产生该日志事件的线程名
%f:输出日志讯息所属的类别的类别名
%c:输出日志讯息所属的类的全名
%d:输出日志时间点的日期或时间,指定格式的方式: %d{yyyy-MM-dd HH:mm:ss}
%l:输出日志事件的发生位置,即输出日志讯息的语句在他所在类别的第几行。
%m:输出代码中指定的讯息,如log(message)中的message
%n:输出一个换行符号
-->
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<Property name="output-pattern">%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{100} %msg%n</Property>
</Properties>
<Appenders>
<!-- 当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类 -->
<RollingFile name="JX-SEREACH-DEBUG"
fileName="${log-dir}/debug.log"
immediateFlush="false" append="true" bufferedIO="true"
filePattern="${log-dir}/debug-%d{yyyy-MM-dd}-%i.log">
<!-- LevelFilter: 级别过滤器,根据日志级别进行过滤 -->
<!-- 用于配置符合过滤条件的操作 ACCEPT:日志会被立即处理,不再经过剩余过滤器 -->
<!-- 这里设置一个INFO,高于INFO及以上的不管,低于则流转到下一个 -->
<ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
<!-- 这里设置一个DEBUG,直接打印 -->
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${output-pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
<RollingFile name="JX-SEREACH-INFO"
fileName="${log-dir}/info.log"
filePattern="${log-dir}/info-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${output-pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
<RollingFile name="JX-SEREACH-WARN"
fileName="${log-dir}/warn.log"
immediateFlush="false" append="true" bufferedIO="true"
filePattern="${log-dir}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${output-pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
<RollingFile name="JX-SEREACH-ERROR"
fileName="${log-dir}/error.log"
immediateFlush="false" append="true" bufferedIO="true"
filePattern="${log-dir}/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="ERROR" onMatch="ACCEPT"/>
<PatternLayout pattern="${output-pattern}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="500MB"/>
</Policies>
</RollingFile>
<RollingFile name="JX-SEREACH-MANAGER"
fileName="${log-dir}/manager.log"
immediateFlush="false" append="true" bufferedIO="true"
filePattern="${log-dir}/manager-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${output-pattern}"/>
<TimeBasedTriggeringPolicy/>
</RollingFile>
<RollingFile name="JX-SEREACH-ACCESS"
fileName="${log-dir}/access.log"
immediateFlush="false" append="true" bufferedIO="true"
filePattern="${log-dir}/access-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${output-pattern}"/>
<TimeBasedTriggeringPolicy/>
</RollingFile>
</Appenders>
<Loggers>
<AsyncRoot level="WARN">
<AppenderRef ref="JX-SEREACH-DEBUG"/>
<AppenderRef ref="JX-SEREACH-INFO"/>
<AppenderRef ref="JX-SEREACH-WARN"/>
<AppenderRef ref="JX-SEREACH-ERROR"/>
</AsyncRoot>
<AsyncLogger name="com.jx.search.logging.manager" level="WARN" additivity="false">
<AppenderRef ref="JX-SEREACH-MANAGER"/>
</AsyncLogger>
<AsyncLogger name="com.jx.search.logging.access" level="INFO" additivity="false">
<AppenderRef ref="JX-SEREACH-ACCESS"/>
</AsyncLogger>
</Loggers>
</Configuration>
项目中想在日志中加几个功能:
1、支持配置动态调整某个Logger的日志级别
2、支持配置白名单,以无视日志级别的方式全量打印该用户的请求所经过的所有日志
3、支持收集log.info()、log.error等函数打印的、格式化好的message,应用侧可以自行拓展。如搜索中收集了之后输出给前端,在测试阶段大大提升debug效率
4、解决了当程序打印日志的速度 > Log4j2将日志写入到文件的速度时,打满队列导致的阻塞问题(多余日志使用丢弃策略)
log4j2中,当使用异步Logger时日志event会写入到Disruptor环形队列中并立刻返回,再由一个线程异步执行appender的打印,默认情况下当Disruptor队列打满时,会阻塞打印日志的线程的入队操作。
使用log4j2.component.properties配置将队列打满的策略设置为丢弃。
以SDK方式提供代码需要以编程的方式来实现这个配置,目前还在探索;
前三个都能通过log4j2的FIlter机制实现。
一些关于log4j2的介绍:
主要组件之间的关系
使用 Log4j 2 API 的应用程序将从 LogManager 请求具有特定名称的 Logger。 LogManager 将找到适当的 LoggerContext,然后从中获取 Logger。如果必须创建 Logger,它将与 LoggerConfig 关联,该 LoggerConfig 包含 a)与 Logger 相同的名称,b)父程序包的名称或 c)根 LoggerConfig。 LoggerConfig 对象是根据配置中的 Logger 声明创建的。 LoggerConfig 与实际提供 LogEvent 的 Appender 关联。
Logger 层级继承
在 Log4j 1.x 中,通过 Logger 之间的关系维护 Logger 层次结构。在 Log4j 2 中,此关系不再存在。而是在 LoggerConfig 对象之间的关系中维护层次结构。
Logger 和 LoggerConfigs 是命名实体。Logger 名称区分大小写,并且遵循分层命名规则:
如果 LoggerConfig 的名称后跟一个点,则该 LoggerConfig 被称为另一个 LoggerConfig 的祖先。如果 LoggerConfig 与子 LoggerConfig 之间没有祖先,则称该 LoggerConfig 为子 LoggerConfig 的父级。
例如,名为“ com.foo”的 LoggerConfig 是名为“ com.foo.Bar”的 LoggerConfig 的父级。同样,“ java”是“ java.util”的父代,也是“ java.util.Vector”的祖先。大多数开发人员都应该熟悉这种命名方案。
根 LoggerConfig 位于 LoggerConfig 层次结构的顶部。它的特殊之处在于它始终存在,并且是每个层次结构的一部分。直接链接到根 LoggerConfig 的 Logger 可以通过以下方式获得:
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
Logger logger = LogManager.getRootLogger();
LogEvent
在调用Logger对象的info、error、trace等函数时,就会产生LogEvent。LogEvent跟LoggerConfig一样,也是由Level的。LogEvent的Level主要是用在Event传递时,判断在哪里停下。
FIlter机制
除了上一节所述的自动日志级别过滤之外,Log4j 还提供了Filter,可以在将控制权传递给任何 LoggerConfig 之前,将控制权传递给 LoggerConfig 之后但在调用任何 Appender 之前,在控制完成之后应用Filter。传递给 LoggerConfig,但在调用特定的 Appender 之前以及在每个 Appender 上传递。以与防火墙过滤器非常相似的方式,每个过滤器可以返回以下三个结果之一:“接受”,“拒绝”或“中性”。接受的响应表示不应调用其他任何筛选器,并且事件应 continue 进行。拒绝的响应意味着应立即忽略该事件,并将控制权返回给调用方。中性的响应表示该事件应传递给其他过滤器。如果没有其他过滤器,则事件将被处理。
Filter允许对日志事件进行评估,以确定是否或如何发布它们。一个过滤器将使用其一种过滤方法被调用并返回一个结果,该结果是一个枚举,具有以下三个值之一:ACCEPT,DENY 或 NEUTRAL。
可以在以下四个位置之一中配置Filter:
上下文范围的Filter直接在Configuration中配置。
这些过滤器拒绝的事件将不会传递给 Logger 以进行进一步处理。事件被上下文范围的过滤器接受后,将不再由其他任何上下文范围的过滤器进行评估,也不会使用 Logger 的级别来过滤事件。但是,该事件将由 Logger 和 Appender 筛选器评估。
Logger Filter在指定的 Logger 上配置。
这些是在上下文范围过滤器和 Logger 的日志级别之后评估的。无论可加性设置如何,这些过滤器拒绝的事件都将被丢弃,并且该事件将不会传递给父 Logger。
Appender 筛选器用于确定特定的 Appender 是否应处理事件的格式和发布。
Appender 参考过滤器用于确定 Logger 是否应将事件路由到 Appender。
filter本身已经自带了许多功能的过滤器,比如日志包含某一字符的过滤器、日志级别的过滤器等等。
我们也可以自己写相关功能的过滤器。完成项目中日志的诉求,也是用了这部分特性。
通过写个类继承AbstractFilter,实现自定义过滤器。
技术方案
日志级别动态调整
简述:ducc配置root和对应log4j2配置文件的logger的日志级别,使用定时线程来查询ducc key(目前30秒,后续可以改为走配置),如果发现配置有更新则刷新为ducc的日志级别(使用log4j2暴露的调整日志级别方法org.apache.logging.log4j.core.config.Configurator#setLevel)
加白用户无视日志级别打印日志
简述:
1.log.isInfoEnable等判断时,会看当前config中有没filter,如果有filter并且filter返回的结果非neutral的话,会根据这个结果来判断是否为true,代码 =>
org.apache.logging.log4j.core.Logger.PrivateConfig#filter(org.apache.logging.log4j.Level, org.apache.logging.log4j.Marker, java.lang.String)
2.因此我们可以通过开发Log4j2 Filter插件,并返回一个Result来决定log.isInfoEnable是否为true
采集日志并输出到响应
PS:
1.继承一个filter时需要实现很多方法,filter配置在config中的不同位置、log的不同方法会调用到filter中的不同方法
2.一条log日志的打印会成为一个logEvent,经过不同的filter最终决定在哪里停下,如果不停下则最终会打印成为一条日志,因此我们的思路很简单,通过我们自己写的filter来收集到log的日志
3.一个logevent会多次进入同一个filter方法,如果不对event进行标记则会被重复采集,因此需要对原生LogEvent进行扩展一下。
4.扩展了之后还需要保证创建log event时创建的是我们的log event,因此需要还需要一个LogEventFactory,并配置到log4j的配置文件中
logger.info 执行流程:
logger.isInfoEnabled执行流程:
参考教程:
lookback
log4j2使用filter过滤日志