最近有一个业务需求,需要提取业务日志数据进行运营分析。由于之前各个应用对于业务数据本来打印的就少,所以想借此机会,提供一个接口来实现业务数据的专用日志,与一般的运维日志消息区分开来。
考虑到侵入性的问题,不希望由具体开发组去配置日志框架,而是通过统一的工具包来实现。现在公司的绝大部分产品用的都是的 log4j2 作为实现,就来看看这个怎么配好了。
Log4j2 的架构如下:
里面几个主要的组件:
- Logger
- LoggerConfig
- Appender
Logger/LoggerConfig/Appender 都是可以用 name 指定的。
当使用 LogManager 获取 Logger 是,要使用一个具体的名字,LogManager 会根据这个名字去寻找合适的 LoggerContext 并且从中获取一个 Logger。如果 Logger 不存在,则创建的时候会根据以下规则寻找到一个 LoggerConfig,并关联到新建的 Logger。
- 与 Logger 的名字相同
- 属于 Logger 的父包
- 根 LoggerConfig
LoggerConfig 关系到将日志事件输出到哪个 Appender。
我们现在需要做的其实就是通过代码,而非是配置文件的方式去新建一个 LoggerConfig 和 Appender,将其交给 LoggerContext 并更新。在官方给出的例子里面大多数是从一开始就 build 一个全新的 Context,只有一个提到了如何去更新 context,这就够了,不一样的细节可以自己去琢磨。
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final Configuration config = ctx.getConfiguration();
Layout layout = PatternLayout.createLayout(PatternLayout.SIMPLE_CONVERSION_PATTERN, config, null,
null,null, null);
Appender appender = FileAppender.createAppender("target/test.log", "false", "false", "File", "true",
"false", "false", "4000", layout, null, "false", null, config);
appender.start();
config.addAppender(appender);
AppenderRef ref = AppenderRef.createAppenderRef("File", null, null);
AppenderRef[] refs = new AppenderRef[] {ref};
LoggerConfig loggerConfig = LoggerConfig.createLogger("false", "info", "org.apache.logging.log4j",
"true", refs, null, config, null );
loggerConfig.addAppender(appender, null, null);
config.addLogger("org.apache.logging.log4j", loggerConfig);
ctx.updateLoggers();
从上面的代码里面可以看到,要动态配置一个已经初始化好的 LoggerContext,基本分为以下几步:
- 读取当前 Context;
- 创建新的 Appender 实例(layout 只是 FileAppender 的一个必须参数,其他 Appender 也有更多的类型需要配置);
- 创建新的 LoggerConfig;
- 将 Appender 实例挂载到 LoggerConfig 里面;
- 将 LoggerConfig 注册进 Context 并更新 Context。
官网的例子只是更新了一个使用普通 FileAppender 的 Logger,在正常环境下,都会选择用 RollingFileAppender 来实现对日志的自动清理。用代码创建 RollingFileAppender 稍微要麻烦一点。
先看 RollingFileAppender 的创建方法:
public static RollingFileAppender createAppender(
// @formatter:off
final String fileName,
final String filePattern,
final String append,
final String name,
final String bufferedIO,
final String bufferSizeStr,
final String immediateFlush,
final TriggeringPolicy policy,
final RolloverStrategy strategy,
final Layout<? extends Serializable> layout,
final Filter filter,
final String ignore,
final String advertise,
final String advertiseUri,
final Configuration config)
关键的组件是 TriggerPolicy 以及 RolloverStrategy。TriggerPolicy 的实现有 SizeBasedTriggeringPolicy 和 TimeBasedTriggeringPolicy,Policy 负责实现判断是否满足 Rollover 的条件。如果满足的话,FileManager 会触发 rollover 动作,而如何 rollover 恰好是 RolloverStrategy 决定的。
以 DefaultRolloverStrategy 为例:
public static DefaultRolloverStrategy createStrategy(
// @formatter:off
@PluginAttribute("max") final String max,
@PluginAttribute("min") final String min,
@PluginAttribute("fileIndex") final String fileIndex,
@PluginAttribute("compressionLevel") final String compressionLevelStr,
@PluginElement("Actions") final Action[] customActions,
@PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
final boolean stopCustomActionsOnError,
@PluginConfiguration final Configuration config)
max 和 min 决定了最多和最少会有几个 rollover 文件存在,超出范围的 rollover 文件会被清理掉。但是在使用 TimeBasedTriggeringPolicy 时会发现 rollover 的文件一直存在不会被 max 所限制住。想要自动清理超期的问题,就得借助自定义的 action。
PathCondition lastModified = IfLastModified.createAgeCondition(Duration.parse(totalTimeLimit), null);
PathCondition fileNameMatch = IfFileName.createNameCondition(
cleanFilePattern,
null,
null
);
PathCondition[] conds = new PathCondition[] {fileNameMatch, lastModified};
DeleteAction action = DeleteAction.createDeleteAction(
fileDir,
false,
1,
false,
null,
conds,
null,
config
);
Action[] actions = new Action[]{action};
有了 action,就可以创建出可用的 strategy,剩下的 Appender、LoggerConfig 以及 LoggerContext 的更新都与官方的例子没什么区别了。