日誌規範化落地方案
問題背景
程序日誌規範化的必要性,相信不少人早就意識到了,可是接下來立刻就會面臨如何快速簡單的落地日誌規範的問題。本文主要介紹一下咱們是如何解決這個問題的java
規範
固然首先須要一個日誌規範,咱們收集了最經常使用的字段,最後定的規範以下表:git
序號 | 字段名 | 類型 | 說明 | Elasticsearch存儲 |
---|---|---|---|---|
1 | LogAt | DateTime | 日誌時間 | iso8601 |
2 | TraceId | string | 跟蹤Id | 不分詞 |
3 | Department | string | 部門 | 不分詞,統一小寫 |
4 | Team | string | 團隊 | 不分詞,統一小寫 |
5 | Project | string | 項目名稱 | 不分詞,統一小寫 |
6 | Host | string | 域名 | 不分詞,統一小寫 |
7 | ServerIP | string | 服務器IP | 不分詞,統一小寫 |
8 | ContextPath | string | 虛擬目錄 | 不分詞,統一小寫 |
9 | UriStem | string | url主幹 | 不分詞,統一小寫 |
10 | QueryString | string | GET參數 | 分詞 |
11 | FormString | string | POST參數 | 分詞 |
12 | UserAgent | string | UserAgent | 分詞 |
13 | Level | string | 日誌級別 | 不分詞,統一大寫 |
14 | Class | string | 記錄日誌所在類名 | 不分詞 |
15 | Method | string | 記錄日誌所在方法名 | 不分詞 |
16 | MethodParams | string | 拋出自定義異常方法參數 | 分詞 |
17 | Line | int | 行號 | 整數 |
18 | Logger | string | 日誌名 | 不分詞 |
19 | IOType | string | 自定義異常io類型 | 不分詞 |
20 | ExceptionType | string | 異常類型 | 不分詞 |
21 | ExceptionMessage | string | 異常信息 | 分詞 |
22 | CustomMessage | string | 自定義信息 | 分詞 |
23 | StackTrace | string | 堆棧信息 | 分詞 |
24 | HawkKey | string | Key | 不分詞 |
這個日誌格式設計具備以下特色:github
- 異常消息(ExceptionMessage)和自定義消息(CustomMessage)分離
- 異常類型(ExceptionType),異常堆棧(StackTrace),異常消息(ExceptionMessage)相互分離
- 擴展了Web相關字段。域名(Host), url主幹(UriStem)等
這些設計都是爲了後面方便日誌搜索和監控報警。web
另外咱們發現不少同事打日誌懼怕異常信息和堆棧丟失,喜歡把異常消息,堆棧和自定義消息拼接到一塊兒,例如:redis
logger.error("my message, " + ex.toString, ex);
形成異常信息,堆棧丟失的緣由是由於原先的日誌裏沒有自動把異常信息,堆棧,包含到格式裏。這也是咱們順帶須要解決的一個問題。spring
規範化實現
咱們團隊統一使用的是slf4j + log4j2來打日誌,log4j2自己提供了很是友好的插件擴展, 這樣咱們就能夠擴展一個本身的Layout出來,將日誌格式規範內化到Layout裏,這樣使用者 只須要應用咱們的Layout,無需關心日誌格式,打出來就是規範的日誌格式,這樣推廣就會 簡單不少,並且還能夠方便的擴展字段,順便解決掉異常消息和堆棧丟失的問題。服務器
咱們擴展的Layout實現類名爲: Autolog4jCsvLayout, 如下配置就能夠實現日誌規範化。post
<RollingFile name="ProgramError" ignoreExceptions="false" fileName="${sys:log.path}/project_error.log" filePattern="${sys:log.path}/project_error.log_%d{yyyy-MM-dd}"> <Autolog4jCsvLayout charset="UTF-8" department="${sys:department}" team="${sys:team}" project="${sys:project}" /> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> </Policies> <EnumFilter allowLevels="WARN,ERROR,FATAL" /> </RollingFile> <RollingFile name="ProgramRun" ignoreExceptions="false" fileName="${sys:log.path}/project_run.log" filePattern="${sys:log.path}/project_run.log_%d{yyyy-MM-dd}"> <Autolog4jCsvLayout charset="UTF-8" department="${sys:department}" team="${sys:team}" project="${sys:project}" /> <Policies> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> </Policies> <EnumFilter allowLevels="TRACE,DEBUG,INFO" /> </RollingFile>
這裏將錯誤日誌和INFO日誌分開寫入。另外咱們發現log4j2的日誌級別過濾器很難理解,因此實現了一種直接枚舉日誌級別的過濾器(EnumFilter),簡單好懂。 解決了異常信息堆棧丟失的問題,你們就能夠開心的使用以下方式打日誌:ui
logger.error("my message", ex);
打出的文本日誌舉例:url
"2018-04-20T15:18:59.773+08:00" "-" "dealer" "dealer.arch" "projectname" "-" "10.1.1.1" "-" "-" "-" "-" "-" "ERROR" "org.springframework.test.context.TestContextManager" "prepareTestInstance" "-" "234" "org.springframework.test.context.TestContextManager" "unknown" "java.lang.IllegalStateException" "Failed to load ApplicationContext" "Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@17a756db] to prepare test instance [com.autohome.daimon.job.service.integration.HawkeyeServiceTest@2d10160a]" "java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hawkeyeService': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'redisDao' is expected to be of type 'RedisDao' but was actually of type 'RedisDao$$EnhancerBySpringCGLIB$$17f5ad58' at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:321) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1268) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) ... 24 more Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'redisDao' is expected to be of type 'IRedisDao' but was actually of type 'RedisDao$$EnhancerBySpringCGLIB$$17f5ad58' at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:384) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ... 40 more " "b83539ac4a40de0d"
目前,咱們已經把日誌擴展類庫開源,但願對別人也有所幫助。github地址: autolog4j
另外日誌規範只是日誌建設的第一步,咱們還開源了基於Elasticsearch數據的開源日誌監控系統, 歡迎使用。frostmourne