只有累積,沒有奇蹟

2019年5月9日 星期四

[NETCore] Serilog 好幫手 - Serilog Analyzer

前言
前面透過幾篇文章介紹了關於 Serilog 在 ASP.NET Core 中的應用,分別是
  • 結構化日誌 Serilog 初體驗
  • 結構化日誌 Serilog - 配置設定
  • 結構化日誌 Serilog - Events Types 和 Enrichment
  • 三篇文章從 Serilog 基本使用以及設定都做簡單的介紹,今天要分享的是 Serilog Analyzer 擴充套件,它是基於 Roslyn 針對代碼進行分析,可以幫助開發者在寫 Code 時檢查潛在的問題及建議改善項目,形容得很抽象以下直接透過實際範例說明,以及自己在使用後覺得不錯及有幫助的地方,幫助各位更快了解,若有問題歡迎提出一起討論。

    下載
    可以在 Marketplace 找到,下載 傳送門

    分析
    在執行時請先注意開啟 cmd 需要使用 Admin 權限執行,否則會有執行異常或是告知沒權限的錯誤訊息,以下為開啟時應用程式時用 administrator 開啟的畫面,用管理者身分執行 cmd

    Exception Usage
    當記錄 Log 是應用在例外情境時,會檢查是否有傳遞  exception  參數,而不是定義在屬性中
    try
    {
        // to do something
    }
    catch (Exception ex)
    {
        Log.Error("Could not save {File}: {Error}", file, ex);
        throw;
    }
    以上代碼會提示 The exception 'ex' should be passed as first argument,如下圖所示
    因此正確的記錄方式應該是以下代碼
    catch (Exception ex)
    {
       Log.Error(ex, "Could not save {File}", file);
    }

    Property Binding Verifier
    檢查紀錄 Log 時的 property 是否有遺漏,舉例來說下列代碼要紀錄  file  與  driectory  ,少傳入一個參數在紀錄時會發生錯誤
    Log.Information("Saving {File} to {Directory}", file);               
    筆記 : 使用 Serilog Analyzer 前後比較,未安裝前是不會提示錯誤的唷

    Message Template Syntax Verifier
    Message Template 檢查也就是會確認你的格式是否正確,如果違反 message template 會提示錯誤,以下為格式錯誤範例(少一個 { )
    Log.Information("Saving {File to {Directory}", file, directory);    
    提示如下
    正確如下
    Log.Information("Saving {File} to {Directory}", file, directory);

    Configuration
    分析 Serilog logging 配置生成對應的 <appSettings> 或是 appSettings.json,不囉嗦直接看官方範例

    後記
    以上簡單僅介紹幾種使用的情境,官方支援更多的檢查與驗證,如果有興趣了解更多可以參考官方網站,另外有在網路上看到使用時會與 Resharper 衝突,但自己在使用時並未遇到衝突而不能使用的情境,如果有遇到也可以留言一起討論解決方案 :)

    參考
    SerilogAnalyzer

    2019年5月8日 星期三

    [Windows] Chrome - This page isn't working ERR_EMPTY_RESPONSE

    問題 
    今天在請客戶測試功能時,客戶反映開發好的功能一直無法正常使用,為了得到更多資訊方便除錯便請客戶截圖查看錯誤訊息,發現使用測試的 Chrome 顯示 'This page is not working, web site did not send any data 此錯誤極少遇到覺得神奇,這篇分享針對此問題的可能發生原因與解決方式,若是有不清楚或是錯誤的地方歡迎討論予糾正。

    解決方法
    發生異常問題當下,測試多次使用者畫面都出現 This page is not working, xx website did not send any data. 且下方顯示  ERR_EMPTY_RESPONSE  等關鍵字,從錯誤訊息表面上的意思看起來是 Server 未回傳正確資訊給呼叫端 Browser (猜測 response byte=0),因此照關鍵字去搜尋後發現有幾種可能原因,嘗試多種可能解決的方式如下 (每個人電腦環境與設定不同,也請斟酌服用)

    重新開機
    根據過去經驗,遇到詭異問題重新開機恢復的機率高達 60%,雖然看起來很腦殘但實用度很高,因此在遇到此問題時謹記此原則使用者嘗試多次失敗之後,立刻請 User 進行重新開機的動作 ( Reboot ),看是否因未設定跑掉而影響到運作,但重開機後透過瀏覽器連線依然失敗,因此宣告此招式無效。

    檢查 Windows Proxy 設定
    有可能也有跟 Windows 的 Proxy 設定有關係,因此得確認 Proxy Server 的設定是否有異常,首先先點選 左下角 Windows 按鈕,接著按下設定
    接著選擇右上方選擇網路與網際網路,下一步是點選左方 Proxy tab 選項,自動偵測設定確認是否已開啟
    由於我的自動更新原本就會設定自動跟新,因此這招對我來說無效

    重置網路
    有可能會因為網路錯誤造成 DNS 異常,造成在 Chrome 回傳 ERR_EMPTY_RESPONSE,因此我們嘗試重置網路而進行修復,首先開啟左下角開始,輸入 CMD 用管理者權限開啟
    接著在命令提示字元中輸入以下 Code ,主要目的是刷新 DNS 的 Cache,重置 刷新DNS缓存,重置Winsock 並重置TCP / IP,輸入完畢後並開始執行
    ipconfig /release
    ipconfig /all
    ipconfig /flushdns
    ipconfig /renew
    netsh int ip set dns
    netsh winsock reset 
    執行完畢之後,請使用者重新開機,再次請使用者進行測試功能是否正常,確認正常執行功能 !

    參考
    修复Win10上Chrome中的ERR_EMPTY_RESPONSE错误
    How to Solve the Windows Could Not Automatically Detect Network Proxy Settings Error


    2019年5月6日 星期一

    [NETCore] 結構化日誌 Serilog - Events Types 和 Enrichment

    前言
    前兩篇分別介紹了關於 Serilog 的基礎應用與設定,這篇就來針對事件類型 Event Type 與 介紹幾個常用的 Enricher,若有問題或是錯誤的地方歡迎提出來一起討論。

    Event Type
    結構化日誌的好處是可以清楚的分辨"每一次"紀錄的事件,舉例來說下列簡單的代碼是透過 Serilog 寫入log 到 Console 與 File 檔案,紀錄內容是 3 筆資料
    Log.Logger = new LoggerConfiguration()
        .WriteTo.Console()
        .WriteTo.File("logs\\log-.txt", rollingInterval: RollingInterval.Day)
        .CreateLogger();
    
    Log.Information("Logging Start");
    var total = 1;
    for (var i = 0; i < 3; ++i)
    {
        total *= i;
        Log.Information("Computed iteration {Counter}, total is {Total}", i, total);
    }
    上述的代碼輸出後如下
    [00:35:28 INF] Logging Start
    [00:35:28 INF] Computed iteration 0, total is 0
    [00:35:28 INF] Computed iteration 1, total is 0
    [00:35:28 INF] Computed iteration 2, total is 0
    上述的 Log 雖然可以記錄事情但是假設資料量大或是 Log 數量很龐大的時候,找到同一事件(群)新增的 Log 紀錄是件很困難的事情,要解決這問題可以在 Log 加上一串識別字串  hashCode  方便查詢同樣事件使用。在 Serilog 解決方案是可以透過 Erinch 解決,首先新增一個類別 EventTypeEnricher 實作 ILogEventEnricher 介面的 Enrich 方法,內容為自訂 hash 的算法
    class EventTypeEnricher : ILogEventEnricher
    {
       public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
       {
          var murmur = MurmurHash.Create32();
          var bytes = Encoding.UTF8.GetBytes(logEvent.MessageTemplate.Text);
          var hash = murmur.ComputeHash(bytes);
          var numericHash = BitConverter.ToUInt32(hash, 0);
          var eventId = propertyFactory.CreateProperty("EventType", numericHash);
          logEvent.AddPropertyIfAbsent(eventId);
       }
    }
    接著在建立 ILogger 時候透過 Enrich.With 將產生 hash 字串內容,並自定義 outputTemplate 模板樣式
    Log.Logger = new LoggerConfiguration()
       .Enrich.With<EventTypeEnricher>()
       .WriteTo.Console(outputTemplate:
           "{Timestamp:HH:mm:ss} [{EventType:x8} {Level:u3}] {Message:lj}{NewLine}{Exception}")
       .CreateLogger();
    接著重新執行一次,可以看到 log 多了自行定義的 hash Code
    01:28:08 [4dd86bd1 INF] Logging Start
    01:28:08 [f20ba6e0 INF] Computed iteration 0, total is 0
    01:28:08 [f20ba6e0 INF] Computed iteration 1, total is 0
    01:28:08 [f20ba6e0 INF] Computed iteration 2, total is 0
    成功透過自訂 Enrich 新增 hashCode 解決此問題

    More Enrichers
    在前一篇 結構化日誌 Serilog - 配置設定 中介紹了基本的應用,在 Serilog 中提供很多實用的 Enrichers 方便開發者使用,像是積木的概念如果有遇到需要的就透過 nuget 套用即可十分方便,這裡在舉例自己在專案上有用到推薦的 Enrich 項目,舉例來說如果想要在每筆 Log 都記錄 MachineName、Env、Application與 RequestID 等資訊時,目的是在 log 中紀錄下圖的資訊
    在 Log 加入這些屬性的目的是為了在 ELK 搜尋時更容易分辨,如果當 Production 與 UAT 的 Log 都存放在同一台 ELK 時候,在查詢資料就可以透過 Environment 來分辨環境找到 Log 以及 Application,並自動在建立 Logger 時定義 MachineName 資訊,不用在 parser 時另外指定。

    加入 Application 與 Environment
    首先先從簡單的開始,分別在建立 Logger 定義 Enrich.WithProperty 屬性 Applicaion 與 Environment 並賦予相關值,在用 Json 格式輸出
    Log.Logger = new LoggerConfiguration()
        .Enrich.WithProperty("Application", "MyApplication")
        .Enrich.WithProperty("Environment", ConfigurationManager.AppSettings["Environment"])
        .WriteTo.File(new CompactJsonFormatter(), "log.txt")
        .CreateLogger();
    以上代碼輸出如下,很簡單的定義了輸出包含 ApplicationName 與 Environment 的 Log 資訊 
    {"@t":"2019-05-07T04:44:40.3369156Z","@mt":"Computed iteration {Counter}, total is {Total}","Counter":0,"Total":0,"Application":"e-Commerce","Environment":"Production"}

    加入 MachineName
    接著,再加上 MachineName,需要透過 nuget 安裝 Serilog.Enrichers.Environment  
    Install-Package Serilog.Enrichers.Environment -Version 2.1.3 
    並在建立 Logger 時加入 Enricher 指定 Machine 
    .Enrich.WithMachineName() 
    輸出的 Log 部分會加上 ServerName 
    {"@t":"2019-05-07T10:24:50.2274022Z","@mt":"Computed iteration {Counter}, total is {Total}","Counter":0,"Total":0,"Application":"MyApplication","Environment":"Production","MachineName":"ServerName"}

    加入 RequestID
    在 Serilog 中可以使用 SerilogWeb.Classic 紀錄 RequestId,使用方式很簡單透過 nuget 下載後使用 WithHttpRequestId 方法,就可以很輕鬆的達到紀錄 RequestId 的需求。在 Serilog 中有另外提供一個好用的物件 
     LogContext  可以讓所有的紀錄事件加入特定的屬性,使用方式是new LoggerConfiguration 加上 Enrich.FromLogContext() 擴充方法,再透過  LogContext.PushProperty  將特定的屬性以 key/value 方式加入紀錄事件中,代碼如下 Enrich.WithMachineName() 指定 Machine 
    var logger = new LoggerConfiguration()
        .Enrich.FromLogContext()
        .Enrich.WithProperty("Application", "MyApplication")
        .Enrich.WithProperty("Environment", "Production")
        .WriteTo.File(new CompactJsonFormatter(), "log.txt")
        .WriteTo.Console(outputTemplate:
            "{Timestamp:HH:mm:ss} [{EventType:x8} {Level:u3}] {Message:lj}{NewLine}{Exception}")
        .CreateLogger(); 
    
    var requestId = Guid.NewGuid();
    using (LogContext.PushProperty("RequestId", requestId, true))
    {
        // Middleware Invoke
        logger.Information("This is a book, not a pencil");
    }
    
    在代碼中透過 pushProperty 方式將 requestId 注入到 LogContent 中,值為隨機產生GUID其結果輸出如下
    {"@t":"2019-05-07T14:56:47.2892546Z","@mt":"This is a book, not a pencil","RequestId":"0e9eaf11-544f-414d-8091-76bb7797edea","Application":"MyApplication","Environment":"Production"} 
    可以看到 Log 順利紀錄了指定的 RequestId 資訊,如果在 ASP.NET Core 中止需要將產生 RequestID 的方式加到 MiddleWare 中的 Invoke 方法,即可順利地產生出 RequestID 資訊,這裡就不在細說
    另外在 LogContext 中也提供指定 source Type 方法
    var valueLog = Log.ForContext<valueController>(); 
    想要了解更細節的部份可以看作者所撰寫的 Context and correlation – structured logging concepts in .NET,相信可以對細節與應用情境有更深入的了解。以上介紹了幾種簡單的應用,官網也有列出 enricher package 項目,如果有興趣的也可以到 github 查看使用方式及使用。
    希望這篇的介紹對各位有幫助,Happy Coding :)

    參考
    Structured logging concepts in .NET Series
    Serilog

    2019年5月4日 星期六

    Copyright © m@rcus 學習筆記 | Powered by Blogger

    Design by Anders Noren | Blogger Theme by NewBloggerThemes.com