小记 | 从 0 到 1,看我如何玩弄千万日志于股掌
程序員的工作離不開日志。
日志就像一個筆記本,可以記錄程序運行時的一些信息。
通過日志,我們可以做很多事情。
日志的作用
記錄系統和接口的使用情況,比如請求日志
記錄和分析用戶的行為,比如網站訪問日志
調試程序,和控制臺的作用類似,但是控制臺中的內容并不會保存到文件中,而日志可以長期保存。
幫助我們排查和定位錯誤。比如在系統拋出異常時,將異常信息記錄到日志,可以事后復盤。
通過分析日志還能夠優化代碼邏輯、提升系統性能、穩定性等。
日志雖然有那么多的作用,但如果數量過多,也會讓開發人員感到頭疼。對于大型的系統,程序員們經常要看幾千、幾萬行日志,常常看日志看到頭暈眼花。
但是,其實處理日志是有很多技巧的,下面魚皮分享自己和日志的故事。
故事分為 7 個階段,從看日志看到懷疑人生,再到玩弄千萬日志于股掌,魚皮都做了哪些事情?
魚皮和日志的愛恨情仇
第一階段 無日志
剛開始搭建新的系統時,為了圖個方便,魚皮沒有給系統接入任何的日志框架,也沒有記錄任何日志,整個項目非常的干凈絲滑。需要調試時就直接用輸出函數將信息打印在控制臺,出了異常就直接打印堆棧。
System.out.println("value = " + value); catch(Exception e) {e.printStackTrace(); }真是無事一身輕,爽的不得了!
可惜,好景不長。在項目上線之后,突然有一天,系統出問題了,數據查不出來了,同事找上門來了。
魚皮笑著說:“問題不大!”
然后登錄服務器,進入項目目錄,瞬間傻眼。
項目目錄依舊干凈絲滑,原來我特么根本沒記日志啊!
這下好了,什么問題都查不出來。乖乖地去給項目加上日志功能吧。
第二階段 日志類庫
Java 語言有很多優秀的日志類庫,比如 Logback、Log4j2 等,提供了很多記錄和打印日志的方法,非常方便。可以直接使用其中一個類庫,而無需自己實現。此處因為魚皮的項目使用 Spring Boot 框架進行開發,直接使用其默認日志庫 Logback 即可。
使用方式很簡單,先添加 logback.xml 配置文件,主要配置了日志文件的存儲路徑和格式。Logback 框架還會自動將日志按天進行壓縮,并且在一定天數后進行刪除,以節約磁盤空間。最大存儲天數也可以在配置文件中指定。
配置文件大概長這樣:
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds"><include resource="org/springframework/boot/logging/logback/defaults.xml"/><appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/application.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!-- Daily rollover with compression --><fileNamePattern>log/application-log-%d{yyyy-MM-dd}.gz</fileNamePattern><maxHistory>30</maxHistory></rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:- } ...</pattern><charset>UTF-8</charset></encoder></appender>... </configuration>在要打印日志的類上創建一個日志對象:
final Logger logger = LoggerFactory.getLogger(MyApp.class);然后就可以使用該對象去記錄日志啦:
catch(Exception e) {logger.error("app error", e); }加上配置文件后,啟動項目,就可以看見生成的日志文件了。歐耶,下次系統再出問題,就不怕缺乏信息來排錯啦!
系統運行了一個小時之后,同事又找上門來了,這次魚皮很有底氣,笑著說:“問題不大!”
然后打開日志文件一看,傻眼了,有幾千行日志,我怎么知道哪行日志是報錯信息呢?
就這你能秒了我?直接用 Linux 命令過濾出帶 “ERROR” 字段的日志行就行了~
cat application.log | grep 'ERROR'雖然解決了問題,但是后面每次報錯,都要輸一遍這個篩選命令,而且隨著文件越來越大,命令執行的速度越來越慢了。
能不能把所有錯誤日志和正常日志區分開,放在不同的文件中呢?
第三階段 日志分級
幸運的是,一般的日志框架都提供了日志分級存儲功能,可以通過修改配置文件來實現。
修改 logback.xml 配置文件,將 ERROR(錯誤)級別的日志單獨輸出到 error.log 文件中,實現日志分級:
<configuration ...><appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/error.log</file><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:- } [%15.15thread] %-50.50logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder></appender> </configuration>啟動項目,日志按預期分級寫到了 application.log 和 error.log 兩個文件。系統再出現異常時,魚皮只需打開 error.log 文件,錯誤信息一覽無遺!
系統運行一段時間后,魚皮上線了一個很重要的服務,記錄了相當多的業務日志。雖然目前錯誤日志可以單獨查看,但是核心服務的日志和其他服務的正常日志都堆積在 application.log 中,想要僅查看核心服務的日志依舊要采用命令過濾的方式,比較麻煩。
有沒有什么辦法,把核心業務的日志單獨記錄到一個文件中呢?
第四階段 按類隔離
幸運的是,Logback 日志框架支持將不同的類產生的日志記錄到不同的文件中,修改配置文件即可。比如將所有 RequestAOP 類產生的請求日志記錄到 request.log 中:
<appender name="REQUEST_HANDLER" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/request.log</file><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">...</rollingPolicy><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ${PID:- } [%15.15thread] %-50.50logger{50} - %msg%n</pattern><charset>UTF-8</charset></encoder> </appender> <!-- logger 中配置類名 --> <logger name="com.yupi.RequestAOP" level="INFO" additivity="false"><appender-ref ref="REQUEST_HANDLER"/> </logger>啟動項目,自動生成了 request.log 文件,打開這個文件,就可以查看所有的請求日志,可以用于流控分析等等,真爽死了!
后來,隨著系統的訪問量越來越大,單臺服務器已經不能滿足對并發的需求,因此魚皮又加了三臺機器,共同提供服務。
有一天,系統又出問題了,同事找上門來,魚皮心想:信不信分分鐘給你解決 bug!
一頓操作猛如虎,登錄一臺服務器查看日志,結果錯誤日志空空如也,比魚皮的兜兒都干凈。
奇怪了,怎么找不到錯誤信息?
對啊,現在有四臺機器,請求可能落在了其他機器上,因此錯誤日志也可能在別的機器上!
哎,沒辦法,一臺一臺登錄服務器去找錯誤信息吧。
其實四臺機器還能忍,但是后來隨著并發量的增大,魚皮負責的系統已經有十臺機器了,每次查看日志要依次登錄十臺機器去找!而且單個日志數據的量已經達到幾十萬行,無論怎么切分看起來都太累了。
哦,喬治,這太難受了!有沒有什么辦法,能讓我在一個地方集中看日志啊!
要不直接把日志記錄到數據庫中?
不行不行,日志數據量太大了,數據庫肯定存不下。而且寫入數據庫的速度受到網絡傳輸等限制,比較緩慢。
怎么辦啊?算了,先睡一覺。
第五階段 日志上報與集中式管理
“嘿,少年,你想要力量么?”
“廢話,誰不想要!”
“聽說過 ELK 么?他會指引你前進的方向。”
魚皮從夢中驚醒,對啊,可以用 ELK 搭建一個分布式日志收集系統啊!
ELK 是 Elasticsearch、Logstash 和 Kibana 的簡稱,不是單獨的一個軟件,而是一整套問題的解決方案。
Elasticsearch(簡稱 ES)是全文搜索引擎,能夠對海量數據進行存儲和高效的搜索。
Logstash 是一個數據管道,能夠從各種數據源(比如 MySQL 數據庫)收集數據,將數據從一處傳輸到另一處,并加以解析和轉換。
Kibana 是數據可視化平臺,可以將 Elasticsearch 中存儲的數據進行展示。在 Kibana 上,我們不僅可以看到所有原始的日志信息,還能夠自定義各種精美直觀的可視化圖表。
通常使用 Logstash 統一收集各個機器上的數據,并傳輸至 Elasticsearch 進行存儲,最后通過 Kibana 進行數據展示,之后就可以利用 Kibana 輕松地查看和分析所有的數據了。
既然可以解決問題,那就接入 ELK 吧~
但是使用 ELK 相當于為系統引入了三個新組件,考慮到系統使用的組件越多,復雜度越高,就越難維護;而且 Logstash 比較重,對 CPU 和內存的占用較高。因此,魚皮靈機一動,干脆舍棄掉 Logstash,直接將 Elasticsearch 當成數據庫來使用。
先在 Spring Boot 中整合 Elasticsearch,然后將日志數據通過依賴包提供的 API 接口存儲到 Elasticsearch,最后接入 Kibana 進行展示。
Maven 引入依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version>2.3.4.RELEASE</version> </dependency>訪問 ES 的接口:
@Repository public interface UserRepository extends ElasticsearchRepository<HouseIndexTemplate, Long> {}想法是美好的,現實卻是賊特么殘酷的。
雖然 Spring Boot 接入 Elasticsearch 的確很方便,但是要把整個項目中的記日志代碼全部替換成寫入 ES 的代碼,對整個項目的改動和侵入性太大了。而且將日志存入 ES 的耗時遠遠大于原來異步寫入文件的耗時,并發量很大時,偶爾出現日志寫入失敗的情況。因此改代碼改到一半時,魚皮就抓狂放棄了,直接把改了的代碼全部還原。
魚皮再次陷入沉思,有沒有一種方式,可以在不改動一行代碼的情況下,將日志寫入 ES 呢?
第六階段 日志代理
如果不改動任何代碼,每臺機器產生的日志仍然是獨立記錄到當前機器的日志文件中的,想要通過一個界面集中查看各機器上的日志非常麻煩。
那如果把日志文件中的數據自動同步到 ES 上,不就能通過 Kibana 方便地查看了么!
誰來做同步這件事呢?難道要我自己寫個定時任務程序把日志文件上傳到 ES 上?如果是那樣,我還不如繼續改原來項目的代碼。
能不能找個代理來幫我做這件事呢?
就像我們每天丟辣雞一樣,把辣雞丟到小區門口的辣雞桶就行了,然后辣雞車會幫我們把辣雞運送至辣雞站集中處理。
我們的日志文件相當于辣雞,代理就相當于辣雞車,而 ES 就相當于辣雞站。
通過百度,發現 ELK 早就升級為 ElasticStack 了,除了上面說的三大組件外,還多了一個 Beats。
Beats 是輕量級數據采集器,針對不同的數據類型提供了不同的組件。
要將日志文件數據上傳到 ES 進行存儲,可以使用 Filebeat。Filebeat 是輕量型日志采集器,其提供了一種輕量型方法,用于轉發和匯總日志與文件,讓我們輕松面對成百上千、甚至成千上萬的服務器、虛擬機和容器生成的日志。
Filebeat 就相當于一個代理(agent),可以幫助收集各個機器上的日志,然后傳輸給 Logstash 進行處理或者直接傳輸到 Elasticsearch 進行存儲。這樣就完全不用修改項目的代碼!
ElasticStack 整體架構如下:
那怎么使用 Filebeat 呢?
其實非常簡單,直接將 Filebeat 安裝到日志文件所在的服務器上,然后在其配置文件中定義輸入(要采集的日志文件路徑)和輸出(要將采集到的數據發送到哪里)即可。比如在下面的配置中,會采集 system 日志并傳輸給 Logstash:
filebeat.inputs: - type: logpaths:- /var/log/system.logoutput.logstash:hosts: ["127.0.0.1:5044"]搞定,這下真是爽死了!曾經幾千行日志就能將我淹沒,而現在,只需要打開 Kibana 控制臺,動動手指,就能輕松地查看和分析幾十萬、幾百萬的日志。
感覺自己就像一個大將軍,這些日志都是我統治的小兵,都得乖乖聽我號令,好不痛快!
第七階段 完善日志架構
利用 ElasticStack,已經能夠輕松地集中管理海量的日志,而且 Elasticsearch 支持水平擴容,可以應對日志量級的持續增大,存個千萬條數據完全沒有問題。
但是,當每秒產生的日志量過多時,ElasticStack 并不是無敵的,雖然 Filebeat、Elasticsearch、Kibana 都很強勁,但往往 Logstash 是那阿喀琉斯之踵(或者三娃的屁股)!
因為 Logstash 要同時接受多個 Filebeat 采集的日志,機器越多,部署的 Filebeat 也就越多,Logstash 的壓力就會越大。雖然也可以像擴容 ES 一樣增加 Logstash 的節點數,但是并不能從根本上解決問題,當日志量級增大到一定程度時,不僅是 Logstash,連 ES 集群都有可能撐不住!
因此,我們需要接入一些中間件來進行緩沖,首選的可靠且高性能的消息隊列 Kafka(依賴分布式協調工具 Zookeeper),最終,完善的分布式日志收集系統架構如下:
至此,魚皮終于將千萬日志玩弄于股掌,這種感覺真的是太爽了。
如果你也正在被日志折磨,一定要試著搭建一套完善的日志系統。
最后分享自己記錄日志的經驗:
1.不要過度依賴日志,什么都記,日志應當簡潔明晰,具有實際價值。
2.在保證可理解的同時適當減少日志的長度,比如把 this is an apple 簡化為 apple。
3.將日志進行分級和分類,僅在開發和測試環境輸出 DEBUG 級別日志,不要在生產環境中使用。
4.統一日志的格式,便于后續處理分析,通常在日志框架配置即可。
5.不要把日志當成存儲數據的工具!注意日志信息中不能出現敏感信息,也不要對外公開!
魚皮從 0 到 1,經歷了七個階段,成功地玩弄千萬日志于股掌。其實,無論是學習還是實際應用,我們都需要有這種持續實踐、探索和優化的精神。會當凌絕頂,一覽眾山小。
總結
以上是生活随笔為你收集整理的小记 | 从 0 到 1,看我如何玩弄千万日志于股掌的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NIS服务器的配置
- 下一篇: [转]数据库主键设计文章绘粹