英文:
Log4J2 with Routing Appenders and ThreadContext
问题
我有一些关于log4j2和路由附加器的问题。
我的应用程序生成不同的线程,在每个线程中我做了以下操作:
ThreadContext.put("threadName", processName);
processName将在每个线程中基于不同的信息生成。但这不重要,唯一重要的信息是,processName在每个线程中都是不同的。
现在我有这个log4j2.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
</Console>
<Routing name="RoutingAppender">
<Routes pattern="${ctx:threadName}">
<Route>
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}-%d{yyyy-MM-dd}.log"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Logger name="de.company" level="INFO" additivity="false">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="console"/>
</Logger>
<Logger name="com.company" level="INFO" additivity="false">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="console"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>
log4j会为每个线程创建日志文件,没有任何问题。
不幸的是,log4j将创建一个名为**${ctx:threadName}.log**的日志文件。在这个日志文件中,来自JSCH的日志消息将出现。我猜测JSCH会创建自己的线程,并且由于JSCH不会将ThredName放入ThreadContext中,log4j无法替换变量,并使用配置中的变量作为日志文件名。
我的目标是将所有没有ThreadContext中的threadName的日志文件写入单个日志文件中。
我在log4j的文档中找到了这个链接:
https://logging.apache.org/log4j/log4j-2.2/faq.html#separate_log_files
但问题是,在那个示例中,他们知道threadName的值。在我的情况下,我不知道这个值。
ChatGPT告诉我,我必须更改<Routes>元素中的pattern,并使用两个$,像这样:
<Routes pattern="$${ctx:threadName}">
然后我可以像这样添加第二个<Route>元素
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route key="$${ctx:threadName}">
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
<Route key="null">
<RollingFile name="Rolling-service"
fileName="../log/default-service.log"
filePattern="../log/default-service.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
但这不起作用 - 我猜log4j将null解释为一个字符串。第二种方法是从fallback route中删除key属性,像这样
<Routes pattern="$${ctx:threadName}">
<Route key="$${ctx:threadName}">
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
<Route>
<RollingFile name="Rolling-service"
fileName="../log/default-service.log"
filePattern="../log/default-service.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
</Routes>
但在这种情况下,所有的日志消息都将存储在default-service.log中。
有人有什么好主意吗?
多谢你的帮助!
英文:
I have some issues with log4j2 and the routing appenders.
My application generates different threads and within each thread I do something like this
ThreadContext.put("threadName", processName);
and processName will be generated within the thread based on different information. But that is not important, the only important information is, that processName will be different in each thread.
Now I have this log4j2.xml file
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
</Console>
<Routing name="RoutingAppender">
<Routes pattern="${ctx:threadName}">
<Route>
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}-%d{yyyy-MM-dd}.log"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Logger name="de.company" level="INFO" additivity="false">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="console"/>
</Logger>
<Logger name="com.company" level="INFO" additivity="false">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="console"/>
</Logger>
<Root level="INFO">
<AppenderRef ref="RoutingAppender"/>
<AppenderRef ref="console"/>
</Root>
</Loggers>
</Configuration>
And log4j creates logfiles for each thread without any issues.
Unfortunately log4j will create a logfile called ${ctx:threadName}.log. In this logfile the log messages from JSCH will appear. I guess that JSCH will create its own thread and since JSCH won't put a ThredName into the ThreadContext, log4j can not replace the variable and uses the variable from the configuration as the logfile name.
My goal would be to write all logfiles without a threadName in the ThreadContext into a single logfile.
I found this link in the documentation from log4j
https://logging.apache.org/log4j/log4j-2.2/faq.html#separate_log_files
but the issue is, that in that example they know what the value of threadName is. In my case I don't know the value.
ChatGPT told me, that I have to change the pattern within the <Routes> element and use a double $ like this
<Routes pattern="$${ctx:threadName}">
and than I can add a second <Route> element like this
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route key="$${ctx:threadName}">
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
<Route key="null">
<RollingFile name="Rolling-service"
fileName="../log/default-service.log"
filePattern="../log/default-service.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
But this is not working - I guess log4j interprets null as a String. The second approach was to remove the key attribute from the fallback route like this
<Routes pattern="$${ctx:threadName}">
<Route key="$${ctx:threadName}">
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
<Route>
<RollingFile name="Rolling-service"
fileName="../log/default-service.log"
filePattern="../log/default-service.log-%d{yyyy-MM-dd}"
createOnDemand="true">
<PatternLayout pattern="%d{ABSOLUTE} %-5p [%-40.-40c{1}] %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
</Route>
</Routes>
But in this case all log messages will be stored in the default-service.log.
Does anyone has a good idea what to do in this case?
Many greetings and thank you for your help!
答案1
得分: 1
路由附加器很棘手,因为除了<Route>
标签的内容之外,其他所有内容都是在编译时插值的,而<Route>
标签的内容只有在选择该路由时才会被评估。因此,如果你有以下配置:
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route key="$${ctx:threadName}">
<!-- 内容1 -->
</Route>
<Route>
<!-- 内容2 -->
</Route>
</Routes>
</Routing>
它将在配置时间进行插值,得到:
<Routing name="RoutingAppender">
<Routes pattern="${ctx:threadName}">
<Route key="${ctx:threadName}">
<!-- 内容1不变 -->
</Route>
<Route>
<!-- 内容2不变 -->
</Route>
</Routes>
</Routing>
在运行时,${ctx:threadName}
模式将被插值并与非插值键进行匹配。因此:
- 如果
ThreadContext.get("threadName")
为null
,表达式${ctx:threadName}
将评估为它自身${ctx:threadName}
并匹配第一个(而不是第二个)路由的键, - 如果
ThreadContext.get("threadName")
不是null
,它将不匹配任何键,并选择第二个(默认)路由。
解决方案
你可以:
- 将第二个路由的内容替换为第一个路由的内容(在你的最后尝试中)反之亦然,
- 或者为
${ctx:threadName}
添加一个默认值,并只使用一个默认路由:
<Properties>
<Property name="threadName" value="default-service"/>
</Properties>
<Appenders>
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route>
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}.log-%d{yyyy-MM-dd}"
createOnDemand="true">
...
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
英文:
The routing appender is tricky, because everything except the contents of <Route>
is interpolated at compile-time, while the contents of <Route>
will be evaluated if that route is chosen. Therefore if you have:
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route key="$${ctx:threadName}">
<!-- Content 1 -->
</Route>
<Route>
<!-- Content 2 -->
</Route>
</Routes>
</Routing>
it will be interpolated at configuration time to obtain:
<Routing name="RoutingAppender">
<Routes pattern="${ctx:threadName}">
<Route key="${ctx:threadName}">
<!-- Content 1 unchanged -->
</Route>
<Route>
<!-- Content 2 unchanged -->
</Route>
</Routes>
</Routing>
At runtime the pattern ${ctx:threadName}
will be interpolated and matched against non-interpolated keys. Therefore:
- if
ThreadContext.get("threadName")
isnull
, the expression${ctx:threadName}
will evaluate to itself${ctx:threadName}
and match the key of the first (not second) route, - if
ThreadContext.get("threadName")
is notnull
, it will not match any key and the second (default) route will be chosen.
Solutions
You can:
- either replace the contents of the second route with the fist one (in your last attempts) and viceversa,
- or add a default value for
${ctx:threadName}
and use just a single default route:
<Properties>
<Property name="threadName" value="default-service"/>
</Properties>
<Appenders>
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route>
<RollingFile name="Rolling-${ctx:threadName}"
fileName="../log/${ctx:threadName}.log"
filePattern="../log/${ctx:threadName}.log-%d{yyyy-MM-dd}"
createOnDemand="true">
...
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论