需求: 將日誌按照不一樣的模塊和日誌級別輸入到不一樣的日誌文件裏.
實現方式1:
最初的想法是用 LoggerFactory.getLogger(logName),而後爲不一樣的logName 定義不一樣的logger指向不一樣的FileAppender
缺點:因爲Logger 的名字改變,再也不能根據每一個類的名字動態調整日誌級別,對錯誤排查影響較大
實現方式2:
利用Log4j2的Lookup功能動態構建文件名 參考https://logging.apache.org/log4j/2.x/manual/lookups.html
缺點是這個只能在Logger 定義以前指定一個模塊:如
static {
System.setProperty("module", "module1");
}
public static final Logger logger = LoggerFactory.getLogger(Module1.class);
沒辦法在運行時動態切換日誌文件
實現方式3:
按利用log4j2 的路由功能 (RoutingAppender)能夠根據上下文變量將日誌動態路由到的不一樣的文件
具體配置以下:
<RollingFile name="RollingFilePre" fileName="test-module1.log" filePattern="test-module1.%d{yyyy-MM-dd}.%i.log" append="true">
<DynaJsonLayout compact="true" eventEol="true"/>
<Policies>
<SizeBasedTriggeringPolicy size="1048576"/>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<DefaultRolloverStrategy max="200"/>
</RollingFile>
<RollingFile name="RollingFileRule" fileName="test-module2.log" filePattern="test-module2.%d{yyyy-MM-dd}.%i.log" append="true">
<DynaJsonLayout compact="true" eventEol="true"/>
<Policies>
<SizeBasedTriggeringPolicy size="1048576"/>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<DefaultRolloverStrategy max="200"/>
</RollingFile>
<Routing name="Routing" >
<Routes pattern="${sys:module}">
<Route key="val1" ref="RollingFilePre">
</Route>
<Route key="val2" ref="RollingFileRule">
</Route>
</Routes>
</Routing>
這樣能夠在程序中隨時調用 System.setProperty() 來切換日誌文件, 也能夠經過其餘選項好比ThreadContext property 來動態切換
注意:若是須要程序中動態切換文件,route 和 異步日誌 async 會有必定的衝突,切換時可能數據還在內存隊列裏,這樣可能打不到正確的文件裏
按日誌級別分文件則是另一種思路,利用ThresholdFilter 將日誌同時輸出到A,B兩個Appender, 再根據日誌級別過濾. 好比A只接收Error 級別的日誌
B只接收Error如下級別的日誌,奇怪的是Log4j2 沒有把二者統一塊兒來
<root level="debug">
<appender-ref ref="Console"></appender-ref>
<appender-ref ref="RoutingFileAsync"/>
</root>
<Async name="RoutingFileAsync" bufferSize="300000">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<AppenderRef ref="Routing"/>
<ArrayBlockingQueue/>
</Async>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="ACCEPT"/>
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
LOGSTASH採集:
利用Log4j2 JsonLayout,程序中會有一個工具類將錯誤日誌格式變成JSON的,相似於 {"errorCode":xxx, "errorMessage":xxx}
可是JsonLayout 會把日誌報文統一放到message 屬性中,這樣在日誌記錄進入Elasticsearch 仍然沒法直接被搜索,咱們的指望是將
JSON消息報文中的屬性展開成多個頂級屬性而不是嵌套在message中
一種思路是在日誌從KAFKA到Elasticsearch 的時候作一些轉換,不肯定Logstash 的mutate是否支持
另外一種思路是定製一個本身的JsonLayout 重載toSerializable方法來自定義輸出的格式和內容, 具體實現步驟以下
1.定義 DynaLogEvent 繼承了log4j 原生的 LogEvent, 增長了一個Map 類型的 attributes 屬性,若是原生Log4jEvent 中的Message是Json格式則就把解析出的鍵值對存到
attributes 中
2. 定義了 DynaJsonLayout 經過 Plugin Annotation 註冊到Log4j2 中,同時log4j2.xml 中須要聲明 <configuration packages="org.apache.logging.log4j.core.layout">
來指定掃描插件的路徑 DynaJsonLayout 主要就是重載了 toSerializable 方法,將原生的 LogEvent 轉化爲 DynaLogEvent 並經過 json ObjectWriter 輸出成json 格式
ObjectWriter 使用了定製的 DynaLog4jJsonObjectMapper, 經過工廠類DynaJacksonFactory來構建,DynaJsonLayout 初始化時將 DynaLog4jJsonObjectMapper
注入ObjectWriter 中
3. 實踐中發現attributes 並不能被Log4jJsonObjectMapper正確的序列化 (注:Log4j2的Log4jJsonObjectMapper使用了大量jackson的高級技巧來定製LogEvent的序列化,
如Module, Mixin 等,你們有興趣能夠閱讀下代碼,這裏不詳細展開了), 因此爲DynaLogEvent中的attributes屬性添加了JsonIgnore Annotation 避免它被自動序列化,
並定製了DynaLogEventSerializer 來自定義序列化 attributes, DynaLogEventSerializer經過 DynaLog4jJsonModule 註冊到 DynaLog4jJsonObjectMapper 中
源代碼:https://github.com/wispershadow/myopensources/tree/master/jsonlogger
html