Skip to content

IFoxLog Interface

Fox Logging이 제공하는 로깅 기능의 핵심은 IFoxLog 인터페이스를 통해 제공됩니다. 어플리케이션은 IFoxLog 인터페이스를 통해 로그를 기록하기만 하면 구성 설정을 통해 실제 로그가 기록될 매체(콘솔, 디버거, 파일, 데이터베이스 등)를 결정할 수 있기 때문 입니다.

IFoxLog Interface Overview

IFoxLog 인터페이스는 다음과 같이 정의되어 있습니다. IFoxLog 인터페이스의 속성 및 메서드들은 이 문서내에서 모두 언급되어 설명할 것입니다.

namespace NeoDEEX.Diagnostics;

public interface IFoxLog
{
    string Name { get; }
    string Source { get; set; }
    FoxLogLevel FilterLevel { get; set; }
    bool IsEnabled(FoxLogLevel level);
    void Write(FoxLogLevel level, object? data);
    void Write(string source, FoxLogLevel level, object? data);
    void WriteFormat(FoxLogLevel level, string format, params object?[] args);
    void WriteFormat(string source, FoxLogLevel level, string format, params object?[] args);
}

어플리케이션은 FoxLogManager 클래스의 GetLogger 메서드를 호출하여 등록된 로거들 중 하나에 대한 IFoxLog 인터페이스를 구할 수 있습니다.

IFoxLog log = FoxLogManager.GetLogger("MyLogger");
log.Write(FoxLogLevel.Information, "This is a log entry.");

Write method

로그를 기록하기 위해 IFoxLog 인터페이스는 Write 메서드와 WriteFormat 메서드를 제공합니다. Write 메서드는 로그 수준과 로그 데이터를 사용하여 로그를 기록할 수 있으며 WriteFormat 메서드는 로그 메시지를 포맷팅하여 로그를 기록합니다. WriteFormat 메서드는 로그가 필터링될 때 불필요한 포맷팅을 수행하지 않도록 제공되는 메서드 입니다.

로그 데이터가 어떻게 로그 매체에 기록될 것인가는 로거에 따라 다릅니다. FoxConsoleLogger, FoxTextFileLogger 클래스가 구현하는 로거는 로그 데이터를 단순히 문자열로 변환(ToString 메서드)하여 로그에 기록합니다. 예를 들어, 예외가 발생하면 Exception 객체를 로그로 기록하는 다음 코드를 생각해 봅시다.

1
2
3
4
5
6
7
8
try
{
    ... 코드 ...
}
catch(Exception ex)
{
    log.Write(FoxLogLevel.Error, ex);
}

로거가 FoxConsoleLoggerFoxTextFileLogger 라면 Exception.ToString() 메서드가 호출되어 문자열이 로그에 기록될 것입니다. 하지만 모든 로거가 문자열에 의존하지는 않습니다. 커스텀 로거를 작성하여 Exception.Message 속성만 기록하는 로거를 작성할 수도 있습니다.

WriteFormat method

로그를 기록할 때, 많은 경우 문자열 포맷팅을 사용합니다. 예를 들어, 메서드에 전달된 매개변수를 로그로 남긴다면 다음과 같은 코드를 사용할 수 있습니다.

1
2
3
4
5
6
7
public void VeryImportantMethod(string id, string arg)
{
    IFoxLog log = FoxLogManager.GetLogger("ImportantLogger");
    log.Write(FoxLogLevel.Information, $"Method parameter: id={id} arg={arg}");

    ... 중요한 코드 ...
}

위와 같은 코드는 가독성이 좋지만 효율성 면에서 매우 좋지 않습니다. C# 문자열 보간(interpolation)이 사용되어 항상 포맷팅을 수행하고 새로운 문자열 객체를 할당하기 때문입니다. 만약 로거의 로그 필터링에 의해 Information 로그 수준이 필터링되는 경우 문자열 포맷팅 및 할당은 전혀 불필요한 작업이 되기 때문입니다. 일반적으로 개발 시 대량의 로그를 매우 상세하게 기록하기 때문에 불필요한 포맷팅과 문자열 할당은 성능적인 문제를 유발할 수도 있습니다.

WriteFormat 메서드는 이러한 문제를 해결하기 위해 제공되는 메서드입니다. WriteFormat 메서드는 C# 문자열 보간 스타일의 포맷팅이 아닌 구식 String.Format 스타일의 포맷팅을 사용하긴 하지만 로그가 필터링되지 않는 경우에만 포맷팅을 수행합니다. 따라서 위 코드는 다음과 같이 WriteFormat 메서드를 사용하여 효율적으로 바꿀 수 있습니다.

1
2
3
4
5
6
7
public void VeryImportantMethod(string id, string arg)
{
    IFoxLog log = FoxLogManager.GetLogger("ImportantLogger");
    log.WriteFormat(FoxLogLevel.Information, "Method parameter: id={0} arg={1}", id, arg);

    ... 중요한 코드 ...
}

WriteFormat 메서드를 사용하면 로그가 필터링 되는 경우 불필요한 포맷팅과 문자열 할당을 피할 수 있습니다. 로거가 정의되어 있지 않아 대체 로거가 사용되는 경우에도 대체 로거는 문자열 포맷팅과 할당을 수행하지 않기 때문에 성능 저하를 유발하지 않습니다.

Logger Name

Fox Logging에서 로거의 이름은 매우 중요합니다. 어플리케이션은 사용하고자 하는 로거의 IFoxLog 인터페이스를 구하기 위해 로거 이름을 사용하여 GetLogger 메서드를 호출하며 로깅 구성 설정에서 로거를 등록할 때에도 로거를 구분하는 유일한 방법으로 로거 이름을 사용하기 때문입니다. 로거 이름은 IFoxLog.Name 속성을 통해 접근이 가능합니다.

로거 이름은 로거가 생성될 때 설정되며 이후 변경할 수 없습니다. 로거는 구성 설정을 통해 생성되고 등록되며 동일한 이름의 로거는 등록될 수 없습니다.

FoxLogManager.RegisterLogger 메서드를 통해 코드를 사용하여 로거를 등록할 수도 있습니다. 이 경우에도 동일한 이름의 로거를 등록하고자 하는 시도는 예외를 발생하게 됩니다.

어플리케이션이 GetLogger 메서드를 호출할 때 사용하는 로거 이름과 반환된 IFoxLog 인터페이스의 Name 속성이 동일하지 않을 수 있습니다. 이는 Fox Logging이 제공하는 대체 로거(fallback logger)와 로거 이름 레벨링 기능 때문입니다.

IFoxLog log = FoxLogManager.GetLogger("NonExistLoggerName");
Console.WriteLine($"log.Name = {log1.Name}");

위 코드는 등록되지 지은 로거 이름을 사용하여 GetLogger를 호출한 경우 입니다. 이 경우, FoxLogManager 클래스는 대체 로거를 반환합니다. 대체 로거의 이름은 NeoDEEX.FallbackLogger 로 고정되어 있습니다.

대체 로거로거 이름 레벨링에 대해서는 관련 문서를 참고 하십시요.

Log Source

로그 소스는 로그를 남기는 주체를 나타냅니다. 로그 소스는 FoxConsoleLogger, FoxDebugLogger, FoxTextFileLogger 와 같이 로그 메시지(문자열)를 남기는 로거의 경우 [] 문자로 로그 소스를 표시합니다. 다음 로그 예제에서 로그 소스는 MyLogger 입니다.

I 2022-04-07 08:49:51.91432 [MyLogger] This is a log entry.
I 2022-04-07 08:49:51.91939 [MyLogger] Method parameter: id=TestUser arg=some data

로그 소스는 WriteWriteFormat 메서드의 매개변수로 지정할 수 있습니다. 다음 에제 코드에서 첫번째 매개변수가 로그 소스 이름을 나타냅니다.

log.Write("MySource", FoxLogLevel.Information, "With source parameter.");

로그 소스를 명시하지 않는 Write, WriteFormat 메서드가 호출되면 로거의 디폴트 소스 이름이 사용됩니다. 디폴트 소스 이름은 로거가 생성될 때 로거 이름으로 초기화 됩니다. 예를 들어, 다음 호출에서 소스 이름은 MyLogger가 됩니다.

IFoxLog log = FoxLogManager.GetLogger("MyLogger");
log.Write(FoxLogLevel.Information, "Wihtout source parameter.");

로거의 디폴트 소스 이름은 IFoxLog.Source 속성을 통해 접근이 가능합니다. 이 속성을 사용하여 디폴트 소스 이름을 변경할 수 있습니다. 이 때 주의할 사항은 이 변경은 이후 이 로거를 사용하는 모든 로그 기록에 영향을 준다는 것입니다. 특히, 여러 스레드가 동시에 하나의 로거를 통해 로그를 기록하는 경우 의도하지 않은 소스 이름이 사용될 수도 있습니다.

디폴트 로그 소스에 의존하여 로그 소스를 관리하는 것은 여러가지 문제를 유발할 수도 있습니다. 로그 소스를 명확히 로그에 남겨야 한다면 권장되는 방법은 Write 혹은 WriteFormat 메서드 호출 시 로그 소스를 명시하는 것입니다. 다음과 같은 헬퍼 메서드를 작성하여 로그를 기록하는 주체(여기에선 클래스 이름)를 로그 소스로 명시하면 됩니다.

1
2
3
4
protected void DebugLog(string fmt, params object[] args)
{
    _log.WriteFormat(this.GetType().Name, FoxLogLevel.Verbose, fmt, args);
}

Log Level & Filter

대부분의 로깅 프레임워크와 마찬가지로 Fox Logging 역시 로그의 수준을 정의합니다. Fox Logging에서 사용하는 로그 수준은 다음과 같습니다.

  • Critical

심각한 수준의 문제를 나타내는 로그 수준입니다. 가장 높은 수준인 만큼 필터링을 통해 필터링되지 않습니다.

  • Error

예외(exception)과 같은 오류를 나타내는 로그 수준입니다.

  • Warning

오류는 아니지만 주의가 필요함을 나타내는 로그 수준입니다. Warning 로그 수준은 Fox Logging의 디폴트 필터 수준입니다.

  • Information

참고 사항 정도의 정보를 나타내는 로그 수준입니다.

  • Verbose

디버깅 혹은 추적(trace)용으로 사용되는 로그 수준입니다.

이들 로그 수준은 FoxLogLevel 열거 타입(enum type)으로 정의됩니다. 가장 높은 수준은 Critical 이며 가장 낮은 수준은 Verbose 입니다. 어플리케이션이 로그를 남길 때에는 항상 해당 로그가 어떤 로그 수준인지를 명시해야 합니다. 실제로 Write 메서드 및 WriteFormat 메서드는 매개변수로 로그 수준을 요구합니다.

로거는 로그 수준에 따라 로그를 기록하지 않도록 하는 필터링 기능을 제공합니다. 로거의 필터링은 IFoxLog.FilterLevel 속성을 통해 접근이 가능합니다. 기본 필터값은 Warning 이 사용됩니다.

Warning

IFoxLog.FilterLevel 속성 값을 변경하면 해당 로거를 사용하는 모든 코드들이 영향을 받습니다. 특히, 여러 스레드가 동시에 단일 로거를 사용하여 로그를 기록하는 상황에서 한 스레드가 필터값을 수정하면 다른 스레드들이 즉시 영향을 받게 됩니다.

구성 설정을 통해 로거가 생성될 때 로거의 필터 수준을 지정할 수 있습니다. 다음은 MyLogger 의 필터 수준을 Information 으로 지정하는 구성 설정입니다.

{
  "logging": {
    "loggers": {
      "MyLogger" : {
        "providerType": "NeoDEEX.Diagnostics.Loggers.FoxConsoleLoggerProvider"
        "filter": "Information"
      }
    }
  }
}

구성 설정에서 로거의 필터가 지정되지 않았다면 전역 필터 수준이 사용됩니다. 전역 필터 수준은 FoxLogManager.GlobalFilterLevel 속성을 통해 접근할 수 있습니다. 전역 필터 수준은 FoxLogManager.GlobalFilterLevel 속성을 직접 수정하거나 구성 설정을 통해 지정할 수 있습니다.

다음 구성 설정은 "logging:filter" 속성을 통해 전역 필터 수준을 Verbose 로 설정하고 있으며 SomeLogger 는 명시적으로 필터를 지정하지 않음으로서 전역 필터 수준인 Verbose 를 사용합니다.

{
  "logging": {
    "filter": "Verbose",
    "loggers": {
      "SomeLogger" : {
        "providerType": "NeoDEEX.Diagnostics.Loggers.FoxDebugLoggerProvider"
      }
    }
  }
}

필터값 보다 낮은 수준의 로그 기록은 필터링 되어 로그에 기록되지 않습니다. 예를 들어 필터가 Warning 으로 지정되면 Warning 이하의 로그 수준(InformationVerbose)은 로그에 기록되지 않습니다. 특정 로그 수준이 필터링 되는지 여부를 확인하난 방법으로 IFoxLog 인터페이스는 IsEnabled 메서드를 제공합니다. IsEnabled 메서드를 사용하여 현재 로거가 주어진 로그 수준을 필터링하는지 여부를 파악할 수 있습니다. 다음 코드는 로거가 어떤 로그 수준을 필터링하는지를 표시하는 예제 코드 입니다.

1
2
3
4
foreach(FoxLogLevel level in Enum.GetValues(typeof(FoxLogLevel)))
{
    Console.WriteLine($"{level} : { (log.IsEnabled(level) == true ? "logged" : "filtered") }");
}

Note

대체 로거로 사용되는 FoxDummyLogger 는 필터 설정에 무관하게 모든 로그 메시지를 필터링 합니다.

IFoxLog extension method

NeoDEEX.Diagnostics 네임스페이스의 FoxLogExtensions 정적 클래스는 IFoxLog 인터페이스를 위한 확장 메서드들을 제공합니다. 이들 확장 메서드들은 로깅 시 FoxLogLevel 매개변수를 생략할 수 있도록 해줍니다. 예를 들어 다음과 같은 코드가 있을 때,

1
2
3
4
5
6
IFoxLog log = FoxLogManager.GetLogger("MyLogger");
log.Write(FoxLogLevel.Critical, "This is a critical method.");
if (log.IsEnabled(FoxLogLevel.Information) == true)
{
    log.WriteFormat(FoxLogLevel.Information, "formatting log message... x64={0}", Environment.Is64BitProcess);
}

위 코드는 확장 메서드들을 이용하여 다음과 같이 작성할 수 있습니다.

1
2
3
4
5
6
IFoxLog log = FoxLogManager.GetLogger("MyLogger");
log.Critical("This is a critical method.");
if (log.InformationEnabled() == true)
{
    log.InformationFormat("formatting log message... x64={0}", Environment.Is64BitProcess);
}

확장 메서드를 사용하면 좀 더 간단하고 가독성 높은 코드를 작성할 수 있습니다. 하지만 확장 메서드들은 로그 소스를 명시할 수 없습니다. 이는 메서드 오버로드에서 모호성을 유발할 수 있기 때문입니다. 따라서 로그 소스를 명시하여 로그를 기록하고자 한다면 여전히 Write 혹은 WriteFormat 메서드를 사용해야 합니다.

예제 코드

이 문서에서 다루었던 예제 코드들은 Simple Loggging 예제 코드 에서 찾을 수 있습니다.