Log套件 - Serilog(on Linux Ubuntu)

寫Web API時最討厭遇到500 (Internal Server Error)這種後端程式錯誤了,只返回一個Error Responese,c#的catch也抓不到錯誤訊息。由於不是本機環境,也沒有辦法下中斷點Debug,所以也不知道該如何除錯,這時候就知道紀錄後端Log的重要性了。

之前使用的原本是ElmahCore,但爬文後,發現Serilog的使用率比較高,相關資料也比較多。

NuGet套件安裝

Serilog.AspNetCore


註冊Serilog服務

Program.cs

var builder = WebApplication.CreateBuilder(args);
var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .CreateLogger();

builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);

設定檔設定
  • Using:要引用的套件(ex:如果要產出log檔,就要在Using加入Serilog.Sinks.File, 想要Console顯示就要加入Serilog.Sinks.Console),我們透過NuGet安裝的Serilog.AspNetCore,裡面已經有包含了,所以不用再額外安裝Serilog.Sinks.File跟Serilog.Sinks.Console.
  • MinimumLevel:Log的最小紀錄層級。假如我們層級設定Information的話,那就會記錄Information. Warning. Error. Fatal層級的訊息。
    • Verbose
    • Debug
    • Informatin
    • Warning
    • Error
    • Fatal
  • WriteTo:Log的紀錄方式

  更多屬性設定可以參考: How To Implement Serilog In ASP.NET Core Web API

appsettings.json

  "Serilog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information"
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "./logs/webapi-.log",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {CorrelationId} {Level:u3} {Username} {Message:lj}{Exception}{NewLine}"
        }
      }
    ]
  },

除了直接寫在appsettings.json裡面,也可以自己另外新增一個json設定檔,並且在Program.cs註冊服務時,透過AddJsonFile(xxxx.json)這個方法來讀取設定檔。

var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(new ConfigurationBuilder()
    .AddJsonFile("seri-log.config.json")
    .Build())
    .Enrich.FromLogContext()
    .CreateLogger();

注入Serilog服務

由於我用的是WebAPI框架,所以我們由最外層的Ctonller注入Serilog。

        private readonly ILogger<StockController> _logger;

        public StockController(

            ILogger<StockController> logger
            )
        {

            _logger = logger;
        }

注入後就可以在Action裡面新增Log了

_logger.LogInformation("Serilog Test.");

[Trouble Shooting] 在Linux環境下,沒有建立logs資料夾

由於我的Stg環境是建立在Linux Server上,所以我原本就預期可能會有問題,果然執行API後,發現logs資料夾(手動新增)裡面還是一樣都沒有東西。

感覺應該是權限的問題,於是連線到Linux看一下資料夾目前的權限設定。可以看到擁有者是sftp_user2,但我們程式service的預設owner是www-data,

sudo ls -al /var/www/html/StockBuyingHelper_Stg

決定把目前的logs資料夾刪除後,再重新新增一次。並把logs資料夾的權限設定為www-data(這是一個system user帳號,詳細請參考:檔案權限, www-data,SUID, SGID, SBIT)

mkdir /var/www/html/StockBuyingHelper_Stg/logs
chown www-data:www-data /var/www/html/StockBuyingHelper_Stg/logs
chmod 755 /var/www/html/StockBuyingHelper_Stg/logs

再重新restart一次在Linux上的Service,log檔就成功的產出了!


log檢視

透過Serilog紀錄的後端log,終於找到bug了,看來是某個dll出現了問題,沒有被複製到發布的資料夾裡面,導致遠端的程式出錯。

2024-02-27 03:12:02.931 +00:00  ERR  Connection id "0HN1N87DLUF8L", Request id "0HN1N87DLUF8L:00000001": An unhandled exception was thrown by the application.System.IO.FileNotFoundException: Could not load file or assembly 'AngleSharp, Version=1.0.7.0, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea'. The system cannot find the file specified.

File name: 'AngleSharp, Version=1.0.7.0, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea'
   at System.Reflection.CustomAttribute._CreateCaObject(RuntimeModule pModule, RuntimeType type, IRuntimeMethodInfo pCtor, Byte** ppBlob, Byte* pEndBlob, Int32* pcNamedArgs)
   at System.Reflection.CustomAttribute.AddCustomAttributes(ListBuilder`1& attributes, RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder`1 derivedAttributes)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType)
   at System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeMethodInfo method, RuntimeType caType, Boolean inherit)
   at System.Attribute.GetCustomAttributes(MemberInfo element, Type attributeType, Boolean inherit)
   at System.Diagnostics.StackTrace.TryResolveStateMachineMethod(MethodBase& method, Type& declaringType)
   at System.Diagnostics.StackTrace.ToString(TraceFormat traceFormat, StringBuilder sb)
   at System.Diagnostics.StackTrace.ToString(TraceFormat traceFormat)
   at System.Exception.get_StackTrace()
   at System.IO.FileNotFoundException.ToString()
   at StockBuingHelper.Web.Controllers.StockController.GetVtiData(ResGetVtiDataDto reqData) in D:\Homer\Project_MyGit\StockBuyingHelper\StockBuingHelper.Web\Controllers\StockController.cs:line 181
   at lambda_method4(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

2024-02-27 03:12:02.936 +00:00  INF  Request finished HTTP/1.1 POST http://---.---.---.--/api/Stock/GetVtiData application/json 55 - 500 0 - 436.6184ms

跟原本的500 (Internal Server Error)的內容是不是差很多啊~


Ref: