寫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: