0%

[2023 15th鐵人賽] Day28 - 事到如今問不出口的 Log 基礎和設計指南

原文連結:今さら聞けないログの基本と設計指針 - Qiita

在進行程式開發時,不論是前後端,應該都對 Log(日誌)再熟悉不過。尤其是在遇到非預期性的錯誤時,通常會需要立即調閱 Log,查看是否顯示錯誤訊息;或是在改良軟體時,需要透過 Log 來分析使用者行為等等。

接下來這篇文章,將會針對 Log(日誌)主題進行介紹,從 Log 相關的基礎知識、設計方法、操作時的注意事項,再到實際應用說明。

文章架構如下:

  • 為什麼要輸出 Log?
  • 為什麼要設計 Log?
  • 有關 Log 的基礎知識
    • Log 的級別與意義
    • Log 的輸出位置
    • Log 格式
  • Log 的設計方法介紹
    • 設計 Log 時的確認事項與技巧
    • 操作 Log 時的注意事項
  • 對實際的 Log 輸出進行說明
    • 基於 VM 的 Log 輸出
    • 基於 Docker 的 Log 輸出
    • 設計 Log 時不該做什麼

那麼,以下正文開始。


前言

各位對於 Log(日誌)的理解是什麼呢?可能有從機制到設計方法都完全理解的工程師,或者只是隨便用用的工程師存在。

Log 是系統中按照時間順序記錄,關於錯誤或故障發生、使用者操作或設定變更、與外部通信等內容。透過深入瞭解有關 Log 的知識,將能夠進行複雜的系統開發和運用。此外,如果使用 AWS、Azure、GCP 等雲端服務,不只能夠實現系統開發,還可能節省經費開支。

本文將介紹 Log 的基本設計方法。如果有任何疑問,請繼續閱讀下去。

為什麼要輸出 Log?

在討論 Log 的基本知識和 Log 的設計之前,首先必須瞭解為什麼需要輸出 Log。

主要考慮以下四個理由:

  • 在問題發生時進行調查
  • 為了防止問題發生
  • 通過資料分析來改良軟體
  • 作為審計日誌使用

通過輸出 Log,不只能夠調查和防止問題,還可以分析使用者的行為。或許目前還不太能感覺到輸出 Log 的必要性,但將來肯定會變得不可或缺。

為什麼要設計 Log?

那麼,為什麼需要設計 Log 呢?

首先,Log 的設計將決定 Log 輸出的訊息和格式,基本方針是根據 Log 的使用目的來反推設計。

而設計 Log 的原因在於,將有助於選擇符合目的的 Log 並適當顯示。如上所述,Log 存在各種不同的類型,也因為如此,如何進行選擇和顯示其實相當複雜。

例如,以下將介紹使用 AWS 等雲端服務,進行系統開發的範例。雲端服務可以輕鬆實現橫向擴充和縮減,是非常方便的功能。

然而,容易擴展也意味著,可能存在大量不知道何時會出現或消失的伺服器。因此,在雲端系統中,各伺服器的 Log 會非同步寫入,即使將範圍縮小到毫秒單位,也很難找到特定的 Log。

因此,Log 的設計就變得非常重要。透過為特定事件的請求和回應,分配唯一可識別的 ID,即可藉此搜尋與目標事件相關的 Log。透過提前設計 Log,將有助於大幅減少 Log 搜尋時間。

以上只是一個範例,但建議在開發和操作複雜的系統時,務必進行 Log 設計。

有關 Log 的基礎知識

在介紹 Log 的設計之前,這裡先來瞭解有關 Log 的基礎知識!以下將簡單解釋相關內容,因此可以只挑選還不熟悉的部分閱讀。

  • Log 的級別
  • Log 的輸出位置
  • Log 的格式

Log 的級別與意義

Log 具備權限分析、故障調查、安全性和稽核等多種用途。

下表整理出代表 Log 重要性的等級和類型,內容可能會依據所使用的開發語言、Log 管理系統和設計而有所變化,但基本如下所示:

種類 意思
EMERG 系統不可利用
ALERT 必須立即採取行動
CRIT 嚴重缺失狀況
ERROR 發生錯誤的狀態
WARN 被警告的狀態
NOTICE 正常但重要的情況
INFO 資訊訊息
DEBUG 有關系統如何運作的訊息

Log 的輸出位置

原則上,Log 應該輸出到開發人員和維運人員容易找到的位置。如果想輸出到文件,建議創建一個 log 的目錄。基本上,預期會輸出以下四個位置:

  • 輸出到文檔
    • 這種方法適合在控制台以外啟動的應用程式。
  • 標準輸出
    • 這種方法適合用於從控制台啟動的應用程式,用於輸出中間過程等資訊。
  • 輸出到外部日誌管理工具的文檔
    • 如果可以使用外部日誌管理工具,建議將 Log 輸出到專用的記錄位置。
  • 輸出到外部系統
    • 為了方便開發者和運維人員的工作和溝通,有時會將 Log 輸出到如 Slack 等聊天工具中。但需注意維護適當的運行效率,避免輸出過多的 Log。

基本上,若使用外部日誌管理系統,或在雲端環境進行開發,這些專用工具通常會輸出到適當的位置。以雲端環境為例,如 Azure Application Insights 或 Amazon CloudWatch Logs 等工具。

此外,需要注意的是,不應該將 Log 輸出到未指定數量的使用者可以訪問的位置,或將系統內重要的資訊輸出到 Log,這通常被視為禁忌。

Log 格式

Log 格式將決定 Log 輸出的內容。 例如輸出以下表格中的項目:

項目 内容 備註
時間 記錄 Log 的時間 年月日時分秒毫秒。最少必須以毫秒為單位輸出
事件 ID 事件的 ID 追蹤事件序列時所需的
Log 級別 Log 的級別 INFO、ERROR 等
請求目標 對哪裡提出的請求 URL 等
使用者資訊 請求的使用者資訊 使用者 ID 和 IP 位置等
執行對象 對什麼進行處理 已執行的來源 ID 等
執行內容 執行什麼樣的處理 DELETE、PUT、GET 等
執行結果 執行結果是什麼 成功、失敗、執行件數等
訊息 其他要輸出的內容 Log 級別若大於 Error 時會希望顯示的訊息

Log 輸出基本上以 5W1H 為基礎,並且輸出必要的資訊,不應過多或不足。

建議注意以下內容:

When → 何時執行該程序

Who → 是誰執行該程序

Where → 在哪執行該程序

What → 執行該程序做什麼

Why → 為什麼執行該程序

例如,以下就是一個理想格式的範例:

(Log 級數) (時間) (IP 地址) (執行對象) (執行結果) (訊息)

此格式的輸出 Log 如下所示:

[Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server

輸出的困難點在於,並非所有可輸出的訊息都要進行輸出,在進入 Log 設計之前,必須先釐清使用用途,這部分將在後半章節進行介紹。

以上資訊作為參考,接下來讓我們更深入理解 Log 設計吧!

Log 的設計方法介紹

這裡開始,將會對 Log 的設計方法進行說明。

在設計 Log 時,比起建立具突破性的出色設計,更重要的是不犯錯誤。對此,接下來將對確認事項、技巧和需注意的部分進行說明。

首先是針對設計時的確認事項和技巧進行說明!

設計 Log 時的確認事項與技巧

接下來,將會說明設計 Log 時必須確認的要點,以及有關設計的技巧。透過牢記這些內容,將能夠有效進行 Log 分析。反之,如果忽略這些要點,Log 將變得毫無用處。

先是針對確認內容進行說明。

・Log 的使用目的是什麼?

Log 可使用於多種用途,如本文開頭附上的「Log 的類型與使用目的」表所介紹。正因為用途非常多樣,因此有必要確立使用目的。不只是開發人員,包括維運人員、企劃和法務部門等相關利益者的意見,都必須納入考量。

舉例來說,Log 的使用目的可能有以下三種:

  • 瞭解來自外部未經授權的訪問
  • 防止資訊洩露等的內部控制
  • 監控系統的使用狀態

請確認適用於哪一個項目。

・Log 的讀者是誰?

確定使用目的後,應該考慮 Log 的讀者是誰。這些讀者可能包括開發人員、運維人員、客戶公司的員工等各種對象。根據不同的讀者,可能需要不同的輸出方式和輸出內容,因此,結合使用目的來考慮將會更理想。

例如,根據使用目的預期目標讀者,可以得出以下結果:

使用目的 讀者
瞭解來自外部未經授權的訪問 運維人員、客戶公司的員工、法務人員、系統負責人等
防止資訊洩露等的內部控制 開發人員、運維人員、客戶公司的員工、法務人員等
監控系統的使用狀態 開發人員、運維人員等

掌握這些確認事項後,設計 Log 的準備工作也差不多完成。

接下來,將介紹設計 Log 時需考慮的要點。

・考慮使用哪種格式

在實際設計 Log 時,需要確定 Log 的格式。Log 的格式代表 Log 中包含哪些資訊,這部分必須和 Log 的目標讀者討論並確定格式。

可參考以下格式:

(Log 級數) (時間) (IP 地址) (執行對象) (執行結果) (訊息)

使用這種格式輸出的 Log,將如下所示:

[Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server

在這裡,可以考慮添加 Log 讀者的需求。例如,如果目標對象是法務人員,那麼列出 IP 地址和執行對象資訊,可能不會有太大意義。

因此,將訊息部分移至 Log 的前半部等其他改進,或許會更有幫助。

(Log 級數) (時間) (訊息) (IP 地址) (執行對象) (執行結果)
[Error] Feb 21 12:32:23, client denied by server, 193.121.123, https-8080, Failed

・思考包括什麼樣的訊息

Log 格式中,包括一個名為訊息的項目。Log 扮演的角色,是以易於理解的方式傳達系統狀態。透過充分利用這些訊息,將能發揮其效用至最大限度。如果是在輸出錯誤 Log 的情況,則務必描述預期錯誤的原因。

此外,也必須考慮設計符合潛在讀者 IT 素養的訊息。假設使用目的是「瞭解來自外部未經授權的訪問」,那麼非技術人員可能也會閱讀這些訊息,因此應該以簡單的語言解釋問題和錯誤發生原因,即使沒有專業知識背景,也能夠理解內容。

・斟酌選擇 Log 級別

正如本文前半部分提供的「Log 級別和含義」表中所示,Log 具有級別的概念。透過定義級別,能夠在達到一定程度以上級別的訊息時通知負責人,同時避免收到不必要的通知造成干擾。

具體來說,除了「監控系統的使用狀態」以外的目的,並不需要使用到 DEBUG 級別的 Log。如果使用目的為「防止資訊洩漏等內部控制」,即使出現無法使用系統的 EMERG 的 Log,也無法有任何作為,因此沒有這麼必要。

請參考這些內容來設計 Log。

操作 Log 時的注意事項

接下來,將會介紹操作 Log 時的注意事項,主要是避免在搜索目標 Log 時出現問題。

將 Log 檔案分割處理

在調查 Log 時,可以透過減少目標 Log的數量來縮短調查時間。 為此,建議將 Log 檔進行分割處理,例如:按時間或網站為單位來分割 Log 檔,藉此提升工作效率。

如果使用的是 logrotate,並希望分割 Log 檔案時,則應修改位於 etc/logrotate.d 的日誌輪替(Log Rotation)設定檔。

舉例來說,sample-service 使用的 Log 設定檔位於 etc/logrotate.d/sample-service ,內容如下所示:

/var/log/sample-service/sample.log { # 目標 Log 檔案
    ifempty            # 即使 Log 檔案為空也進行輪替
    missingok          # 即使沒有 Log 檔案也不會發出錯誤
    compress           # 進行壓縮
    daily              # 毎日輪替
    rotate 10          # 保留最近 10 個分割後的 Log 檔案
    create             # 更新後建立一個新的 Log 檔案
    endscript
}

create 這個指令,可用於分割 Log 檔案,因此請確認檔案中是否有此指令。

此外,如果希望新建立的 Log 檔能包含更新日期,請執行以下操作:

/var/log/sample-service/sample.log { # 目標 Log 檔案
    ifempty            # 即使 Log 檔案為空也進行輪替
    missingok          # 即使沒有 Log 檔案也不會發出錯誤
    compress           # 進行壓縮
    daily              # 毎日輪替
    rotate 10          # 保留最近 10 個分割後的 Log 檔案
    dateext            # 建立一個包含更新日期的檔案
    endscript
}

透過添加 dateext 這個指令,檔名將會更改為 原檔案名稱 + -YYYYMMDD

採用結構化日誌記錄

如果情況允許,請務必採用結構化日誌記錄(Structured Logging)。 結構化日誌記錄是透過標準化 Log 輸出格式,並作為結構化資料合集而非單文本處理,常見格式為 Json。 結構化日誌記錄不只易於和其他工具連結,還具備高性能搜索等優點。

請參考以下範例:

[Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server

以上顯示的 Log 會是:

jsonPayload: {
    "level": "[Error]",
    "timestamp": "Feb 21 12:32:23",
    "host": "193.121.123",
    "port-number": "https-8080",
    "result": "Failed",
    "message": "client denied by server",
}

可以像這樣進行結構化。

對人類來說可能有點難以閱讀,但從機器的角度來看,結構化日誌將更容易閱讀,因此建議導入使用。

時間同步和時區設定

在調查 Log 時,可能想知道該 Log 的輸出時間。如果未完成伺服器和本地時間同步,或沒有設定相同時區,將無法好好處理時間。此外,若是更改設定,則需重新啟動伺服器以使其生效!

若要更改本地時間 /etc/localtime ,請在終端機輸入以下指令:

// 東京時區
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

// 台北時區
cp /usr/share/zoneinfo/Asia/Taipei /etc/localtime

接著輸入 date 指令,確認是否顯示以下內容(是否為 JST):

Wed Dec  12 13:15:11 JST 2015

此外,可透過 /etc/timezone 檢查時區。 輸入 $ cat /etc/timezone 指令後,檢查輸出是否類似以下內容:

Asia/Tokyo

Asia/Taipei

如果輸出結果不同,請試著重寫內容。

確認日誌輪替的基本設定

日誌輪替(Log rotation)是種按照一定容量或時間間隔,刪除較舊的 Log 或拆分檔案,以防止 Log 無限增長的功能。在執行日誌輪替時,應確認使用指令的預設配置。輪替不一定是在 0 點進行,有時可能也無法按日期拆分檔案。

如果要更改日誌輪替的時間,請按照以下步驟進行(以使用 logrotate 為例)。

首先,從正在運行 logrotate 的 etc/cron.daily 中,刪除 anacron 設定,請註解或刪除以下行列:

[hour]     [minutes]     cron.daily     nice run-parts /etc/cron.daily

接下來,在 crontab 中註冊 /etc/cron.daily 寫上希望運行的時間。 具體來說,就是開啟 etc/crontab 檔案,並添加以下行列:

23     59     cron.daily     root run-parts /etc/cron.daily

如此一來,日誌輪換將會在 23:59 進行。

使用 Dokcer 時需注意

如果使用 Docker 的情況,可能會在 Docker 內部容器中的檔案輸出 Log。在這種情況下,若是重新建立 Docker 容器,將導致 Log 輸出檔案也一併被刪除,因此請務必更改輸出位置。

想要更改輸出位置,請在啟動 Docker 時執行以下指令:

docker run --log-driver=<Driver Name>

本文的後半部分,將會詳細介紹有關 Docker 的 Log 輸出。如果對 Driver(驅動程式)部分感到疑惑,也請繼續往下閱讀。

使用 CDN 或反向代理時需注意

如果使用 CDN 或反向代理,需注意其 IP 地址可能會被輸出為 Log。但由於 Client 端的 IP 地址已經正確記錄在別處的標頭欄位中,因此需要更改設定以記錄相關內容。

  • Nginx
  • Varnish
  • Apache Traffic Server
  • HAProxy

更改設定的方法,將根據反向代理而有所差異。 以上列舉幾項反向代理,建議根據自己使用的工具,查詢更改設定的相關方法。

但我希望您能找到如何使用您正在使用的內容更改設置。

對實際的 Log 輸出進行說明

到目前為止,已介紹通用的 Log 設計相關基礎知識。在最後部分中,將藉由一系列的情境來說明 Log 輸出的不同階段。

在實際輸出 Log 時,總共有三個階段:

  • 日誌輸出 Log Output
    • 是以什麼格式,在何時輸出
  • 日誌輪換 Log Rotation
    • 如何處理不斷增長的日誌數量
  • 日誌聚合 Log Aggregation
    • 如何長時間保存日誌

在每個階段中,都需要注意不同事項。以下是針對各種情境的注意事項進行說明。

基於 VM 的 Log 輸出

首先,將對基於 VM(虛擬機器)的 Log 輸出流程進行說明。舉例來說,對於在 AWS 的 EC2 上運行的應用程式,在上述三個階段(誌輸出、日誌輪替、日誌集約)當中,應特別注意「日誌輸出」。

日誌輸出

通常最初想到的輸出方法,是由應用程式直接輸出到 Log 檔案的模式,這是許多框架的預設配置。

另一方面,被稱為應用程式開發的最佳實踐 The Twelve-factor App(12 要素應用程式),則表示應用程式生成的 Log 不應該直接由 Log 檔案進行管理。
而作為替代方案,建議採取以下方法:

  • 以標準輸出的方式輸出日誌,並使用日誌管理系統將其輸出到檔案中

透過這種方式,在測試或部署環境中,將無法透過應用程式查看或設定 Log,只能由執行環境進行全面管理。

基於 Docker 的 Log 輸出

如「操作 Log 時的注意事項」段落所述,在 Docker 容器中,刪除容器時會將連同所有寫入的資料一起被棄用。換句話說,基於 Docker 的 Log 輸出之所以困難,在於日誌聚合。為了正確執行日誌聚合,可參考以下兩種方法:

  • 指定 volume 目標並永久保存
  • 使用 log driver 傳輸

以下將會逐一進行介紹。

指定 volume 目標並永久保存

透過使用 volume,將能夠永久保存主機的資料。為了避免中斷引用,請使用 --volumes-from <container_name> 或 --v <host_path>:<container_path> 將其保存在資料容器中。

然而另一方面,若是因為自動規模化等功能,導致實例本身也被頻繁刪除的情況,則必須將 Log 轉移到其他位置保存。在這種情況下,則必須準備一個類似於 fluentd 容器,透過能夠將收集的資料暫存到緩衝區的機制,將 volume 掛載(mount)以進行傳送等處理。

使用 Logging Driver 傳輸

透過 Docker 提供的日誌記錄驅動機制(Logging Driver),能夠將 Log 傳輸到外部日誌記錄軟體,即使是在結構複雜的容器,也能夠實現日誌聚合。

在 Logging Driver 中,可以在容器啟動時指定輸出目標,將 Log 輸出到指定的日誌管理系統。通常使用「json-file」Driver,但從 Docker 1.12 版本開始,還提供以下幾種類型的 Driver 使用:

Driver 輸出目標
json-file 以 Json 格式儲存在檔案
(Docker Default)
none 不記錄 Log
syslog syslog
journald journald
gelf 支援 gelf 的 Log 管理系統
fluentd Log 管理工具「fluentd」
awslogs AWS 提供的 Log 管理系統「Amazon CloudWatch Logs」
splunk Log 管理系統「Splunk」
etwlogs Windows 的 Event Tracing for Windows
gcplogs GCP 提供的 Log 管理系統「Google Cloud Logging」

若要實際指定 Driver,可在容器啟動時輸入 docker run --log-driver=<driver name>。如果只輸入 docker run,則會使用預設的「json-file」Driver。

設計 Log 時不該做什麼

最後要介紹的,是設計 Log 時不應該執行的兩件事。

不輸出機密資訊

導致資訊洩露的原因之一,就是 Log 輸出機密資訊。 對於應該只有開發人員和負責人才能知道 Log,是否就此感到安心了? 事實上,透過未經授權的訪問,將有可能查看 Log。 因此為了以防萬一,請再三確保重要的機密資訊不會作為 Log 輸出。

請注意並確保重要的機密資訊不會作為日誌輸出,

如下所示:

  • 姓名
  • 地址
  • 電話號碼
  • 電子郵件地址
  • 密碼
  • Access Token
  • 信用卡號碼

等機密資訊。

不輸出不必要的資訊

不輸出不必要的資訊,原因有以下三種:

  • 若增加 Log 的大小,將會增加服務使用費。
  • 進行調查時可能造成妨礙
  • 將導致系統性能下降

總結

在本文中,介紹了如何設計 Log! 如果正在進行開發,一定會使用到這些 Log,希望從明天開始就能夠實際應用開發中!

敝公司 Nuco 目前正在招募中,詳細可參考這裡

15th鐵人賽目錄傳送門:https://ithelp.ithome.com.tw/users/20135558/ironman/6290