使用ServiceSelf解決.NET應用程式做服務的難題

jiulang發表於2023-04-23

1 ServiceSelf

.NET 泛型主機的應用程式提供自安裝為服務程式的能力,支援windows和linux平臺。

功能

  • 自我服務安裝
  • 自我服務解除安裝
  • 自我服務日誌監聽

2 自我服務安裝

雖然.NetCore提供了Microsoft.Extensions.Hosting.SystemdMicrosoft.Extensions.Hosting.WindowsServices兩個服務生命週期包,但在服務安裝這塊目前還非常不便:在windows平臺,需要管理員身份使用sc.exe工具來安裝服務;在linux平臺,需要自己手動寫服務單元檔案和使用systemctl載入服務。不常用的sc和服務單元檔案的內容知識,就像學了外語之後又長期不用外語的我們一樣,時間一久就忘記。而且windows服務程式的預設工作目錄是%SystemRoot%\System32,在沒有日誌元件的幫助下,sc.exe安裝的服務在執行後我們可能就掉到工作目錄的坑裡,影響包括但不限於配置檔案的讀取、asp.netcore的ContentRoot、wwwroot靜態檔案等。

ServiceSelf提供自我服務安裝的能力,它提供了windows服務和linux的systemd服務的公共參,同時另外提供windows獨有的服務配置和systemd獨有的完整服務配置,此外還解決了windows服務沒有工作目錄配置的缺陷。

現在,你可以在使用ServiceSelf描述服務:

var serviceName = "myapp";
var serviceOptions = new ServiceOptions
{
    Arguments = new[] { new Argument("key", "value") },
    Description = "這是演示示例應用",
};
serviceOptions.Linux.Service.Restart = "always";
serviceOptions.Linux.Service.RestartSec = "10";
serviceOptions.Windows.DisplayName = "演示示例";
serviceOptions.Windows.FailureActionType = WindowsServiceActionType.Restart;

// serviceName和serviceOptions甚至可以為null
if (Service.UseServiceSelf(args, serviceName, serviceOptions))
{
    var host = Host.CreateDefaultBuilder(args)
        // 為Host配置UseServiceSelf()
        .UseServiceSelf()                     
        .Build();

    host.Run();
}

然後在控制檯下以管理員或root身份執行如下命令:

./myapp start // 安裝並啟動服務

3 自我服務解除安裝

在控制檯下以管理員或root身份執行如下命令:

./myapp stop // 停止並刪除服務

4 自我服務日誌監聽

雖然有檔案日誌、大型的日誌採集平臺或框架等,但他們也取代不了控制檯實時顯示的日誌,相反他們是互補的。控制檯模式啟動時,我們很容易直接在控制檯看到實時日誌的列印,但安裝為服務後,檢視控制檯日誌變得不容易或無法實現,在linux平臺有journalctl,它是基於管道的,它無法知道一條日誌內容的邊界,很難把符合過濾特徵的日誌完整顯示;windows平臺有session隔離機制,服務程式和桌面使用者程式不在同一個session,所以桌面使用者看不到服務程式的控制檯,也沒有管道可以重定向來讀取服務程式的控制輸出。

ServiceSelf為服務程式整合了"自研的"的基於管道傳輸的Google.Protobuf結構化日誌提供者,在監聽者開啟監聽之後,這個日誌提供者才會工作,把結構化的日誌傳輸給監聽者,監聽者可以使用關鍵詞來過濾得到完整的一條結構化日誌,而不是隻過濾得一條日誌內容的某一行或幾行,再把完整的結構化日誌列印到監聽者的Console上。也就是它不會在服務程式上讓日誌無腦地輸出到序列化輸出的低效能控制檯,也不會讓服務程式在沒有監聽者的情況下無腦的輸出Google.Protobuf結構化日誌,即這個日誌元件對服務程式沒有效能影響。

之所以要自己實現基於管道傳輸的Google.Protobuf結構化日誌提供者,而不直接使用Microsoft的EventSourceLoggerProvider,是因為跨程式讀取日誌時需要依賴Microsoft.Diagnostics.Tracing.TraceEvent,這個包非常大而全,其依賴項也特別多,而我們僅僅日誌這一小功能而已。

由於監聽者與服務程式是同一個應用程式的不同程式,當應用程式的OutputType是WinExe模式且執行在windows時,這時候是沒有Console的,ServiceSelf做為監聽者角色時會檢測和動態建立Console然後將日誌輸出到Console。

現在輸入logs子命令,就在Console上輸出服務程式的實時日誌:

./myapp logs // 控制檯輸出服務的日誌
./myapp logs filter="key words" // 控制檯輸出匹配了"key words"的服務的日誌

5 後記

ServiceSelf在api設計上十分精煉,你只要關注Service.UseServiceSelf()IHostBuilder.UseServiceSelf()兩個函式即可,但可以為你的服務程式提供非常完整的解決方案,您可以到 github上關注此專案。

相關文章