log4j异常堆栈文件输出

目的:log4j异常堆栈关联到traceId一句话中,方便搜索

1、获取堆栈后一起打印

java 复制代码
 private void logException(Throwable t, ProceedingJoinPoint joinPoint) {
        if (this.printErrorStackSys) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            String errorMsg = sw.toString();

            log.error("error->" + errorMsg);
        } else {// 子定义打印
            Signature signature = joinPoint.getSignature();
            String service = signature.toShortString();
            StringBuilder builder = new StringBuilder();
            builder.append("\r\n========================================== \r\n");
            builder.append("Invoke exception!!! \r\n");
            builder.append("Service = ").append(service).append("\r\n");

            Throwable cause = t.getCause();
            while (cause != null) {
                builder.append("\n\t\tCause: ").append(cause.getClass().getCanonicalName());
                builder.append(": ").append(cause.getMessage());
                cause = cause.getCause();
            }

            builder.append(" StackTrace=");
            StackTraceElement[] stackTraceElements = t.getStackTrace();
            for(int i = 0; i<stackTraceElements.length; i++){
                StackTraceElement stackTrace = stackTraceElements[i];
                builder.append(stackTrace.getClassName()).append(":").append(stackTrace.getMethodName()).append(":").append(stackTrace.getLineNumber()).append("\r\n;");
            }
            builder.append("\r\n==========================================");

            log.error(builder);
        }
    }

结果示例:

使用printStackTrace方法打印

自定义打印

2、使用%throwable打印堆栈跟踪信息

%throwable 用于在日志配置文件中输出异常的堆栈跟踪信息。

它不是 XML 的标准标签或属性,而是日志框架中的占位符

xml 复制代码
<!--控制台输出-->
<Console name="Console" target="SYSTEM_OUT">
			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}|${ctx:uuid}|%t|%-5p|%c{1}:%L|%msg%n" />
		</Console>
<!--文件输出-->
		<RollingFile name="RollingFile" filename="${logPath}/${rollingLogName}.log" filepattern="${logPath}/%d{yyyyMMdd}/${rollingLogName}-%i.log">
			<PatternLayout pattern='{"time":"%d{yyyy-MM-dd HH:mm:ss.SSS}","thread":"%t","tid":"${ctx:uuid}","level":"%-5p","class_line":"%c:%L","msg":"{%msg}%throwable"}%n'>
			</PatternLayout>
			<Policies>
				<TimeBasedTriggeringPolicy interval="1" modulate="true" />
				<SizeBasedTriggeringPolicy size="1024 MB" />
			</Policies>
			<DefaultRolloverStrategy max="100" />
		</RollingFile>

%d{HH:mm:ss,SSS} 表示日期和时间。

[%t] 表示线程名称。

%-5p 表示日志级别,左对齐,宽度为 5。

%c{1} 表示日志记录器的名称,只显示最后一级。

:%L 表示日志记录器的行号。

%m 表示日志消息。

%throwable 表示异常的堆栈跟踪信息。

%n 表示换行符。

结果示例:

3、自定义格式化插件

这种方式与2一样,2使用的是ThrowablePatternConverter

java 复制代码
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.impl.LocationAware;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;

@Plugin(
        name = "CustomerThrowablePatternConverter",
        category = "Converter"
)
@ConverterKeys({"ct", "customerThrowable"})

public class CustomerThrowablePatternConverter extends ThrowablePatternConverter implements LocationAware {

    protected CustomerThrowablePatternConverter(String[] options, Configuration config) {
        super("CT", "CTStyle", options, config);
    }

    // 被触发的得到实例的方法
    public static CustomerThrowablePatternConverter newInstance(Configuration config, String[] options) {
        return new CustomerThrowablePatternConverter(options, config);
    }
    @Override
    public void format(LogEvent event, StringBuilder buffer) {
        Throwable thrown = event.getThrown();
        if (thrown != null && thrown instanceof CustomerException) {
            buffer.append("自定义异常");
        }

        super.format(event, buffer);
    }

    // 位置信息
    @Override
    public boolean requiresLocation() {
        return true;
    }
}

<PatternLayout pattern='{"tid":"${ctx:uuid}","time":"%d{yyyy-MM-dd HH:mm:ss.SSS}","thread":"%t","level":"%-5p","class_line":"%c:%L","msg":"%enc{%msg-%customerThrowable}{JSON}"}%n'>

4、源码分析

Disruptor 是一个高性能的无锁并发框架,常用于异步日志记录、消息传递等场景。

  • LogEvent:表示一个日志事件的类,将要输出的日志上下文信息封装成事件。
java 复制代码
 public LogEvent createEvent(String loggerName, Marker marker, String fqcn, StackTraceElement location, Level level, Message data, List<Property> properties, Throwable t) {
        return new Log4jLogEvent(loggerName, marker, fqcn, location, level, data, properties, t);
    }
  • RingBuffer:一个循环缓冲区,用于存储事件数据。
java 复制代码
 private static final EventTranslatorTwoArg<Log4jEventWrapper, LogEvent, AsyncLoggerConfig> TRANSLATOR = new EventTranslatorTwoArg<Log4jEventWrapper, LogEvent, AsyncLoggerConfig>() {
        public void translateTo(Log4jEventWrapper ringBufferElement, long sequence, LogEvent logEvent, AsyncLoggerConfig loggerConfig) {
            ringBufferElement.event = logEvent;
            ringBufferElement.loggerConfig = loggerConfig;
        }
    };
  • Sequencer:负责管理和分配序列号的组件。 方法用于发布一个序列号,表示某个事件已经准备好,可以被消费者线程处理。发布后的消息通常由一个或多个消费者类(也称为事件处理器或 EventHandler)进行消费。
java 复制代码
private <A, B> void translateAndPublish(EventTranslatorTwoArg<E, A, B> translator, long sequence, A arg0, B arg1) {
        try {
            translator.translateTo(this.get(sequence), sequence, arg0, arg1);
        } finally {
            this.sequencer.publish(sequence);
        }

    }
  • EventHandler:负责处理事件的消费者类,定义了一个 onEvent 方法,该方法会在事件准备好后被调用。
java 复制代码
   public void onEvent(Log4jEventWrapper event, long sequence, boolean endOfBatch) throws Exception {
            event.event.setEndOfBatch(endOfBatch);
            // 去消费
            event.loggerConfig.logToAsyncLoggerConfigsOnCurrentThread(event.event);
            event.clear();
            this.notifyIntermediateProgress(sequence);
        }

5、主要流程

org.apache.logging.log4j.core.async.AsyncLoggerConfig#log

->org.apache.logging.log4j.core.Logger#logMessage

->org.apache.logging.log4j.core.config.LoggerConfig#log(java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable)

->org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier<org.apache.logging.log4j.core.config.LoggerConfig>, java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable)

->org.apache.logging.log4j.core.appender.RollingFileAppender#append

->org.apache.logging.log4j.core.layout.PatternLayout#encode

->org.apache.logging.log4j.core.layout.PatternLayout.PatternSerializer#toSerializable(org.apache.logging.log4j.core.LogEvent, java.lang.StringBuilder)

->LogEventPatternConverter的子类进行格式化操作拼接打印日志输出

java 复制代码
// 异步输出日志方法重写
 protected void log(LogEvent event, LoggerConfig.LoggerConfigPredicate predicate) {
        if (predicate == LoggerConfigPredicate.ALL && ASYNC_LOGGER_ENTERED.get() == Boolean.FALSE && this.hasAppenders()) {
            ASYNC_LOGGER_ENTERED.set(Boolean.TRUE);

            try {
                super.log(event, LoggerConfigPredicate.SYNCHRONOUS_ONLY);
                this.logToAsyncDelegate(event);
            } finally {
                ASYNC_LOGGER_ENTERED.set(Boolean.FALSE);
            }
        } else {
            super.log(event, predicate);
        }

    }
//父类的方法,1、同步日志调用, 2、EventHandler回调使用
 protected void log(LogEvent event, LoggerConfigPredicate predicate) {
        if (!this.isFiltered(event)) {
            this.processLogEvent(event, predicate);
        }

    }
// 日志处理--打印输出
  private void processLogEvent(LogEvent event, LoggerConfigPredicate predicate) {
        event.setIncludeLocation(this.isIncludeLocation());
        // 判断当前 !config instanceof AsyncLoggerConfig是否为非异步
        if (predicate.allow(this)) {
        // 打印处理
            this.callAppenders(event);
        }

        this.logParent(event, predicate);
    }
// 队列处理
 private void logToAsyncDelegate(LogEvent event) {
        if (!this.isFiltered(event)) {
            this.populateLazilyInitializedFields(event);
            // 发布序列号,添加到缓冲区
            if (!this.delegate.tryEnqueue(event, this)) {
            // 缓存区满了处理
                this.handleQueueFull(event);
            }
        }

    }


  public void format(LogEvent event, StringBuilder buf) {
        if (this.skipFormattingInfo) {
            this.converter.format(event, buf);
        } else {
            this.formatWithInfo(event, buf);
        }
    }
相关推荐
百香果果ccc5 小时前
Java中的File和IO流
java·开发语言·log4j
武昌库里写JAVA10 小时前
SpringBoot+SpringCloud面试题整理附答案
java·开发语言·算法·spring·log4j
天才少女爱迪生1 天前
python代码制作数据集的测试和数据质量检测思路
开发语言·python·log4j
东方巴黎~Sunsiny1 天前
Spring Boot 3.3高级日志配置详解:从 Logback 到 Log4j 2 的全面掌握
spring boot·log4j·logback
菜鸟起航ing1 天前
Java中日志采集框架-JUL、Slf4j、Log4j、Logstash
java·开发语言·log4j·logback
yrldjsbk1 天前
org.apache.log4j的日志记录级别和基础使用Demo
log4j·apache
hummhumm2 天前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
武昌库里写JAVA5 天前
最新6.7分非肿瘤纯生信,使用机器学习筛选慢阻肺中的关键基因。机器学习在非肿瘤生信文章中正火,可重复!
java·开发语言·算法·spring·log4j
卷心菜是俺8 天前
Sping全面复习
java·开发语言·数据库·junit·java-ee·log4j·maven
武昌库里写JAVA8 天前
R语言机器学习与临床预测模型77--机器学习预测常用R语言包
java·开发语言·算法·spring·log4j