프로그래밍

ASP.NET CORE Serilog 설정 및 사용

itssue-host 2024. 10. 17. 12:45

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를 사용하는 것을 권장한다.

 

도움이 되길 바라며...