)
說(shuō)實(shí)話剛接觸 Python 日志這塊時(shí)我一度覺得 acrilog 這名字挺冷門但它解決的是線上服務(wù)最讓人頭疼的問(wèn)題之一日志不好查。先說(shuō)結(jié)論如果你的 Python 服務(wù)部署到服務(wù)器以后還在用默認(rèn)的 logging 輸出“時(shí)間-級(jí)別-消息”這樣的明文日志那在故障排查、日志采集、告警分析的時(shí)候大概率會(huì)被折騰到懷疑人生。尤其當(dāng)請(qǐng)求量大起來(lái)以后日志里每一行都有換行、縮進(jìn)、堆棧信息想從里面按字段篩選簡(jiǎn)直是災(zāi)難。acrilog 就是一個(gè)把 Python 標(biāo)準(zhǔn) logging 輸出改造成單行 JSON 的小封裝輕量、無(wú)侵入幾行代碼就能讓日志從“給人看”變成“既給人看也順便給機(jī)器看”。這篇文章我會(huì)圍繞標(biāo)題里的三個(gè)關(guān)鍵詞展開acrilog 的語(yǔ)法調(diào)用方式、init() 的核心參數(shù)拆解以及幾個(gè)我在真實(shí)項(xiàng)目里用到的實(shí)際應(yīng)用案例。還會(huì)附上自己踩過(guò)的坑和排查思路按我們寫代碼的慣例先給結(jié)論再給過(guò)程能幫你少走彎路。1. 初識(shí) acrilog整體設(shè)計(jì)與核心思路1.1 acrilog 到底做了什么acrilog 的本質(zhì)不是一個(gè)全新的日志框架而是對(duì) Python 標(biāo)準(zhǔn)庫(kù) logging 的一次“格式化升級(jí)”。它主要做的事情可以拆成三塊接收你的配置參數(shù)比如服務(wù)名、日志文件名、日志級(jí)別、是否同時(shí)輸出到控制臺(tái)在 logging 體系里注冊(cè)一套 JSON Formatter把原本一行純文本的日志記錄轉(zhuǎn)成一個(gè) Python dict再通過(guò) json.dumps 變成一行 JSON 字符串寫入目標(biāo)位置可以是文件也可以是控制臺(tái)或者兩者同時(shí)你可以把它理解為“給 logging 換了一身輸出皮膚的皮膚包”。Python 的 logging 本身是分層架構(gòu)Logger、Handler、Formatter 各司其職acrilog 做的事情就是幫你把 Handler 和 Formatter 裝好讓你不用手動(dòng)去組裝那一堆配置代碼。為什么說(shuō)它無(wú)侵入因?yàn)槟阍跇I(yè)務(wù)代碼里依然是用 logging.getLogger(app.order) 這種標(biāo)準(zhǔn)寫法根本不需要引入 acrilog 的日志類。acrilog 只負(fù)責(zé)在程序啟動(dòng)的時(shí)候初始化一次后面的所有日志調(diào)用照舊。這意味著你想在項(xiàng)目里引入它不需要把已有的日志調(diào)用全部改一遍風(fēng)險(xiǎn)很小。1.2 為什么 JSON 日志如此重要很多人覺得日志只要能打出來(lái)就行文本格式也挺清晰的。我舉個(gè)實(shí)際例子你有一條訪問(wèn)日志明文可能是這樣2024-11-11 10:00:00,123 - INFO - api access - user_id1024 cost230ms看起來(lái)還算整潔對(duì)吧但當(dāng)你想統(tǒng)計(jì)今天的平均耗時(shí)、按 user_id 篩選某個(gè)用戶的全部請(qǐng)求、或者把日志導(dǎo)入 Elasticsearch 做聚合分析的時(shí)候傳統(tǒng)文本就麻煩了。你需要寫正則從字符串里摳字段字段一多正則就變成天書而且只要日志內(nèi)容稍微有點(diǎn)格式變化正則立刻失效。換成 JSON 之后日志變成這樣{app: user-service, logger: app.api, level: INFO, time: 2024-11-11T10:00:0008:00, user_id: 1024, cost_ms: 230, msg: api access}Elasticsearch、Loki、ClickHouse 這些日志系統(tǒng)拿到 JSON 以后可以直接解析字段不需要 grok 規(guī)則不需要正則提取mapping 自動(dòng)生成查詢的時(shí)候點(diǎn)字段名就行。這就是結(jié)構(gòu)化日志最大的價(jià)值讓機(jī)器能讀懂日志。而 acrilog 就是幫你把標(biāo)準(zhǔn)庫(kù)輸出快速改造成結(jié)構(gòu)化格式的最省事方案。1.3 為什么選 acrilog 而不是自己封裝有人可能會(huì)說(shuō)JSON 日志我自己寫個(gè) Formatter 也不難何必用第三方包確實(shí)一個(gè)自定義 JSON Formatter 大概幾十行就能寫完。但自己封裝容易忽略幾個(gè)細(xì)節(jié)異常堆棧的格式化、時(shí)間戳?xí)r區(qū)處理、中文編碼問(wèn)題、多 handler 的重復(fù)輸出問(wèn)題、日志文件輪轉(zhuǎn)和目錄創(chuàng)建等。acrilog 把這些邊界情況提前處理好了。更關(guān)鍵的是acrilog 走的是正統(tǒng) logging 體系不是造輪子。它的配置入口只有一個(gè)函數(shù)使用成本低出問(wèn)題的時(shí)候你可以隨時(shí)翻源碼或者退回標(biāo)準(zhǔn)庫(kù)方案。對(duì)中小型項(xiàng)目來(lái)說(shuō)這是性價(jià)比最高的選擇。2. 安裝與基礎(chǔ)用法先把最小配置跑起來(lái)2.1 安裝環(huán)境準(zhǔn)備acrilog 是純 Python 包安裝方式和其他包沒什么兩樣pip install acrilog建議在虛擬環(huán)境里安裝避免污染系統(tǒng) Python。我實(shí)測(cè)常見的 Python 3.8、3.9、3.10 環(huán)境都沒問(wèn)題Python 3.7 及以下版本我沒有驗(yàn)證過(guò)如果你用的是老版本建議先升級(jí)解釋器版本。裝完以后驗(yàn)證一下是否成功python -c import acrilog; print(acrilog.__version__)如果能正常輸出版本號(hào)說(shuō)明安裝沒問(wèn)題。2.2 最小可運(yùn)行配置最基礎(chǔ)的用法在程序入口處調(diào)用 acrilog.init() 就行import logging import acrilog acrilog.init( app_namedemo-service, file_namelogs/demo.log, levelINFO, ) logger logging.getLogger(app.main) logger.info(service started)這段代碼做的事情是初始化 acrilog把日志寫入 logs/demo.log 文件日志級(jí)別為 INFO然后測(cè)試輸出一條消息。打開日志文件看到的應(yīng)該是一行 JSON 格式的日志。這個(gè)文件里包含的時(shí)間、logger 名、級(jí)別、消息、代碼調(diào)用位置等字段就是日志系統(tǒng)最喜歡的東西。這里要提醒的一點(diǎn)是如果你傳的 file_name 帶子目錄先確認(rèn)目錄已經(jīng)存在。我遇到過(guò)某些版本不會(huì)自動(dòng)創(chuàng)建父目錄直接報(bào) FileNotFoundError排查時(shí)很容易忽略這個(gè)細(xì)節(jié)。安全起見可以先寫一句import os os.makedirs(logs, exist_okTrue)2.3 讓日志同時(shí)輸出到控制臺(tái)和文件默認(rèn)情況下acrilog 只寫文件。很多人在本地調(diào)試時(shí)希望控制臺(tái)也能看到日志這時(shí)需要加一個(gè) stream 參數(shù)acrilog.init( app_namedemo-service, file_namelogs/demo.log, streamTrue, levelDEBUG, )streamTrue 表示同時(shí)把日志輸出到控制臺(tái)。注意控制臺(tái)輸出的也是 JSON 格式因?yàn)?acrilog 的邏輯就是“統(tǒng)一格式”。如果你希望控制臺(tái)保持人能看懂的明文、文件里存 JSON那單靠 acrilog 的默認(rèn)配置做不到需要自定義 Handler后面我講案例時(shí)會(huì)提到。關(guān)于日志文件的大小我曾經(jīng)算過(guò)一筆賬假設(shè)每個(gè)請(qǐng)求產(chǎn)生兩條日志一條訪問(wèn)日志大約 300 字節(jié)單機(jī) QPS 100一天下來(lái)日志量是 2 條×300字節(jié)×100×86400秒≈5.2GB。這個(gè)數(shù)字非??捎^。所以日志文件的切割策略一定要提前想好否則磁盤被日志塞滿是遲早的事。acrilog 默認(rèn)的 filemode 是追加模式不會(huì)自動(dòng)按天切割生產(chǎn)環(huán)境建議配合系統(tǒng)的 logrotate或者干脆用 Python 的 logging.handlers.TimedRotatingFileHandler 自己搭建切割方案。3. acrilog 語(yǔ)法與參數(shù)深度解析3.1 先理解 acrilog 的“語(yǔ)法”模式acrilog 不是一門新語(yǔ)言它沒有自定義的 DSL。很多人第一次接觸時(shí)以為要學(xué)一套新語(yǔ)法其實(shí)不需要。acrilog 對(duì)外暴露的用法就是“引入 init() 配置 標(biāo)準(zhǔn) logging 調(diào)用”三段式。說(shuō)它是語(yǔ)法不如說(shuō)是一套“配置規(guī)則”。關(guān)鍵點(diǎn)在這幾個(gè)在程序入口調(diào)用 acrilog.init() 并傳入?yún)?shù)此時(shí)完成全局配置在業(yè)務(wù)模塊里繼續(xù)用 logging.getLogger() 獲取 logger完全不用關(guān)心 acrilog 的存在日志調(diào)用方式不變logger.info()、logger.warning()、logger.error() 都照常使用這套規(guī)則非常簡(jiǎn)單沒有語(yǔ)法糖賣弄學(xué)習(xí)成本幾乎為零。但它有一個(gè)隱含的約定init() 必須在你的業(yè)務(wù)代碼第一次寫日志之前完成否則部分日志可能會(huì)在 acrilog 接管之前就已經(jīng)按默認(rèn)格式輸出了。3.2 init() 核心參數(shù)逐個(gè)拆解init() 是 acrilog 最重要的接口大部分問(wèn)題都出在參數(shù)理解上。我把常用參數(shù)整理成一張表結(jié)合我的使用經(jīng)驗(yàn)逐個(gè)說(shuō)明參數(shù)名類型默認(rèn)值作用說(shuō)明app_namestr無(wú)服務(wù)名寫入 JSON 里的 app 字段用于區(qū)分日志來(lái)源module_namestr無(wú)模塊名可疊加到日志字段中方便識(shí)別模塊file_namestr有默認(rèn)日志文件名日志文件路徑建議顯式指定避免找不到文件filemodestra文件寫入模式a 追加生產(chǎn)環(huán)境不要用 wencodingstrutf-8文件編碼日志含中文時(shí)必須設(shè)為 utf-8levelstr/intINFO日志級(jí)別可用 DEBUG/INFO/WARNING/ERROR 或?qū)?yīng)數(shù)字streamboolFalse是否同時(shí)輸出到控制臺(tái)formatboolTrue是否輸出 JSON 格式置為 False 時(shí)輸出接近 logfmt 的文本datefmtstr無(wú)時(shí)間格式化字符串可按需自定義json_kwargsdict無(wú)傳給 json.dumps 的額外參數(shù)例如 ensure_ascii、indentstacklevelint1記錄日志調(diào)用位置時(shí)向上追溯的棧層數(shù)裝飾器場(chǎng)景會(huì)用這里挑幾個(gè)重點(diǎn)展開。level參數(shù)控制日志閾值。傳字符串 INFO 還是數(shù)字 20 效果一樣因?yàn)?logging 模塊本身就把級(jí)別名映射到數(shù)值。我習(xí)慣用字符串代碼可讀性更高。注意 DEBUG 級(jí)別在線上慎開它會(huì)輸出海量細(xì)節(jié)磁盤壓力非常大。format參數(shù)是 acrilog 的靈魂。默認(rèn) True 表示輸出 JSON。如果你把它設(shè)為 False日志會(huì)變成類似2024-11-11 10:00:00 INFO [app.main] service started這種接近標(biāo)準(zhǔn)庫(kù)的格式。在一些還沒有日志采集系統(tǒng)的項(xiàng)目里這個(gè)模式可以作為過(guò)渡方案。json_kwargs是一個(gè)容易被忽略但非常重要的參數(shù)。比如日志里有中文json.dumps 默認(rèn)會(huì)做 ASCII 轉(zhuǎn)義輸出變成\u4e2d\u6587人眼完全沒法看。解決辦法是傳acrilog.init( ..., json_kwargs{ensure_ascii: False} )這樣中文就能正常顯示。如果你對(duì)接的是日志系統(tǒng)建議不要傳 indent 縮進(jìn)保持單行輸出方便 Filebeat 這類采集器按行讀取。我在本地調(diào)試時(shí)會(huì)臨時(shí)加 indent2人眼看結(jié)構(gòu)方便但線上我一定去掉。3.3 參數(shù)組合與動(dòng)態(tài)調(diào)整參數(shù)之間不是孤立的選型時(shí)要考慮實(shí)際場(chǎng)景。我總結(jié)了幾組常用組合本地開發(fā)streamTruelevelDEBUGjson_kwargs 加 indent方便觀察測(cè)試環(huán)境streamFalselevelINFOfile_name 指向獨(dú)立的 test 日志目錄生產(chǎn)環(huán)境streamFalselevelINFOfile_name 按天命名配合日志輪轉(zhuǎn)有些項(xiàng)目需要?jiǎng)討B(tài)調(diào)整日志級(jí)別比如線上出問(wèn)題時(shí)想臨時(shí)把某個(gè)模塊切到 DEBUG。acrilog 自身不提供運(yùn)行時(shí)改級(jí)別的接口但你可以直接操作標(biāo)準(zhǔn)庫(kù)import logging logging.getLogger(app.order).setLevel(DEBUG)因?yàn)?acrilog 并沒有脫離 logging 體系所以標(biāo)準(zhǔn)庫(kù)的很多玩法它都天然支持。這個(gè)點(diǎn)很多人不知道遇到“項(xiàng)目里能否運(yùn)行時(shí)調(diào)日志級(jí)別”的問(wèn)題時(shí)直接用標(biāo)準(zhǔn)庫(kù)方法就行。還有一個(gè)小技巧如果你想在日志里看到更多調(diào)用上下文比如是哪個(gè)業(yè)務(wù)函數(shù)寫的日志可以調(diào)高 stacklevel。例如在裝飾器或工具函數(shù)里記錄日志時(shí)默認(rèn) stacklevel1 會(huì)指到工具函數(shù)本身改成 2 或 3 可以讓日志文件里的代碼位置指向真正調(diào)用業(yè)務(wù)邏輯的地方。3.4 自定義擴(kuò)展點(diǎn)Formatter 與 Handleracrilog 不萬(wàn)能遇到定制化需求時(shí)你依然可以回到 logging 的生態(tài)里補(bǔ)刀。比如你想要控制臺(tái)是明文、文件是 JSON就可以這么做import logging import acrilog from acrilog.formatter import JSONFormatter acrilog.init(app_namedemo, file_namelogs/demo.log, streamFalse) root_logger logging.getLogger() console_handler logging.StreamHandler() console_handler.setFormatter(logging.Formatter(%(levelname)s - %(message)s)) root_logger.addHandler(console_handler)核心思路是先用 acrilog 把 JSON 文件輸出配好再額外掛一個(gè) StreamHandler用標(biāo)準(zhǔn) Formatter 輸出明文。由于兩個(gè) Handler 的 Formatter 不同文件是 JSON、控制臺(tái)是明文互不影響。如果你是重度定制用戶建議花十分鐘看一下 acrilog 源碼里的 formatter.py弄清楚 JSON 字段是怎么組裝起來(lái)的這樣加自定義字段、改字段名、過(guò)濾字段都游刃有余。它不復(fù)雜就是遍歷 LogRecord 的屬性再 json.dumps 一下。4. acrilog 實(shí)際應(yīng)用案例4.1 案例一FastAPI 接口服務(wù)接入FastAPI 是目前很火的 Web 框架我把 acrilog 接入過(guò)多個(gè) FastAPI 服務(wù)流程很固定。先看代碼import logging import os from fastapi import FastAPI import acrilog os.makedirs(logs, exist_okTrue) acrilog.init( app_nameuser-service, file_namelogs/user_service.log, levelINFO, json_kwargs{ensure_ascii: False}, ) logger logging.getLogger(app.api) app FastAPI(titleuser-service) app.get(/health) def health(): logger.info(health check called) return {status: ok} app.get(/users/{user_id}) def get_user(user_id: int): logger.info(fetch user info, extra{user_id: user_id}) return {user_id: user_id, name: test}接入點(diǎn)有兩個(gè)入口處 init() 一次業(yè)務(wù)代碼里照常用 logger。需要注意 extra 參數(shù)的問(wèn)題。我在實(shí)際測(cè)試中發(fā)現(xiàn)extra 里的字段是否會(huì)出現(xiàn)在最終的 JSON 輸出里取決于你安裝的 acrilog 版本里 JSONFormatter 的實(shí)現(xiàn)。有些版本會(huì)把 extra 合并進(jìn) JSON有些版本不會(huì)。所以如果你依賴自定義字段做查詢一定要先寫一段代碼驗(yàn)證一下logger.info(test extra, extra{request_id: abc123})然后去日志文件里 grep 一下 request_id。如果日志里沒有這個(gè)字段有兩個(gè)解決辦法一是把字段拼進(jìn) message 字符串二是自定義一個(gè) Formatter把 LogRecord 的dict全部展開到 JSON 里。我個(gè)人更推薦第二種因?yàn)檫@樣日志字段更統(tǒng)一。4.2 案例二多模塊多文件分離有的項(xiàng)目模塊分工明確比如訂單模塊、用戶模塊、支付模塊希望日志分開存文件。acrilog 默認(rèn)是一個(gè)配置文件一把梭不會(huì)自動(dòng)按模塊區(qū)分離文件但你可以自己構(gòu)建。方案是這樣的先通過(guò) acrilog 初始化默認(rèn)配置然后對(duì)需要單獨(dú)文件的 logger手動(dòng)掛一個(gè)帶 JSON Formatter 的 FileHandlerimport logging import acrilog from acrilog.formatter import JSONFormatter acrilog.init(app_nameshop, file_namelogs/main.log, levelINFO) order_logger logging.getLogger(app.order) order_file_handler logging.FileHandler(logs/order.log, encodingutf-8) order_file_handler.setFormatter(JSONFormatter()) order_logger.addHandler(order_file_handler)這樣 order_logger 的日志會(huì)同時(shí)寫到主日志和 order.log 里。如果你不希望重復(fù)寫可以把 order_logger 的 propagate 設(shè)置為 False讓它不再向 root 傳遞order_logger.propagate False這個(gè)操作相當(dāng)于為某個(gè) logger 建立了一條獨(dú)立的日志通道適合專題類日志比如支付回調(diào)、第三方接口調(diào)用。這些日志單獨(dú)存放排查時(shí)直接翻獨(dú)立文件效率提高很多。4.3 案例三對(duì)接 ELK 或 Loki 日志采集服務(wù)跑起來(lái)以后日志最終要送到日志平臺(tái)。最常見的是用 Filebeat 采集日志文件然后送到 Elasticsearch。acrilog 在這里的價(jià)值非常明顯因?yàn)檩敵鍪菃涡?JSONFilebeat 不需要任何解析規(guī)則直接按 JSON 采集就行。一個(gè)最小可用的 filebeat.yml 配置片段長(zhǎng)這樣filebeat.inputs: - type: log enabled: true paths: - /data/logs/*.log json: keys_under_root: true add_error_key: true output.elasticsearch: hosts: [localhost:9200]注意 json.keys_under_root 這個(gè)參數(shù)它表示把 JSON 里的字段直接映射到文檔的根級(jí)別查詢時(shí)可以直接用 level、app、logger 這些字段做過(guò)濾。如果用的是 Loki Promtail配置也類似Promtail 對(duì)于 JSON 日志可以通過(guò) json 解析器提取標(biāo)簽scrape_configs: - job_name: demo static_configs: - targets: [localhost] labels: job: demo __path__: /data/logs/*.log pipeline_stages: - json: expressions: level: level app: app這里的 json 表達(dá)式就是把 acrilog 輸出的 JSON 字段映射成 Loki 的 label。沒有 acrilog 的話這一層解析會(huì)痛苦得多尤其在字段多、嵌套深的情況下。4.4 案例四把第三方組件的訪問(wèn)日志也統(tǒng)一成 JSON我在實(shí)際項(xiàng)目里遇到一個(gè)很煩的情況業(yè)務(wù)日志通過(guò) acrilog 全部 JSON 化了但 FastAPI 底層用的 uvicorn 訪問(wèn)日志還是老樣子。日志平臺(tái)里一半是 JSON一半是純文本檢索時(shí)體驗(yàn)割裂。解決思路是把 uvicorn 相關(guān) logger 的默認(rèn) handler 清掉讓它們把日志傳播給 root logger由 root 上的 acrilog Handler 統(tǒng)一輸出 JSON。import logging import acrilog acrilog.init(app_nameapi-gateway, file_namelogs/access.log, levelINFO) for name in (uvicorn, uvicorn.error, uvicorn.access): log logging.getLogger(name) log.handlers.clear() log.propagate True清掉 handler 以后uvicorn 的日志會(huì)向上傳播到 root loggerroot logger 上掛著 acrilog 的 JSON Handler于是所有訪問(wèn)日志都被改造成 JSON 格式。實(shí)測(cè)下來(lái)效果很好日志平臺(tái)里格式完全統(tǒng)一了。這里有一個(gè)注意點(diǎn)清 handler 的操作必須在 uvicorn 開始打印訪問(wèn)日志之前完成所以最好放在入口模塊的最前面。另外如果你關(guān)閉了 uvicorn 的 access log上面的操作對(duì)訪問(wèn)日志無(wú)效因?yàn)楦緵]有日志產(chǎn)生。5. 常見問(wèn)題與排查技巧實(shí)錄5.1 六個(gè)高頻問(wèn)題速查現(xiàn)象可能原因解決辦法日志文件沒生成file_name 目錄不存在或權(quán)限不足先創(chuàng)建目錄確認(rèn)進(jìn)程對(duì)目錄有寫權(quán)限中文變成 \uXXXXjson.dumps 默認(rèn) ASCII 轉(zhuǎn)義init 時(shí)傳 json_kwargs{ensure_ascii: False}控制臺(tái)輸出亂糟糟的 JSON 串streamTrue 導(dǎo)致控制臺(tái)也輸出 JSON關(guān)掉 stream自定義一個(gè)明文 StreamHandler日志重復(fù)出現(xiàn)兩遍init() 被調(diào)用多次root 掛了多個(gè) handler保證 init 只調(diào)用一次重復(fù) handler 時(shí)先清 root.handlers第三方庫(kù)日志還是明文第三方用了自己的 logger 或 handler清除對(duì)應(yīng) logger 的 handlers設(shè)置 propagateTrue日志級(jí)別調(diào)整不生效init 之后又用 basicConfig 重置了 root不要在 init 后調(diào)用 basicConfig用 logger.setLevel 調(diào)節(jié)這里面有一個(gè)坑特別常見不少人習(xí)慣在代碼某處順手調(diào)了 logging.basicConfig()這個(gè)函數(shù)會(huì)在 root logger 上再注冊(cè)一個(gè) handler輕則導(dǎo)致日志重復(fù)輸出重則把 acrilog 配置的 JSON Formatter 覆蓋掉。排查時(shí)如果發(fā)現(xiàn) JSON 格式突然失效優(yōu)先懷疑是不是有人調(diào)了 basicConfig。還有一個(gè)關(guān)于 streamTrue 的體驗(yàn)問(wèn)題。開發(fā)時(shí)開著 stream 確實(shí)爽但控制臺(tái)輸出的 JSON 字符串通常很長(zhǎng)日志一多眼都花了。我的習(xí)慣是本地調(diào)試時(shí)把 json_kwargs 里加 indent2線上再改回單行。雖然多寫幾行配置但調(diào)試體驗(yàn)提升明顯。5.2 排查思路與避坑筆記如果你已經(jīng)接入 acrilog卻發(fā)現(xiàn)日志行為不符合預(yù)期我的排查順序通常是這樣第一步用最精簡(jiǎn)的腳本復(fù)現(xiàn)。單獨(dú)寫一個(gè) py 文件只做 init 和一條 INFO 輸出看結(jié)果對(duì)不對(duì)。如果最小復(fù)現(xiàn)正常說(shuō)明問(wèn)題出在業(yè)務(wù)代碼里比如 init 被多次調(diào)用、某個(gè) logger 被單獨(dú)設(shè)置了 handler。如果最小復(fù)現(xiàn)也不正常多半是版本問(wèn)題或安裝問(wèn)題重新確認(rèn) acrilog 版本和 Python 版本。第二步檢查 logger 的傳播關(guān)系。Python logging 的日志傳遞符合“子 logger 沒有 handler 就往父級(jí)傳播”的規(guī)則。你寫 logger logging.getLogger(app.order) 時(shí)如果 app.order 這個(gè) logger 本身有 handler消息就不會(huì)往 root 走acrilog 配置的 JSON Handler 自然收不到。很多“JSON 格式只對(duì)部分模塊生效”的問(wèn)題都出在這里。第三步看 JSON 里的時(shí)間字段。默認(rèn)輸出的時(shí)間有時(shí)帶微秒和時(shí)區(qū)信息這對(duì)接入 Elasticsearch 很友好但如果你的日志系統(tǒng)對(duì)時(shí)間格式有要求就需要自己調(diào) datefmt。舉個(gè)例子我希望日志時(shí)間精確到毫秒且?guī)r(shí)區(qū)可以設(shè)置acrilog.init( ..., datefmt%Y-%m-%dT%H:%M:%S.%f%z )這個(gè)格式是 ISO 8601 風(fēng)格Elasticsearch 可以直接識(shí)別不用另外做日期格式轉(zhuǎn)換。再說(shuō)一個(gè)容易被忽略的生產(chǎn)問(wèn)題日志文件切割。acrilog 本身不做按大小或按天的自動(dòng)輪轉(zhuǎn)生產(chǎn)環(huán)境日志量一大日志文件會(huì)變成一個(gè)幾十 GB 的巨型文件到時(shí)候別說(shuō)采集了連打開文件排查都卡。我的做法是把 acrilog 輸出的日志文件名設(shè)計(jì)成帶日期的固定命名比如 logs/app-20241111.log然后交給系統(tǒng)的 logrotate 或其他日志輪轉(zhuǎn)工具去處理。如果你不太想在系統(tǒng)層面配置可以在代碼里手動(dòng)調(diào)用 TimedRotatingFileHandler 完成輪轉(zhuǎn)from logging.handlers import TimedRotatingFileHandler handler TimedRotatingFileHandler( logs/app.log, whenmidnight, backupCount7, encodingutf-8 )設(shè)置 backupCount7 表示只保留最近 7 天的日志。這個(gè)數(shù)值怎么定我建議根據(jù)你的磁盤容量和單日日志量算一下寧可多留兩天也不要因?yàn)槿罩颈磺宓魧?dǎo)致丟了關(guān)鍵排查線索。5.3 用 jq 快速排查 JSON 日志日志全部變成 JSON 之后日常排查其實(shí)變得更爽了因?yàn)槟憧梢灾苯佑?jq 命令做結(jié)構(gòu)化篩選。這在服務(wù)器上沒有 Kibana、沒有 Loki 的情況下尤其好用。比如我想找出今天所有 ERROR 級(jí)別的日志cat logs/app.log | jq -c select(.level ERROR)想看某個(gè)用戶 ID 相關(guān)的日志cat logs/app.log | jq -c select(.user_id 1024)按時(shí)間范圍過(guò)濾cat logs/app.log | jq -c select(.time 2024-11-11T00:00:00) | {time, level, msg}這一套操作在線上應(yīng)急排查時(shí)非常高效。以前我遇到問(wèn)題都是 grep 一串字符串再對(duì)著多行明文日志猜結(jié)構(gòu)現(xiàn)在直接 jq 一條命令結(jié)果一目了然。這也算是我用了 acrilog 之后意外收獲的一個(gè)排查習(xí)慣。提示jq 是 Linux 環(huán)境下的 JSON 處理命令行工具沒有的話用 yum install jq 或 apt install jq 裝一下幾十 KB 的大小排查日志非常值。寫在最后的一點(diǎn)體會(huì)acrilog 這個(gè)包不大代碼量也不算多但它在“讓日志從人能看變成機(jī)器也能看”這件事上幫我省了非常多的時(shí)間。我在幾個(gè)生產(chǎn)項(xiàng)目里接入之后最大的感受是去日志平臺(tái)檢索問(wèn)題時(shí)不再依賴正則和字符串匹配直接按字段拖拽組合查詢排查效率提升了一個(gè)量級(jí)。如果你只是一個(gè)小腳本、一個(gè)一次性的任務(wù)用不用 acrilog 都無(wú)所謂但只要你做的是長(zhǎng)期運(yùn)行的服務(wù)或者已經(jīng)接入了日志采集平臺(tái)我非常建議花十分鐘把日志切換到 JSON 格式。acrilog 是一個(gè)不錯(cuò)的起點(diǎn)它的學(xué)習(xí)成本幾乎為零退出成本也低——就算將來(lái)不滿足需求你也能輕松回到標(biāo)準(zhǔn) logging 生態(tài)里做自定義改造。最后再分享一個(gè)小技巧接入 acrilog 之后可以在項(xiàng)目的初始化腳本里加一句自檢日志輸出 acrilog 的版本號(hào)和當(dāng)前配置。這樣你排查問(wèn)題的時(shí)候第一眼就能判斷線上跑的是哪個(gè)版本的包、配置是否生效避免在版本不一致上花時(shí)間。項(xiàng)目越來(lái)越大以后這個(gè)習(xí)慣能幫你省掉很多無(wú)謂的猜測(cè)。