SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

幹貨滿滿張哈希 2021-08-15 05:41:38 阅读数:685

本文一共[544]字,预计阅读时长:1分钟~
springcloud 之路 2020.0.x 使用 log4j2

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

本系列代碼地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

我們使用 Log4j2 异步日志配置,防止日志過多的時候,成為性能瓶頸。這裏簡單說一下 Log4j2 异步日志的原理:Log4j2 异步日志基於高性能數據結構 Disruptor,Disruptor 是一個環形 buffer,做了很多性能優化(具體原理可以參考我的另一系列:高並發數據結構(disruptor)),Log4j2 對此的應用如下所示:

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

簡單來說,多線程通過 log4j2 的門面類 org.apache.logging.log4j.Logger 進行日志輸出,被封裝成為一個 org.apache.logging.log4j.core.LogEvent,放入到 Disruptor 的環形 buffer 中。在消費端有一個單線程消費這些 LogEvent 寫入對應的 Appender.

這裏我們給出一個我們日志配置的模板,供大家參考:

<?xml version="1.0" encoding="UTF-8"?><configuration> <Properties> <Property name="springAppName">app名稱</Property> <Property name="LOG_ROOT">log</Property> <Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property> <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property> <!--對於日志級別,為了日志能對齊好看,我們占 5 個字符--> <Property name="LOG_LEVEL_PATTERN">%5p</Property> <Property name="logFormat"> %d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} [${springAppName},%X{traceId},%X{spanId}] [${sys:PID}] [%t][%C:%L]: %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} </Property> </Properties> <appenders> <RollingFile name="file" append="true" filePattern="${LOG_ROOT}/app.log-%d{yyyy.MM.dd.HH}" immediateFlush="false"> <PatternLayout pattern="${logFormat}"/> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true"/> </Policies> <DirectWriteRolloverStrategy maxFiles="72"/> </RollingFile> </appenders> <loggers> <!--default logger --> <Asyncroot level="info" includeLocation="true"> <appender-ref ref="file" /> </Asyncroot> <AsyncLogger name="org.mybatis" level="off" additivity="false" includeLocation="false"> <appender-ref ref="file"/> </AsyncLogger> </loggers></configuration>

對於其中一些重要的配置,我們這裏單獨拿出來分析下。

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

我們項目的依賴中包含了 spring-cloud-sleuth 這個鏈路追踪相關的依賴,其核心基於 Opentracing 標准實現。日志中可以通過打印 Span 的 SpanContext 中的 traceId 以及 spanId,就能通過這些信息,確定日志中的一條完整鏈路。spring-cloud-sleuth 是如何將這些信息放入日志中的呢? Log4j2 中有這樣一個抽象,即 org.apache.logging.log4j.ThreadContext,這個其實就是 Java 日志中 MDC(Mapped Diagnostic Context)的實現,可以理解成是一個線程本地的 Map,每個線程可以將日志需要的元素放入這個 ThreadContext 中,這樣這個線程在打印日志的時候,就可以從這個 ThreadContext 中取出放入日志內容。日志需要有對應的占比特符,例如下面這個就是將 ThreadContext 中 key 為 traceId 以及 spanId 的值取出輸出:

%X{traceId},%X{spanId}

Spring Cloud 2020.0.x 之後,也就是 spring-cloud-sleuth 3.0.0 之後,放入 ThreadContext 的 key 發生了變化,原來的 traceId 與 spanId 分別是 X-B3-traceIdX-B3-spanId,現在改成了更為通用的 traceIdspanId

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

這個主要因為你打日志的地方不在 spring-cloud-sleuth 管理的範圍內,或者是 Span 提前結束了。這種時候,你可以在確定有 Span 的地方將 Span 緩存起來,之後再沒有鏈路追踪信息的地方使用這個 Span,例如:

import brave.Tracer;@Autowireprivate Tracer tracer;//在確定有 span 的地方獲取當前 span 將 span 緩存起來Span span = tracer.currentSpan();//之後在沒有鏈路追踪信息的地方,使用 span 包裹起來try (Tracer.SpanInScope cleared = tracer.withSpanInScope(span)) { //你的業務代碼}

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

設置 includeLocation=false,這樣在日志中就無法看到日志屬於的代碼以及行數了。獲取這個代碼行數,其實是通過獲取當前調用堆棧實現的。Java 9 之前是通過 new 一個 Exception 獲取堆棧,Java 9 之後是通過 StackWalker。兩者其實都有性能問題,在高並發的情况下,會吃掉很多 CPU,得不償失。所以我推薦,在日志內容中直接體現所在代碼行數,就不通過這個 includeLocation 獲取當前堆棧從而獲取代碼行數了。

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

關閉 immediateFlush,可以减少硬盤 IO,會先寫入內存 Buffer(默認是 8 KB),之後在 RingBuffer 目前消費完或者 Buffer 寫滿的時候才會刷盤。這個 Buffer 可以通過系統變量 log4j.encoder.byteBufferSize 改變。

這裏的原理對應源碼:

AbstractOutputStreamAppender.java

protected void directEncodeEvent(final LogEvent event) { getLayout().encode(event, manager); //如果配置了 immdiateFlush (默認為 true)或者當前事件是 EndOfBatch if (this.immediateFlush || event.isEndOfBatch()) { manager.flush(); }}

那麼對於 Log4j2 Disruptor 异步日志來說,什麼時候 LogEventEndOfBatch 呢?是在消費到的 index 等於生產發布到的最大 index 的時候,這也是比較符合性能設計考慮,即在沒有消費完的時候,盡可能地不 flush,消費完當前所有的時候再去 flush:

BatchEventProcessor.java

private void processEvents(){ T event = null; long nextSequence = sequence.get() + 1L; while (true) { try { final long availableSequence = sequenceBarrier.waitFor(nextSequence); if (batchStartAware != null) { batchStartAware.onBatchStart(availableSequence - nextSequence + 1); } while (nextSequence <= availableSequence) { event = dataProvider.get(nextSequence); //這裏 nextSequence == availableSequence 就是 EndOfBatch eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence); nextSequence++; } sequence.set(availableSequence); } catch (final TimeoutException e) { notifyTimeout(sequence.get()); } catch (final AlertException ex) { if (running.get() != RUNNING) { break; } } catch (final Throwable ex) { exceptionHandler.handleEventException(ex, nextSequence, event); sequence.set(nextSequence); nextSequence++; } }}

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

我們這一節詳細分析了我們微服務框架中日志相關的各種配置,包括基礎配置,鏈路追踪實現與配置以及如果沒有鏈路追踪信息時候的解决辦法,並且針對一些影響性能的核心配置做了詳細說明。下一節我們將會開始分析針對日志的 RingBuffer 進行的監控。

微信搜索“我的編程喵”關注公眾號,每日一刷,輕松提昇技術,斬獲各種offer

SpringCloud昇級之路2020.0.x版-10.使用Log4j2以及一些核心配置

版权声明:本文为[幹貨滿滿張哈希]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815054125308p.html