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

2023-04-23 15:00:43

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上關注此專案。