功能需求

项目里将User分成了各个区域(domain),这些domain有个标志domainId,现在要求在打印日志的时候,不仅将所有User的日志都打印到日志文件logs/CNTCore.log中,还需要另外再打印到对应domain的日志文件logs/{domainId}/CNTCore.log

比如User A的domainId是RD2,那么除了logs/CNTCore.log外,还需要将该User A的日志额外打印到logs/RD2/CNTCore.log中。

实现思路

将所有User的日志都打印到日志文件logs/CNTCore.log中,这个可以直接使用配置文件log4j2.xml来解决,一个简单的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration monitorInterval="30"> <Appenders>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="%-5p %m%n" />
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
</Console> <RollingFile name="cntCorelog" immediateFlush="true" fileName="logs/CNTCore.log" filePattern="logs/CNTCore.log.%d{yyyy-MM-dd-a}.gz"
append="true">
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}:%p %t %X{TracingMsg} %c - %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1" />
</Policies>
</RollingFile>
</Appenders> <Loggers>
<Logger name="com.lewis" level="debug" additivity="true">
<AppenderRef ref="cntCorelog" />
</Logger>
<Root level="error">
<AppenderRef ref="stdout" />
</Root>
</Loggers> </configuration>

在上边的配置中,配置了cntCorelog这个appender来生成对应的回滚日志文件,具体由com.lewis这个logger来使用该appender进行拼接日志信息。

至于另外再打印到对应domain的日志文件logs/{domainId}/CNTCore.log,这个可以通过代码来动态生成各个domain的appender,并交由com.lewis这个logger来进行拼接日志。

代码的具体实现

项目的Log4j2依赖

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>

动态生成appender

public static void createDomainAppender(final String domainId){
final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
final org.apache.logging.log4j.core.config.Configuration config = ctx.getConfiguration();
if (config.getAppender(domainId + "DomainCntCoreLog") != null) {
return;
}
final PatternLayout layout = PatternLayout.newBuilder()
.withCharset(Charset.forName("UTF-8"))
.withConfiguration(config)
.withPattern("%d %t %p %X{TracingMsg} %c - %m%n")
.build();
final TriggeringPolicy policy = TimeBasedTriggeringPolicy.newBuilder()
.withModulate(true)
.withInterval(1)
.build();
final Appender appender = RollingFileAppender.newBuilder()
.withName(domainId + "DomainCntCoreLog")
.withImmediateFlush(true)
.withFileName("logs/" + domainId + "/CNTCore.log")
.withFilePattern("logs/" + domainId + "/CNTCore.log.%d{yyyy-MM-dd-a}.gz")
.withLayout(layout)
.withPolicy(policy)
.build();
appender.start();
config.addAppender(appender);
final KeyValuePair[] pairs = {KeyValuePair.newBuilder().setKey("domainId").setValue(domainId).build()};
final Filter filter = ThreadContextMapFilter.createFilter(pairs, null, Result.ACCEPT, Result.DENY);
config.getLoggerConfig("com.lewis").addAppender(appender, Level.DEBUG, filter);
ctx.updateLoggers(config);
}

这段代码动态生成一个名为omainCntCoreLog的RollingFileAppender,该appender交由com.lewis这个logger来使用,并将日志信息输入到logs/{domainId}/CNTCore.log

该logger在使用omainCntCoreLog这个RollingFileAppender时还设置了一个过滤器ThreadContextMapFilter,这个Filter用来控制logger只能对指定了domainId的进行打印日志。

ThreadContext是Log4j2用来存放线程信息的,相当于Log4j 1.X中的MDC和NDC,MDC是map,NDC是stack。当每个User登录时,就将该User的domainId存放到ThreadContext中,当退出登录时就将该domainId从ThreadContext中移除。

假如有10个User登录了,一个User对应一个线程,每个线程都存放了User对应的domainId。在用户登录时,调用上边的方法来动态生成domain appender;假如有10个domainId,就会生成10个domain appender。

由于这10个domain appender都被add到同一个logger里了,如果不通过ThreadContextMapFilter来控制,就会造成每个User的日志信息都会被输入到所有domain appender里去。

在加载配置文件后拼接domain appender

需要注意的是,必须在读取配置文件后才能去动态生成appender或者其他的日志对象,否则会被原本的配置文件覆盖掉。

public static void main(final String[] args) {
ThreadContext.put("domainId", "RD2");
final String domainId = "RD2";
final LoggerContext context1 = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);
try {
context1.setConfigLocation(Loader.getResource("log4j2.xml", null).toURI());
createDomainAppender(domainId);
} catch (final Exception e) {
LogManager.getRootLogger().error("load log4j2 configuration error", e);
ThreadContext.remove("domainId");
} }

上边的代码简单地动态生成了RD2 domain的appender,需要注意的是,如果启用了Log4j2的动态加载配置文件功能,那么当配置文件被改动后并被重新加载时,会导致原本动态生成的domain appender无效。

因为重新加载配置文件会生成新的LoggerContext对象,这时候可能会丢失一部分日志信息到对应的domain日志文件里。对于这个暂时没找到很好的解决方法,目前只能是在每个User登录时去创建domain appender对象,如果已存在就不创建。

对ThreadContextMapFilter的补充

上边通过代码动态生成了RollingFileAppender和ThreadContextMapFilter,下边记录下配置文件里的写法:

<RollingFile name="domainCntCoreLog" immediateFlush="true" fileName="logs/RD2/CNTCore.log" filePattern="logs/RD2/CNTCore.log.%d{yyyy-MM-dd-a}.gz" append="true">
<ThreadContextMapFilter onMatch="ACCEPT"
onMismatch="DENY">
<KeyValuePair key="domainId" value="RD2" />
</ThreadContextMapFilter>
<PatternLayout pattern="%d %t %p %X{TracingMsg} %c - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1" />
</Policies>
</RollingFile>

从上边的配置就可以看出来短板了,只能配置死某个domainId的RollingFileAppender以及ThreadContextMapFilter,假如有10个domainId,就要手动配置十个对应的appender和Filter,很是繁琐。

就算通过占位符${ctx:domainId}的写法来避免写死,也只能生成某个domainId的appender:

<RollingFile name="domainCntCoreLog" immediateFlush="true" fileName="logs/${ctx:domainId}/CNTCore.log" filePattern="logs/${ctx:domainId}/CNTCore.log.%d{yyyy-MM-dd-a}.gz" append="true">
<ThreadContextMapFilter onMatch="ACCEPT"
onMismatch="DENY">
<KeyValuePair key="domainId" value="${ctx:domainId}" />
</ThreadContextMapFilter>
<PatternLayout pattern="%d %t %p %X{TracingMsg} %c - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1" />
</Policies>
</RollingFile>

这种方法只能生成一个domain appender,此外如果启用了动态加载配置文件的功能,在扫描配置文件是否改动时,还会报错,原因是在RollingFileAppender的FileName和filePattern里使用了占位符。在另起线程扫描配置文件时,该占位符时取不到值的,于是就会报错。

参考链接

Log4j2 - 动态生成Appender的更多相关文章

  1. log4j2 不使用配置文件,动态生成logger对象

    大家平时使用Log4j一般都是在classpath下放置一个log4j的配置文件,比如log4j.xml,里面配置好Appenders和Loggers,但是前一阵想做某需求的时候,想要的效果是每一个任 ...

  2. Aop动态生成代理类时支持带参数构造函数

    一.背景 在某些情况下,我们需要植入AOP代码的类并没有默认构造函数.那么此时动态生成的代理类也需要相同签名的构造函数,并且内部调用原始类的构造函数.自己折腾了1晚上没搞定,现在搞定了发出来供大家一起 ...

  3. dynamic-css 动态 CSS 库,使得你可以借助 MVVM 模式动态生成和更新 css,从 js 事件和 css 选择器的苦海中脱离出来

    dynamic-css 使得你可以借助 MVVM 模式动态生成和更新 css,从而将本插件到来之前,打散.嵌套在 js 中的修改样式的代码剥离出来.比如你要做元素跟随鼠标移动,或者根据滚动条位置的变化 ...

  4. ABP(现代ASP.NET样板开发框架)系列之20、ABP展现层——动态生成WebApi

    点这里进入ABP系列文章总目录 ABP(现代ASP.NET样板开发框架)系列之20.ABP展现层——动态生成WebApi ABP是“ASP.NET Boilerplate Project (ASP.N ...

  5. 【.NET深呼吸】Zip文件操作(2):动态生成Zip文档

    通过前面一篇烂文的介绍,大伙儿知道,ZipArchive类表示一个zip文档实例,除了用上一篇文章中所列的方法来读写zip文件外,还可以直接通过ZipArchive类,动态生成zip文件. 文件流操作 ...

  6. jquery动态生成的元素添加事件的方法

    动态生成的元素如果要添加事件,要写成 $(document).on("click", "#txtName", function() { alert(this.v ...

  7. 利用Java动态生成 PDF 文档

    利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那 ...

  8. 用C#从数据库动态生成AdminLTE菜单的一种方法

    当前的应用设计风格趋于Flat扁平化,很多基于BootStrap实现了很多UI非常漂亮的管理界面(Bootstrap admin template). 此核心文件开源在Github:https://g ...

  9. 动态生成二维码插件 jquery.qrcode.js

    前段时间做项目,需要动态生成一个二维码,于是就在网上找了一下发现一个jquery插件jquery.qrcode.js,所以今天就简单说一下这个插件的使用: jquery.qrcode.js是依赖jqu ...

随机推荐

  1. machine learning for hacker记录(3) 贝叶斯分类器

    本章主要介绍了分类算法里面的一种最基本的分类器:朴素贝叶斯算法(NB),算法性能正如英文缩写的一样,很NB,尤其在垃圾邮件检测领域,关于贝叶斯的网上资料也很多,这里推荐那篇刘未鹏写的http://mi ...

  2. vsftp登录时间太长的解决办法

    与ssh一样,vsftp的配置文件默认开启了DNS反向解析,这可能会造成用户在登陆到FTP服务器的时候奇慢无比,只要在配置文件中禁用DNS反向解析即可解决文件. 编辑/etc/vsftpd/vsftp ...

  3. CISCO-从路由器上下载IOS

    准备工作:一台装有TFTP服务器的PC,一台带有IOS的路由器,并用网线连接上 设置路由器接口和计算机网卡的IP地址在同一网段,并且互相能ping通. 1,安装Cisco TFTP Server 2, ...

  4. BZOJ_2801_[Poi2012]Minimalist Security_dfs树+特判+乱搞

    BZOJ_2801_[Poi2012]Minimalist Security_dfs树+特判+乱搞 Description 给出一个N个顶点.M条边的无向图,边(u,v)有权值w(u,v),顶点i也有 ...

  5. codevs 3269 混合背包(复习混合背包)

    传送门 [题目大意]给出物品的数量.-1为无限个. [思路]混合背包.... [code] #include<iostream> #include<cstdio> #inclu ...

  6. bzoj 1127 [POI2008]KUP——思路(悬线法)

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1127 大于2*K的视为不能选的“坏点”.有单个格子满足的就直接输出. 剩下的都是<K的 ...

  7. bzoj2875随机数生成器——矩阵快速幂

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2875 矩阵快速幂,把x和c分开求,最后加上即可: 为防止爆long long,要用快速乘. ...

  8. Nwjs简单配置

    1.创建一个工程,配置一个  package.json 文件 { "name": "application-name", "version" ...

  9. linux下监控用户的操作记录---录像播放性质

    想知道用户登陆系统后都操作了什么,怎么办? 别急,linux下有一个script工具,专门记录终端会话中所有输入输出结果,并存放到指定文件中. 先看看怎么录制吧! 1.创建日志存放目录 # mkdir ...

  10. fullpage中高度错误的解决方法

    今天我再用fullpage写全屏页面的时候,发现在ie中,一整屏的页面总是不能铺满,高度总是少一截儿,各种搜索,找到了个合适的方法,这里记录下,也希望给之后遇到这个问题的人提供一种方式,当然不一定能解 ...