繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM