다음을 통해 공유


.NET의 고성능 로깅

.NET 6 이상 버전의 고성능 로깅 시나리오의 경우 LoggerMessageAttribute을 사용합니다. 이 방법은 런타임에 boxing, 임시 할당 및 메시지 템플릿 구문 분석을 제거하여 최상의 성능을 제공합니다.

원본 생성 로깅은 다음과 같은 로거 확장 메서드에 비해 다음과 같은 LogInformationLogDebug성능 이점을 제공합니다.

  • 박싱 제거: 로거 확장 메서드에서는 값 형식(예: int)을 object로 변환하는 "박싱"이 필요합니다. 소스 생성 로깅은 강력한 형식의 매개 변수를 사용하여 'boxing'을 방지합니다.
  • 컴파일 시간에 템플릿을 구문 분석합니다. 로거 확장 메서드는 로그 메시지를 쓸 때마다 메시지 템플릿(명명된 형식 문자열)을 구문 분석해야 합니다. 소스 생성 로깅은 컴파일 시간에 템플릿을 한 번 구문 분석합니다.
  • 할당을 줄입니다. 원본 생성기는 개체 할당 및 임시 메모리 사용을 최소화하는 최적화된 코드를 만듭니다.

샘플 앱은 우선 순위 큐 처리 작업자 서비스를 사용하는 고성능 로깅 기능을 보여 줍니다. 앱은 작업 항목을 우선 순위에 따라 처리합니다. 이러한 작업이 수행되면 원본에서 생성된 로깅을 사용하여 로그 메시지가 생성됩니다.

팁 (조언)

모든 로깅 예제 소스 코드는 샘플 브라우저에서 다운로드할 수 있습니다. 자세한 내용은 코드 샘플 찾아보기: .NET의 로깅을 참조하세요.

원본 생성을 사용하여 로거 메시지 정의

.NET 6 이상에서는 partial로 명시된 데코레이터 메서드를 LoggerMessageAttribute하여 고성능 로그 메시지를 생성합니다. 소스 생성기는 컴파일 시간에 구현을 만듭니다.

기본 로깅 방법

간단한 로그 메시지의 경우 이벤트 ID, 로그 수준 및 메시지 템플릿을 지정하는 특성을 사용하여 부분 메서드를 정의합니다.

public static partial class Log
{
    [LoggerMessage(
        EventId = 13,
        Level = LogLevel.Critical,
        Message = "Epic failure processing item!")]
    public static partial void FailedToProcessWorkItem(
        ILogger logger, Exception ex);
}

메시지 템플릿은 메서드 매개 변수로 채워진 자리 표시자를 사용합니다. 자리 표시자 이름은 템플릿 전체에서 설명적이고 일관적이어야 합니다. 구조적 로그 데이터 내에서 속성 이름 역할을 합니다. 자리 표시자 이름에는 파스칼 표기법을 사용하는 것이 좋습니다. 예: {Item}, {DateTime}

코드에서 로깅 메서드를 호출합니다. 예를 들어 작업 항목 처리 중에 예외가 발생하는 경우:

try
{
    // Process work item.
}
catch (Exception ex)
{
    Log.FailedToProcessWorkItem(logger, ex);
}

이 코드는 다음과 같은 콘솔 출력을 생성합니다.

crit: WorkerServiceOptions.Example.Worker[13]
      Epic failure processing item!
      System.Exception: Failed to verify communications.

매개 변수를 사용하여 로깅

로그 메시지에 매개 변수를 전달하려면 메서드 매개 변수로 추가합니다. 매개 변수 이름은 메시지 템플릿의 자리 표시자와 일치합니다.

public static partial class Log
{
    [LoggerMessage(
        EventId = 1,
        Level = LogLevel.Information,
        Message = "Processing priority item: {Item}")]
    public static partial void PriorityItemProcessed(
        ILogger logger, WorkItem item);
}

로거 및 매개 변수 값을 사용하여 메서드를 호출합니다.

var workItem = queue.Dequeue();
Log.PriorityItemProcessed(logger, workItem);

이 코드는 다음과 같은 콘솔 출력을 생성합니다.

info: WorkerServiceOptions.Example.Worker[1]
      Processing priority item: Priority-Extreme (50db062a-9732-4418-936d-110549ad79e4): 'Verify communications'

구조적 로깅 저장소는 이벤트 ID와 함께 제공될 때 이벤트 이름을 사용하여 로깅을 보강할 수 있습니다. 예를 들어 Serilog 는 이벤트 이름을 사용합니다.

원본 생성을 사용하여 로거 메시지 범위 정의

로그 범위를 정의하여 일련의 로그 메시지를 추가 컨텍스트로 래핑할 수 있습니다. 소스 생성 로깅을 사용하여 LoggerMessageAttribute 메서드를 표준 ILogger.BeginScope 메서드와 결합합니다.

appsettings.json콘솔 로거 섹션에서 IncludeScopes.

{
    "Logging": {
        "Console": {
            "IncludeScopes": true
        },
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    }
}

다음 BeginScope을 사용하여 소스 생성 로깅 메서드를 만들고 범위 안에서 래핑합니다.

public static partial class Log
{
    [LoggerMessage(
        EventId = 1,
        Level = LogLevel.Information,
        Message = "Processing priority item: {Item}")]
    public static partial void PriorityItemProcessed(
        ILogger logger, WorkItem item);
}

애플리케이션 코드의 범위 내에서 로깅 메서드를 사용합니다.

using (_logger.BeginScope("Processing scope, started at: {DateTime}", DateTime.Now))
{
    Log.PriorityItemProcessed(_logger, workItem);
}

앱의 콘솔 출력에서 로그 메시지를 검사합니다. 다음 결과는 로그 범위 메시지가 포함된 로그 메시지의 우선 순위 순서를 보여줍니다.

info: WorkerServiceOptions.Example.Worker[1]
      => Processing scope, started at: 04/11/2024 11:27:52
      Processing priority item: Priority-Extreme (7d153ef9-8894-4282-836a-8e5e38319fb3): 'Verify communications'

레거시 접근 방식: LoggerMessage.Define(.NET Framework 및 .NET Core 3.1용)

.NET 6에서 원본 생성 로깅이 도입되기 전에 권장되는 고성능 로깅 방법은 메서드를 사용하여 LoggerMessage.Define 캐시 가능한 대리자를 만드는 것이었습니다. 이 방법은 이전 버전과의 호환성을 위해 계속 지원되지만 새 코드는 소스 생성 로깅 LoggerMessageAttribute 을 대신 사용해야 합니다.

클래스는 LoggerMessage로거 확장 메서드(예: LogInformation, LogDebug)에 비해 개체 할당이 적고 계산 오버헤드가 줄어드는 캐시 가능한 대리자를 생성할 수 있는 기능을 제공합니다. LoggerMessage 로거 확장 메서드에 비해 다음과 같은 성능 이점을 제공합니다.

  • 로거 확장 메서드는 int에 대한 object와 같은 "boxing"(변환) 값 형식이 필요합니다. LoggerMessage 패턴은 강력한 형식의 매개 변수가 있는 정적 Action 필드 및 확장 메서드를 사용하여 boxing을 방지합니다.
  • 로거 확장 메서드는 로그 메시지가 기록될 때마다 메시지 템플릿(명명된 형식 문자열)을 구문 분석해야 합니다. LoggerMessage는 메시지가 정의될 때 템플릿 구문 분석이 한번만 필요합니다.

비고

사용하는 LoggerMessage.Define코드를 유지 관리하는 경우 원본 생성 로깅으로 마이그레이션하는 것이 좋습니다. .NET Framework 또는 .NET Core 3.1 애플리케이션의 경우 LoggerMessage.Define를 계속 사용하십시오.

로거 메시지 정의

Define(LogLevel, EventId, String)를 사용하여 메시지 로깅을 위한 대리자를 Action 만듭니다. Define 오버로드는 명명된 형식 문자열(템플릿)에 최대 6개의 형식 매개 변수를 전달할 수 있습니다.

메서드에 Define 제공된 문자열은 보간된 문자열이 아니라 템플릿입니다. 자리 표시자는 형식이 지정된 순서대로 채워집니다. 템플릿의 자리 표시자 이름은 템플릿 전체에서 설명적이고 일관적이어야 합니다. 구조적 로그 데이터 내에서 속성 이름 역할을 합니다. 자리 표시자 이름에는 파스칼 표기법을 사용하는 것이 좋습니다. 예: {Item}, {DateTime}

각 로그 메시지는 ActionLoggerMessage.Define에서 만든 정적 필드에 저장됩니다. 예를 들어 샘플 앱은 작업 항목 처리에 대한 로그 메시지를 설명하는 필드를 만듭니다.

private static readonly Action<ILogger, Exception> s_failedToProcessWorkItem;

Action을(를) 지정합니다.

  • 로그 수준입니다.
  • 정적 확장 메서드의 이름을 가진 고유 이벤트 식별자(EventId)입니다.
  • 메시지 템플릿(명명된 형식 문자열)입니다.

작업 항목이 처리를 위해 큐에서 해제되면 작업자 서비스 앱은 다음을 설정합니다.

  • 로그 수준을 LogLevel.Critical로 설정합니다.
  • 13의 이벤트 ID를 FailedToProcessWorkItem 메서드의 이름으로 사용합니다.
  • 문자열에 대한 메시지 템플릿(명명된 형식 문자열)입니다.
s_failedToProcessWorkItem = LoggerMessage.Define(
    LogLevel.Critical,
    new EventId(13, nameof(FailedToProcessWorkItem)),
    "Epic failure processing item!");

LoggerMessage.Define 메서드는 로그 메시지를 나타내는 대리자를 Action 구성하고 정의하는 데 사용됩니다.

구조적 로깅 저장소는 이벤트 ID와 함께 제공될 때 이벤트 이름을 사용하여 로깅을 보강할 수 있습니다. 예를 들어 Serilog 는 이벤트 이름을 사용합니다.

강력히 형식화된 Action 확장 메서드를 통해 호출됩니다. 이 메서드는 PriorityItemProcessed 작업 항목이 처리될 때마다 메시지를 기록합니다. FailedToProcessWorkItem 은 예외가 발생할 경우 호출됩니다.

protected override async Task ExecuteAsync(
    CancellationToken stoppingToken)
{
    using (IDisposable? scope = logger.ProcessingWorkScope(DateTime.Now))
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                WorkItem? nextItem = priorityQueue.ProcessNextHighestPriority();

                if (nextItem is not null)
                {
                    logger.PriorityItemProcessed(nextItem);
                }
            }
            catch (Exception ex)
            {
                logger.FailedToProcessWorkItem(ex);
            }

            await Task.Delay(1_000, stoppingToken);
        }
    }
}

앱의 콘솔 출력을 검사합니다.

crit: WorkerServiceOptions.Example.Worker[13]
      Epic failure processing item!
      System.Exception: Failed to verify communications.
         at WorkerServiceOptions.Example.Worker.ExecuteAsync(CancellationToken stoppingToken) in
         ..\Worker.cs:line 27

로그 메시지에 매개 변수를 전달하려면 정적 필드를 만들 때 최대 6개의 형식을 정의합니다. 샘플 앱은 필드에 대한 WorkItem 형식을 정의함으로써 Action 항목을 처리할 때 작업 항목 세부 정보를 기록합니다.

private static readonly Action<ILogger, WorkItem, Exception> s_processingPriorityItem;

대리자의 로그 메시지 템플릿은 제공된 형식에서 자리 표시자 값을 받습니다. 샘플 앱은 항목 매개 변수가 다음과 같은 작업 항목을 추가하기 위한 대리자를 WorkItem정의합니다.

s_processingPriorityItem = LoggerMessage.Define<WorkItem>(
    LogLevel.Information,
    new EventId(1, nameof(PriorityItemProcessed)),
    "Processing priority item: {Item}");

작업 항목이 처리 PriorityItemProcessed되고 있는 로깅에 대한 정적 확장 메서드는 작업 항목 인수 값을 수신하고 대리자에게 Action 전달합니다.

public static void PriorityItemProcessed(
    this ILogger logger, WorkItem workItem) =>
    s_processingPriorityItem(logger, workItem, default!);

작업자 서비스의 ExecuteAsync 메서드 PriorityItemProcessed 에서 메시지를 기록하기 위해 호출됩니다.

protected override async Task ExecuteAsync(
    CancellationToken stoppingToken)
{
    using (IDisposable? scope = logger.ProcessingWorkScope(DateTime.Now))
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                WorkItem? nextItem = priorityQueue.ProcessNextHighestPriority();

                if (nextItem is not null)
                {
                    logger.PriorityItemProcessed(nextItem);
                }
            }
            catch (Exception ex)
            {
                logger.FailedToProcessWorkItem(ex);
            }

            await Task.Delay(1_000, stoppingToken);
        }
    }
}

앱의 콘솔 출력을 검사합니다.

info: WorkerServiceOptions.Example.Worker[1]
      Processing priority item: Priority-Extreme (50db062a-9732-4418-936d-110549ad79e4): 'Verify communications'

로그 수준 보호 최적화

해당 Log* 메서드를 호출하기 전에 LogLevelILogger.IsEnabled(LogLevel)를 확인하여 성능을 최적화할 수 있습니다. 지정된 LogLevel에 대해 로깅이 구성되지 않으면 ILogger.Log이(가) 호출되지 않습니다. 또한 값 형식 boxing과 매개 변수를 나타내기 위한 object[]의 할당이 방지됩니다.

자세한 내용은 다음을 참조하세요.

참고하십시오