[英]How to log multiple threads in different log files?
我有一个JAVA类,可以启动具有唯一ID的各种线程。 每个线程应登录到一个唯一的日志文件中,该文件以ID.log命名。
因为我仅在运行时获得唯一ID,所以我必须以编程方式配置Log4J:
// Get the jobID
myJobID = aJobID;
// Initialize the logger
myLogger = Logger.getLogger(myJobID);
FileAppender myFileAppender;
try
{
myFileAppender = new FileAppender(new SimpleLayout(), myJobID + ".log", false);
BasicConfigurator.resetConfiguration();
BasicConfigurator.configure(myFileAppender);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
现在,如果我按顺序启动作业,此方法就可以正常工作-但是,当我同时启动2个线程(同一个类)时,会创建两个日志,但日志混合在一起:第二个线程同时登录第一个和第二个日志。
我如何确保每个实例都是唯一的? 我已经尝试为每个记录器实例指定一个唯一的名称,但是它没有任何改变。
Logback有一个名为SiftingAppender的特殊附加程序,它为您描述的问题类型提供了非常好的解决方案。 SiftingAppender可用于根据任何运行时属性(包括线程ID)来分离(或筛选)日志记录。
对于log4j v2,可以使用RoutingAppender动态路由消息。 您可以将键“ threadId”的值放入ThreadContext映射,然后将此ID用作文件名的一部分。 有一个我可以轻松地用于与您相同目的的示例。 参见http://logging.apache.org/log4j/2.x/faq.html#separate_log_files
在将值放入ThreadContext映射时,请注意:“子线程会自动继承其父级的映射诊断上下文的副本。” 因此,如果您将键“ threadId”的值放入父线程并最终从其创建多个线程,则所有子线程都将继承“ threadId”值的值。 我不能再使用一次put()
来简单地覆盖此值-您需要使用ThreadContext.clear()
或从线程上下文映射中显式remove()
该值。
这是我的工作log4j.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN">
<properties>
<property name="logMsgPattern">%d{HH:mm:ss} %-5level - %msg%n</property>
<property name="logDir">test logs</property><!-- ${sys:testLogDir} -->
</properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${logMsgPattern}"/>
</Console>
<Routing name="Routing">
<Routes pattern="$${ctx:threadId}">
<Route>
<RollingFile name="RollingFile-${ctx:threadId}" fileName="${logDir}/last-${ctx:threadId}.log" filePattern="${logDir}/%d{yyyy-MM-dd}/archived_%d{HH-mm}-${ctx:threadId}.log">
<PatternLayout pattern="${logMsgPattern}"/>
<Policies>
<OnStartupTriggeringPolicy />
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
</appenders>
<loggers>
<root level="debug">
<appender-ref ref="Console" level="debug" />
<appender-ref ref="Routing" level="debug"/>
</root>
</loggers>
</configuration>
@havexz的方法非常好: 将所有内容写入同一日志文件并使用嵌套的诊断上下文 。
如果您担心几个JVM写入同一个FileAppender,那么我建议两件事:
在谨慎模式下,即使存在其他FileAppender实例运行在不同的JVM(可能在不同的主机上运行)的情况下,FileAppender也会安全地写入指定的文件。
这是来自有效log4j.xml文件的“路由”代码段。
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %-50logger{4}: %msg%n</pattern>
</PatternLayout>
</Console>
<Routing name="RoutingAppender">
<Routes pattern="${ctx:logFileName}">
<!-- This route is chosen if ThreadContext has a value for logFileName.
The value dynamically determines the name of the log file. -->
<Route>
<RollingFile name="Rolling-${ctx:logFileName}"
fileName="${sys:log.path}/${ctx:logFileName}.javalog"
filePattern="./logs/${date:yyyy-MM}/${ctx:logFileName}_%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %-50logger{4}: %msg%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6"
modulate="true" />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Route>
<!-- This route is chosen if ThreadContext has no value for key logFileName. -->
<Route key="${ctx:logFileName}" ref="ConsoleAppender" />
</Routes>
</Routing>
</Appenders>
<loggers>
<root level="debug">
<appender-ref ref="RoutingAppender" level="debug" />
</root>
</loggers>
可以将“ logFileName”键添加到Runnable类的run()方法中的线程上下文映射中,如下所示,
public class SomeClass implements Runnable{
private int threadID;
public SomeClass(int threadID){
this.threadID=threadID;
}
@Override
public void run() {
String logFileName = "thread_log_"+ threadID;
ThreadContext.put("logFileName", logFileName);
//Some code
ThreadContext.remove("threadId");
}
}
另外,必须导入正确的log4j软件包,如下所示。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
请注意,以下导入无效。 LogManager和Logger也必须来自org.apache.logging.log4j。
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
向您的类添加静态实例计数器变量呢? 然后,您将需要一个同步方法,该方法将为创建的每个对象增加计数器,并从该值创建日志文件名。 像这样:
class yourClass {
private static int cnt = 0;
public yourClass(){
...
initLogger();
}
private synchronized initLogger(){
yourClass.cnt++;
myJobid = yourClass.cnt;
//include your logging code here
}
}
据我所知, ThreadLocal API旨在执行您描述的操作。
如下所示的代码将使用各自的(每线程)FileAppender建立每线程记录器:
/**
* usage: threadLocalLogger.get().info("hello thread local logger")
*/
static ThreadLocal<Logger> threadLocalLogger = newThreadLocalLogger("myJobId");
private static ThreadLocal<Logger> newThreadLocalLogger(final String myJobID) {
return new ThreadLocal<Logger>() {
@Override
protected Logger initialValue() {
return logger(myJobID, Thread.currentThread().getId());
}
};
}
private static Logger logger(String myJobID, long threadId) {
// Initialize the logger
String loggerId = myJobID + "-" + threadId;
Logger myLogger = Logger.getLogger(loggerId);
FileAppender myFileAppender;
try
{
myFileAppender = new FileAppender(new SimpleLayout(),
loggerId + ".log", false);
BasicConfigurator.resetConfiguration();
BasicConfigurator.configure(myFileAppender);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return myLogger;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.