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