Log4j2 - 动态生成Appender

功能需求

项目里将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("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("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里使用了占位符。在另起线程扫描配置文件时,该占位符时取不到值的,于是就会报错。

参考链接

原文地址:https://www.cnblogs.com/yulinlewis/p/10217385.html

时间: 2024-10-21 07:37:38

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

C# 动态生成WebService,无需添加引用

C#项目调用WebService是很常见的现象,但一旦修改链接地址就需要重新更新引用很是麻烦,这里跟大家分享一个通过地址,无需添加引用动态生成Webservice的小方法 方法类: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.ServiceModel.Channels

C++实现根据类名动态生成类对象

在开发后台服务的过程中,我们常常需要从数据库中取数据,并将数据缓存在本地中,另外,我们的服务还需要有更新数据的能力:包括定时的主动更新以及数据库数据更新时服务收到通知的被动更新. 之前在需要用到以上功能的时候,模仿着组内通用的数据Cache部分的代码来写,十分方便,基本上只需要自己写两个类:一个是取数据并缓存数据的类XXXData,一个是扇出数据的类XXXFetcher. 在需要使用数据的时候,通过: FetcherFactory::getFetcher<XXXFetcher>() 即可获取一

[搬运自我的CSDN博客] python抓取javascript动态生成HTML内容的实践

<注:CSDN博客在美国访问特别卡,所以转移到cnblogs来发文章> 本实验在Ubuntu14.04上完成.使用的浏览器是火狐(Firefox 33.0),python版本是2.7.6. 大家都知道用urllib配合正则表达式抓取静态HTML的内容很方便,但是如果网页中有javascript动态生成的内容,urllib就无能为力了. 此时我们要借助一个额外的工具:selenium.它的工作原理是操纵(火狐)浏览器浏览目标网页,等待网页中的javascript全部执行完毕后再对HTML源码进行

动态生成二级菜单

现在越来越多的用到二级甚至多级菜单,前台菜单的显示,手动指定也越来越不能满足要求,所以,动态生成菜单是必须的 思路 + 示例代码(以二级菜单为例) 先取出一级菜单内容值,接下来遍历一级菜单,将其id当做本次检索的parentid,将与之对应的二级菜单值获取到, 并加入到当前数组中(后台) 二层循环,当获取一个值时,检查其对于的二级菜单项是否有数据,有的话,则输出来,没有则跳过(前台) 以PHP后台为例 $res = mysql_query('*** where parentid = 0');  

nginx利用image_filter动态生成缩略图

原文:http://www.open-open.com/lib/view/open1416193847945.html "我现在是有些图片需要生成缩略图,这个现在加了image_filter这个已经实现了,但我不知道怎么样才能访问我上传的原图" 刚开始觉得也不太好弄,让他用程序区处理,实际上稍微动脑筋分析一下也可以不修改程序实现动态生成缩略图且能够访问原图. 前提是需要定好图片的访问规则. 先来看一下什么是nginx的image filter模块. HttpImageFilterMod

动态生成随机背景色表格

<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>动态生成随机背景色表格</title> <style> table{margin-top:20px;width:800px;border:1px solid #ddd;border-collapse:collapse;} td{border:1px solid #ddd;padding:

Android动态生成表格

最近刚刚学习完Android的五大布局,现在我们进一步深入学习,尝试做一个动态生成表格功能的例子 样式布局代码如下: 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4

js动态生成数据列表

我们通常会使用table标签来展示数据内容,由于需要展示的数据内容是随时更换的,所以不可能将展示的数据列表写死在html写死在页面中,而是需要我们根据后台传来的数据随时更换,这个时候就需要我们使用js来动态生成表格. 首先我们需要先写好页面的样式. html部分 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; chars

用Aspose.Words for .NET动态生成word文档中的数据表格

1.概述 最近项目中有一个这样的需求:导出word 文档,要求这个文档的格式不是固定的,用户可以随便的调整,导出内容中的数据表格列是动态的,例如要求导出姓名和性别,你就要导出这两列的数据,而且这个文档不是导出来之后再调整而是导出来后已经是调整过了的.看到这里,您也许马上想到用模板导出!而且.NET中自带有这个组件:Microsoft.Office.Interop.Word,暂且可以满足需求吧.但这个组件也是有局限性的,例如客户端必须装 office组件,而且编码复杂度高.最麻烦的需求是后面那个-