ASP.NET CORE Serilog 설정 및 사용
ASP.NET Core에서 Serilog를 사용하여 logging 하는 방법에 대해 알아보자.
먼저 Serilog 관련 패키지를 설치해야 한다.
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageReference Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="10.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
Enrichers는 로깅 부분에 기록될 사항중
EnvironmentName
ProcessId
ThreadId
등을 기록할 때 사용된다.
Sinks는 출력 부분으로 각각 Console, Debug, File 등을 의미한다.
설정 코드를 보자.
builder.Host.UseSerilog((context, services, config) =>
{
config.ReadFrom.Configuration(context.Configuration);
});
위 코드는 appsettings.json 또는 appsettings.Development.json 파일을 읽어 Key가 "Serilog"인 부분이 적용되겠다.
그렇다면 json 파일의 설정을 보자.
"Serilog": {
"Using": [
"Serilog.Enrichers.Environment",
"Serilog.Enrichers.Process",
"Serilog.Enrichers.Thread",
"Serilog.Sinks.ElasticSearch"
],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information",
"System": "Information",
"Hangfire": "Warning",
"Microsoft.EntityFrameworkCore": "Debug",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj} {NewLine}{Properties:j}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "Logs/log.txt",
"rollingInterval": "Day",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj} {NewLine}{Properties:j}{NewLine}{Exception}"
}
},
{
"Name": "Elasticsearch",
"Args": {
"nodeUris": "[url]",
"indexFormat": "[index 형식 지정]",
"templateName": "[template 명]",
"inlineFields": true,
"autoRegisterTemplate": true,
"customFormatter": "Serilog.Formatting.Elasticsearch.ExceptionAsObjectJsonFormatter, Serilog.Formatting.Elasticsearch"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithEnvironmentName",
"WithProcessId",
"WithThreadId"
],
"Properties": {
"Application": "Demo.Api"
}
},
위와 같이 되겠다.
WriteTo 부분을 보면 Console, File, Elasticsearch를 지원하도록 되어 있다.
console과 file은 outputTemplate에 따라 기록되고 elasticsearch의 경우 서비스에 별도의 형식을 지정해야 할 수도 있다.
( elasticsearch는 데모 만료로 확인이 불가했다.)
위와 같이 설정하였다면 이제 콘솔에 로깅 문자가 보일 것이다.
기본적으로 serilog는 구조화 로깅으로 프로그램에서 설정 내역에 따라 출력을 확인할 수 있다.
(기본 동작 로그 및 라이프 사이클에 따른 로그도 확인 할 수 있다.)
또한 Microsoft.Extensions.Logging을 사용하여 기본 로깅을 처리할 수 있다.
예제를 보자.
public abstract class BackgroundServiceBase<TSelf>(ILogger<TSelf> logger) : BackgroundService
where TSelf : class
{
protected ILogger Logger = logger;
protected string SelfName = typeof(TSelf).Name;
}
public abstract class BackgroundServiceBase<TSelf, TRequest>(
ILogger<TSelf> logger,
IServiceScopeFactory serviceScopeFactory,
int interval = 500) : BackgroundServiceBase<TSelf>(logger) where TSelf : class
where TRequest : class
{
private readonly IServiceScopeFactory _serviceScopeFactory = serviceScopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
if (Logger.IsEnabled(LogLevel.Information))
{
Logger.LogInformation("{name} Worker running at: {time}", this.SelfName, DateTimeOffset.Now);
}
try
{
var item = await ExecuteProduceAsync(stoppingToken);
if (item.xIsNotEmpty())
{
await ExecuteConsumeAsync(item, stoppingToken);
}
}
catch (Exception e)
{
Logger.LogError(e, "{name} Worker Error: {message}", this.SelfName, e.Message);
}
await Task.Delay(interval, stoppingToken);
}
}
protected abstract Task<TRequest> ExecuteProduceAsync(CancellationToken cancellationToken);
protected abstract Task ExecuteConsumeAsync(TRequest request, CancellationToken stoppingToken);
}
위 코드는 BackgroundService를 상속하여 Custom한 코드이다.
여기서 ILogger를 상속하여 처리하게 되는데, 기본적으로 Microsoft.Exetions.Logging을 사용하고 있다.
필자는 이전에 Serilog.Logger 인스턴스를 상속받는 방식으로 개발하였고 이는 틀린 방법이므로 꼭 위와 같이 선언하여 사용하길 바란다.
출력 결과는 아래와 같다.
{"SourceContext":"Demo.Kafka.Consumer.Workers.MetadataWorker","MachineName":"DESKTOP-xxxxxx","EnvironmentName":"Development","ProcessId":143692,"ThreadId":21,"Application":"Demo.Kafka.Consumer"}
[2024-10-10 15:28:23.028 +09:00 INF] MetadataWorker Worker running at: "2024-10-10T15:28:23.0282896+09:00"
{"SourceContext":"Demo.Kafka.Consumer.Workers.MetadataWorker","MachineName":"DESKTOP-xxxxxx","EnvironmentName":"Development","ProcessId":143692,"ThreadId":21,"Application":"Demo.Kafka.Consumer"}
[2024-10-10 15:28:23.545 +09:00 INF] MetadataWorker Worker running at: "2024-10-10T15:28:23.5456503+09:00"
위와 같은 출력 결과를 볼 수 있을 것이다.
만약 Serilog에 추가적 속성 선언이 필요하다면 Middleware나 Filter에 아래와 같이 할 수 있다.
using (LogContext.PushProperty("[선언명]", "[값]"))
{
await next();
}
위와 같이 할 경우 로깅에서 선언명 속성이 추가된 것을 확인 할 수 있다.
구조화 로깅이 필요하다면 Serilog를 사용하자.
운영 배포 로깅을 한다면 elasticsearch를 사용하는 것을 권장한다.
도움이 되길 바라며...