로거 래퍼 모범 사례


91

내 응용 프로그램에서 nlogger를 사용하고 싶습니다. 나중에 로깅 시스템을 변경해야 할 수도 있습니다. 그래서 저는 로깅 파사드를 사용하고 싶습니다.

기존 예제를 작성하는 방법에 대한 권장 사항을 알고 있습니까? 또는이 분야의 모범 사례에 대한 링크를 제공하십시오.


4


답변:


207

Common.Logging 과 같은 로깅 파사드를 사용 했지만 (저의 CuttingEdge.Logging 라이브러리 를 숨기는 경우에도 ) 요즘에는 Dependency Injection 패턴을 사용 하며이를 통해 두 종속성 을 모두 준수하는 내 자신의 (간단한) 추상화 뒤에 로거를 숨길 수 있습니다. 반전 원리인터페이스 분리 원리(ISP) 멤버가 하나 있고 인터페이스가 내 애플리케이션에 의해 정의되기 때문입니다. 외부 라이브러리가 아닙니다. 애플리케이션의 핵심 부분이 외부 라이브러리의 존재에 대한 지식을 최소화하는 것이 좋습니다. 로깅 라이브러리를 교체 할 의도가없는 경우에도 마찬가지입니다. 외부 라이브러리에 대한 엄격한 종속성으로 인해 코드 테스트가 더 어려워지고 애플리케이션을 위해 특별히 설계되지 않은 API로 애플리케이션이 복잡해집니다.

이것은 내 응용 프로그램에서 종종 추상화가 보이는 것입니다.

public interface ILogger
{
    void Log(LogEntry entry);
}

public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };

// Immutable DTO that contains the log information.
public class LogEntry 
{
    public readonly LoggingEventType Severity;
    public readonly string Message;
    public readonly Exception Exception;

    public LogEntry(LoggingEventType severity, string message, Exception exception = null)
    {
        if (message == null) throw new ArgumentNullException("message");
        if (message == string.Empty) throw new ArgumentException("empty", "message");

        this.Severity = severity;
        this.Message = message;
        this.Exception = exception;
    }
}

선택적으로이 추상화는 몇 가지 간단한 확장 방법으로 확장 할 수 있습니다 (인터페이스가 좁아지고 ISP를 계속 준수하도록 허용). 이렇게하면이 인터페이스의 소비자를위한 코드가 훨씬 간단 해집니다.

public static class LoggerExtensions
{
    public static void Log(this ILogger logger, string message) {
        logger.Log(new LogEntry(LoggingEventType.Information, message));
    }

    public static void Log(this ILogger logger, Exception exception) {
        logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
    }

    // More methods here.
}

인터페이스는 단지 하나의 방법을 포함하기 때문에, 당신은 쉽게 만들 수 있습니다 ILogger구현하는 log4net에 프록시 , Serilog에 , Microsoft.Extensions.Logging , NLog 또는 다른 로깅 라이브러리를하고이 클래스에 주입하기 위해 DI 컨테이너를 구성 ILogger에서 자신의 건설자.

단일 메서드가있는 인터페이스 위에 정적 확장 메서드를 사용하는 것은 많은 멤버가있는 인터페이스를 사용하는 것과 상당히 다릅니다. 확장 메서드는 LogEntry메시지 를 만들고 ILogger인터페이스 의 유일한 메서드를 통해 전달 하는 도우미 메서드 일뿐 입니다. 확장 메서드는 소비자 코드의 일부가됩니다. 추상화의 일부가 아닙니다. 이것은 추상화, 확장 방법 및 확장 방법을 변경할 필요없이 확장 방법을 발전시킬 수있을뿐만 아니라LogEntry생성자는 로거 추상화가 사용될 때 항상 실행됩니다. 심지어 해당 로거가 스텁 / 모의 된 경우에도 마찬가지입니다. 이것은 테스트 스위트에서 실행될 때 로거에 대한 호출의 정확성에 대해 더 확실하게합니다. 1 인 인터페이스는 테스트를 훨씬 쉽게 만듭니다. 많은 구성원이있는 추상화를 사용하면 구현 (예 : 모의, 어댑터 및 데코레이터)을 만들기가 어렵습니다.

이렇게하면 로깅 파사드 (또는 다른 라이브러리)가 제공 할 수있는 정적 추상화가 거의 필요하지 않습니다.


4
@GabrielEspinoza : 확장 메서드를 배치하는 네임 스페이스에 전적으로 의존합니다. 인터페이스와 동일한 네임 스페이스 또는 프로젝트의 루트 네임 스페이스에 배치하면 문제가 발생하지 않습니다.
Steven

2
@ user1829319 단지 예일뿐입니다. 귀하의 특정 요구에 맞는이 답변을 기반으로 구현할 수 있다고 확신합니다.
Steven

2
나는 아직도 그것을 얻지 못한다 ... 5 Logger 메서드를 ILogger의 확장으로 사용하고 ILogger의 구성원이 아닌 이점은 어디입니까?
Elisabeth

3
@Elisabeth 장점은 "ILogger :: Log"라는 단일 함수를 구현하여 모든 로깅 프레임 워크에 Facade 인터페이스를 적용 할 수 있다는 것입니다. 확장 메서드는 사용하기로 결정한 프레임 워크에 관계없이 '편리한'API (예 : "LogError", "LogWarning"등)에 액세스 할 수 있도록합니다. C # 인터페이스로 작업 함에도 불구하고 일반적인 '기본 클래스'기능을 추가하는 방법입니다.
BTownTKD

2
이것이 좋은 이유를 다시 강조 할 필요가 있습니다. 코드를 DotNetFramework에서 DotNetCore로 상향 변환합니다. 이 작업을 수행 한 프로젝트에서는 새 콘크리트 하나만 작성하면되었습니다. 내가하지 않은 것들 .... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! 이 "돌아가는"것을 발견하게되어 기쁩니다.
granadaCoder


8

현재로서는 가장 좋은 방법은 Microsoft.Extensions.Logging 패키지 를 사용하는 것입니다 (Julian가 지적한대로 ). 대부분의 로깅 프레임 워크를 이것과 함께 사용할 수 있습니다.

Steven의 답변에 설명 된대로 고유 한 인터페이스를 정의하는 것은 간단한 경우에 적합하지만 중요하다고 생각하는 몇 가지 사항이 누락되었습니다.

  • 구조화 된 로깅 및 구조 해제 객체 (Serilog 및 NLog의 @ 표기법)
  • 지연된 문자열 구성 / 포맷 : 문자열을 가져 오므로 호출시 모든 것을 평가 / 포맷해야합니다. 결국 이벤트가 임계 값 (성능 비용, 이전 포인트 참조) 미만이기 때문에 기록되지 않더라도
  • IsEnabled(LogLevel)다시 한 번 공연상의 이유로 원하는 조건부 검사

이 모든 것을 자신의 추상화로 구현할 수 있지만 그 시점에서 바퀴를 재발 명하게 될 것입니다.


4

일반적으로 나는 다음과 같은 인터페이스를 만드는 것을 선호합니다.

public interface ILogger
{
 void LogInformation(string msg);
 void LogError(string error);
}

런타임에서이 인터페이스에서 구현 된 구체적인 클래스를 삽입합니다.


11
그리고 잊지 마세요 LogWarningLogCritical방법과 모든 오버로드. 이렇게하면 인터페이스 분리 원칙을 위반하게됩니다 . ILogger단일 Log메소드로 인터페이스를 정의하는 것이 좋습니다.
Steven

2
정말 미안 해요. 제 의도가 아니 었어요. 부끄러워 할 필요가 없습니다. 많은 개발자가 인기있는 log4net (이 정확한 디자인을 사용하는)을 예로 사용하기 때문에이 디자인을 자주 봅니다. 불행히도 그 디자인은 정말 좋지 않습니다.
스티븐

2
@Steven의 답변보다 이것을 선호합니다. 그는에 의존 소개 LogEntry, 따라서에 대한 종속성 LoggingEventType. ILogger구현이 처리해야 LoggingEventTypes아마하지만, case/switch코드 냄새 . LoggingEventTypes의존성을 숨기는 이유는 무엇 입니까? 구현은 어쨌든 로깅 수준을 처리해야 하므로 일반 인수를 사용하여 단일 메서드 뒤에 숨기는 것보다 구현이 수행해야하는 작업 을 명시 하는 것이 좋습니다 .
DharmaTurtle

1
극단적 인 예로서, 상상 ICommand갖는 Handle걸린다을 object. 구현은 case/switch인터페이스의 계약을 이행하기 위해 가능한 유형을 넘어야합니다. 이것은 이상적이지 않습니다. 어쨌든 처리해야하는 종속성을 숨기는 추상화가 없습니다. 대신 "모든 로거가 경고, 오류, 치명적 오류 등을 처리 할 것으로 예상합니다"와 같이 예상되는 것을 명확하게 설명하는 인터페이스를 사용하십시오. 이것은 "모든 로거가 경고, 오류, 치명적 등 을 포함하는 메시지 를 처리 할 것으로 기대합니다."보다 바람직합니다 .
DharmaTurtle

@Steven과 @DharmaTurtle 모두에 동의합니다. 게다가 타입은 클래스이고 OOP에서 그렇게 코딩 LoggingEventType되어야하기 때문에 호출 LoggingEventLevel되어야합니다. 나를 위해 인터페이스 방법을 사용하지 않는 것과 해당 enum값을 사용하지 않는 것 사이에는 차이가 없습니다 . 대신에 사용 ErrorLoggger : ILogger, InformationLogger : ILogger그것은 자신의 레벨마다 로거 정의를 어디. 그런 다음 DI는 키 (열거 형)를 통해 필요한 로거를 삽입해야하지만이 키는 더 이상 인터페이스의 일부가 아닙니다. (당신은 이제 SOLID입니다).
Wouter

4

이 문제에 대한 훌륭한 해결책이 LibLog 프로젝트 의 형태로 나타났습니다 .

LibLog는 Serilog, NLog, Log4net 및 Enterprise 로거를 포함한 주요 로거에 대한 지원이 내장 된 로깅 추상화입니다. NuGet 패키지 관리자를 통해 대상 라이브러리에 .dll 참조 대신 소스 (.cs) 파일로 설치됩니다. 이러한 접근 방식을 사용하면 라이브러리가 외부 종속성을 가지지 않고도 로깅 추상화를 포함 할 수 있습니다. 또한 라이브러리 작성자는 사용하는 응용 프로그램이 라이브러리에 로거를 명시 적으로 제공하지 않고도 로깅을 포함 할 수 있습니다. LibLog는 리플렉션을 사용하여 사용중인 구체적인 로거를 파악하고 라이브러리 프로젝트에서 명시적인 배선 코드없이 연결합니다.

따라서 LibLog는 라이브러리 프로젝트 내에서 로깅을위한 훌륭한 솔루션입니다. 메인 애플리케이션 또는 서비스에서 구체적인 로거 (승리를위한 Serilog)를 참조하고 구성하고 라이브러리에 LibLog를 추가하십시오!


나는 이것을 사용하여 log4net 브레이킹 체인지 문제 (yuck) ( wiktorzychla.com/2012/03/pathetic-breaking-change-between.html ) 너겟에서 이것을 얻으면 실제로 .cs 파일을 생성합니다 미리 컴파일 된 dll에 대한 참조를 추가하는 대신 코드에서. .cs 파일은 프로젝트에 네임 스페이스가 지정됩니다. 따라서 서로 다른 계층 (csprojs)이있는 경우 여러 버전이 있거나 공유 된 csproj로 통합해야합니다. 그것을 사용하려고 할 때 이것을 알아낼 것입니다. 그러나 내가 말했듯이 이것은 log4net 주요 변경 문제로 인한 생명의 은인이었습니다.
granadaCoder


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.