로깅 - Logging

Logging

설명

Axs FrameworkCastle인터페이스를 구현한 Serilog의 로깅 기능을 기본으로 사용하며 Log4Net, NLog등 다영한 로깅라이브러로 변경 할 수 있습니다.

예제

먼저 로그를 작성하기 위해 Logger 객체를 가져와야합니다.

// 네임스페이스 선언
using Castle.Core.Logging;

public class SampleAppController : ISampleAppController
{
    // 객체 속성 주입을 위해 선언
    public ILogger Logger { get; set; }

    public TaskAppController()
    {
        // 기본값 선언
        Logger = NullLogger.Instance;
    }

    public void CreateSample(CreateSampleDto input)
    {
        // 로그 저장
        Logger.Info("샘플 코드 저장 description : " + input.Description);

        //TODO: 기타 작업 코드 작성
    }
}

위 코드가 실행이 되고 로그 파일을 확인하면 아래와 같이 확인이 가능합니다.

08:09:35,556 [INF] 샘플 코드 저장 description : Description!
{"SourceContext": "SampleAppController", "CorrelationId": "9dade8b9-de5a-4e70-a79c-788148daa86a", "ThreadId": 23}
08:09:39,015 [ERR] 에러 로깅 테스트
{"SourceContext": "SampleAppController", "CorrelationId": "9dade8b9-de5a-4e70-a79c-788148daa86a", "ThreadId": 21, "ExceptionDetail": {"Type": "System.ApplicationException", "HResult": -2146232832, "Message": "에러 로깅 테스트", "Source": "MyCompanyName.MyProjectName.Web.Mvc", "TargetSite": "Void MoveNext()"}}
System.ApplicationException: 에러 로깅 테스트
   at MyCompanyName.MyProjectName.Web.Areas.App.Pages.Users.IndexModel.OnGetAsync() in ...\Index.cshtml.cs:line 29
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.NonGenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()

로그 관련 환경 설정

기본 구성의 로그 형식은 다음과 같습니다.

  • 날짜 및 시간 : 로그 라인이 작성된 시간입니다.

  • 로그 수준 : DBG, INF, WRN, ERR, FTL.

  • 로그 텍스트 : 작성하는 실제 로그 텍스트입니다.

  • Properties : 출력의 다른 곳에 표시되지 않는 모든 이벤트 속성 값입니다.

    • 로거 이름 : 일반적으로 로그 라인을 작성하는 클래스 이름입니다.

    • 상관관계 ID : 현재 웹 요청에 대한 로그를 추적하기 위해 Correlation Id 추가합니다.

    • 스레드 번호 : 로그 라인을 작성한 스레드 번호입니다.

    • 예외 상세 : 예외가 발생할경우 구조화된 예외정보 노출

  • 예외 : 예외가 발생할경우 메세지 원문

아래와 같이 응용 프로그램의 Program.csappsettings.json 파일에 정의되어 있습니다.

  • Program.cs로 기본 DEBUG시 Console 로깅이 동작하면 appsettings.json에 정의를 통해서 Override된 설정으로 최종 구성된다.

public class Program
{
    public static void Main(string[] args) {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Warning()
            .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
            .Enrich.FromLogContext()
            .Enrich.WithCorrelationIdHeader()
            .Enrich.WithAxsThreadId()
#if DEBUG
            .Enrich.WithExceptionDetails()
            .WriteTo.Async(config => config
                .Console(outputTemplate: "{Timestamp:HH:mm:ss,fff} [{Level:u3}] {Message:lj}{NewLine}{Properties:j}{NewLine}{Exception}",
                    theme: Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme.Code)
            )
#endif
            .ReadFrom.Configuration(configuration)
            .CreateLogger();        
        // some code...
    }  
}
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Warning",
      "Override": {
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "Properties": {
      "Application": "Axs WebApp"
    },
    "WriteTo": [
      {
        "Name": "Async",
        "Args": {
          "configure": [
            {
              "Name": "File",
              "Args": {
                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Properties:j}{NewLine}{Exception}",
                "path": "Logs\\App-.log",       // 파일명 - App-yyyyMMdd.log
                "fileSizeLimitBytes": 10240000, // 파일 사이즈
                "rollingInterval": "Day",       // 롤링 기준 - 하루 단위로 구별
                "rollOnFileSizeLimit": true,    // 파일 사이즈 제한 여부
                "retainedFileCountLimit": 5,    // 파일 갯수
                "buffered": false,
                "shared": true,
                "encoding": "System.Text.Encoding::UTF8"
              }
            }
          ]
        }
      }
    ]
  }  
}

Serilog은 강력한 로깅 라이브러리입니다. 다른 형식과 다른 대상 (텍스트 파일, 데이터베이스 ...)으로 로그를 저장할 수 있습니다. 최소 로그 수준을 설정하여 로그 저장의 횟수를 지정할 수 있습니다. 자세한 내용은 Serilog의 환경설정 문서(documentation)를 참조하십시오.

마지막으로 Startup.cs 파일에서 appsettings.json 파일과 함께 Serilog의 환경 설정 정보를 사용하도록 합니다.

services.AddAxs<MyProjectNameWebMvcModule>(options => {
    options.IocManager.IocContainer.AddFacility<LoggingFacility>(
        f => f.UseAxSerilog()
    );

웹과 호스트 프로젝트에서 초기화 코드를 추가하면 됩니다.

확인

appsettings.json에서 serilog 세셕에서 아래 정보를 확인하고 폴더에서 해당 로그 파일을 확인하면 된다.

// 파일로그 옵션
{
  "Name": "File",
  "Args": {
    "path": "Logs\\App.log"
  }
}

로그 파일을 열어 보면 아래와 같은 패턴으로 저장이 되는 것을 확인 할 수 있습니다.

2023-09-19 08:09:19.192 [INF] Starting web server...
{"ThreadId":1}
2023-09-19 08:09:20.883 [INF] Web Server Booting Up...
{"SourceContext":"Default","ThreadId":1}
2023-09-19 08:07:20.563 [INF] Initialize Seed Db.
{"SourceContext":"MyCompanyName.MyProjectName.Persistence.MyProjectNamePersistenceModule","ThreadId":1}
2023-09-19 08:07:20.566 [INF] >>  Configuration.Modules.AxsEfCore().AddDbContext ....
{"SourceContext":"MyCompanyName.MyProjectName.Persistence.MyProjectNamePersistenceModule","ThreadId":1}
2023-09-19 08:07:20.972 [INF] Executed DbCommand (3ms) [Parameters=[@__language_Name_1='ko' (Size = 128)], CommandType='Text', CommandTimeout='30']
SELECT CASE
    WHEN EXISTS (
        SELECT 1
        FROM [AxsLanguages] AS [a]
        WHERE [a].[TenantId] IS NULL AND ([a].[Name] = @__language_Name_1)) THEN CAST(1 AS bit)
    ELSE CAST(0 AS bit)
END
{"SourceContext":"Microsoft.EntityFrameworkCore.Database.Command","ThreadId":1}
2023-09-19 08:09:35.834 [DBG] Debug!!!!!
{"SourceContext":"MyCompanyName.MyProjectName.Web.Areas.App.Pages.Users.IndexModel","CorrelationId":"87449cf6-c4bf-40d4-9390-116ea4cd35fe","ThreadId":21}
2023-09-19 08:09:35.834 [INF] Info!!!!!
{"SourceContext":"MyCompanyName.MyProjectName.Web.Areas.App.Pages.Users.IndexModel","CorrelationId":"87449cf6-c4bf-40d4-9390-116ea4cd35fe","ThreadId":21}
2023-09-19 08:09:35.834 [WRN] Warn!!!!!
{"SourceContext":"MyCompanyName.MyProjectName.Web.Areas.App.Pages.Users.IndexModel","CorrelationId":"87449cf6-c4bf-40d4-9390-116ea4cd35fe","ThreadId":21}
2023-09-19 08:09:39.015 [ERR] 에러 로깅 테스트
{"SourceContext":"Axs.AspNetCore.Mvc.ExceptionHandling.AxsExceptionPageFilter","CorrelationId":"87449cf6-c4bf-40d4-9390-116ea4cd35fe","ThreadId":21,"ExceptionDetail":{"Type":"System.ApplicationException","HResult":-2146232832,"Message":"에러 로깅 테스트","Source":"MyCompanyName.MyProjectName.Web.Mvc","TargetSite":"Void MoveNext()"}}
System.ApplicationException: 에러 로깅 테스트
   at MyCompanyName.MyProjectName.Web.Areas.App.Pages.Users.IndexModel.OnGetAsync() in ...\Index.cshtml.cs:line 29
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.NonGenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()
   at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()

웹 요청에 대한 로그를 추적하기

  • CorrelationId: 297e08af-3194-4700-973c-b93a3d030a92

  • API 호출시 관계된 로그를 분석할수 있습니다.

2023-09-19 08:44:54.018 [INF] GetLanguages 언어조회
{"SourceContext": "SampleAppController", "CorrelationId": "297e08af-3194-4700-973c-b93a3d030a92", "ThreadId": 23}

2023-09-19 08:44:54.018 [INF] >>  Configuration.Modules.AxsEfCore().AddDbContext ....
{"SourceContext":"MyProjectNamePersistenceModule","CorrelationId":"297e08af-3194-4700-973c-b93a3d030a92","ThreadId":34}

2023-09-19 08:44:54.025 [INF] Executed DbCommand (2ms) [Parameters=[@p0='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.31' (Size = 512), @p1='::1' (Size = 64), @p2=NULL (Size = 128), @p3=NULL (Size = 2000), @p4=NULL (Size = 2000), @p5='28', @p6='2023-09-19T08:44:53', @p7=NULL (DbType = Int32), @p8=NULL (DbType = Int64), @p9='GetLanguages' (Size = 256), @p10='{}' (Size = 1024), @p11=NULL (Size = 4000), @p12='Axs.Starter.Localization.LanguageAppService' (Size = 256), @p13=NULL (DbType = Int32), @p14='1' (Nullable = true)], CommandType='Text', CommandTimeout='30']
SELECT *
FROM [AxsLanguages]
{"SourceContext":"Microsoft.EntityFrameworkCore.Database.Command","CorrelationId":"297e08af-3194-4700-973c-b93a3d030a92","ThreadId":31}

Last updated