|
@@ -0,0 +1,97 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ *
|
|
|
|
|
+ * Copyright (c) 2018-2025, hnqz All rights reserved.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Redistribution and use in source and binary forms, with or without
|
|
|
|
|
+ * modification, are permitted provided that the following conditions are met:
|
|
|
|
|
+ *
|
|
|
|
|
+ * Redistributions of source code must retain the above copyright notice,
|
|
|
|
|
+ * this list of conditions and the following disclaimer in the
|
|
|
|
|
+ * documentation and/or other materials provided with the distribution.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Author: hnqz
|
|
|
|
|
+ *
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+package net.yyc.common.log.logback;
|
|
|
|
|
+
|
|
|
|
|
+import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
|
|
|
+import ch.qos.logback.classic.spi.IThrowableProxy;
|
|
|
|
|
+import ch.qos.logback.classic.spi.StackTraceElementProxy;
|
|
|
|
|
+import com.fasterxml.jackson.core.JsonGenerator;
|
|
|
|
|
+import net.logstash.logback.composite.AbstractFieldJsonProvider;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 将异常堆栈按帧拆成 JSON 字符串数组输出,默认字段名 {@code stack_frames}。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>与 LogstashEncoder 自带的 {@code stack_trace}(单行转义字符串,6.6 默认完整不截断)并存,
|
|
|
|
|
+ * 数组元素依次为:异常头(类名: 消息)、各调用帧 {@code at ...}、
|
|
|
|
|
+ * {@code Caused by:} 异常头及其帧、{@code ... N common frames omitted}(与原生堆栈输出一致)。
|
|
|
|
|
+ * 不做任何截断,便于 SLS 对单个堆栈帧进行检索与聚合统计。
|
|
|
|
|
+ *
|
|
|
|
|
+ * <p>用法(LogstashEncoder 支持追加自定义 provider):
|
|
|
|
|
+ * <pre>
|
|
|
|
|
+ * <encoder class="net.logstash.logback.encoder.LogstashEncoder">
|
|
|
|
|
+ * <provider class="net.yyc.common.log.logback.StackTraceFramesJsonProvider"/>
|
|
|
|
|
+ * </encoder>
|
|
|
|
|
+ * </pre>
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author hnqz
|
|
|
|
|
+ * @date 2026-08-28
|
|
|
|
|
+ */
|
|
|
|
|
+public class StackTraceFramesJsonProvider extends AbstractFieldJsonProvider<ILoggingEvent> {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 默认输出字段名
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String FIELD_STACK_FRAMES = "stack_frames";
|
|
|
|
|
+
|
|
|
|
|
+ public StackTraceFramesJsonProvider() {
|
|
|
|
|
+ setFieldName(FIELD_STACK_FRAMES);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
|
|
|
|
|
+ IThrowableProxy throwable = event.getThrowableProxy();
|
|
|
|
|
+ if (throwable == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> frames = new ArrayList<>();
|
|
|
|
|
+ IThrowableProxy current = throwable;
|
|
|
|
|
+ while (current != null) {
|
|
|
|
|
+ // 首个异常直接输出异常头,其余 cause 链补充 Caused by: 前缀,与原生堆栈格式保持一致
|
|
|
|
|
+ if (current == throwable) {
|
|
|
|
|
+ frames.add(buildHeader(current));
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ frames.add("Caused by: " + buildHeader(current));
|
|
|
|
|
+ }
|
|
|
|
|
+ // cause 链中每个异常的帧数组是全量的,与上层重复的公共帧数量记录在 commonFrames,
|
|
|
|
|
+ // 原生输出只打印 length - commonFrames 帧,再以 "... N common frames omitted" 结尾,此处保持一致
|
|
|
|
|
+ StackTraceElementProxy[] steArray = current.getStackTraceElementProxyArray();
|
|
|
|
|
+ int commonFrames = current.getCommonFrames();
|
|
|
|
|
+ for (int i = 0; i < steArray.length - commonFrames; i++) {
|
|
|
|
|
+ frames.add("at " + steArray[i].getStackTraceElement());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (commonFrames > 0) {
|
|
|
|
|
+ frames.add("... " + commonFrames + " common frames omitted");
|
|
|
|
|
+ }
|
|
|
|
|
+ current = current.getCause();
|
|
|
|
|
+ }
|
|
|
|
|
+ generator.writeArrayFieldStart(getFieldName());
|
|
|
|
|
+ for (String frame : frames) {
|
|
|
|
|
+ generator.writeString(frame);
|
|
|
|
|
+ }
|
|
|
|
|
+ generator.writeEndArray();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String buildHeader(IThrowableProxy throwable) {
|
|
|
|
|
+ String message = throwable.getMessage();
|
|
|
|
|
+ return message == null ? throwable.getClassName() : throwable.getClassName() + ": " + message;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|