在日志中显示线程ID而不是线程名称

时间:2013-01-16 12:57:51

标签: java logging log4j

我有一个带有log4j的Struts应用程序来显示有关应用程序的信息。

格式化日志输出的模式如下:

log4j.appender.RALL.layout.ConversionPattern=[%p] %d{dd/MM/yyyy HH:mm:ss} [THREAD ID=%t] [CLASS=(%C{1}:%L)] %m%n 

我需要在日志中显示主题ID 而不是主题名称。显示线程名称的转换字符是%t。我没有在log4j文档中看到获取它的方法。

任何人都可以帮助我吗?

10 个答案:

答案 0 :(得分:8)

可能但不是那么容易,只需使用一些预先配置的模式。

Log4j 1.X和Log4j 2.x没有任何预先配置的模式来打印线程ID,但你总是可以使用一些“魔术”。

PatternLayout正在使用标记为PatternParser类的final类,并将“patterns”的静态映射作为键,将Converters类作为值。每当Parses找到使用从%开始的记录模式格式的模式时,它就会使用与地图中的此模式键匹配的转换器。

您无法将自己的规则添加到该地图,但您仍然可以编写自己的MyOwnPatternLayout:

public class MyOwnPatternLayout extends PatternLayout

将在其format方法中执行此类操作:

public String format(LoggingEvent event) {
   String log = super.format(event);
   /*
   Now you just have to replace with regex all occurences of %i or 
   any mark you would like to use as mark to represent Thread ID 
   with Thread ID value.
   Only thing you have to be sure to not use any mark as your Thread ID
   that already is defined by PatterParser class
   */
   return log.replaceAll("%i", someThreadID);
}

唯一的问题是你必须以某种方式获得该线程ID。有时你所要做的就是解析线程名称,你可以轻松收集它:

String threadName = event.getThreadName();

例如,Apache-Tomcat将线程ID放在线程名称的末尾 http-nio- / 127.0.0.1-8084“-exec-41

为了确保线程ID是正确的,您还可以创建自己的LogginEvent和Logger(MyLoggingEvent和MyLogger)子类,并在MyLogger内创建MyLoggingEvent,这也将作为参数线程ID而不仅仅是线程名称。然后你可以在上面的代码中轻松收集它。

对不起,答案很长,我希望这至少会给你一些帮助。

答案 1 :(得分:7)

您可以使用log4j MDC自行添加它。我们使用它来添加Web请求的用户名。我们在每个请求开始时在过滤器中执行此操作。例如

import org.apache.log4j.MDC;

...

  // Add username to MDC
  String username = ...;
  MDC.put("user", username);

然后将[%X{user}]添加到您的转化模式中。

答案 2 :(得分:6)

我为即将到来的2.6实现了线程ID和线程优先级。在此处跟踪:https://issues.apache.org/jira/browse/LOG4J2-1299

您可以从Apache快照存储库中获取2.6-SNAPSHOT版本:https://repository.apache.org/content/repositories/snapshots/

答案 3 :(得分:5)

您可以使用ThreadContext Map向log4j2提供元数据。这是您 CAN 通过正常格式添加的值的字符串映射。

String threadId = String.valueOf(Thread.currentThread().getId());
ThreadContext.put("TId", threadId);

更合理的模式:

    <PatternLayout pattern="%d{yyyyMMdd}T%d{HHmmss.SSS} %-5level [%t] [%5X{TId}] %15c{1} - %msg%n"/>

Full Log4j2 documentation on "Fish Tagging"

答案 4 :(得分:2)

我认为不可能使用标准的log4j格式显示线程ID。我还通过PatterParser类的代码进行了调查,发现什么都没有用。我找到了一些自定义解决方案,但仅适用于具有%i选项的IBM服务器:

  

%i:插入线程ID。与线程名称(由%t表示)不同,这是线程的数字ID。   请注意,此参数特定于Initiate,而此处列出的其他参数是log4j的标准。

请参阅this link

答案 5 :(得分:2)

如下所示展开PatternLayout,然后在格式字符串中指定MyPatternLayout $X{threadId}

此实现使用ThreadLocal来最小化计算线程ID的性能影响:

    MyPatternLayout extends PatternLayout {

        private final ThreadLocal<String> threadId = new ThreadLocal<String>() {

            @Override
            protected String initialValue() {
                String t = Long.toString(Thread.currentThread().getId());
                MDC.put("threadId", t);
                return t;
            }
        };

        @Override
        public String format(LoggingEvent event) {

            this.threadId.get();
            return super.format(event);
        }
    }

答案 6 :(得分:1)

我不知道它何时引入,但在log4j2中,我们有%tid 用于

  

输出生成日志事件的线程的ID。

https://logging.apache.org/log4j/2.x/manual/layouts.html

答案 7 :(得分:0)

一种可能的解决方案是创建自己的类,它位于代码和Log4J之间,并将线程ID附加到每条日志消息:

public class ThreadLogger
{
    // Constructor declared private to prevent instantiation.  Use static methods instead.
    private ThreadLogger() {}

    private static enum LogLevel
    {
        TRACE,
        DEBUG,
        INFO,
        WARN,
        ERROR
    }

    public static void trace(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    public static void debug(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    public static void info(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    public static void warn(String message)
    {
        logMessage(message, LogLevel.WARN);
    }

    public static void error(String message)
    {
        logMessage(message, LogLevel.ERROR);
    }

    private static void logMessage(String message, LogLevel logLevel)
    {
        // Get the Log4J logger for the class that originally wanted to log the message
        String callingClassName = Thread.currentThread().getStackTrace()[3].getClassName();
        Class callingClass;
        try
        {
            callingClass = Class.forName(callingClassName);
        }
        catch(ClassNotFoundException e)
        {
            String errorMessage = String.format("Could not reference class [%s].  Unable to log call!", callingClassName);
            throw new RuntimeException(errorMessage);
        }
        Logger logger = Logger.getLogger(callingClass);

        // Get the thread ID and place it in front of the logged message
        long threadId = Thread.currentThread().getId();
        String formattedMessage = String.format("[%s] %s", threadId, message);

        // Log the message
        switch(logLevel)
        {
            case TRACE:
                logger.trace(formattedMessage);
                break;
            case DEBUG:
                logger.debug(formattedMessage);
                break;
            case INFO:
                logger.info(formattedMessage);
                break;
            case WARN:
                logger.warn(formattedMessage);
                break;
            case ERROR:
                logger.error(formattedMessage);
                break;
        }
    }
}

缺点:

  • 性能?这为每个日志语句添加了一些额外的步骤。
  • 稳定性?这会增加潜在的失败点(Class.forName调用)。
  • 您必须使用对新类的调用替换所有现有日志语句。
  • 在常规Log4J格式化之后,线程ID不会出现。 IE:

&#13;
&#13;
1234 [main] INFO com.foo.bar.Baz - [1] Hello world on thread #1!
1234 [main] INFO com.foo.bar.Baz - [2] Hello world on thread #2!
&#13;
&#13;
&#13;

答案 8 :(得分:0)

我创建自己的appender并将Thread.currentThread()。getId()设置为MDC属性。 %X {threadId}应该给我线程ID。此解决方案自1.2.15开始工作。然后,您可以将AsyncAppender附加到此。

public class CurrentThreadIdAppender extends AppenderSkeleton implements AppenderAttachable {

    private final AppenderAttachableImpl appenders = new AppenderAttachableImpl();

...

    @Override
    protected void append(LoggingEvent event) {   
        synchronized (appenders) {
            event.setProperty("threadId", String.valueOf(Thread.currentThread().getId()));
            appenders.appendLoopOnAppenders(event);
        }
    }

...

}

答案 9 :(得分:0)

log4j2的另一个优雅解决方案是使用org.apache.logging.log4j.core.pattern.LogEventPatternConverter

你可以写一个像这样的课程

@Plugin(name = "ThreadIdConverter", category = "Converter")
@ConverterKeys({ "tid" })
public class ThreadIdConverter extends LogEventPatternConverter {

    protected ThreadIdConverter(String name, String style) {
        super(name, style);
    }

    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        toAppendTo.append(getThreadId());
    }

    protected String getThreadId() {
        long id = Thread.currentThread().getId();
        return Long.toHexString(id);
    }

    public static ThreadIdConverter newInstance(String[] options) {
        return new ThreadIdConverter("tid", "tid");
    }
}

通过这种方式,您将创建一个新模式tid,并且可以在定义appender的布局时使用它

<Appenders>
    <Console name="console" target="SYSTEM_OUT">
        <PatternLayout>
            <Pattern>%d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n</Pattern>
        </PatternLayout>
    </Console>
</Appenders>

要记住的最后一件重要事情是如何激活你的log4j2插件。为此,您必须使用package节点上的Configuration属性在log4j2配置文件中添加包含插件的包

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE Configuration>
<Configuration status="warn"
    packages="my.package.logging.plugins">
    <Appenders>
        <Console name="console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>%d{dd-MMM HH:mm:ss.SSS} %-7level [%5tid] %logger - %message%n</Pattern>
            </PatternLayout>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="warn">
            <AppenderRef ref="console" />
        </Root>
        <Logger name="my.package" level="trace" />
    </Loggers>
</Configuration>