如何在不同的日志文件中记录多个线程?

我有一个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的特殊appender,它为你描述的问题类型提供了一个非常好的解决方案。 SiftingAppender可用于根据任何运行时属性(包括线程ID)分离(或筛选)日志记录。

对于log4j v2,您可以使用RoutingAppender动态路由消息。 您可以将键’threadId’的值放入ThreadContext映射中,然后将此id用作文件名的一部分。 有一个例子我很容易应用于你的目的。 请参阅http://logging.apache.org/log4j/2.x/faq.html#separate_log_files

在将值放入ThradContext映射时要注意:“子线程会自动inheritance其父级的映射诊断上下文的副本。” 因此,如果您已将键’threadId’的值放入父线程并最终从其创建多个线程,则所有子线程将inheritance’threadId’值的值。 我再也不能通过使用put()简单地覆盖此值 – 您需要使用ThreadContext.clear()或从线程上下文映射中显式删除()值。

这是我的工作log4j.xml:

    %d{HH:mm:ss} %-5level - %msg%n test logs                          

@havexz的方法非常好: 将所有内容写入同一个日志文件并使用嵌套的诊断上下文 。

如果您关注的是几个JVM写入同一个FileAppender,那么我建议两件事:

  • 使用SLF4J作为日志记录
  • 在审慎模式下使用logback作为日志记录实现

在谨慎模式下,即使存在可能在不同主机上运行的不同JVM中运行的其他FileAppender实例,FileAppender也会安全地写入指定文件。

以下是工作log4j.xml文件中路由代码的片段。

    %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %-50logger{4}: %msg%n         %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %-50logger{4}: %msg%n                  

键’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; 

如何在类中添加静态实例计数器变量。 然后,您需要一个synchronized方法,该方法会为每个创建的对象增加计数器,并从该值创建日志文件名。 像这样的东西:

 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旨在实现您所描述的function。

像下面的代码将建立每个线程记录器,每个使用自己的(每个线程)FileAppender:

 /** * usage: threadLocalLogger.get().info("hello thread local logger") */ static ThreadLocal threadLocalLogger = newThreadLocalLogger("myJobId"); private static ThreadLocal newThreadLocalLogger(final String myJobID) { return new ThreadLocal() { @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; }