[料理佳餚] 自製 NLog 的 Target(以 Slack 的 Incoming WebHooks 為例)

使用 log4net 有好些年了,但是這個開源專案目前處於休眠的狀態,慢慢凋零了,所以決定將手上的專案轉使用 NLog,過去開發的幾個在 log4net 上的 Appender 現在必須要轉成 NLog 的 Target,我就以 Slack 的 Incoming WebHooks 為例,將它做成一個 NLog 的 Target。

繼承 TargetWithLayout 或 AsyncTaskTarget

我將我要做的 Target 命名為 SlackTarget,在這邊我們可以選擇繼承 TargetWithLayout 或是非同步版本的 AsyncTaskTarget,那我選擇繼承的是 AsyncTaskTarget,並且透過 TargetAttribute 設定 Target 的名稱。

[Target("Slack")]
public class SlackTarget : AsyncTaskTarget
{
    protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken)
    {
    }
}

參數設定

建立 Incoming WebHooks 的過程我就不說明了,請大家自行上網搜尋,建好 Incoming WebHooks 會得到一串 WebhookUrl,我們不可能在 Target 裡面將這個 WebhookUrl 寫死,一定是透過 NLog 的設定檔傳遞進來,Target 要接收從設定檔傳進來的參數,只需要建立一個相同名稱的公開屬性即可,不分大小寫,如果參數是必填的,則加上 RequiredParameterAttribute

[Target("Slack")]
public class SlackTarget : AsyncTaskTarget
{
    [RequiredParameter]
    public string WebhookUrl { get; set; }

    protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken)
    {
    }
}

補完傳送訊息的程式碼

Slack 的訊息是可以很豐富的,還能有互動,這個就留待大家針對自己的需求去實作了,我這邊的範例很陽春,單純只是傳送文字訊息到 Slack 上,頂多加上紅綠燈的顏色用來區分 LogLevel。

[Target("Slack")]
public class SlackTarget : AsyncTaskTarget
{
    [RequiredParameter]
    public string WebhookUrl { get; set; }

    protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken)
    {
        var hostName = Environment.MachineName;
        var appDomain = AppDomain.CurrentDomain;
        var renderedLog = this.RenderLogEvent(this.Layout, logEvent);

        var title = $"[{logEvent.Level.ToString().ToUpper()}] on {hostName}";

        var payload = JsonSerializer.Serialize(
            new
            {
                Username = appDomain.FriendlyName,
                Fallback = title,
                Color = GetColor(logEvent.Level),
                Fields = new[] { new { Title = title, Value = renderedLog, Short = false } }
            },
            new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

        var webhook = new Uri(this.WebhookUrl);

        var request = new HttpRequestMessage(HttpMethod.Post, webhook)
                      {
                          Content = new StringContent(
                              string.Concat("payload=", payload),
                              Encoding.UTF8,
                              "application/x-www-form-urlencoded")
                      };

        var httpClient = HttpClientFactory.Instance.CreateClient(new Uri($"{webhook.Scheme}{Uri.SchemeDelimiter}{webhook.Host}"));

        await httpClient.SendAsync(request, cancellationToken);
    }
}

調整設定檔

最後在設定檔的 <extensions> 加入 Target 所屬性的組件名稱,以及加入一個 xsi:type 為 Target Name 的 <target>

顯示在 Slack 上就長這樣:

 < Source Code >

 

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學