Spring Boot中如何用Log4j合并多行System.out日志至Graylog
解决方案:合并Spring Boot中SSL握手日志为单个Graylog条目
方案一:自定义PrintStream接管System.out
直接替换System.out,将连续的javax.net.ssl开头日志行合并后输出,其他行正常处理。
实现逻辑:
- 自定义
PrintStream,维护缓冲区暂存javax.net.ssl开头的日志内容 - 遇到非SSL日志行时,先输出缓冲区的合并内容,再处理当前行;连续SSL日志行直接追加到缓冲区
- Spring Boot启动时替换默认输出流
- 自定义
代码示例:
import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; public class SslMergingPrintStream extends PrintStream { private final PrintStream originalOut; private final ByteArrayOutputStream sslBuffer = new ByteArrayOutputStream(); private boolean inSslLog = false; public SslMergingPrintStream(PrintStream originalOut) { super(originalOut); this.originalOut = originalOut; } @Override public void println(String x) { if (x.startsWith("javax.net.ssl")) { inSslLog = true; sslBuffer.write(x.getBytes(StandardCharsets.UTF_8)); sslBuffer.write('\n'); } else { flushSslBuffer(); originalOut.println(x); } } @Override public void print(String s) { if (inSslLog && s != null) { sslBuffer.write(s.getBytes(StandardCharsets.UTF_8)); } else { originalOut.print(s); } } private void flushSslBuffer() { if (inSslLog) { originalOut.print(sslBuffer.toString(StandardCharsets.UTF_8)); sslBuffer.reset(); inSslLog = false; } } @Override protected void finalize() throws Throwable { flushSslBuffer(); super.finalize(); } }
- 启动时替换输出流:
在Spring Boot启动类的main方法中添加:
public static void main(String[] args) { System.setOut(new SslMergingPrintStream(System.out)); SpringApplication.run(YourApplication.class, args); }
方案二:Log4j2自定义Filter(推荐)
先将System.out重定向到Log4j2,再通过自定义Filter合并SSL日志事件。
实现逻辑:
- 开启Spring Boot的
logging.system.out.redirect-to-slf4j=true配置,将System.out输出转至SLF4J/Log4j2 - 自定义Filter拦截
javax.net.ssl开头的日志事件,合并连续的同类事件后再输出
- 开启Spring Boot的
核心代码示例:
import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.filter.AbstractFilter; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class SslLogMergerFilter extends AbstractFilter { private final BlockingQueue<String> sslEventBuffer = new ArrayBlockingQueue<>(100); @Override public Result filter(LogEvent event) { String message = event.getMessage().getFormattedMessage(); if (message.startsWith("javax.net.ssl")) { sslEventBuffer.offer(message); return Result.DENY; // 先拦截暂存 } else { mergeAndFlushSslEvents(event); return Result.NEUTRAL; // 正常处理非SSL日志 } } private void mergeAndFlushSslEvents(LogEvent baseEvent) { if (sslEventBuffer.isEmpty()) return; StringBuilder mergedMsg = new StringBuilder(); String line; while ((line = sslEventBuffer.poll()) != null) { mergedMsg.append(line).append('\n'); } LogEvent mergedEvent = new LogEvent.Builder(baseEvent) .setMessage(mergedMsg.toString()) .build(); getNext().filter(mergedEvent); } @Override public void stop() { mergeAndFlushSslEvents(new LogEvent.Builder().build()); super.stop(); } }
- Log4j2配置示例(log4j2.xml):
在Graylog Appender中添加自定义Filter:
<Appenders> <Gelf name="Graylog" server="your-graylog-server" port="12201"> <PatternLayout pattern="%msg"/> <Filters> <SslLogMergerFilter onMatch="ACCEPT" onMismatch="NEUTRAL"/> </Filters> </Gelf> </Appenders>
方案三:调整SSL日志输出方式(根源解决)
直接让SSL调试日志通过Log4j输出,避免使用System.out:
- 添加JVM启动参数:
-Djavax.net.debug=ssl:handshake -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
- 引入Log4j JUL桥接依赖:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jul</artifactId> <version>${log4j.version}</version> </dependency>
此配置会将SSL调试日志从JUL转至Log4j,后续可直接通过Log4j的批量输出能力合并日志。
内容的提问来源于stack exchange,提问作者sge
相关产品推荐
相关产品推荐

