간단한 로깅

GitHub에서 이 문서의 샘플을 다운로드할 수 있습니다.

EF Core(Entity Framework Core) 간단한 로깅을 사용하여 애플리케이션을 개발하고 디버깅하는 동안 로그를 쉽게 가져올 수 있습니다. 이 형태의 로깅에는 최소한의 구성과 추가 NuGet 패키지가 필요하지 않습니다.

EF Core는 더 많은 구성이 필요하지만 프로덕션 애플리케이션에서 로깅하는 데 더 적합한 Microsoft.Extensions.Logging과도 통합됩니다.

구성

EF Core 로그는 DbContext 인스턴스를 구성할 때 LogTo를 사용하여 어떤 유형의 애플리케이션에서도 액세스할 수 있습니다. 이 구성은 일반적으로 DbContext.OnConfiguring의 재정의에서 수행됩니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

LogToAddDbContext 또는 DbContextOptions의 일부로 또는 DbContext 생성자에 전달할 인스턴스를 만들 때 호출할 수 있습니다.

AddDbContext를 사용하거나 DbContextOptions 인스턴스가 DbContext 생성자에 전달될 때 OnConfiguring은 계속해서 호출됩니다. 이는 DbContext가 생성되는 방식에 관계없이 컨텍스트 구성을 적용하기에 이상적인 위치입니다.

로그 지시

콘솔에 로깅

LogTo에는 문자열을 허용하는 Action<T> 대리자가 필요합니다. EF Core는 생성된 각 로그 메시지에 대한 문자열을 사용하여 이 대리자를 호출합니다. 그런 다음, 지정된 메시지로 작업을 수행하는 것은 대리자에게 달려 있습니다.

Console.WriteLine 메서드는 위와 같이 이 대리자로 자주 사용됩니다. 그러면 각 로그 메시지가 콘솔에 기록됩니다.

디버그 창에 로깅

Debug.WriteLine은 Visual Studio 또는 기타 IDE의 디버그 창으로 출력을 보내는 데 사용할 수 있습니다. Debug 클래스가 릴리스 빌드에서 컴파일되므로 이 경우 람다 구문을 사용해야 합니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(message => Debug.WriteLine(message));

파일에 로깅

파일에 쓰려면 파일에 대한 StreamWriter 또는 유사한 것을 만들어야 합니다. 그런 다음 위의 다른 예제에서와 같이 WriteLine 메서드를 사용할 수 있습니다. 컨텍스트가 삭제될 때 기록기를 삭제하여 파일이 완전히 닫혀 있는지 확인해야 합니다. 예시:

private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(_logStream.WriteLine);

public override void Dispose()
{
    base.Dispose();
    _logStream.Dispose();
}

public override async ValueTask DisposeAsync()
{
    await base.DisposeAsync();
    await _logStream.DisposeAsync();
}

프로덕션 애플리케이션의 파일에 로깅하기 위해 Microsoft.Extensions.Logging을 사용하는 것이 좋습니다.

자세한 메시지 가져오기

중요한 데이터

기본적으로 EF Core는 예외 메시지에 데이터의 값을 포함하지 않습니다. 이러한 데이터는 기밀일 수 있으며 예외가 처리되지 않으면 프로덕션 용도로 표시될 수 있기 때문입니다.

그러나 특히 키에 대한 데이터 값을 아는 것은 디버깅할 때 매우 유용할 수 있습니다. EnableSensitiveDataLogging()을 호출하여 EF Core에서 사용하도록 설정할 수 있습니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableSensitiveDataLogging();

자세한 쿼리 예외

성능상의 이유로 EF Core는 try-catch 블록에서 데이터베이스 공급자의 값을 읽기 위해 각 호출을 래핑하지 않습니다. 그러나 이로 인해 특히 모델에서 허용하지 않을 때 데이터베이스가 NULL을 반환하는 경우 진단하기 어려운 예외가 발생하는 경우가 있습니다.

EnableDetailedErrors를 켜면 EF에서 이러한 try-catch 블록을 도입하여 더 자세한 오류를 제공합니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableDetailedErrors();

필터링

로그 수준

모든 EF Core 로그 메시지는 LogLevel 열거형으로 정의된 수준에 할당됩니다. 기본적으로 EF Core 단순 로깅에는 Debug 수준 이상의 모든 메시지가 포함됩니다. LogTo에는 일부 메시지를 필터링하기 위해 더 높은 최소 수준을 전달할 수 있습니다. 예를 들어 Information을 전달하면 데이터베이스 액세스 및 일부 하우스키핑 메시지로 제한되는 최소 로그 집합이 생성됩니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

특정 메시지

모든 로그 메시지에는 EventId가 할당됩니다. 이러한 ID는 관계형 관련 메시지에 대한 CoreEventId 클래스 또는 RelationalEventId 클래스에서 액세스할 수 있습니다. 데이터베이스 공급자도 비슷한 클래스에 공급자별 ID가 있을 수 있습니다. 예를 들어 SQL Server 공급자의 경우 SqlServerEventId입니다.

LogTo는 하나 이상의 이벤트 ID와 연결된 메시지만 기록하도록 구성할 수 있습니다. 예를 들어 초기화되거나 삭제되는 컨텍스트에 대한 메시지만 기록하려면 다음을 수행합니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });

메시지 범주

모든 로그 메시지는 명명된 계층적 로거 범주에 할당됩니다. 관련 범주는 다음과 같습니다.

범주 메시지
Microsoft.EntityFrameworkCore 모든 EF Core 메시지
Microsoft.EntityFrameworkCore.Database 모든 데이터베이스 상호 작용
Microsoft.EntityFrameworkCore.Database.Connection 데이터베이스 연결 사용
Microsoft.EntityFrameworkCore.Database.Command 데이터베이스 명령 사용
Microsoft.EntityFrameworkCore.Database.Transaction 데이터베이스 트랜잭션 사용
Microsoft.EntityFrameworkCore.Update 데이터베이스 상호 작용을 제외한 엔터티 저장
Microsoft.EntityFrameworkCore.Model 모든 모델 및 메타데이터 상호 작용
Microsoft.EntityFrameworkCore.Model.Validation 모델 유효성 검사
Microsoft.EntityFrameworkCore.Query 데이터베이스 상호 작용을 제외한 쿼리
Microsoft.EntityFrameworkCore.Infrastructure 컨텍스트 만들기와 같은 일반 이벤트
Microsoft.EntityFrameworkCore.Scaffolding 데이터베이스 리버스 엔지니어링
Microsoft.EntityFrameworkCore.Migrations 마이그레이션
Microsoft.EntityFrameworkCore.ChangeTracking 변경 내용 추적 상호 작용

LogTo는 하나 이상의 범주에서만 메시지를 기록하도록 구성할 수 있습니다. 예를 들어 데이터베이스 상호 작용만 기록하려면 다음을 수행합니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });

DbLoggerCategory 클래스는 범주를 찾기 위한 계층적 API를 제공하며 문자열을 하드 코딩할 필요가 없습니다.

범주는 계층적이므로 Database 범주를 사용하는 이 예제에는 하위 범주 Database.Connection, Database.Command, Database.Transaction에 대한 모든 메시지가 포함됩니다.

사용자 지정 필터

LogTo를 사용하면 위의 필터링 옵션 중 어느 것도 충분하지 않은 경우 사용자 지정 필터를 사용할 수 있습니다. 예를 들어 Information 수준 이상의 메시지와 연결을 열고 닫기 위한 메시지를 기록하려면 다음을 수행합니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(
            Console.WriteLine,
            (eventId, logLevel) => logLevel >= LogLevel.Information
                                   || eventId == RelationalEventId.ConnectionOpened
                                   || eventId == RelationalEventId.ConnectionClosed);

사용자 지정 필터를 사용하거나 여기에 표시된 다른 옵션을 사용하여 필터링하는 것이 LogTo 대리자의 필터링보다 더 효율적입니다. 필터가 메시지를 기록해서는 안 된다고 판단하면 로그 메시지도 생성되지 않기 때문입니다.

특정 메시지에 대한 구성

EF Core ConfigureWarnings API를 사용하면 애플리케이션이 특정 이벤트가 발생할 때 발생하는 작업을 변경할 수 있습니다. 이를 사용하여 다음을 수행할 수 있습니다.

  • 이벤트가 기록되는 로그 수준 변경
  • 이벤트 로깅 모두 건너뛰기
  • 이벤트가 발생할 때 예외를 throw합니다.

이벤트의 로그 수준 변경

이전 예제에서는 사용자 지정 필터를 사용하여 LogLevel.Debug에 대해 정의된 두 개의 이벤트뿐만 아니라 LogLevel.Information의 모든 메시지를 기록했습니다. 두 Debug 이벤트의 로그 수준을 Information으로 변경하여 동일한 작업을 수행할 수 있습니다.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(
            b => b.Log(
                (RelationalEventId.ConnectionOpened, LogLevel.Information),
                (RelationalEventId.ConnectionClosed, LogLevel.Information)))
        .LogTo(Console.WriteLine, LogLevel.Information);

이벤트 로깅 표시 안 함

비슷한 방식으로 개별 이벤트를 로깅에서 표시하지 않을 수 있습니다. 이 기능은 검토 및 이해된 경고를 무시하는 데 특히 유용합니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
        .LogTo(Console.WriteLine);

이벤트에 대한 throw

마지막으로, 지정된 이벤트에 대해 EF Core를 throw하도록 구성할 수 있습니다. 이 기능은 경고를 오류로 변경하는 데 특히 유용합니다. (실제로 이는 ConfigureWarnings 메서드의 원래 목적이므로 그러한 이름이 붙여졌습니다.) 예를 들어:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
        .LogTo(Console.WriteLine);

메시지 내용 및 서식 지정

LogTo의 기본 콘텐츠는 여러 줄에 걸쳐 서식이 지정됩니다. 첫 번째 줄에는 메시지 메타데이터가 포함됩니다.

  • 4자 접두사인 LogLevel
  • 현재 문화권에 대한 형식이 지정된 로컬 타임스탬프
  • CoreEventId 또는 다른 EventId 클래스 중 하나에서 멤버를 가져오기 위해 복사/붙여넣을 수 있는 양식의 EventId 및 원시 ID 값
  • 위에서 설명한 것과 같은 이벤트 범주.

예시:

info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Blogs" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
          "Name" INTEGER NOT NULL
      );
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committed transaction.

이 콘텐츠는 다음 섹션에 표시된 대로 DbContextLoggerOptions에서 값을 전달하여 사용자 지정할 수 있습니다.

로그 서식을 더 자세히 제어하려면 Microsoft.Extensions.Logging을 사용하는 것이 좋습니다.

UTC 시간 사용

기본적으로 타임스탬프는 디버깅하는 동안 로컬 사용을 위해 설계되었습니다. 문화권에 구애받지 않은 UTC 타임스탬프를 대신 사용하지만 다른 모든 항목은 동일하게 유지하려면 DbContextLoggerOptions.DefaultWithUtcTime을 사용합니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithUtcTime);

이 예제에서는 다음과 같은 로그 서식을 생성합니다.

info: 2020-10-06T17:55:39.0333701Z RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Blogs" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
          "Name" INTEGER NOT NULL
      );
dbug: 2020-10-06T17:55:39.0333892Z RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committing transaction.
dbug: 2020-10-06T17:55:39.0351684Z RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committed transaction.

한 줄 로깅

로그 메시지당 정확히 한 줄을 가져오는 것이 유용한 경우도 있습니다. 이 기능은 DbContextLoggerOptions.SingleLine에서 사용하도록 설정할 수 있습니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);

이 예제에서는 다음과 같은 로그 서식을 생성합니다.

info: 10/6/2020 10:52:45.723 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" (    "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,    "Name" INTEGER NOT NULL);
dbug: 10/6/2020 10:52:45.723 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committing transaction.
dbug: 10/6/2020 10:52:45.725 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committed transaction.

기타 콘텐츠 옵션

DbContextLoggerOptions의 다른 플래그를 사용하여 로그에 포함된 메타데이터의 양을 트리밍할 수 있습니다. 이는 한 줄 로깅과 함께 유용할 수 있습니다. 예시:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);

이 예제에서는 다음과 같은 로그 서식을 생성합니다.

2020-10-06T17:52:45.7320362Z -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" (    "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,    "Name" INTEGER NOT NULL);
2020-10-06T17:52:45.7320531Z -> Committing transaction.
2020-10-06T17:52:45.7339441Z -> Committed transaction.

EF6에서 이동

EF Core 간단한 로깅은 두 가지 중요한 방면에서 EF6에서 Database.Log와 다릅니다.

  • 로그 메시지는 데이터베이스 상호 작용으로만 제한되지 않습니다.
  • 컨텍스트 초기화 시 로깅을 구성해야 합니다.

첫 번째 차이점의 경우 위에서 설명한 필터링을 사용하여 기록되는 메시지를 제한할 수 있습니다.

두 번째 차이점은 필요하지 않을 때 로그 메시지를 생성하지 않음으로써 성능을 개선하기 위한 의도적인 변경입니다. 그러나 DbContext에서 Log 속성을 만든 다음 설정된 경우에만 사용하여 EF6과 유사한 동작을 가져올 수 있습니다. 예시:

public Action<string> Log { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(s => Log?.Invoke(s));