[![hackmd-github-sync-badge](https://hackmd.io/exS-HCCWQ220wxXwrGqcGA/badge)](https://hackmd.io/exS-HCCWQ220wxXwrGqcGA) ###### tags: `2023鐵人賽` `qiita` `Back-End` `Log` # [2023 15th鐵人賽] Day28 - 事到如今問不出口的 Log 基礎和設計指南 > 原文連結:[今さら聞けないログの基本と設計指針 - Qiita](https://qiita.com/tadashiro_ninomiya/items/19c774898c68add6185e?utm_source=Qiitaニュース&utm_campaign=507f09554c-Qiita_newsletter_583_09_13&utm_medium=email&utm_term=0_e44feaa081-507f09554c-62820449) 在進行程式開發時,不論是前後端,應該都對 Log(日誌)再熟悉不過。尤其是在遇到非預期性的錯誤時,通常會需要立即調閱 Log,查看是否顯示錯誤訊息;或是在改良軟體時,需要透過 Log 來分析使用者行為等等。 接下來這篇文章,將會針對 Log(日誌)主題進行介紹,從 Log 相關的基礎知識、設計方法、操作時的注意事項,再到實際應用說明。 文章架構如下: + 為什麼要輸出 Log? + 為什麼要設計 Log? + 有關 Log 的基礎知識 + Log 的級別與意義 + Log 的輸出位置 + Log 格式 + Log 的設計方法介紹 + 設計 Log 時的確認事項與技巧 + 操作 Log 時的注意事項 + 對實際的 Log 輸出進行說明 + 基於 VM 的 Log 輸出 + 基於 Docker 的 Log 輸出 + 設計 Log 時不該做什麼 那麼,以下正文開始。 --- [toc] ## 前言 各位對於 Log(日誌)的理解是什麼呢?可能有從機制到設計方法都完全理解的工程師,或者只是隨便用用的工程師存在。 Log 是系統中按照時間順序記錄,關於錯誤或故障發生、使用者操作或設定變更、與外部通信等內容。透過深入瞭解有關 Log 的知識,將能夠進行複雜的系統開發和運用。此外,如果使用 AWS、Azure、GCP 等雲端服務,不只能夠實現系統開發,還可能節省經費開支。 本文將介紹 Log 的基本設計方法。如果有任何疑問,請繼續閱讀下去。 ## 為什麼要輸出 Log? 在討論 Log 的基本知識和 Log 的設計之前,首先必須瞭解為什麼需要輸出 Log。 主要考慮以下四個理由: - 在問題發生時進行調查 - 為了防止問題發生 - 通過資料分析來改良軟體 - 作為審計日誌使用 通過輸出 Log,不只能夠調查和防止問題,還可以分析使用者的行為。或許目前還不太能感覺到輸出 Log 的必要性,但將來肯定會變得不可或缺。 ## 為什麼要設計 Log? 那麼,為什麼需要設計 Log 呢? 首先,Log 的設計將決定 Log 輸出的訊息和格式,基本方針是根據 Log 的使用目的來反推設計。 而設計 Log 的原因在於,將有助於選擇符合目的的 Log 並適當顯示。如上所述,Log 存在各種不同的類型,也因為如此,如何進行選擇和顯示其實相當複雜。 例如,以下將介紹使用 AWS 等雲端服務,進行系統開發的範例。雲端服務可以輕鬆實現橫向擴充和縮減,是非常方便的功能。 然而,容易擴展也意味著,可能存在大量不知道何時會出現或消失的伺服器。因此,在雲端系統中,各伺服器的 Log 會非同步寫入,即使將範圍縮小到毫秒單位,也很難找到特定的 Log。 ![](https://hackmd.io/_uploads/rJGyp9rZT.png) 因此,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 → 為什麼執行該程序 例如,以下就是一個理想格式的範例: ```bash (Log 級數) (時間) (IP 地址) (執行對象) (執行結果) (訊息) ``` 此格式的輸出 Log 如下所示: ```bash [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 的目標讀者討論並確定格式。 可參考以下格式: ```bash (Log 級數) (時間) (IP 地址) (執行對象) (執行結果) (訊息) ``` 使用這種格式輸出的 Log,將如下所示: ```bash [Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server ``` 在這裡,可以考慮添加 Log 讀者的需求。例如,如果目標對象是法務人員,那麼列出 IP 地址和執行對象資訊,可能不會有太大意義。 因此,將訊息部分移至 Log 的前半部等其他改進,或許會更有幫助。 ```bash (Log 級數) (時間) (訊息) (IP 地址) (執行對象) (執行結果) ``` ```bash [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](https://zh.wikipedia.org/zh-tw/%E6%97%A5%E5%BF%97%E8%BD%AE%E6%9B%BF),並希望分割 Log 檔案時,則應修改位於 `etc/logrotate.d` 的日誌輪替(Log Rotation)設定檔。 舉例來說,`sample-service` 使用的 Log 設定檔位於 `etc/logrotate.d/sample-service` ,內容如下所示: ```bash /var/log/sample-service/sample.log { # 目標 Log 檔案 ifempty # 即使 Log 檔案為空也進行輪替 missingok # 即使沒有 Log 檔案也不會發出錯誤 compress # 進行壓縮 daily # 毎日輪替 rotate 10 # 保留最近 10 個分割後的 Log 檔案 create # 更新後建立一個新的 Log 檔案 endscript } ``` `create` 這個指令,可用於分割 Log 檔案,因此請確認檔案中是否有此指令。 此外,如果希望新建立的 Log 檔能包含更新日期,請執行以下操作: ```bash /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。 結構化日誌記錄不只易於和其他工具連結,還具備高性能搜索等優點。 請參考以下範例: ```bash [Error] Feb 21 12:32:23, 193.121.123, https-8080, Failed, client denied by server ``` 以上顯示的 Log 會是: ```bash 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` ,請在終端機輸入以下指令: ```bash // 東京時區 cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime // 台北時區 cp /usr/share/zoneinfo/Asia/Taipei /etc/localtime ``` 接著輸入 `date` 指令,確認是否顯示以下內容(是否為 JST): ```bash Wed Dec 12 13:15:11 JST 2015 ``` 此外,可透過 `/etc/timezone` 檢查時區。 輸入 `$ cat /etc/timezone` 指令後,檢查輸出是否類似以下內容: ```bash Asia/Tokyo Asia/Taipei ``` 如果輸出結果不同,請試著重寫內容。 #### 確認日誌輪替的基本設定 日誌輪替(Log rotation)是種按照一定容量或時間間隔,刪除較舊的 Log 或拆分檔案,以防止 Log 無限增長的功能。在執行日誌輪替時,應確認使用指令的預設配置。輪替不一定是在 0 點進行,有時可能也無法按日期拆分檔案。 如果要更改日誌輪替的時間,請按照以下步驟進行(以使用 logrotate 為例)。 首先,從正在運行 logrotate 的 `etc/cron.daily` 中,刪除 anacron 設定,請註解或刪除以下行列: ```bash [hour] [minutes] cron.daily nice run-parts /etc/cron.daily ``` 接下來,在 crontab 中註冊 `/etc/cron.daily` 寫上希望運行的時間。 具體來說,就是開啟 `etc/crontab` 檔案,並添加以下行列: ```bash 23 59 cron.daily root run-parts /etc/cron.daily ``` 如此一來,日誌輪換將會在 23:59 進行。 #### 使用 [Dokcer](https://zh.wikipedia.org/zh-tw/Docker) 時需注意 如果使用 Docker 的情況,可能會在 Docker 內部容器中的檔案輸出 Log。在這種情況下,若是重新建立 Docker 容器,將導致 Log 輸出檔案也一併被刪除,因此請務必更改輸出位置。 想要更改輸出位置,請在啟動 Docker 時執行以下指令: ```bash docker run --log-driver=<Driver Name> ``` 本文的後半部分,將會詳細介紹有關 Docker 的 Log 輸出。如果對 Driver(驅動程式)部分感到疑惑,也請繼續往下閱讀。 #### 使用 CDN 或反向代理時需注意 如果使用 CDN 或反向代理,需注意其 IP 地址可能會被輸出為 Log。但由於 Client 端的 IP 地址已經正確記錄在別處的標頭欄位中,因此需要更改設定以記錄相關內容。 - [Nginx](https://zh.wikipedia.org/wiki/Nginx) - [Varnish](https://zh.wikipedia.org/zh-tw/Varnish_cache) - [Apache Traffic Server](https://zh.wikipedia.org/wiki/Traffic_Server) - [HAProxy](https://zh.wikipedia.org/zh-tw/HAProxy) 更改設定的方法,將根據反向代理而有所差異。 以上列舉幾項反向代理,建議根據自己使用的工具,查詢更改設定的相關方法。 但我希望您能找到如何使用您正在使用的內容更改設置。 ## 對實際的 Log 輸出進行說明 到目前為止,已介紹通用的 Log 設計相關基礎知識。在最後部分中,將藉由一系列的情境來說明 Log 輸出的不同階段。 在實際輸出 Log 時,總共有三個階段: - **日誌輸出 Log Output** - 是以什麼格式,在何時輸出 - **日誌輪換 Log Rotation** - 如何處理不斷增長的日誌數量 - **日誌聚合 Log Aggregation** - 如何長時間保存日誌 在每個階段中,都需要注意不同事項。以下是針對各種情境的注意事項進行說明。 ### 基於 VM 的 Log 輸出 首先,將對基於 VM(虛擬機器)的 Log 輸出流程進行說明。舉例來說,對於在 AWS 的 EC2 上運行的應用程式,在上述三個階段(誌輸出、日誌輪替、日誌集約)當中,應特別注意「日誌輸出」。 #### 日誌輸出 通常最初想到的輸出方法,是由應用程式直接輸出到 Log 檔案的模式,這是許多框架的預設配置。 另一方面,被稱為應用程式開發的最佳實踐 [The Twelve-factor App(12 要素應用程式)](https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology),則表示應用程式生成的 Log 不應該直接由 Log 檔案進行管理。 而作為替代方案,建議採取以下方法: - 以標準輸出的方式輸出日誌,並使用日誌管理系統將其輸出到檔案中 透過這種方式,在測試或部署環境中,將無法透過應用程式查看或設定 Log,只能由執行環境進行全面管理。 ### 基於 Docker 的 Log 輸出 如「操作 Log 時的注意事項」段落所述,在 Docker 容器中,刪除容器時會將連同所有寫入的資料一起被棄用。換句話說,基於 Docker 的 Log 輸出之所以困難,在於日誌聚合。為了正確執行日誌聚合,可參考以下兩種方法: - 指定 volume 目標並永久保存 - 使用 log driver 傳輸 以下將會逐一進行介紹。 #### 指定 volume 目標並永久保存 透過使用 volume,將能夠永久保存主機的資料。為了避免中斷引用,請使用 `--volumes-from <container_name>` 或 `--v <host_path>:<container_path>` 將其保存在資料容器中。 然而另一方面,若是因為[自動規模化](https://zh.wikipedia.org/zh-tw/%E5%BC%B9%E6%80%A7%E4%BC%B8%E7%BC%A9)等功能,導致實例本身也被頻繁刪除的情況,則必須將 Log 轉移到其他位置保存。在這種情況下,則必須準備一個類似於 [fluentd](https://en.wikipedia.org/wiki/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 目前正在招募中,詳細可[參考這裡](https://www.recruit.nuco.co.jp/?qiita_item_id=19c774898c68add6185e)。