C# 및 .NET에서 로깅

.NET은 ILogger API를 통해 고성능의 구조화된 로깅을 지원하여 애플리케이션 동작을 모니터링하고 문제를 진단하는 데 도움을 줍니다. 다른 로깅 공급자를 구성하여 다른 대상에 로그를 기록할 수 있습니다. 기본 로깅 공급자는 기본으로 제공되며 타사 공급자도 많이 있습니다.

시작하기

이 첫 번째 예제에서는 기본 사항을 보여 주지만 간단한 콘솔 앱에만 적합합니다. 다음 섹션에서는 규모, 성능, 구성 및 일반적인 프로그래밍 패턴을 고려하여 코드를 개선하는 방법을 알아봅니다.

using Microsoft.Extensions.Logging;

using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

위의 예제는 다음과 같습니다.

  • ILoggerFactory를 만듭니다. ILoggerFactory에는 로그 메시지가 전송되는 위치를 결정하는 모든 구성이 저장됩니다. 이 경우 로그 메시지가 콘솔에 기록되도록 콘솔 로깅 공급자를 구성합니다.
  • "Program"이라는 범주를 사용하여 ILogger을(를) 만듭니다. 범주ILogger 객체가 로깅한 각 메시지와 연관된 string입니다. 이는 로그를 검색하거나 필터링할 때 동일한 클래스(또는 범주)의 로그 메시지를 그룹화하는 데 사용됩니다.
  • LogInformation을 호출하여 Information 수준에 메시지를 로그합니다. 로그 수준은 기록된 이벤트의 심각도를 나타내며 덜 중요한 로그 메시지를 필터링하는 데 사용됩니다. 로그 항목에는 메시지 템플릿"Hello World! Logging is {Description}."과(와) 키-값 쌍 Description = fun도 포함됩니다. 키 이름(또는 자리 표시자)은 템플릿의 중괄호 안에 있는 단어에서 비롯되며 값은 나머지 메서드 인수에서 가져옵니다.

이 예제의 이 프로젝트 파일에는 두 개의 NuGet 패키지가 포함되어 있습니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
  </ItemGroup>

</Project>

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

단순하지 않은 앱 로그인

덜 단순한 시나리오에서 로깅할 때 이전 예제를 몇 가지 변경해야 합니다.

  • 애플리케이션이 DI(종속성 주입)를 사용하거나 ASP.NET의 WebApplication 또는 제네릭 호스트와 같은 호스트를 사용하는 경우 직접 생성하지 말고 해당 DI 컨테이너에서 ILoggerFactoryILogger 객체를 사용해야 합니다. 자세한 내용은 DI 및 호스트 통합을 참조하세요.

  • 컴파일 시간 원본 생성을 로깅하는 것이 일반적으로 LogInformation과(와) 같은 ILogger 확장 방법보다 더 나은 대안입니다. 로깅 원본 생성은 더 나은 성능과 강력한 입력을 제공하며 메서드 전체에 string 상수가 분산되는 것을 방지합니다. 단점은 이 기술을 사용하려면 좀 더 많은 코드가 필요하다는 것입니다.

using Microsoft.Extensions.Logging;

internal partial class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger("Program");
        LogStartupMessage(logger, "fun");
    }

    [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")]
    static partial void LogStartupMessage(ILogger logger, string description);
}
  • 로그 범주 이름에 권장되는 방법은 로그 메시지를 만드는 클래스의 정규화된 이름을 사용하는 것입니다. 이렇게 하면 로그 메시지를 생성한 코드와 다시 연결하고 로그를 필터링할 때 적절한 수준의 제어를 제공합니다. CreateLogger은(는) 이 명명을 쉽게 수행할 수 있도록 Type을(를) 허용합니다.
using Microsoft.Extensions.Logging;

internal class Program
{
    static void Main(string[] args)
    {
        using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());
        ILogger logger = factory.CreateLogger<Program>();
        logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;

using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddOtlpExporter();
    });
});
ILogger logger = factory.CreateLogger("Program");
logger.LogInformation("Hello World! Logging is {Description}.", "fun");

호스트 및 종속성 주입과 통합

애플리케이션이 DI(종속성 주입)를 사용하거나 ASP.NET의 WebApplication 또는 제네릭 호스트와 같은 호스트를 사용하는 경우 직접 생성하지 말고 DI 컨테이너에서 ILoggerFactoryILogger 객체를 사용해야 합니다.

DI에서 ILogger 가져오기

이 예제에서는 ASP.NET 최소 API를 사용하여 호스트된 앱에서 ILogger 개체를 가져옵니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ExampleHandler>();

var app = builder.Build();

var handler = app.Services.GetRequiredService<ExampleHandler>();
app.MapGet("/", handler.HandleRequest);

app.Run();

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public string HandleRequest()
    {
        LogHandleRequest(logger);
        return "Hello World";
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger);
}

위의 예제는 다음과 같습니다.

  • ExampleHandler(이)라는 싱글톤 서비스를 만들고 들어오는 웹 요청을 매핑하여 ExampleHandler.HandleRequest 함수를 실행합니다.
  • 8번째 줄은 C# 12에 추가된 기능인 ExampleHandler에 대한 기본 생성자를 정의합니다. 이전 스타일의 C# 생성자를 사용하면 똑같이 잘 작동하지만 조금 더 장황합니다.
  • 생성자는 ILogger<ExampleHandler> 형식의 매개 변수를 정의합니다. ILogger<TCategoryName>은(는) ILogger에서 파생되며 ILogger 개체에 있는 범주를 나타냅니다. DI 컨테이너는 올바른 범주를 가진 ILogger을(를) 찾아 생성자 인수로 제공합니다. 해당 범주의 ILogger이(가) 아직 없는 경우 DI 컨테이너는 서비스 공급자의 ILoggerFactory에서 자동으로 만듭니다.
  • 생성자에서 받은 logger 매개 변수는 HandleRequest 함수에 로깅하는 데 사용되었습니다.

호스트 제공 ILoggerFactory

호스트 작성기는 기본 구성을 초기화한 다음 호스트가 빌드되면 구성된 ILoggerFactory 개체를 호스트의 DI 컨테이너에 추가합니다. 호스트가 빌드되기 전에 다른 호스트에서 HostApplicationBuilder.Logging, WebApplicationBuilder.Logging 또는 유사한 API를 통해 로깅 구성을 조정할 수 있습니다. 또한 호스트는 기본 구성 원본의 로깅 구성을 appsettings.json 및 환경 변수로 적용합니다. 자세한 내용은 .NET의 구성을 참조하세요.

이 예제는 이전 예제를 확장하여 WebApplicationBuilder에서 제공하는 ILoggerFactory을(를) 사용자 지정합니다. 이는 OTLP(OpenTelemetry 프로토콜)를 통해 로그를 전송하는 로깅 공급자로 OpenTelemetry를 추가합니다.

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());
builder.Services.AddSingleton<ExampleHandler>();
var app = builder.Build();

DI를 사용하여 ILoggerFactory 만들기

호스트 없이 DI 컨테이너를 사용하는 경우 AddLogging을(를) 사용하여 컨테이너에 ILoggerFactory을(를) 구성하고 추가합니다.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

// Add services to the container including logging
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddConsole());
services.AddSingleton<ExampleService>();
IServiceProvider serviceProvider = services.BuildServiceProvider();

// Get the ExampleService object from the container
ExampleService service = serviceProvider.GetRequiredService<ExampleService>();

// Do some pretend work
service.DoSomeWork(10, 20);

class ExampleService(ILogger<ExampleService> logger)
{
    public void DoSomeWork(int x, int y)
    {
        logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y);
    }
}

위의 예제는 다음과 같습니다.

  • 콘솔에 쓰도록 구성된 ILoggerFactory이(가) 포함된 DI 서비스 컨테이너 생성함
  • 컨테이너에 싱글톤 ExampleService 추가됨
  • 또한 생성자 인수로 사용할 ILogger<ExampleService>을(를) 자동으로 만든 DI 컨테이너에서 ExampleService의 인스턴스를 만들었습니다.
  • ILogger<ExampleService>을(를) 사용하여 콘솔에 메시지를 기록하는 ExampleService.DoSomeWork이(가) 호출되었습니다.

로깅 구성

로깅 구성은 코드 또는 구성 파일 및 환경 변수와 같은 외부 원본을 통해 설정됩니다. 애플리케이션을 다시 빌드하지 않고 변경할 수 있으므로 가능한 경우 외부 구성을 사용하는 것이 좋습니다. 그러나 로깅 공급자 설정과 같은 일부 작업은 코드에서만 구성할 수 있습니다.

코드 없이 로깅 구성

호스트를 사용하는 앱의 경우 로깅 구성은 일반적으로 appsettings.{Environment}.json"Logging" 섹션에서 제공됩니다. 호스트를 사용하지 않는 앱의 경우 외부 구성 원본을 명시적으로 설정하거나 대신 코드에서 구성합니다.

다음 appsettings.Development.json 파일은 .NET 작업자 서비스 템플릿으로 생성되었습니다.

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

앞의 JSON에서:

  • "Default", "Microsoft""Microsoft.Hosting.Lifetime" 로그 수준 범주가 지정됩니다.
  • "Default" 값은 달리 지정되지 않은 모든 범주에 적용되어 모든 범주에 대한 모든 기본값을 효과적으로 "Information"(으)로 만듭니다. 범주에 대한 값을 지정하여 이 동작을 재정의할 수 있습니다.
  • "Microsoft" 범주는 "Microsoft"로 시작하는 모든 범주에 적용됩니다.
  • "Microsoft" 범주는 Warning 이상의 로그 수준에서 로그됩니다.
  • "Microsoft.Hosting.Lifetime" 범주는 "Microsoft" 범주보다 더 구체적이므로 "Microsoft.Hosting.Lifetime" 범주는 로그 수준 "Information" 이상에서 로그됩니다.
  • 특정 로그 공급자를 지정하지 않았으므로 LogLevelWindows EventLog를 제외하고 사용하도록 설정한 모든 로깅 공급자에 적용됩니다.

Logging 속성은 LogLevel 및 로그 공급자 속성을 포함할 수 있습니다. LogLevel 속성은 선택한 범주에 대해 로그할 최소 수준을 지정합니다. 위의 JSON에서는 InformationWarning 로그 수준을 지정했습니다. LogLevel 로그의 심각도를 나타내며 0에서 6 사이의 범위입니다.

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 및 None = 6

LogLevel을 지정하면 지정된 수준 이상의 메시지에 대해 로깅을 사용하도록 설정됩니다. 위의 JSON에서 Default 범주는 Information 이상에 대해 로그됩니다. 예를 들어 Information, Warning, ErrorCritical 메시지가 로그됩니다. LogLevel 지정하지 않으면 로깅은 Information 수준으로 기본 설정됩니다. 자세한 내용은 로그 수준을 참조하세요.

공급자 속성에서 LogLevel 속성을 지정할 수 있습니다. 공급자 아래의 LogLevel은 해당 공급자에 대해 로그할 수준을 지정하고 공급자 이외의 로그 설정을 재정의합니다. 다음 appsettings.json 파일을 고려하세요.

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

Logging.{ProviderName}.LogLevel의 설정은 Logging.LogLevel의 설정을 재정의합니다. 위의 JSON에서는 Debug 공급자의 기본 로그 수준이 Information로 설정되었습니다.

Logging:Debug:LogLevel:Default:Information

위 설정은 Microsoft.Hosting을 제외하고 모든 Logging:Debug: 범주에 대해 Information 로그 수준을 지정합니다. 특정 범주를 나열하면 특정 범주로 기본 범주가 재정의됩니다. 위의 JSON에서 Logging:Debug:LogLevel 범주 "Microsoft.Hosting""Default"Logging:LogLevel의 설정을 재정의합니다.

최소 로그 수준은 다음에 대해 지정할 수 있습니다.

  • 특정 공급자: 예를 들면 Logging:EventSource:LogLevel:Default:Information과 같습니다.
  • 특정 범주: 예를 들어 Logging:LogLevel:Microsoft:Warning
  • 모든 공급자와 모든 범주: Logging:LogLevel:Default:Warning

최소 수준 이하의 모든 로그는 다음과 같이 되지 않습니다.

  • 공급자에 전달됩니다.
  • 로그되거나 표시됩니다.

모든 로그를 표시하지 않으려면 LogLevel.None을 지정합니다. LogLevel.None의 값은 LogLevel.Critical(5)보다 높은 6입니다.

공급자가 로그 범위를 지원하는 경우 IncludeScopes는 사용 가능 여부를 나타냅니다. 자세한 내용은 로그 범위를 참조하세요.

다음 appsettings.json 파일에는 모든 기본 제공 공급자에 대한 설정이 포함되어 있습니다.

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

앞의 예제에서:

  • 범주 및 수준은 제안된 값이 아닙니다. 기본 공급자를 모두 표시하기 위해 제공되는 샘플입니다.
  • Logging.{ProviderName}.LogLevel의 설정은 Logging.LogLevel의 설정을 재정의합니다. 예를 들어 Debug.LogLevel.Default 수준은 LogLevel.Default수준을 재정의합니다.
  • 각 기본 공급자의 ‘별칭’을 사용합니다. 각 공급자는 정규화된 형식 이름 대신 구성에서 사용할 수 있는 별칭을 정의합니다. 기본 제공 공급자의 별칭은 다음과 같습니다.
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

명령줄, 환경 변수 및 기타 구성으로 로그 수준 설정

로그 수준은 구성 공급자 중 하나로 설정할 수 있습니다. 예를 들어 값이 InformationLogging:LogLevel:Microsoft라는 지속형 환경 변수를 만들 수 있습니다.

로그 수준 값이 지정되면 지속형 환경 변수를 만들고 할당합니다.

:: Assigns the env var to the value
setx "Logging__LogLevel__Microsoft" "Information" /M

명령 프롬프트의 ‘새’ 인스턴스에서 환경 변수를 읽습니다.

:: Prints the env var value
echo %Logging__LogLevel__Microsoft%

이전의 환경 설정은 환경에서 지속됩니다. .NET 작업자 서비스 템플릿을 사용하여 만든 앱을 사용할 때 설정을 테스트하려면 환경 변수가 할당된 후에 프로젝트 디렉터리에서 dotnet run 명령을 사용합니다.

dotnet run

환경 변수를 설정한 후에는 IDE(통합 개발 환경)를 다시 시작하여 새로 추가된 환경 변수를 사용할 수 있는지 확인하세요.

Azure App Service설정 > 구성 페이지에서 새 애플리케이션 설정을 선택합니다. Azure App Service 애플리케이션 설정은,

  • 미사용 시 암호화되고 암호화된 채널을 통해 전송됩니다.
  • 환경 변수로 노출됩니다.

환경 변수를 사용하여 .NET 구성 값을 설정하는 방법에 대한 자세한 내용은 환경 변수를 참조하세요.

코드를 사용하여 로깅 구성

코드에서 로깅을 구성하려면 ILoggingBuilder API를 사용합니다. 이는 다른 위치에서 액세스할 수 있습니다.

이 예제에서는 콘솔 로깅 공급자 및 여러 필터를 설정하는 방법을 보여 줍니다.

using Microsoft.Extensions.Logging;

using var loggerFactory = LoggerFactory.Create(static builder =>
{
    builder
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
        .AddConsole();
});

ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogDebug("Hello {Target}", "Everyone");

앞의 예제에서 AddFilter은(는) 다양한 범주에 대해 사용하도록 설정된 로그 수준을 조정하는 데 사용됩니다. AddConsole은(는) 콘솔 로깅 공급자를 추가하는 데 사용됩니다. 기본적으로 Debug 심각도가 있는 로그는 사용하도록 설정되지 않지만 구성에서 필터를 조정했기 때문에 디버그 메시지 "Hello Everyone"이 콘솔에 표시됩니다.

필터링 규칙 적용 방식

ILogger<TCategoryName> 개체를 만들 때 ILoggerFactory 개체는 공급자마다 해당 로거에 적용할 단일 규칙을 선택합니다. ILogger 인스턴스에서 작성된 모든 메시지는 선택한 규칙에 따라 필터링됩니다. 사용 가능한 규칙 중에서 각 공급자 및 범주 쌍과 가장 관련이 많은 규칙이 선택됩니다.

지정된 범주에 대한 ILogger가 생성되면 다음 알고리즘이 각 공급자에 사용됩니다.

  • 공급자 또는 공급자의 별칭과 일치하는 모든 규칙을 선택합니다. 일치하는 규칙이 없는 경우 빈 공급자가 있는 모든 규칙을 선택합니다.
  • 앞 단계의 결과에서 일치하는 범주 접두사가 가장 긴 규칙을 선택합니다. 일치하는 규칙이 없는 경우 범주를 지정하지 않는 규칙을 모두 선택합니다.
  • 여러 규칙을 선택하는 경우 마지막 규칙을 사용합니다.
  • 규칙을 선택하지 않은 경우 LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel)를 사용하여 최소 로깅 수준을 지정합니다.

로그 범주

ILogger 개체가 생성되면 ‘범주’가 지정됩니다. 해당 범주는 ILogger의 해당 인스턴스에서 만든 각 로그 메시지에 포함됩니다. 범주 문자열은 임의로 지정되지만 규칙은 정규화된 클래스 이름을 사용하는 것입니다. 예를 들어 서비스가 다음 개체와 같이 정의된 애플리케이션에서 범주는 "Example.DefaultService"일 수 있습니다.

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

추가 분류가 필요한 경우 규칙은 정규화된 클래스 이름에 하위 범주를 추가하여 계층적 이름을 사용하고 LoggerFactory.CreateLogger을(를) 사용하여 범주를 명시적으로 지정하는 것입니다.

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory");

        // ...
    }
}

고정 이름을 사용하여 CreateLogger를 호출하면 여러 클래스/형식에서 사용할 때 유용할 수 있으므로 이벤트를 범주별로 구성할 수 있습니다.

ILogger<T>T의 정규화된 형식 이름으로 CreateLogger를 호출하는 것과 동일합니다.

로그 수준

다음 표에서는 LogLevel 값, 편리한 Log{LogLevel} 확장 메서드 및 추천 사용법을 설명합니다.

LogLevel 메서드 설명
Trace 0 LogTrace 가장 자세한 메시지를 포함합니다. 해당 메시지는 중요한 앱 데이터를 포함할 수 있습니다. 해당 메시지는 기본적으로 사용하지 않도록 설정되며 프로덕션에서 사용하도록 설정하면 안 됩니다.
디버그 1 LogDebug 디버깅 및 개발을 위한 수준입니다. 너무 많으므로 프로덕션에서는 주의해서 사용합니다.
정보 2 LogInformation 앱의 일반적인 흐름을 추적합니다. 장기적인 값이 있을 수 있습니다.
경고 3 LogWarning 비정상적이거나 예기치 않은 이벤트를 위한 수준입니다. 일반적으로 앱의 오류를 발생시키지 않는 오류 또는 조건을 포함합니다.
오류 4 LogError 처리할 수 없는 오류 및 예외입니다. 해당 메시지는 전체 앱 오류가 아닌 현재 작업 또는 요청의 오류를 의미합니다.
위험 5 LogCritical 즉각적인 대응이 필요한 오류를 위한 수준입니다. 예: 데이터 손실 시나리오, 디스크 공간 부족.
없음 6 메시지를 기록하지 않도록 지정합니다.

위의 표에서는 LogLevel이 심각도가 낮은 것에서 높은 것 순으로 표시됩니다.

Log 메서드의 첫 번째 매개 변수인 LogLevel은 로그의 심각도를 나타냅니다. 대부분의 개발자는 Log(LogLevel, ...) 대신 Log{LogLevel} 확장 메서드를 호출합니다. Log{LogLevel} 확장 메서드는 Log 메서드를 호출하고 LogLevel을 지정합니다. 예를 들어 다음 두 로깅 호출은 기능이 동일하며 같은 로그를 생성합니다.

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details는 이벤트 ID이며 상수 Int32 값으로 암시적으로 표시됩니다. AppLogEvents는 여러 개의 명명된 식별자 상수를 노출하고 로그 이벤트 ID 섹션에 표시되는 클래스입니다.

다음 코드는 InformationWarning 로그를 만듭니다.

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

위의 코드에서 첫 번째 Log{LogLevel} 매개 변수 AppLogEvents.Read로그 이벤트 ID입니다. 두 번째 매개 변수는 나머지 메서드 매개 변수가 제공하는 인수 값에 대한 자리 표시자를 포함하고 있는 메시지 템플릿입니다. 메서드 매개 변수는 이 문서의 뒷부분에 있는 메시지 템플릿 섹션에 설명되어 있습니다.

적절한 로그 수준을 구성하고 올바른 Log{LogLevel} 메서드를 호출하여 특정 스토리지 매체에 기록되는 로그 출력의 크기를 제어합니다. 예시:

  • 프로덕션:
    • Trace 또는 Debug 수준 로깅은 자세한 로그 메시지를 대량으로 생성합니다. 비용을 관리하고 데이터 스토리지 제한을 초과하지 않으려면 TraceDebug 수준 메시지를 대용량, 저비용 데이터 저장소에 로그합니다. TraceDebug을 특정 범주로 제한하는 것이 좋습니다.
    • Warning~Critical 수준의 로깅은 적은 로그 메시지를 생성합니다.
      • 비용과 스토리지 제한이 일반적으로 문제가 되지 않습니다.
      • 로그가 적으므로 데이터 저장소를 더 유연하게 선택할 수 있습니다.
  • 개발 중:
    • Warning로 설정합니다.
    • 문제를 해결할 때는 Trace 또는 Debug 메시지를 추가합니다. 출력을 제한하려면 조사 중인 범주에 대해서만 Trace 또는 Debug을 설정합니다.

다음 JSON에서는 Logging:Console:LogLevel:Microsoft:Information을 설정합니다.

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

로그 이벤트 ID

각 로그에서 ‘이벤트 식별자’를 지정할 수 있으며, EventIdId와 선택적 Name 읽기 전용 속성을 포함하는 구조체입니다. 샘플 소스 코드는 AppLogEvents 클래스를 사용하여 이벤트 ID를 정의합니다.

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static EventId Create = new(1000, "Created");
    internal static EventId Read = new(1001, "Read");
    internal static EventId Update = new(1002, "Updated");
    internal static EventId Delete = new(1003, "Deleted");

    // These are also valid EventId instances, as there's
    // an implicit conversion from int to an EventId
    internal const int Details = 3000;
    internal const int Error = 3001;

    internal static EventId ReadNotFound = 4000;
    internal static EventId UpdateNotFound = 4001;

    // ...
}

int을(를) EventId(으)로 변환하는 방법에 대한 자세한 내용은 EventId.Implicit(Int32에서 EventId로) 연산자를 참조하세요.

이벤트 ID는 이벤트 집합을 연결합니다. 예를 들어 리포지토리에서 값 읽기와 관련된 모든 로그는 1001일 수 있습니다.

로깅 공급자는 이벤트 ID를 ID 필드에 기록하거나, 로그 메시지에 기록하거나, 전혀 기록하지 않을 수 있습니다. 디버그 공급자는 이벤트 ID를 표시하지 않습니다. 콘솔 공급자는 범주 뒤에 대괄호로 이벤트 ID를 표시합니다.

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

일부 로깅 공급자는 ID에 대한 필터링을 허용하는 필드에 이벤트 ID를 저장합니다.

로그 메시지 템플릿

각 로그 API는 메시지 템플릿을 사용합니다. 메시지 템플릿에는 인수가 제공되는 자리 표시자를 포함할 수 있습니다. 번호가 아닌 자리 표시자의 이름을 사용합니다. 값을 제공하는 데 사용되는 매개 변수를 결정하는 것은 자리 표시자의 이름이 아닌 순서입니다. 다음 코드에서는 매개 변수 이름이 메시지 템플릿의 순서와 일치하지 않습니다.

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

앞의 코드는 매개 변수 값을 순서대로 사용하여 로그 메시지를 만듭니다.

Parameter values: param1, param2

참고 항목

서수 기반인 단일 메시지 템플릿 내에서 여러 자리 표시자를 사용할 때는 주의해야 합니다. 이름은 인수를 자리 표시자에 맞추는 데 사용되지 않습니다.

해당 접근 방식을 통해 로깅 공급자는 의미 체계 또는 구조적 로깅를 구현할 수 있습니다. 인수 자체는 서식이 지정된 메시지 템플릿뿐만 아니라 로깅 시스템에 전달됩니다. 따라서 로깅 공급자는 매개 변수 값을 필드로 저장할 수 있습니다. 다음 로거 메서드를 살펴보세요.

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

Azure Table Storage에 로그할 경우를 예로 듭니다.

  • 각 Azure Table 엔터티에는 IDRunTime 속성이 있을 수 있습니다.
  • 속성이 있는 테이블은 로그된 데이터의 쿼리를 쉽게 합니다. 예를 들어 문자 메시지의 시간 초과를 구문 분석하지 않고도 특정 RunTime 범위 내에 있는 모든 로그를 쿼리를 통해 찾을 수 있습니다.

로그 메시지 템플릿 서식 지정

로그 메시지 템플릿은 자리 표시자 서식을 지원합니다. 템플릿은 주어진 형식 인수에 대해 유효한 형식을 자유롭게 지정할 수 있습니다. 예를 들어 다음 Information 로거 메시지 템플릿을 살펴보겠습니다.

_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);
// Logged on January 06, 2022

앞의 예제에서 DateTimeOffset 인스턴스는 로거 메시지 템플릿의 PlaceHolderName에 해당하는 형식입니다. 이 이름은 값이 서수 기반이므로 무엇이든 될 수 있습니다. MMMM dd, yyyy 서식은 DateTimeOffset 형식에 유효합니다.

DateTimeDateTimeOffset 서식에 대한 자세한 내용은 사용자 지정 날짜 및 시간 서식 문자열을 참조하세요.

예제

다음 예제에서는 {} 자리 표시자 구문을 사용하여 메시지 템플릿의 서식을 지정하는 방법을 보여 줍니다. 또한 {} 자리 표시자 구문을 이스케이프하는 예제가 출력과 함께 표시됩니다. 마지막으로 템플릿 자리 표시자를 사용하여 문자열 보간도 표시됩니다.

logger.LogInformation("Number: {Number}", 1);               // Number: 1
logger.LogInformation("{{Number}}: {Number}", 3);           // {Number}: 3
logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5);    // {Number}: 5

  • 대부분의 경우 로깅할 때 로그 메시지 템플릿 서식을 사용해야 합니다. 문자열 보간을 사용하면 성능 문제가 발생할 수 있습니다.
  • 코드 분석 규칙 CA2254: 템플릿은 정적 표현식이어야 함은 로그 메시지가 적절한 서식을 사용하지 않는 위치에 대해 경고하는 데 도움이 됩니다.

예외 기록

로거 메서드에는 예외 매개 변수를 사용하는 오버로드가 있습니다.

public void Test(string id)
{
    try
    {
        if (id is "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id);
    }
}

예외 로깅은 공급자별로 다릅니다.

기본 로그 수준

기본 로그 수준을 설정하지 않으면 기본 로그 수준 값은 Information입니다.

예를 들어 다음 작업자 서비스 앱을 살펴보세요.

  • .NET 작업자 템플릿을 사용하여 만들었습니다.
  • appsettings.jsonappsettings.Development.json을 삭제하거나 이름을 변경했습니다.

이전 설정을 사용하여 개인정보처리방침이나 홈페이지로 이동하면 범주 이름에 Microsoft가 포함된 Trace, DebugInformation 메시지가 많이 생성됩니다.

다음 코드에서는 구성에 기본 로그 수준이 설정되어 있지 않은 경우 기본 로그 수준을 설정합니다.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.SetMinimumLevel(LogLevel.Warning);

using IHost host = builder.Build();

await host.RunAsync();

필터 함수

필터 함수는 구성 또는 코드를 통해 규칙이 할당되지 않은 모든 공급자와 범주에 대해 호출됩니다.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddFilter((provider, category, logLevel) =>
{
    return provider.Contains("ConsoleLoggerProvider")
        && (category.Contains("Example") || category.Contains("Microsoft"))
        && logLevel >= LogLevel.Information;
});

using IHost host = builder.Build();

await host.RunAsync();

앞의 코드는 범주에 Example 또는 Microsoft가 포함되어 있고 로그 수준이 Information 이상인 경우 콘솔 로그를 표시합니다.

로그 점수

범위는 논리 작업 집합을 그룹화합니다. 이 그룹화는 집합의 일부로 생성된 각 로그에 동일한 데이터를 연결하는 데 사용될 수 있습니다. 예를 들어 트랜잭션 처리의 일부로 생성되는 모든 로그에는 트랜잭션 ID가 포함될 수 있습니다.

범위:

  • BeginScope 메서드에서 반환하는 IDisposable 형식입니다.
  • 삭제될 때까지 유지됩니다.

범위를 지원하는 공급자는 다음과 같습니다.

using 블록에 로거 호출을 래핑하여 범위를 사용합니다.

public async Task<T> GetAsync<T>(string id)
{
    T result;
    var transactionId = Guid.NewGuid().ToString();

    using (_logger.BeginScope(new List<KeyValuePair<string, object>>
        {
            new KeyValuePair<string, object>("TransactionId", transactionId),
        }))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

다음 JSON은 콘솔 공급자에 대해 범위를 사용하도록 설정합니다.

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

다음은 콘솔 공급자에 대한 범위를 사용하도록 설정하는 코드입니다.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.IncludeScopes = true);

using IHost host = builder.Build();

await host.RunAsync();

Main에서 로그 만들기

다음 코드는 호스트를 빌드한 후 DI에서 ILogger 인스턴스를 가져와 Main에 로그합니다.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using IHost host = Host.CreateApplicationBuilder(args).Build();

var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Host created.");

await host.RunAsync();

위의 코드는 두 개의 NuGet 패키지를 사용합니다.

프로젝트 파일은 다음과 유사합니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
  </ItemGroup>

</Project>

비동기 로거 메서드 미지원

로깅은 매우 빨라서 비동기 코드의 성능 비용을 들일 필요가 없습니다. 로깅 데이터 저장소가 느린 경우 직접 로깅 데이터 저장소에 작성하지 마세요. 로그 메시지를 처음에 빠른 저장소에 작성한 다음, 나중에 느린 저장소로 이동하는 것이 좋습니다. 예를 들어 SQL Server에 로그하는 경우 Log 메서드는 동기식이므로 Log 메서드에서 직접 로그하지 마세요. 대신 동기적으로 로그 메시지를 메모리 내 큐에 추가하고 백그라운드 작업자가 큐에서 메시지를 풀하여 SQL Server에 대해 비동기 데이터 푸시 작업을 수행하도록 합니다.

실행 중인 앱에서 로그 수준 변경

로깅 API는 앱이 실행되는 동안 로그 수준을 변경하는 시나리오를 포함하지 않습니다. 그러나 일부 구성 공급자는 구성을 다시 로드할 수 있으며 이는 로깅 구성에 대한 즉각적인 영향을 줍니다. 예를 들어 파일 구성 공급자는 기본적으로 로깅 구성을 다시 로드합니다. 앱이 실행되는 동안 코드에서 구성이 변경되면 앱 IConfigurationRoot.Reload를 호출하여 앱의 로깅 구성을 업데이트할 수 있습니다.

NuGet 패키지

ILogger<TCategoryName>ILoggerFactory 인터페이스와 구현은 대부분의 .NET SDK에 암시적 패키지 참조로 포함되어 있습니다. 암시적으로 참조되지 않는 경우 다음 NuGet 패키지에서 명시적으로 사용할 수도 있습니다.

암시적 패키지 참조를 포함하는 .NET SDK에 대한 자세한 내용은 .NET SDK: 암시적 네임스페이스에 대한 테이블을 참조하세요.

참고 항목