如何在时间间隔后使用log4j2或Java中的logback批量收集日志并将其写入文件

问题描述

我正在尝试使用log4j2创建自定义日志附加程序。我的问题是我不想立即将日志写入文件追加器,而是之后。因此,理想情况下,我的spring boot应用程序应在某种数据结构中收集所有日志,然后在分批延迟3分钟后触发写入文件。 (我不应该使用spring batch,因为它不是批处理应用程序,而是简单的spring boot启动器)

解决方法

当我为Logback编写自定义提供程序时(作为Loki4j项目的一部分),我想到了一个线程安全的buffer的简洁实现,可以按批大小触发输出操作或自上次输出以来超时。

使用模式:

private static final LogRecord[] ZERO_EVENTS = new LogRecord[0];
private ConcurrentBatchBuffer<ILoggingEvent,LogRecord> buffer = new ConcurrentBatchBuffer<>(batchSize,LogRecord::create,(e,fl) -> eventFileLine(e,fl));


// somewhere in code where new log event arrives
var batch = buffer.add(event,ZERO_EVENTS);
if (batch.length > 0)
    handleBatch(batch);


// somewhere in scheduled method that triggers every timeoutMs
var batch = buffer.drain(timeoutMs,ZERO_EVENTS);
if (batch.length > 0)
    return handleBatch(batch).thenApply(r -> null);

// handling batches here
private void handleBatch(LogRecord[] lines) {
    // flush to file
}

,

尝试查看 log4j2 附带的附加程序。它们中的大多数实现的功能类似于您所描述的。例如。 RandomAccessFileAppender 只有在从异步 appender 基础设施接收到完整的一批日志事件后才会写入文件。它们都共享封装此逻辑的 OutputStreamManagerFileManager

protected synchronized void write(final byte[] bytes,final int offset,final int length,final boolean immediateFlush) {
        if (immediateFlush && byteBuffer.position() == 0) {
...

不幸的是,这似乎没有时间限制的解决方案。

我写了 log4j2 appender for Loki,它有自己的环形缓冲区和一个线程,当有足够的数据或用户指定的超时时间过去时,它将发送一批,here

 if (exceededBatchSizeThreshold() || exceededWaitTimeThreshold(currentTimeMillis)) {
            try {
                httpClient.log(outputBuffer);
            } finally {
                outputBuffer.clear();
                timeoutDeadline = currentTimeMillis + batchWaitMillis;
            }
        }