在生產環境中,日誌管理是系統穩定性與可維護性的關鍵基石。一個配置不當的日誌系統可能導致磁碟空間爆滿、系統崩潰,甚至影響業務運作。本文將深入探討 Docker、PM2 以及 Go、Node.js、Python 等常見運行環境的日誌管理最佳實踐,幫助你避免「日誌爆炸」的災難。

為什麼日誌管理如此重要?

在生產環境中,日誌管理不當可能導致以下問題:

  • 磁碟空間耗盡: 未限制的日誌檔案會持續增長,最終佔滿磁碟空間
  • 系統效能下降: 過度的日誌寫入會影響 I/O 效能
  • 除錯困難: 日誌過多或過少都會影響問題排查效率
  • 合規風險: 敏感資訊洩漏或日誌保存期限不符合規範

[!IMPORTANT]
生產環境的日誌策略應該在可觀測性資源消耗之間取得平衡。過多的日誌會浪費資源,過少的日誌則無法有效除錯。

Docker 日誌管理

Docker 日誌驅動概述

Docker 支援多種日誌驅動 (Logging Driver),每種驅動適用於不同的場景:

驅動名稱 適用場景 優點 缺點
json-file 本地開發、小型部署 預設驅動、簡單易用 需手動管理日誌輪替
syslog 集中式日誌管理 與系統日誌整合 需額外配置 syslog 服務
journald systemd 系統 與 systemd 深度整合 僅限 Linux
fluentd 大規模日誌聚合 強大的日誌處理能力 需部署 Fluentd
awslogs AWS 環境 無縫整合 CloudWatch 僅限 AWS
gcplogs GCP 環境 無縫整合 Cloud Logging 僅限 GCP

配置 Docker Daemon 全域日誌設定

透過 /etc/docker/daemon.json 設定全域日誌策略,其好處在於一次配置、全主機生效:所有新建立的容器都會繼承這些設定,省去逐一配置的工作量。

核心可調整的日誌參數包含:日誌驅動類型log-driver)、單檔大小上限max-size:建議生產環境設為 10-50MB)、保留檔案數max-file:搭配 max-size 計算總佔用空間)、以及壓縮舊日誌compress:可節省 60-80% 的儲存空間)。此外,labelsenv 參數可將容器標籤和環境變數注入日誌中繼資料,便於日後篩選和追蹤。

修改 daemon.json 之後需要重啟 Docker 服務才能生效(Linux 執行 sudo systemctl restart docker;Docker Desktop 則在設定介面操作)。但需特別注意:現有容器不受影響,只有新建立或重新建立的容器才會套用新設定。因此在轉換日誌策略時,需規劃容器的輪替重建計劃。

[!WARNING]
修改 daemon.json 會影響所有新建立的容器。現有容器需要重新建立才會套用新設定。

Docker Compose 中的日誌配置

Docker Compose 允許在服務級別覆寫全域日誌設定,為不同服務訂製最適合的日誌策略。這種服務化日誌設計讓你能夠根據各服務的特性和重要程度進行精細化管理。

本地開發與小型部署最常使用 json-file 驅動,搭配 max-size(如 10m)和 max-file(如 3)參數。雖然設定簡單,但記得日誌輪替後的舊檔仍在本機,需要定期清理。

企業內部日誌聚合通常選用 syslog 驅動,將日誌轉發至中央 syslog 伺服器。設定時要指定 syslog-address(格式如 tcp://192.168.1.100:514)和 tag(用於識別服務來源),並選擇適合的 syslog 格式(rfc5424 有更豐富的攜帶資訊)。

大規模日誌處理通常採用 fluentd 驅動,透過 Fluentd 作為中介層收集並路由日誌。fluentd-async-connect 設為 true 讓容器不會因 Fluentd 未就緒而阻塞啟動;fluentd-retry-waitfluentd-max-retries 則控制重連行為,避免瞬時故障導致日誌遺失。

雲端環境則優先選用平台原生驅動,如 AWS 的 awslogs(直接推送至 CloudWatch Logs,可設定 awslogs-create-group: true 自動建立 Log Group)或 GCP 的 gcplogs。這類驅動無需自行維護日誌基礎設施,但要注意雲端儲存的費用計算。

Docker 日誌查看與管理指令

docker logs 指令是除錯的第一線工具,營識其核心選項能讓你快速定位問題:追蹤模式-f--follow)等同於 tail -f,適合即時觀察服務啟動或異常行為。搭配 --tail <N> 可限制初始輸出行數,避免大量歷史日誌淨沒終端。

時間過濾--since)支持 RFC3339 時間格式(如 2026-01-25T10:00:00)或相對時間(如 1h30m),是事後追查特定時段事件的利器。

日誌位置docker inspect --format='{{.LogPath}}')可找出容器日誌檔案的實際路徑,在需要直接操作日誌檔案時使用。不過要注意,直接截斷日誌檔案(truncate)需要先停止容器,在生產環境應謹慎使用,優先依賴 max-size 輪替機制。

Docker 日誌輪替最佳實踐

生產環境中,合理設定日誌輪替參數是防止磁碟爆滿的關鍵防線。以下是推薦的參數組合策略:

  • 單檔大小(max-size):建議 50-100MB。太小會頻繁產生日誌檔案,太大則在輪替時一次釋放過多空間,可能在短時間內產生磁碟壓力。
  • 保留檔案數(max-file):建議 3-7 個,搭配 max-size 計算總佔用空間。例如 50m × 5 = 250MB,規劃時要考慮服務的日誌產生速率。
  • 壓縮舊日誌(compress: “true”):強烈建議開啟。文字日誌的壓縮率通常在 5:1 到 10:1 之間,可顯著節省磁碟空間。
  • 標籤與環境變數注入:透過 labelsenv 將服務標識資訊嵌入日誌,便於在集中式日誌系統中進行跨服務的關聯查詢。

PM2 日誌管理

PM2 日誌配置

PM2 是 Node.js 應用的流行進程管理器,提供強大的日誌管理功能。

基本配置檔案 (ecosystem.config.js)

ecosystem.config.js 是 PM2 的中央配置文件,日誌相關設定主要包含以下層面:

日誌路徑管理:透過 error_fileout_file 將錯誤輸出與標準輸出分離到不同檔案,便於按類型篩選。log_file 可指定合併日誌路徑,包含兩種輸出的完整記錄。在叢群模式(exec_mode: 'cluster')下,merge_logs: true 讓所有工作進程寫入同一組日誌檔,避免日誌碎片化。

時間戳記格式log_date_format 支持 Moment.js 語法,建議使用帶時區的格式(如 YYYY-MM-DD HH:mm:ss Z),便於跨時區環境的時間對齊。time: true 則會在每行日誌前加上 PM2 的時間戳,是最簡單的格式化方式。

環境分離:PM2 支持透過 envenv_production 等命名空間區分環境設定,搭配 pm2 start --env production 啟動時自動載入對應的環境變數,讓開發與生產的日誌等級差異化(如開發用 debug,生產用 warn)。

PM2 日誌輪替配置

安裝 PM2 日誌輪替模組:

1
pm2 install pm2-logrotate

配置日誌輪替參數,核心選項包含:max_size(單檔上限,預設 10MB,建議調整為 50M)、retain(保留檔案數,預設 10,建議 7 對應一周)、compress(是否壓縮舊日誌,建議開啟)、rotateInterval(輪替排程,支持 cron 語法,如每日午夜的 0 0 * * *)、以及 dateFormat(日誌歸檔的日期命名格式)。設定完成後透過 pm2 conf pm2-logrotate 驗證當前配置是否正確生效。

PM2 日誌管理指令

PM2 日誌管理的核心指令分為三類:

即時查看pm2 logs 串流所有服務的混合輸出;加上服務名稱(pm2 logs api-server)限縮到單一服務;--err--out 分別過濾錯誤/輸出流;--lines <N> 控制顯示的歷史行數(預設 15 行)。

維護操作pm2 flush 清空所有日誌(不重啟服務),適合在磁碟空間吃緊時的緊急處理;pm2 reloadLogs 則重新開啟日誌檔記寫入(常用於 log rotation 後的訊號發送,讓進程切換到新的日誌檔)。

最佳實踐:在生產環境不建議頻繁使用 pm2 flush,這會永久刪除歷史日誌。應依賴 pm2-logrotate 模組的自動輪替機制,確保歷史日誌有序保留。

PM2 進階日誌配置

PM2 的進階日誌配置主要在兩個層面實現精細化控制:

JSON 格式日誌:設定 log_type: 'json' 讓 PM2 以 JSON 格式輸出每條日誌記錄,而非純文字行。這對於接入 ELK Stack 或 Loki 等集中式日誌平台至關重要——結構化欄位讓自動解析和索引成為可能,索引後的日誌可以按服務名、等級、時間等任意欄位快速檢索。

服務差異化策略:對不同角色的服務採用不同的日誌策略。核心 API 服務通常需要完整的日誌記錄以支援審計和監控;背景工作服務(Worker)如果只關心錯誤,可以將標準輸出重定向至 /dev/nullout_file: '/dev/null'),只保留錯誤日誌,大幅減少磁碟寫入壓力。

叢群模式注意事項:設定 instances: 'max' 或具體數字時,需要評估 merge_logs 的取襨——開啟合併日誌(true)方便集中查看但難以追蹤到具體進程;關閉(false)則每個工作進程獨立記錄,便於定位特定進程的問題,但日誌管理複雜度增加。

Go 應用日誌管理

使用標準函式庫 log/slog

Go 1.21 正式引入 log/slog 套件,確立了官方的結構化日誌標準。相比舊的 log 套件,slog 的核心改進在於鍵值對(key-value pair)語意,讓每條日誌都攜帶機器可解析的結構化資料,而非單純的文字字串。

slog 的架構分為兩層:Logger(前端,提供 InfoError 等 API)和 Handler(後端,負責格式化和輸出)。官方內建兩種 Handler:TextHandler(人類易讀的 key=value 格式)和 JSONHandler(機器可解析的 JSON 格式)。生產環境幾乎都應該使用 JSONHandler,因為 JSON 格式的日誌可以直接被 Logstash、Fluentd 這類工具解析,無需額外的格式轉換。

HandlerOptions 中的 Level 設定決定哪些等級的日誌會被記錄(低於此等級的直接丟棄,效能幾乎為零);AddSource: true 會在每條日誌中加入原始碼位置(文件名和行號),這在排查問題時非常有用,但會有微小的效能開銷。

1
2
3
4
5
6
7
8
// 配置 JSON 格式日誌,適用於生產環境
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo, // 僅記錄 Info 及以上等級
AddSource: true, // 包含原始碼位置
}))
slog.SetDefault(logger)

slog.Info("server started", "port", 8080, "env", "production")

設定為全域預設 logger(slog.SetDefault)後,不需傳遞 logger 實例,直接呼叫套件層級函式(如 slog.Info)即可,適合應用程式的核心日誌記錄。

生產環境日誌配置

在生產環境中,日誌不能只寫到標準輸出——我們需要本地檔案持久化加上自動輪替機制。Go 生態系的首選方案是 lumberjackgopkg.in/natefinch/lumberjack.v2),一個專門處理日誌輪替的 io.Writer 實作,可以無縫接入 slog 或其他日誌庫。

lumberjack 配置要點Filename 指定日誌檔路徑;MaxSize(MB 為單位)控制單檔上限,達到後自動輪替;MaxBackups 決定保留的歷史備份數量;MaxAge(天為單位)設定最長保留期限;Compress: true 對舊日誌進行 gzip 壓縮,通常可節省 70-80% 空間;LocalTime: true 使用本地時間戳命名輪替檔案(否則使用 UTC)。

多輸出目標:使用 io.MultiWriter 可同時將日誌輸出到多個目的地——既保留到本地檔案,又輸出到標準輸出(便於容器日誌收集)。這在 Kubernetes 環境中很常見:容器 stdout 被 Fluentd 或 Promtail 收集,同時日誌也本地保留一份備用。

自訂時間格式:透過 HandlerOptions.ReplaceAttr 函式可以攔截並修改日誌屬性,常見用途是將預設的時間格式標準化為 RFC3339(如 2026-01-25T14:30:00+08:00),這在跨時區的分散式系統中非常重要。

使用 Zap 高效能日誌庫

go.uber.org/zap 是 Go 生態系中效能最極致的日誌庫,由 Uber 開源並在極高流量服務中驗證。Zap 採用避免記憶體配置的設計哲學:使用強型別欄位(zap.Int("port", 8080) 而非介面型)消除反射開銷,透過物件池複用日誌結構體,最終實現比標準庫快 4-10 倍的日誌寫入效能。

Zap vs slog 的選擇依據:如果你的服務每秒產生數十萬條日誌(如高頻交易、遊戲伺服器),Zap 的極致效能值得引入額外依賴。對於一般 Web API 服務,slog 的效能已完全足夠,且作為標準庫無需管理外部依賴。

Zap 的核心概念zapcore.Core 是日誌的底層控制器,負責綁定 Encoder(格式化方式)、WriteSyncer(輸出目標)和 LevelEnabler(等級過濾)。EncoderConfig 讓你精確控制每個欄位的鍵名和編碼方式(如 TimeKey: "timestamp"EncodeTime: zapcore.ISO8601TimeEncoder),讓輸出的 JSON 格式完全符合下游系統的期望。

SugaredLogger:如果覺得強型別 API(zap.String(...))稍嫌繁瑣,Zap 提供 logger.Sugar() 轉換為鬆散型別的 SugaredLogger,使用 Printf 風格寫日誌,效能略低但更接近傳統使用習慣。

Node.js 應用日誌管理

使用 Winston 日誌庫

winston 是 Node.js 生態系中最成熟的日誌庫,其核心設計圍繞三個概念:Transport(輸出目標)、Format(格式化管線)和 Logger(統一接口)。

Transport 的組合策略:Winston 支援多個 Transport 並存,常見的生產環境組合是:DailyRotateFile(透過 winston-daily-rotate-file 套件實現按日期輪替,對應生產環境的長期日誌管理)加上一個 Console Transport(僅在非生產環境下啟用,供本機除錯)。分離錯誤日誌(level: 'error')到獨立檔案,讓運維人員不需掃全文就能快速定位異常。

Format 的管線機制winston.format.combine() 將多個格式化步驟串聯,典型的生產格式鏈包含:timestamp()(加入時間戳)→ errors({ stack: true })(展開 Error 物件的堆疊追蹤)→ json()(最終序列化為 JSON)。defaultMeta 讓每條日誌都自動帶上服務名稱和環境標識,在集中式日誌系統中是必要的分類依據。

輪替配置要點DailyRotateFiledatePattern 決定輪替粒度(YYYY-MM-DD 為每日輪替);maxSize 可額外限制單檔大小(如 100m,在日誌爆增時觸發提前輪替);maxFiles(如 30d)控制保留的歷史檔案期限。

1
2
3
4
// 使用範例
logger.info('Server started', { port: 3000 });
logger.error('Database connection failed', { error: err.message, host: 'localhost' });
logger.warn('High memory usage', { usage: '85%', threshold: '80%' });

Python 應用日誌管理

使用標準函式庫 logging

Python 內建的 logging 模組採用階層化設計,理解其層次關係是正確配置的先決條件:Logger(各模組申請的日誌句柄)→ Handler(決定日誌的輸出目標)→ Formatter(控制日誌的格式)。

生產環境的日誌配置重點在於以下幾個決策:

Handler 的選擇與組合RotatingFileHandler 按大小輪替(maxBytesbackupCount),適合日誌量穩定的服務;TimedRotatingFileHandler 按時間輪替(when='midnight'interval=1backupCount=30),更符合運維習慣(按日歸檔便於按日取用)。生產環境建議為 error 等級單獨設一個 RotatingFileHandler,讓錯誤日誌不被海量 info 日誌淹沒。

Formatter 格式設計:推薦在 format string 中包含 asctime(時間)、name(模組名)、levelname(等級)、filename+lineno(原始碼位置)以及 message。這些欄位在問題排查時缺一不可。datefmt 建議使用 ISO 8601 格式(%Y-%m-%dT%H:%M:%S)保持國際相容性。

避免 Handler 重複添加:在函式或類別中配置 Logger 時,務必先檢查 if logger.handlers: return logger,否則每次呼叫都會累積新的 Handler,導致日誌被重複輸出。

使用時間輪替

TimedRotatingFileHandler 相比 RotatingFileHandler 在運維層面有明顯優勢:輪替後的檔案以日期命名(透過 suffix 屬性設定,如 %Y-%m-%d),直觀反映時間範圍;when='midnight' 確保每天固定在零時輪替,便於按日對齊日誌與業務事件;utc=False 使用本地時間命名,符合多數運維場景的直覺。

選擇依據:如果你的服務日誌量在不同時段差異懸殊(白天高流量,夜間幾乎無輸出),按大小輪替可能在業務繁忙時頻繁切換,按時間輪替則更可預期。反之,如果日誌持續穩定產生,按大小輪替能更精準地控制每個備份檔的體積。

使用 structlog 結構化日誌

structlog 將 Python 日誌帶入結構化時代,核心理念是以事件字典(event dict)代替格式化字串。每條日誌在到達最終輸出前,會依序通過一組 Processor(處理器)的管線(pipeline),每個 Processor 可以讀取、修改或豐富事件字典。

典型的 Processor 管線包含:

  • filter_by_level:按等級過濾
  • add_logger_name / add_log_level:注入元資訊
  • TimeStamper(fmt="iso"):加入 ISO 8601 時間戳
  • StackInfoRenderer + format_exc_info:自動展開例外堆疊追蹤
  • JSONRenderer():最終序列化為 JSON(或 ConsoleRenderer() 用於開發環境的彩色輸出)

structlog 的主要優勢在於:呼叫端可以在呼叫時逐步積累上下文(log.bind(request_id=req_id)),不同請求的日誌自動帶上各自的上下文,是實現分散式追蹤的基礎能力。

集中式日誌管理方案

ELK Stack (Elasticsearch + Logstash + Kibana)

ELK Stack 是企業級集中式日誌管理的標準解決方案,三個元件各司其職:

Elasticsearch:倒排索引引擎,負責儲存日誌、支援全文搜索和聚合查詢。生產部署時需注意 JVM Heap 大小(ES_JAVA_OPTS=-Xms2g -Xmx2g,建議不超過物理記憶體的 50%)以及索引管理策略(按日期建立索引,如 app-logs-2026.01.25,配合 ILM 自動刪除過期索引)。

Logstash:日誌處理管線,分為三個階段:input 接收來源(可同時接受多協議,如 TCP、Beats、Kafka)、filter 轉換處理(grok 解析非結構化日誌、JSON 解析結構化日誌、date 標準化時間戳、mutate 欄位增刪改)、output 路由輸出(主要目標是 Elasticsearch,同時可輸出到 S3 做冷備份)。

Kibana:視覺化與查詢界面,提供 Discover(互動查詢)、Dashboard(儀表板)和 Alerts(告警規則)等功能。部署時的資源評估:單節點 Elasticsearch 適合每日數 GB 級別的日誌量;更大規模需要多節點叢集。整個 ELK Stack 的記憶體佔用以 GB 計,需要妥善規劃遠大於應用服務本身的基礎設施資源。

flowchart LR
    A[應用服務\nApp Logs] -->|JSON/TCP| B[Logstash\nInput → Filter → Output]
    C[容器日誌\nFilebeat] -->|Beats Protocol| B
    B -->|Index by date| D[(Elasticsearch\n儲存與索引)]
    D --> E[Kibana\n查詢與視覺化]
    D -->|Cold Storage| F[S3/對象儲存\n長期歸檔]

Loki + Promtail + Grafana

Grafana Loki 是 ELK Stack 的輕量替代方案,其設計哲學是**「只索引標籤,不索引內容」**。與 Elasticsearch 對每個欄位建立倒排索引不同,Loki 只對少量標籤(如 servicelevelhost)建立索引,日誌內容本身以壓縮二進制格式儲存。這個設計讓 Loki 的儲存成本和記憶體佔用遠低於 ELK,特別適合資源有限的中小型部署。

Promtail 是 Loki 的日誌採集代理,負責從系統日誌目錄和 Docker 容器中採集日誌,自動附加 Kubernetes 標籤或容器標籤後推送給 Loki。

選型建議

  • 選 ELK:需要強大的全文搜索、複雜的 Logstash 轉換管線,或已有 Elasticsearch 基礎設施
  • 選 Loki:已使用 Prometheus + Grafana 監控棧、希望日誌和指標在同一界面中關聯分析,且日誌規模中等(每日數十 GB 以內)

兩套方案在 Grafana 界面中都能整合,透過 Grafana 的 Explore 功能和 Correlate 特性,可以實現日誌與 Prometheus 指標的時間軸對齊,大幅提升問題排查效率。

日誌管理最佳實踐總結

1. 日誌等級策略

環境 建議等級 說明
開發 DEBUG 詳細除錯資訊
測試 INFO 一般資訊與警告
預發布 WARN 警告與錯誤
生產 ERROR 僅記錄錯誤

2. 日誌輪替建議

按大小輪替適合日誌量不均勻的服務:單檔建議 50-100MB,保留 5-10 個備份(可壓縮舊日誌節省 60-80% 空間),總佔用空間 = max-size × max-file。

按時間輪替適合運維習慣以「天」為單位管理日誌:每天零時輪替,保留 30-90 天(依合規要求和磁碟容量決定)。優點是日誌邊界與日期對齊,便於按日調取特定時段的日誌。

3. 敏感資訊處理

[!CAUTION]
絕對不要在日誌中記錄以下資訊:

  • 密碼、API 金鑰、Token
  • 信用卡號、身分證字號
  • 個人隱私資料 (Email、電話等)

日誌記錄應始終使用使用者識別碼(如 userId)而非敏感的個人資料本身。對於需要記錄請求內容的場景,建議實作**欄位遮罩(field masking)**函式,自動對已知敏感欄位進行遮罩處理。

4. 效能考量

  • 使用非同步日誌寫入
  • 避免在高頻路徑記錄過多日誌
  • 使用結構化日誌格式 (JSON)
  • 考慮使用日誌取樣 (高流量場景)

5. 監控與告警

日誌系統本身也需要被監控。關鍵指標包含:磁碟使用率(設定 > 80% 告警,留有緩衝應對突發)、日誌產生速率(速率突增通常意味著系統異常)、錯誤日誌數量(設定 5 分鐘滑動窗口,超過閾值觸發告警)、以及日誌寫入延遲(寫入延遲暴增可能預示 I/O 或磁碟問題)。

建議在 Prometheus + Grafana 體系中使用 node_exporter 監控磁碟使用率,並搭配應用層面的指標(日誌計數器)實現告警策略的閉環。

快速檢查清單

在部署生產環境前,請確認以下項目:

  • 已設定日誌輪替 (按大小或時間)
  • 已限制日誌檔案總大小
  • 已配置適當的日誌等級
  • 已移除敏感資訊
  • 已設定日誌監控與告警
  • 已測試日誌輪替是否正常運作
  • 已配置集中式日誌管理 (可選)
  • 已設定日誌備份策略

結語

日誌管理是生產環境運維的基礎設施,一個良好的日誌策略能夠:

  • 預防災難: 避免磁碟空間耗盡導致的系統崩潰
  • 快速除錯: 在問題發生時快速定位根因
  • 效能優化: 透過日誌分析發現效能瓶頸
  • 合規審計: 滿足法規要求的日誌保存與追蹤

記住:日誌不是越多越好,而是要恰到好處。在可觀測性與資源消耗之間找到平衡,才是生產環境日誌管理的精髓。

[!TIP]
定期檢視日誌配置,隨著業務成長調整策略。建議每季度審查一次日誌管理策略,確保其符合當前需求。