从多个线程进行Java日志记录的最佳实践?

时间:2009-02-19 15:40:01

标签: java multithreading logging

我想要一个由管理数据的多个任务生成的诊断日志。这些任务可能在多个线程中。每个任务都需要将一个元素(可能带有子元素)写入日志;进去快点出去。如果这是一个单任务情况,我会使用XMLStreamWriter,因为它似乎是简单/功能的最佳匹配,而不必在内存中保存一个膨胀的XML文档。

但这不是一个单任务的情况,我不确定如何最好地确保这是“线程安全”,其中“线程安全”在这个应用程序中意味着每个日志元素应该正确和连续地写入日志(一个接一个,不以任何方式交错)。

有什么建议吗?我有一个模糊的直觉,即要使用的方法是使用一个日志元素队列(每个元素都可以快速生成:我的应用程序忙于执行对性能敏感的实际工作),并且有一个单独的线程来处理日志元素并将它们发送到文件,以便日志记录不会中断生成器。

日志记录不一定是XML,但我确实希望它具有结构化和机器可读性。

编辑:我在引号中加上“threadsafe”。 Log4j似乎是一个显而易见的选择(对我来说很新但对社区来说很老),为什么重新发明轮子......

12 个答案:

答案 0 :(得分:22)

我认为你走错了路。你说“线程安全”,但实际上你的意思是“序列化”。 Threadsafe意味着一个线程不会干扰来自其他线程的数据。大多数情况下,线程问题事先得到解决,你不应该只为了记录而担心它。例如,如果你写:

myVariableSum = 0 + myVariable;
//here comes other thread - Not very likely!
logger.info("Log some INFO; myVariable has value" + myVariable.toString());

在执行计算(第一行)但调用日志记录方法之前,您必须确保myVariable尚未被其他某个线程更改。如果发生这种情况,您将记录未用于执行操作的脏值,而是记录由其他某个线程分配的值。这通常是照顾;例如,本地(方法级别)变量不能被其他线程更改。无论如何,如果您在登录时不得不担心这一点,那么99%的程序已经存在严重的线程问题。
所有主要的日志记录框架本身都是“线程安全的”,这意味着它们可以部署在多线程环境中,并且不会在内部显示与上述类似的问题。
使跟踪按顺序出现在日志中实际上通常称为调用的“序列化”。序列化日志写入将是任何多线程应用程序的主要性能瓶颈。如果您使用日志框架(如log4j),则所有线程的跟踪将在单个位置或多或少地出现,以便它们发生。但是,一列通常是线程名称,因此您可以通过线程轻松过滤日志数据;每个线程将按时间顺序记录其数据。看看这个链接: http://logging.apache.org/log4j/1.2/faq.html#1.7
最后,如果序列化日志写入是您真正需要的,那么您可以使用某种结构,如java.util.concurrent.BlockingQueue来路由您的消息。

答案 1 :(得分:21)

使用日志框架,例如Log4j

答案 2 :(得分:9)

使用logback-classic。它是log4j的更新更好的实现。

答案 3 :(得分:5)

我倾向于在Log4J之上使用SLF4J。如果您要在生产环境中关闭很多日志记录语句,parameterized logging功能特别有吸引力。

它也可以运行在java.util.logging的顶部或使用它自己的简单输出。

答案 4 :(得分:4)

您可以使用同步机制(如监视器或信号器)来确保在接受下一个请求之前处理一个日志请求。这可以从调用日志记录例程的代码中隐藏。

答案 5 :(得分:4)

  

使用日志框架,例如Log4。

如果您对输出不满意,可以编写自己的Appender,Filter,无论如何调整它只是写。所以你甚至可以做一些缓存来重新安排条目,虽然我并不是说这是一个好主意。

答案 6 :(得分:4)

使用实现某种形式的the NDC pattern的日志框架,例如Log4J

答案 7 :(得分:3)

log4j已经成为多年来java日志记录的标准。但是如果你不喜欢外部依赖,那么java.util.logging包提供了一个可接受的解决方案。

答案 8 :(得分:2)

我只对特殊日志有类似的问题和实施要求。我的解决方案是:

  1. 我的blockinglinkedqueue大小为应用流量/分钟*2的大小。

  2. 所有线程都将对象放入队列并完成作业。

  3. 将头对象从队列中分离出来的Log-Writer线程,并使用单独的appender将其写入log4j文件。这个appender没有用于系统日志。

  4. 这可确保日志按顺序写入并始终按顺序排列。

    这不会影响应用程序的性能,因为日志写入是一个完全独立的过程,不会造成瓶颈。

    您还可以使用aysncappender的{​​{1}}。

答案 9 :(得分:1)

如果必须,您可以使用单写入器/单读取器FIFO或队列自行滚动。

答案 10 :(得分:0)

以线程安全的方式自己开发这个并不简单,所以你应该真正使用一个线程安全的现有日志框架。最常用的是Log4J,它是线程安全的(请参阅FAQ)。

答案 11 :(得分:0)

这是一个老问题,但这是我以编程方式使用Log4J的解决方案。

LogFactory类

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import java.util.Properties;

public class LogFactory {

    private final static ThreadLocal<Logger> logFactory = new ThreadLocal<>();

    public static void createNewLogger(String className) {

        Logger log = Logger.getLogger("Thread" + className);

        Properties props = new Properties();
        props.setProperty("log4j.appender.file", "org.apache.log4j.RollingFileAppender");

        props.setProperty("log4j.appender.file.maxFileSize", "100MB");
        props.setProperty("log4j.appender.file.Append", "false");
        props.setProperty("log4j.", "100MB");
        props.setProperty("log4j.appender.file.maxBackupIndex", "100");
        props.setProperty("log4j.appender.file.File", "logs/" + className + ".log");
        props.setProperty("log4j.appender.file.threshold", "info");
        props.setProperty("log4j.appender.file.layout", "org.apache.log4j.PatternLayout");
        props.setProperty("log4j.appender.file.layout.ConversionPattern", "%d{yyyy-MM-dd HH-mm-ss} | %-5p | %C{1}:%L | %m%n");
        props.setProperty("log4j.appender.stdout", "org.apache.log4j.ConsoleAppender");
        props.setProperty("log4j.appender.stdout.Target", "System.out");
        props.setProperty("log4j.logger." + "Thread" + className, "INFO, file");
        PropertyConfigurator.configure(props);
        logFactory.set(log);
    }

    public static Logger getLogger() {
        return logFactory.get();
    }

}

然后使用以下方法初始化记录器

logFactory.createNewLogger(String.valueOf(Thread.currentThread().getId()));
logFactory.getLogger().info(" TEST . Thread id is: " + id);