我有一个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个线程(同一个类)时,会创建两个日志,但日志会混淆:第二个线程会记录到第一个和第二个线程日志中。
我怎样才能确保每个实例都是唯一的? 我已经尝试为每个记录器实例提供一个唯一的名称,但它没有改变任何内容。
答案 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;
}