打印日誌是一門藝術,但長期被開發同窗所忽視。日誌就像車輛保險,沒人願意爲保險付錢,可是一旦出了問題都又想有保險可用。咱們打印日誌的時候都很隨意,但是用的時候會吐槽各類 SB 包括本身!寫好每一條日誌吧,與君共勉!html
日誌,維基百科的定義是記錄服務器等電腦設備或軟件的運做。java
日誌文件提供精確的系統記錄,根據日誌最終定位到錯誤詳情和根源。日誌的特色是,它描述一些離散的(不連續的)事件。 例如:應用經過一個滾動的文件輸出 INFO 或 ERROR 信息,並經過日誌收集系統,存儲到一些存儲引擎(Elasticsearch)中方便查詢。git
在上文中咱們解釋了日誌的做用是提供精準的系統記錄方便根因分析。那麼具體在哪些具體方面它能夠發揮做用?github
根因分析(甩鍋必備):即在關鍵地方記錄日誌。方便在和各個終端定位問題時,別人說時你的程序問題,你能夠義正詞嚴的拿出你的日誌說,看,我這裏運行了,狀態也是對的。這樣,對方就會乖乖去定位他的代碼,而不是互相推脫。spring
上文說了日誌的重要性,那麼何時須要記錄日誌。數據庫
Slf4j 英文全稱爲 「 Simple Logging Facade for Java 」,爲 Java 提供的簡單日誌門面。Facade 門面,更底層一點說就是接口。它容許用戶以本身的喜愛,在工程中經過 Slf4j 接入不一樣的日誌系統。編程
Logback 是 Slf4j 的原生實現框架,一樣也是出自 Log4j 一我的之手,但擁有比 Log4j 更多的優勢、特性和更作強的性能,Logback 相對於 Log4j 擁有更快的執行速度。基於咱們先前在 Log4j 上的工做,Logback 重寫了內部的實現,在某些特定的場景上面,甚至能夠比以前的速度快上 10 倍。在保證 Logback 的組件更加快速的同時,同時所需的內存更加少。json
日誌文件放置於固定的目錄中,按照必定的模板進行命名,推薦的日誌文件名稱:安全
當前正在寫入的日誌文件名:<應用名>[-<功能名>].log 如:example-server-book-service-access.log 已經滾入歷史的日誌文件名:<應用名>[-<功能名>].yyyy-MM-dd-hh.[滾動號].log
如:example-server-book-service-access.2019-12-01-10.1.log服務器
推薦使用 lombok(代碼生成器) 註解 @lombok.extern.slf4j.Slf4j 來生成日誌變量實例。
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
代碼示例
import lombok.extern.slf4j.Slf4j; @Slf4j public class LogTest { public static void main(String[] args) { log.info("this is log test"); } }
日誌記錄採用分級記錄,級別與日誌文件名相對應,不一樣級別的日誌信息記錄到不一樣的日誌文件中。若有特殊格式日誌,如 access log,單獨使用一個文件,請注意避免重複打印(可以使用 additivity="false" 避免 )。
使用參數化形式 {} 佔位,[] 進行參數隔離,這樣的好處是可讀性更高,並且只有真正準備打印的時候纔會處理參數。
// 正確示例,必須使用參數化信息的方式 log.debug("order is paying with userId:[{}] and orderId : [{}]",userId, orderId); // 錯誤示例,不要進行字符串拼接,那樣會產生不少 String 對象,佔用空間,影響性能。及日誌級別高於此級別也會進行字符串拼接邏輯。 log.debug("order is paying with userId: " + userId + " and orderId: " + orderId);
做爲日誌產生的日期和時間,這個數據很是重要,通常精確到毫秒。
yyyy-MM-dd HH:mm:ss.SSS
日誌的輸出都是分級別的,不一樣的設置不一樣的場合打印不一樣的日誌。
主要使用以下的四個級別:
DEBUG / INFO 的選擇
DEBUG 級別比 INFO 低,包含調試時更詳細的瞭解系統運行狀態的東西,好比變量的值等等,均可以輸出到 DEBUG 日誌裏。 INFO 是在線日誌默認的輸出級別,反饋系統的當前狀態給最終用戶看的。輸出的信息,應該對最終用戶具備實際意義的。從功能角度上說,INFO 輸出的信息能夠看做是軟件產品的一部分,因此須要謹慎對待,不可隨便輸出。若是這條日誌會被頻繁打印或者大部分時間對於糾錯起不到做用,就應當考慮下調爲 DEBUG 級別。
WARN / ERROR 的選擇
當方法或者功能處理過程當中產生不符合預期結果或者有框架報錯時能夠考慮使用,常見問題處理方法包括:
通常來講,WARN 級別不會短信報警,ERROR 級別則會短信報警甚至電話報警,ERROR 級別的日誌意味着系統中發生了很是嚴重的問題,必須有人立刻處理,好比數據庫不可用,系統的關鍵業務流程走不下去等等。錯誤的使用反而帶來嚴重的後果,不區分問題的重要程度,只要有問題就error記錄下來,其實這樣是很是不負責任的,由於對於成熟的系統,都會有一套完整的報錯機制,那這個錯誤信息何時須要發出來,不少都是依據單位時間內 ERROR 日誌的數量來肯定的。
強調ERROR報警
ERROR日誌目標
問題定位:
輸出該日誌的線程名稱,通常在一個應用中一個同步請求由同一線程完成,輸出線程名稱能夠在各個請求產生的日誌中進行分類,便於分清當前請求上下文的日誌。
在分佈式應用中,用戶的一個請求會調用若干個服務完成,這些服務可能仍是嵌套調用的,所以完成一個請求的日誌並不在一個應用的日誌文件,而是分散在不一樣服務器上不一樣應用節點的日誌文件中。該標識是爲了串聯一個請求在整個系統中的調用日誌。
經過搜索 trace id 就能夠查到這個 trace id 標識的請求在整個系統中流轉(處理)過程當中產生的全部日誌。
在業務開發中,咱們的日誌都是和業務相關聯的,有時候是須要根據用戶或者業務作聚類的,所以一次請求若是能夠經過某項標識作聚類的時候,能夠將聚類標識打印到日誌中。
日誌記錄器名稱通常使用類名,日誌文件中能夠輸出簡單的類名便可,看實際狀況是否須要使用包名和行號等信息。主要用於看到日誌後到哪一個類中去找這個日誌輸出,便於定位問題所在。
禁用 System.out.println 和 System.err.println
變參替換日誌拼接
輸出日誌的對象,應在其類中實現快速的 toString 方法,以便於在日誌輸出時僅輸出這個對象類名和 hashCode
預防空指針:不要在日誌中調用對象的方法獲取值,除非確保該對象確定不爲 null,不然頗有可能會由於日誌的問題而致使應用產生空指針異常。
異常堆棧通常會出如今 ERROR 或者 WARN 級別的日誌中,異常堆棧含有方法調用鏈的系統,以及異常產生的根源。異常堆棧的日誌屬於上一行日誌的,在日誌收集時須要將其劃至上一行中。
2019-12-01 00:00:00.000|pid|log-level|[svc-name,trace-id,span-id,user-id,biz-id]|thread-name|package-name.class-name : log message
日誌模塊是基於如下技術點作擴展的。
在每一個 tracing 鏈路中,將 Opentracing Scope 中的上下文信息放置 MDC 中,根據 Spring Boot Logging 擴展接口擴展的取值邏輯 logging.pattern.level 的取值邏輯。
相關源碼參考
[Spring Cloud Sleuth][https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java](https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/log/Slf4jCurrentTraceContext.java)
修改 logback 配置文件中每一個 appender 的 pattern 爲如下默認值便可實現標準化。
%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}|${PID:- }|%level|${LOG_LEVEL_PATTERN:-%5p}|%t|%-40.40logger{39}: %msg%n
logback.xml 節選
<configuration> <property name="LOG_PATH" value="${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}"/> <springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="spring-boot-fusion"/> <!-- 全局統一 pattern --> <property name="LOG_PATTERN" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}|${PID:- }|%level|${LOG_LEVEL_PATTERN:-%5p}|%t|%-40.40logger{39}: %msg%n"/> <!-- 輸出模式 file,滾動記錄文件,先將日誌文件指定到文件,當符合某個條件時,將日誌記錄到其餘文件 --> <appender name="fileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--被寫入的文件名,能夠是相對目錄,也能夠是絕對目錄,若是上級目錄不存在會自動建立,沒有默認值。--> <file>${LOG_PATH}/${APP_NAME}-info.log</file> <!--滾動策略 基於時間的分包策略 --> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- yyyy-MM-dd 時間策略則爲一天一個文件 --> <FileNamePattern>${LOG_PATH}/${APP_NAME}-info.%d{yyyy-MM-dd-HH}.%i.log</FileNamePattern> <!--日誌文件保留小時數--> <MaxHistory>48</MaxHistory> <maxFileSize>1GB</maxFileSize> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <!-- layout 負責把事件轉換成字符串,格式化的日誌信息的輸出 --> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>${LOG_PATTERN}</pattern> </layout> <!--級別過濾器,根據日誌級別進行過濾。若是日誌級別等於配置級別,過濾器會根據onMath 和 onMismatch接收或拒絕日誌--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <!--設置過濾級別--> <level>INFO</level> <!--用於配置符合過濾條件的操做--> <onMatch>ACCEPT</onMatch> <!--用於配置不符合過濾條件的操做--> <onMismatch>DENY</onMismatch> </filter> </appender> </configuration>
代碼使用示例:
@Override public Result<PagingObject<SimpleResponse>> page(@RequestParam(value = "page-num", defaultValue = "1") int pageNum, @RequestParam(value = "page-size", defaultValue = "10") int pageSize) { LogStandardUtils.putUserId("userId123"); LogStandardUtils.putBizId("bizId321"); producerService.sendMsg("xxx"); simpleClient.page(pageNum, pageSize); return new Result<>(simpleService.page(pageNum, pageSize)); }
日誌記錄
2019-12-04 16:29:08.223|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-example-server-order-service 2019-12-04 16:29:08.224|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.netflix.loadbalancer.BaseLoadBalancer : Client: example-server-order-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 2019-12-04 16:29:08.234|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater 2019-12-04 16:29:08.247|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client example-server-order-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:ConsulServerList{serviceId='example-server-order-service', tag=null} 2019-12-04 16:29:08.329|43546|WARN|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.p.f.l.ctl.common.rule.StrategyRule : No up servers available from load balancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:ConsulServerList{serviceId='example-server-order-service', tag=null} 2019-12-04 16:29:08.334|43546|WARN|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.p.f.l.ctl.common.rule.StrategyRule : No up servers available from load balancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:ConsulServerList{serviceId='example-server-order-service', tag=null} 2019-12-04 16:29:08.342|43546|ERROR|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.p.f.w.c.advice.ExceptionHandlerAdvice : 當前程序進入到異常捕獲器,出錯的 url 爲:[ http://127.0.0.1:10011/simples ],出錯的參數爲:[ {"querystring":"{}","payload":""} ] java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: example-server-order-service
阿里雲日誌服務(簡稱 SLS)是針對日誌類數據的一站式服務,在阿里巴巴集團經歷大量大數據場景錘鍊而成。您無需開發就能快捷完成日誌數據採集、消費、投遞以及查詢分析等功能,提高運維、運營效率,創建 DT 時代海量日誌處理能力。
項目、管理日誌基礎單元,服務日誌建議一個環境建爲一個 Project,這樣日誌記錄是總體一個閉環,日誌記錄隨整個環境內的服務調用產生。
日誌庫,日誌庫建議按照日誌類型分爲不一樣的,如特定格式的 access 日誌,以及 info / warn / error 日誌,特定格式能夠配置更爲方面的索引以及告警設置。
注意:請勿按照應用服務區分爲不一樣的 logstore,在微服務架構中,一次請求交叉了多個應用服務,日誌是散落在各個應用服務中的,按照服務區分 logstore,須要開發同窗十分了解應用運行情況和調用拓撲圖,這點每每是不具有的。
功能:
用途:數據清洗(ETL)、流計算(Stream Compute)、監控與報警、 機器學習與迭代計算。
實時索引、查詢分析數據。
用途:DevOps / 線上運維,日誌實時數據分析,安全診斷與分析,運營與客服系統。
穩定可靠的日誌投遞。將日誌中樞數據投遞至存儲類服務進行存儲。支持壓縮、自定義Partition、以及行列等各類存儲方式。
用途:數據倉庫 + 數據分析、審計、推薦系統與用戶畫像。
日誌服務的告警功能基於儀表盤中的查詢圖表實現。在日誌服務控制檯查詢頁面或儀表盤頁面設置告警規則,並指定告警規則的配置、檢查條件和通知方式。設置告警後,日誌服務按期對儀表盤的查詢結果進行檢查,檢查結果知足預設條件時發送告警通知,實現實時的服務狀態監控。
阿里雲的日誌服務功能至關強大,想用好日誌服務能夠參看:
https://help.aliyun.com/document_detail/29090.html?spm=a2c4g.11186623.6.1079.4edd3aabvs50OW
ELK 是 Elasticsearch、Logstash、Kibana 三大開源框架首字母大寫簡稱。市面上也被成爲 Elastic Stack。其中 Elasticsearch 是一個基於 Lucene、分佈式、經過 Restful 方式進行交互的近實時搜索平臺框架。像相似百度、谷歌這種大數據全文搜索引擎的場景均可以使用 Elasticsearch 做爲底層支持框架,可見 Elasticsearch 提供的搜索能力確實強大,市面上不少時候咱們簡稱 Elasticsearch 爲 es。Logstash 是 ELK 的中央數據流引擎,用於從不一樣目標(文件/數據存儲/MQ)收集的不一樣格式數據,通過過濾後支持輸出到不一樣目的地(文件/ MQ / Redis / Elasticsearch / Kafka 等)。Kibana 能夠將 Elasticsearch 的數據經過友好的頁面展現出來,提供實時分析的功能。
2019-11-26 15:01:03.332|1543|INFO|[example-server-book-service,28f019d57b8336ab,630697c7f34ca4fa,105,45982043|XNIO-1 task-42]|c.p.f.w.pay.PayServiceImpl : order is paying with userId: 105 and orderId: 45982043
普通日誌前綴是固定的,能夠固定分詞索引,方便更快的查詢分析。
以 access 日誌爲例
2019-11-26 15:01:03.332|1543|INFO|[example-server-book-service,28f019d57b8336ab,630697c7f34ca4fa,105,45982043|XNIO-1 task-42]|c.p.f.w.logging.AccessLoggingFilter : > url: http://liweichao.com:10011/actuator/health > http-method: GET > request-header: [Accept:"text/plain, text/*, */*", Connection:"close", User-Agent:"Consul Health Check", Host:"liweichao.com:10011", Accept-Encoding:"gzip"] > request-time: 2019-11-26 15:01:03.309 > querystring: - > payload: - > extra-param: - < response-time: 2019-11-26 15:01:03.332 < take-time: 23 < http-status: 200 < response-header: [content-type:"application/vnd.spring-boot.actuator.v2+json;charset=UTF-8", content-size:"15"] < response-data: {"status":"UP"}
特定格式日誌可按格式建立索引更方便聚焦查詢分析和告警,如根據 take-time,http-status,biz-code 等值。
來自讀者投稿,有好的文章歡迎聯繫我微信 jihuan900