寫日志也有十誡?高手都在偷偷遵守的高質量日志十法則!
工程體系里有很多“表面不起眼,但真正能拉開工程師層次差距”的細節,其中日志絕對是最容易被忽視的一環。
多數人寫日志的方式,就像做飯時隨便往鍋里扔鹽——反正能吃就行。但當真正遇到生產事故時,你才會意識到日志寫法水平的差距,完全可以決定排障時間是10 分鐘還是整整熬夜到天亮。
我曾見過一個團隊,僅僅因為業務鏈路日志缺失,整整排查了一周;看日志就像在看“無意義的人類詩歌”:
Processing started
...
Processing finished沒有上下文、沒有關鍵參數、沒有定位線索,就像拿著一張白紙找 bug。
為了避免大家繼續在“見招拆招”中浪費生命,我將實踐中踩過的坑、看過的事故、經歷過的慘案整理成這篇“日志世界觀重構指南”。如果你能堅持把這些原則融入開發習慣里,你的整體工程能力會立刻上一個臺階——甚至可能在下一次線上事故中成為團隊的救世主。
日志的本質意義:不是寫給代碼看的,而是寫給凌晨 3 點的自己看的
1. 日志首先是用來“被人閱讀”的
很多開發下意識以為日志只是“為了排查”,但真正應該問的是:是誰來排查?
——當然是你自己,或者倒霉的同事。
所以寫日志時一定要假設:你凌晨三點頂著黑眼圈在翻日志,能否快速看懂現在發生了什么?
錯誤示范:
log.info("Processing started");
...
log.info("Processing finished");改進示范:
log.info("Begin processing payment. userId={}, orderId={}, amount={}",
userId, orderId, amount);
...
log.info("Payment processing completed. userId={}, orderId={}, result={}",
userId, orderId, result);日志不是隨便寫一句話,它是一個“定位線索”。
2. “多打點日志”不是解決問題,而是制造噪音
在某個項目里,我見過 5 分鐘生成 50MB 日志的詭異現場,內容如下:
log.debug("Entering method A");
log.debug("Exiting method A");
log.debug("Entering method B");
log.debug("Exiting method B");這種日志不是幫助排障,是污染系統。
每一行日志都有代價:I/O、存儲、閱讀成本。 日志不是越多越好,而是越有用越好。
3. 開發視角與業務視角的區分
普通日志寫法往往是“程序員自言自語”:
log.info("User data parsed, updating cache");高級日志應該更接近業務含義:
log.info("Order status changed. userId={}, orderId={}, from={}, to={}",
userId, orderId, oldStatus, newStatus);這樣的日志不但技術人員能看懂,產品、運營也能感知業務變化。
日志級別:正確使用日志級別,是專業工程師的基本素養
1. 標準級別使用規則
級別 | 用途 |
ERROR | 系統出現無法繼續運行的嚴重錯誤,需要立即處理 |
WARN | 潛在風險,今天沒出事不代表明天不會出事 |
INFO | 業務流程關鍵節點 |
DEBUG | 開發調試用信息,通常不在生產環境開啟 |
TRACE | 最細粒度執行路徑,僅在極端場景使用 |
2. 90% 的錯誤級日志都是濫用
經典錯誤寫法:
try {
// biz logic
} catch (Exception e) {
log.error("Processing failed", e);
}但很多異常其實是正常的業務分支:
try {
authService.login(username, password);
} catch (InvalidCredentialsException e) {
log.info("Login failed: wrong password. username={}", username);
return "Incorrect password";
} catch (Exception e) {
log.error("Unexpected exception during login. user={}", username, e);
return "System error";
}業務異常不是 ERROR,系統異常才是。
3. 不同運行環境的日志策略
環境 | 推薦級別 |
開發環境 | DEBUG / TRACE |
測試環境 | INFO |
生產環境 | INFO 或 WARN |
典型配置示例:
logging.level.root=WARN
logging.level.com.icoderoad.core=INFO
logging.level.com.icoderoad.thirdparty=ERROR日志內容:什么樣的日志才算“有價值”?
一條高質量日志的要素
必須包含:
- 時間戳
- 服務標識
- 請求鏈路 ID(TraceId)
- 操作主體(誰)
- 行為(做了什么)
- 參數(關鍵信息)
- 結果(成功/失敗)
反例:
log.info("Payment success");正確寫法:
log.info("[OrderCenter] Payment success. userId={}, orderId={}, amount={}, channel={}, cost={}ms",
userId, orderId, amount, channel, costTime);防止日志污染的實踐技巧
① 不要在循環里打印日志
錯誤寫法:
for (Item item : items) {
log.info("Processing item: {}", item);
}改進:
log.info("Processing {} items", items.size());
// ...
log.info("Finished processing. total={}, success={}, failed={}",
items.size(), successCount, failCount);② 禁止字符串拼接寫法
log.debug("User:" + user.getName()); // 會提前拼接,浪費性能正確寫法:
log.debug("User: {}, ID: {}", user.getName(), user.getId());③ 避免輸出整個大對象
log.debug("User data: {}", user); // 危險④ 敏感數據打碼
包括密碼、手機號、身份證號等。
⑤ 使用 MDC 關聯鏈路日志
⑥ 過期日志要及時清理
⑦ 對于復雜日志打印,先判斷級別
if (log.isDebugEnabled()) {
log.debug("Details: {}", buildDetail(obj));
}結構化日志:現代系統的必備能力
1. 為什么 JSON 日志逐漸取代文本日志?
- 機器可讀
- 結構清晰
- 索引友好
- 易于可視化與分析
- 容易接入 ELK/EFK
2. Spring Boot JSON 日志配置示例
logback-spring.xml:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>示例輸出(JSON):
{
"timestamp": "2023-01-01T12:00:00.000Z",
"level": "INFO",
"logger": "com.icoderoad.order.OrderService",
"message": "User payment successful",
"userId": "12345",
"orderId": "ORD9876",
"amount": 99.99
}性能:日志不是免費的,寫得多寫得重會拖垮系統
1. 日志對性能的影響來源
- 磁盤 I/O
- 字符串拼接
- 同步寫入鎖競爭
- 緩沖區刷新
重負載場景下,日志甚至會讓系統吞吐下降 30%-50%。
2. 使用異步日志降低阻塞
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
</appender>3. 高并發下的日志策略
- 采樣日志(Sampled Logging)
- 批量寫入
- 非阻塞隊列
- 增大 Log Buffer
示例:
if (Math.random() < 0.01) {
log.info("Sampled request: {}, headers={}", request, headers);
}日志治理:從可用到可觀測的跨越
1. 日志滾動與歸檔
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>2. 使用集中式日志系統:ELK / EFK
標準鏈路:
App → Filebeat → Logstash → Elasticsearch → Kibana3. 日志告警的設計
- 錯誤量異常升高
- 特定關鍵詞
- 日志模式突變
- 預期行為缺失
合理的噪音過濾非常重要,不然告警就會形同虛設。
真實案例:日志的重要性不是夸出來的,是血淚教訓堆出來的
案例一:因為日志太爛,一個大促損失百萬
支付鏈路失敗后,日志只有:
ERROR Process failed結果團隊花了 4 小時才查出來是第三方超時。
改造后日志包含:
- orderId
- userId
- channel
- third-party errorCode
- traceId
同樣的問題只需要 10 分鐘即可定位。
案例二:一個系統的日志進化史,反映工程能力的成熟過程
從簡單文本:
Message pushed successfully進化到:
JSON + 鏈路追蹤 + 業務指標 + 錯誤分類統計
日志體系的進化直接推動了系統可觀測性建設,最終整個推送服務的穩定性顯著提高。
總結:日志水平是工程能力的放大器
日志,看似只是“順手寫幾行”,但實際上它是工程體系的核心基石之一。
一個工程師的日志寫法,會深刻影響:
- 系統可維護性
- 故障恢復速度
- 團隊協作效率
- 線上穩定性
- 技術體系的成熟度
當你把日志寫好時,你并不只是提高自己的效率,而是在幫助整個團隊、整個系統、整個業務。
優秀的日志不是“錦上添花”,而是雪中送炭。


























