翻譯自:https://benfoster.io/blog/serilog-best-practices/
Serilog是 Microsoft .NET 的結構化日志記錄庫,并已成為[Checkout.com .NET 的首選日志記錄庫。.它支援各種日志記錄目的地(稱為接收器)包從标準控制台和基于檔案的接收器到日志服務,如 Datadog。
本指南最初是我們工程手冊中的一篇文章,在收到内部積極回報後,我決定在我的部落格上釋出它。
标準日志屬性
日志記錄基礎知識
記錄一切
選擇合适的日志記錄級别
定時操作源
上下文
HTTP 日志記錄
日志的職責
日志内容的采集
Serilog标準采集方法
通過全局屬性采集日志
關聯日志
消息模闆
消息模闆推薦
日志和診斷上下文
日志上下文
診斷上下文
配置
生産日志
當日志變得不僅僅是日志
其他工具和實用程式
在本地使用 Seq
按日志類型記錄日志
按屬性塊記錄日志
按請求記錄日志
标準化日志事件屬性使您能夠充分利用日志搜尋和分析工具。在适用的情況下使用以下屬性:
<code>ApplicationName</code>
生成日志事件的應用程式的名稱
<code>ClientIP</code>
送出請求的用戶端的 IP 位址
<code>CorrelationId</code>
可用于跨多個應用程式邊界跟蹤請求的 ID
<code>Elapsed</code>
操作完成所用的時間(以毫秒為機關)
<code>EventType</code>
用于确定消息類型的消息模闆的哈希值
<code>MachineName</code>
運作應用程式的機器的名稱
<code>Outcome</code>
手術的結果
<code>RequestMethod</code>
HTTP 請求方法,例如 <code>POST</code>
<code>RequestPath</code>
HTTP 請求路徑
<code>SourceContext</code>
日志源自的元件/類的名稱
<code>StatusCode</code>
HTTP 響應狀态碼
<code>UserAgent</code>
HTTP 使用者代理
<code>Version</code>
正在運作的應用程式的版本
上面的很多屬性都來自于 Serilog 自己的擴充,例如Serilog Timings(用于計時操作)和Serilog 請求日志記錄。
通常,記錄所有可以深入了解您的應用程式和使用者行為的内容,例如:
代碼中的主要分支點
遇到錯誤或意外值時
任何 IO 或資源密集型操作
重大領域事件
請求失敗和重試
耗時的批處理操作的開始和結束
對您的日志記錄要慷慨,但對您的日志記錄級别要嚴格。在幾乎所有情況下,您的日志級别都應該是<code>Debug</code>. 使用<code>Information</code>的日志事件,将在生産中需要确定運作狀态或應用程式的正确性,<code>Warning</code>或<code>Error</code>突發事件,如異常。
請注意,該<code>Error</code>級别應保留用于您打算對其采取行動的事件。如果某些事情成為正常的應用程式行為(例如,請求輸入驗證失敗),您應該降級日志級别以減少日志“噪音”。
将應用程式中的每個資源密集型操作(例如 IO)與名額代碼一起記錄下來。這在本地運作應用程式以檢視應用程式瓶頸或響應時間消耗的情況時非常有用。該Serilog時序庫提供了一個友善的方式來做到這一點:
該<code>SourceContext</code>屬性用于跟蹤日志事件的來源,通常是使用記錄器的 C# 類。<code>ILogger</code>使用依賴注入将 Serilog 注入到類中是很常見的。為確定<code>SourceContext</code>正确設定,請使用<code>ForContext</code>擴充名:
使用Serilog 請求日志記錄中間件來記錄 HTTP 請求。這會自動包含上面列出的許多 HTTP 屬性并生成以下日志消息:
将以下内容添加到您的應用程式啟動中以添加中間件:
請注意,在health 和 metrics 中間件之後添加了 Serilog中間件。這是為了避免每次 AWS 負載均衡器命中您的健康檢查端點時生成日志。
Serilog 中間件預設記錄請求路徑。如果您确實需要檢視對應用程式中特定端點的所有請求,如果路徑包含辨別符等動态參數,您可能會遇到挑戰。
為了解決這個問題,記錄資源名稱,在我們的應用程式中,按照慣例,它是<code>Name</code>賦予相應路由的屬性。這是這樣檢索的:
過度全面的日志記錄不僅會對您的應用程式産生性能影響,而且還會使診斷問題變得更加困難,并增加暴露敏感資訊的風險。
Serilog 支援結構化對象輸出,允許将複雜對象作為日志中的參數傳遞。這應該謹慎使用,如果您的主要目标是對相關屬性進行分組,您最好初始化一個新的匿名對象,這樣您就可以明确哪些資訊被推送到日志中。
傾向于使用 Serilog 的診斷上下文功能(下面讨論)将日志折疊為單個日志條目。
将附加資訊推送到您的日志中有助于提供有關特定事件的附加上下文。
您可以使用收集器來豐富應用程式生成的所有日志事件。我們建議使用以下 Serilog 濃縮器:
日志上下文收集器 - 内置于 Serilog,此豐富器可確定添加到日志上下文的任何屬性都被推送到日志事件中
環境收集器- 使用機器或目前使用者名采集日志
可以使用<code>Enrich.With</code>Serilog的fluent API<code>LoggerConfiguration</code>或通過您的<code>appsettings.json</code>檔案(推薦)指定增強器:
您還可以全局指定屬性。上面的片段<code>appsettings.json</code>示範了我們通常如何設定<code>ApplicationName</code>屬性。在某些情況下,我們需要在啟動時計算屬性,這可以使用 Fluent API 來完成:
為了關聯屬于同一請求的日志,甚至跨多個應用程式,請<code>CorrelationId</code>向日志添加一個屬性。
在 HTTP 應用程式中,我們通常從<code>HttpContext.TraceIdentifier</code>屬性映射它。這是使用<code>Cko-Correlation-Id</code>标頭在内部 API 之間傳遞的。我們使用以下擴充來擷取 _current_correlation ID:
請注意,如果應用程式面向公衆,則不應依賴提供的相關 ID 标頭。
為了確定将關聯 ID 推送到每個日志事件中,我們使用以下使用 Serilog 的中間件<code>LogContext</code>(本文稍後将詳細讨論):
日志消息應提供事件的簡短描述。我們通常看到開發人員建立過于冗長的消息作為在事件中包含額外資料的手段,例如:
相反,您可以使用<code>ForContext</code>(或本文底部的屬性包豐富器)仍然包含資料但具有更簡潔的消息:
好的 Serilog 事件使用屬性名稱作為消息中的内容來提高可讀性并使事件更緊湊,例如:
日志事件消息是片段,而不是句子;為了與使用 Serilog 的其他庫保持一緻,請盡可能避免尾随句點/句号。
Serilog 事件具有關聯的消息模闆,而不是消息。在内部,Serilog 解析和緩存每個模闆(最多固定大小限制)。将日志方法的字元串參數視為消息,如下例所示,會降低性能并消耗緩存記憶體。例如,避免:
而是使用消息屬性:
除了在日志消息中使用字元串連接配接/插值的性能開銷之外,它還意味着無法計算一緻的事件類型(請參閱事件類型豐富器),進而無法找到特定類型的所有日志。
Serilog 支援兩種可用于增強日志的上下文感覺功能。
<code>LogContext</code>可用于動态地添加和移除來自周圍“執行上下文”性能; 例如,在事務期間寫入的所有消息都可能帶有該事務的 id,等等。
在<code>RequestLogContextMiddleware</code>上面的介紹示範了如何推動<code>CorrelationId</code>請求到<code>LogContext</code>在請求的開始。這可確定該請求中的所有日志都包含該屬性。
更多資訊可以在Serilog wiki上找到。
日志記錄的一個挑戰是上下文并不總是預先知道。例如,在處理 HTTP 請求的過程中,随着我們通過 HTTP 管道(例如了解使用者的身份)獲得額外的上下文。雖然<code>LogContext</code>我們所有人都會在附加資訊可用時建立新上下文,但此資訊僅在 _subsequent_log 條目中可用。這通常會導緻日志數量增加,隻是為了捕獲有關整個請求或操作的所有資訊。
診斷上下文提供了一個執行上下文(類似于<code>LogContext</code>),其優點是可以在其整個生命周期中進行豐富。請求日志中間件然後使用它來豐富最終的“日志完成事件”。這允許我們将許多不同的日志操作折疊為一個日志條目,其中包含來自請求管道中許多點的資訊,例如:

在這裡您可以看到,不僅有中間件發出的 HTTP 屬性,還有應用程式資料,例如<code>AcquirerId</code>、<code>MerchantName</code>和<code>ResponseCode</code>。這些資料點來自請求中的不同點,但通過<code>IDiagnosticContext</code>接口推送到診斷上下文中:
診斷上下文不限于在 ASP.NET Core 中使用。它也可以以與請求日志中間件非常相似的方式在非 HTTP 應用程式中使用。例如,我們使用它在 SQS 使用者中生成完成日志事件。
Serilog 可以使用 Fluent API 或通過 Microsoft 配置系統進行配置。我們建議使用配置系統,因為可以在不釋出應用程式新版本的情況下更改日志配置。
為此,添加Serilog.Settings.Configuration包并按如下方式配置 Serilog:
您現在可以通過任何支援的配置提供程式配置 Serilog。通常我們<code>appsettings.json</code>用于全局設定并通過生産中的環境變量配置實際接收器(因為我們不想在本地運作時使用我們的遠端日志服務):
在生産中部署應用程式時,請確定相應地配置日志記錄:
控制台日志記錄應限于<code>Error</code>. 在 .NET 中,寫入控制台是一個阻塞調用,會對性能産生重大影響。
應為<code>Information</code>及以上配置全局日志記錄。
據了解,在新項目釋出期間,您可能需要更多資訊來建立對解決方案的信心或診斷任何預期的初期問題。與其<code>Information</code>為此更新您的日志條目,不如考慮<code>Debug</code>在有限的時間内啟用級别。
從開發人員那裡聽到的一個常見問題是他們如何在運作時動态切換日志級别。雖然這是可能的,但也可以使用藍/綠部署來實作。使用降低的日志級别配置和部署非活動環境,然後通過權重目标組切換部分或全部流量。
日志可以提供對應用程式的大量洞察,并且在許多情況下足以處理日常支援請求或故障排除。然而,在某些情況下,日志不是工作的正确工具,有許多警告信号:
您發現自己向非技術使用者開放應用程式日志
日志用于生成應用程式名額
更多資訊被“塞進”日志以滿足常見的支援請求或報告要求
在這些情況下,您可能需要為您的産品考慮專用工具。許多團隊開發了類似“Inspector”的應用程式,将關鍵系統和業務資料聚合在一起,以處理可以提供給非技術利益相關者的 BAU 請求。此外,您可能會發現需要将應用程式中的資料推送到報告和分析工具中。
日志的有效性取決于您記錄的内容和未記錄的内容。
Seq是由 Serilog 的作者建立的免費(供本地使用)日志記錄工具。它提供進階搜尋和過濾功能以及對結構化日志資料的完全通路。雖然我們的日志記錄要求現在超出了 Seq 所能提供的範圍,但它仍然是本地測試的一個很好的選擇。
我們通常在 docker 中啟動 Seq 作為單獨的 docker-compose 檔案 ( <code>docker-compose-logging.hml</code>) 的一部分:
并配置我們的<code>appsettings.Development.json</code>檔案以使用 Seq 接收器:
通常我們需要唯一辨別相同類型的日志。一些接收器(例如Seq)通過散列消息模闆來自動執行此操作。為了在其他接收器中複制相同的行為,我們建立了以下使用Murmerhash 算法的收集器:
如果您想向日志事件添加多個屬性,請使用<code>PropertyBagEnricher</code>:
Serilog 請求日志記錄中間件允許提供一個函數,該函數可用于将來自 HTTP 請求的附加資訊添加到完成日志事件。我們使用它來記錄<code>ClientIP</code>,<code>UserAgent</code>和<code>Resource</code>屬性:
© 2021 Ben Foster https://benfoster.io/