.NET 라이브러리 작성자를 위한 로깅 지침

라이브러리 작성자로서 로깅을 노출하는 것은 소비자에게 라이브러리의 내부 작동에 대한 인사이트를 제공할 수 있는 좋은 방법입니다. 이 지침은 다른 .NET 라이브러리 및 프레임워크와 일관된 방식으로 로깅을 노출하는 데 도움이 됩니다. 또한 다른 방법으로는 명확하지 않을 수 있는 일반적인 성능 병목 현상을 방지하는 데도 도움이 됩니다.

ILoggerFactory 인터페이스를 사용하는 경우

로그를 내보내는 라이브러리를 작성할 때 로그를 기록하려면 ILogger 개체가 필요합니다. 해당 개체를 가져오기 위해 API는 ILogger<TCategoryName> 매개 변수를 허용하거나 ILoggerFactory를 허용한 후 ILoggerFactory.CreateLogger를 호출할 수 있습니다. 어떤 방식이 기본 설정되어야 하나요?

  • 모든 클래스가 로그를 내보낼 수 있도록 여러 클래스에 전달할 수 있는 로깅 개체가 필요한 경우 ILoggerFactory를 사용합니다. 각 클래스는 클래스와 동일한 이름의 별도 범주로 로그를 만드는 것이 좋습니다. 이렇게 하려면 로그를 내보내는 각 클래스에 대해 고유한 ILogger<TCategoryName> 개체를 만드는 팩터리가 필요합니다. 일반적인 예로는 라이브러리에 대한 공용 진입점 API 또는 내부적으로 도우미 클래스를 만들 수 있는 형식의 공용 생성자가 있습니다.

  • 하나의 클래스 내에서만 사용되며 절대 공유되지 않는 로깅 개체가 필요한 경우 ILogger<TCategoryName>을 사용합니다. 여기서 TCategoryName은 로그를 생성하는 형식입니다. 이에 대한 일반적인 예는 종속성 삽입으로 만들어진 클래스의 생성자입니다.

시간이 지나도 안정적으로 유지되어야 하는 공용 API를 설계하는 경우 나중에 내부 구현을 리팩터링하고 싶을 수도 있다는 점을 유념해야 합니다. 클래스가 처음에 내부 도우미 형식을 만들지 않더라도 코드가 발전함에 따라 변경될 수 있습니다. ILoggerFactory를 사용하면 공용 API를 변경하지 않고도 새 클래스에 대한 새 ILogger<TCategoryName> 개체를 만들 수 있습니다.

자세한 내용은 필터링 규칙 적용 방식을 참조하세요.

원본 생성 로깅을 선호합니다.

ILogger API는 API 사용에 대한 두 가지 방식을 지원합니다. LoggerExtensions.LogErrorLoggerExtensions.LogInformation과 같은 메서드를 호출하거나 로깅 원본 생성기를 사용하여 강력한 형식의 로깅 메서드를 정의할 수 있습니다. 대부분의 상황에서는 뛰어난 성능과 강력한 타이핑 기능을 제공하는 원본 생성기를 사용하는 것이 좋습니다. 또한 호출 코드에서 메시지 템플릿, ID, 로그 수준과 같은 로깅 관련 문제를 격리합니다. 원본 생성되지 않은 방식은 코드를 더 간결하게 만들기 위해 이러한 이점을 기꺼이 포기하려는 시나리오에 주로 유용합니다.

using Microsoft.Extensions.Logging;

namespace Logging.LibraryAuthors;

internal static partial class LogMessages
{
    [LoggerMessage(
        Message = "Sold {Quantity} of {Description}",
        Level = LogLevel.Information)]
    internal static partial void LogProductSaleDetails(
        this ILogger logger,
        int quantity,
        string description);
}

앞의 코드가 하는 역할은 다음과 같습니다.

  • LogMessages라는 partial class를 정의합니다. 이는 ILogger 형식에 대한 확장 메서드를 정의하는 데 사용할 수 있도록 static입니다.
  • LoggerMessage 특성과 Message 템플릿을 사용하여 LogProductSaleDetails 확장 메서드를 장식합니다.
  • ILogger를 확장하고 quantitydescription을 허용하는 LogProductSaleDetails를 선언합니다.

원본 생성 코드는 이를 호출하는 코드와 동일한 어셈블리의 일부이기 때문에 디버깅 중에 원본 생성 코드로 한 단계씩 이동할 수 있습니다.

비용이 많이 드는 매개 변수 평가를 방지하려면 IsEnabled를 사용합니다.

매개 변수를 평가하는 데 비용이 많이 드는 상황이 있을 수 있습니다. 이전 예를 확장하여 description 매개 변수가 컴퓨팅 비용이 많이 드는 string이라고 상상해 보세요. 아마도 판매되는 제품은 친숙한 제품 설명을 가져오고 데이터베이스 쿼리에 의존하거나 파일에서 읽는 것일 수 있습니다. 이러한 상황에서는 원본 생성기에 IsEnabled 가드를 건너뛰고 호출 사이트에 IsEnabled 가드를 수동으로 추가하도록 지시할 수 있습니다. 이를 통해 사용자는 가드가 호출되는 위치를 결정할 수 있으며 컴퓨팅 비용이 많이 드는 매개 변수가 실제로 필요할 때만 평가되도록 할 수 있습니다. 다음 코드를 생각해 봅시다.

using Microsoft.Extensions.Logging;

namespace Logging.LibraryAuthors;

internal static partial class LogMessages
{
    [LoggerMessage(
        Message = "Sold {Quantity} of {Description}",
        Level = LogLevel.Information,
        SkipEnabledCheck = true)]
    internal static partial void LogProductSaleDetails(
        this ILogger logger,
        int quantity,
        string description);
}

LogProductSaleDetails 확장 메서드가 호출되면 IsEnabled 가드가 수동으로 호출되고 비용이 많이 드는 매개 변수 평가가 필요할 때만 제한됩니다. 다음 코드를 생각해 봅시다.

if (_logger.IsEnabled(LogLevel.Information))
{
    // Expensive parameter evaluation
    var description = product.GetFriendlyProductDescription();

    _logger.LogProductSaleDetails(
        quantity,
        description);
}

자세한 내용은 컴파일 시간 로깅 원본 생성.NET의 고성능 로깅을 참조하세요.

로깅 시 문자열 보간 방지

흔히 저지르는 실수는 문자열 보간을 사용하여 로그 메시지를 빌드하는 것입니다. 해당 LogLevel이 사용하도록 설정되지 않은 경우에도 문자열이 평가되므로 로깅의 문자열 보간은 성능에 문제가 있습니다. 문자열 보간 대신 로그 메시지 템플릿, 서식 지정 및 인수 목록을 사용합니다. 자세한 내용은 .NET에 로그인: 로그 메시지 템플릿을 참조하세요.

무작동 로깅 기본값 사용

로거를 제공하지 않으려는 로깅 API를 노출하는 ILoggerILoggerFactory라이브러리를 사용하는 경우가 있을 수 있습니다. 이러한 경우 Microsoft.Extensions.Logging.Abstractions NuGet 패키지는 no-op 로깅 기본값을 제공합니다.

라이브러리 소비자는 제공된 항목이 없는 ILoggerFactory 경우 기본적으로 null 로깅을 사용할 수 있습니다. null 로깅 사용은 형식이 null이 아니기 때문에 형식을 null 허용(ILoggerFactory?)으로 정의하는 것과 다릅니다. 이러한 편의 기반 형식은 아무 것도 기록하지 않으며 기본적으로 작동하지 않습니다. 해당되는 경우 사용 가능한 추상화 형식 중 하나를 사용하는 것이 좋습니다.