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

时间:2011-12-02 11:44:45

标签: java multithreading log4j

我有一个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个线程(同一个类)时,会创建两个日志,但日志会混淆:第二个线程会记录到第一个和第二个线程日志中。

我怎样才能确保每个实例都是唯一的? 我已经尝试为每个记录器实例提供一个唯一的名称,但它没有改变任何内容。

6 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:8)

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

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

这是我的工作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>

答案 2 :(得分:5)

@havexz的方法非常好:writing everything to the same log file and using nested diagnostic contexts

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

  

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

答案 3 :(得分:3)

以下是来自正在运行的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;

答案 4 :(得分:0)

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

class yourClass {

  private static int cnt = 0;

  public yourClass(){
    ...
    initLogger();
  }

  private synchronized initLogger(){
     yourClass.cnt++;
     myJobid = yourClass.cnt;

     //include your logging code here
  }
}

答案 5 :(得分:0)

据我所知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;
}