라이브러리에 ILogger, ILogger <T>, ILoggerFactory 또는 ILoggerProvider를 가져와야합니까?


113

이것은 AspNet Core의 생성자에 ILogger 또는 ILoggerFactory를 전달하는 것과 다소 관련이있을 수 있습니다 . 그러나 이것은 특히 라이브러리 디자인 에 관한 것이지 해당 라이브러리를 사용하는 실제 응용 프로그램이 로깅을 구현하는 방법에 관한 것이 아닙니다.

Nuget을 통해 설치 될 .net Standard 2.0 라이브러리를 작성하고 있으며 해당 라이브러리를 사용하는 사람들이 디버그 정보를 얻을 수 있도록 Microsoft.Extensions.Logging.Abstractions 에 의존 하여 표준화 된 로거를 삽입 할 수 있습니다.

그러나 여러 인터페이스를보고 있으며 웹의 샘플 코드는 때때로 ILoggerFactory클래스의 ctor에서 로거를 사용 하고 만듭니다. 또한 ILoggerProviderFactory의 읽기 전용 버전처럼 보이지만 구현은 두 인터페이스를 모두 구현할 수도 있고 구현하지 않을 수도 있으므로 선택해야합니다. (Factory는 Provider보다 더 일반적으로 보입니다).

내가 본 일부 코드는 비 제네릭 ILogger인터페이스를 사용하고 동일한 로거의 한 인스턴스를 공유 할 수도 있으며, 일부는 ILogger<T>ctor를 사용하여 DI 컨테이너가 개방형 제네릭 유형 또는 ILogger<T>내 라이브러리 의 각 및 모든 변형에 대한 명시 적 등록을 지원할 것으로 기대합니다. 사용합니다.

지금 당장 ILogger<T>은 이것이 올바른 접근 방식 이라고 생각합니다. 아마도 그 인수를 받아들이지 않고 대신 Null Logger를 전달하는 액터 일 수도 있습니다. 이렇게하면 로깅이 필요하지 않으면 아무 것도 사용되지 않습니다. 그러나 일부 DI 컨테이너는 가장 큰 액터를 선택하므로 어쨌든 실패합니다.

나는 내가하고있는 무슨의 궁금 되어 원하는 경우 여전히 적절한 로깅 지원을 허용하면서, 사용자를위한 두통의 최소 금액을 만들려면 여기 일을 할 수 있습니다.

답변:


113

정의

3 개의 인터페이스 : ILogger, ILoggerProviderILoggerFactory. 그들의 책임을 알아보기 위해 소스 코드 를 살펴 보겠습니다 .

ILogger : 주어진 로그 레벨 의 로그 메시지를 작성합니다 .

ILoggerProvider : 인스턴스 생성을 담당합니다 ILogger( ILoggerProvider로거 생성에 직접 사용해서는 안 됨 ).

ILoggerFactory : ILoggerProvider팩토리에 하나 이상의를 등록 할 수 있으며 , 이는 차례로 모든를 사용하여 ILogger. ILoggerFactory컬렉션을 보유하고 있습니다 ILoggerProviders.

아래 예에서는 2 개의 공급자 (콘솔 및 파일)를 공장에 등록하고 있습니다. 로거를 만들 때 팩토리는이 두 공급자를 모두 사용하여 로거 인스턴스를 만듭니다.

ILoggerFactory factory = new LoggerFactory().AddConsole();    // add console provider
factory.AddProvider(new LoggerFileProvider("c:\\log.txt"));   // add file provider
Logger logger = factory.CreateLogger(); // <-- creates a console logger and a file logger

따라서 로거 자체는 ILoggers 모음을 유지 하고 로그 메시지를 모두에 기록합니다. 로거 소스 코드를 보면 우리가 확인 할 수 Logger의 배열이 ILoggers(즉 LoggerInformation[])하고, 동시에 구현되는 ILogger인터페이스를.


의존성 주입

MS 문서 는 로거를 주입하는 두 가지 방법을 제공합니다.

1. 공장 주입 :

public TodoController(ITodoRepository todoRepository, ILoggerFactory logger)
{
    _todoRepository = todoRepository;
    _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
}

Category = TodoApi.Controllers.TodoController로 로거를 생성합니다 .

2. 제네릭 주입 ILogger<T>:

public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
{
    _todoRepository = todoRepository;
    _logger = logger;
}

TodoController의 Category = 정규화 된 유형 이름으로 로거를 생성합니다.


제 생각에 문서를 혼란스럽게 만드는 것은 비 제네릭을 주입하는 것에 대해 아무것도 언급하지 않는다는 것 ILogger입니다. 위의 동일한 예에서 우리는 비 제네릭을 주입 ITodoRepository하고 있지만 .NET에 대해 동일한 작업을 수행하지 않는 이유를 설명하지 않습니다 ILogger.

Mark Seemann 에 따르면 :

주입 생성자는 종속성을받는 것 이상을 수행해서는 안됩니다.

컨트롤러에 공장을 주입하는 것은 로거를 초기화하는 (SRP 위반) 컨트롤러의 책임이 아니기 때문에 좋은 접근 방식이 아닙니다. 동시에 제네릭을 주입하면 ILogger<T>불필요한 노이즈가 추가됩니다. 자세한 내용은 Simple Injector의 블로그를 참조하십시오. ASP.NET Core DI 추상화에 어떤 문제가 있습니까?

주입해야하는 것은 (적어도 위의 기사에 따라) 일반 ILogger이 아니지만 Microsoft의 내장 DI 컨테이너가 할 수있는 작업이 아니므로 타사 DI 라이브러리를 사용해야합니다. 문서는 .NET Core에서 타사 라이브러리를 사용하는 방법을 설명합니다.


이것은 Nikola Malovic의 또 다른 기사 로 IoC의 5 가지 법칙을 설명합니다.

니콜라의 IoC 제 4 법칙

확인되는 클래스의 모든 생성자는 자체 종속성 집합을 수락하는 것 외에 다른 구현을 가져서는 안됩니다.


3
귀하의 답변과 Steven의 기사는 가장 정확하고 동시에 가장 우울합니다.
bokibeg

30

를 제외하고 모두 유효합니다 ILoggerProvider. ILogger그리고 ILogger<T>당신은 로깅에 사용하기로하고 무엇. 를 얻으려면 ILogger, 당신은을 사용합니다 ILoggerFactory. ILogger<T>특정 카테고리에 대한 로거를 가져 오는 단축키입니다 (카테고리 유형에 대한 단축키).

를 사용하여 ILogger로깅을 수행 하면 등록 된 각 ILoggerProvider로그 메시지를 처리 ​​할 수 ​​있습니다. 코드를 사용하여 ILoggerProvider직접 호출하는 것은 실제로 유효하지 않습니다 .


감사. 이것은 ILoggerFactory하나의 의존성 ( "제게 공장을 주면 나만의 로거를 만들겠습니다" ) 만으로 소비자를위한 DI를 쉽게 연결할 수있는 방법 이라고 생각 하지만 기존 로거 ( 소비자가 일부 래퍼를 사용하지 않는 한). 촬영 ILogger- 일반 아닌지 - 소비자가 나에게 그들이 특별히 준비한 로거를 제공 할 수 있지만 잠재적으로 훨씬 더 복잡한 (A DI 컨테이너를 제외하고 지원 열기 제네릭 사용하는 것이)을 DI 설정을합니다. 맞습니까? 그럴 때는 공장에 갈 것 같아요.
Michael Stum

3
@MichaelStum-여기에 당신의 논리를 따르는 지 잘 모르겠습니다. 소비자가 DI 시스템을 사용하기를 기대하지만 라이브러리 내에서 종속성을 인계 받아 수동으로 관리하기를 원하십니까? 이것이 올바른 접근 방식으로 보이는 이유는 무엇입니까?
Damien_The_Unbeliever

@Damien_The_Unbeliever 좋은 지적입니다. 공장을 가져가는 것이 이상해 보입니다. 나는를 사용하는 대신에를 사용 하거나 심지어 대신 사용할 것이라고 생각합니다 ILogger<T>. 이렇게 하면 사용자는 DI 컨테이너에 공개 제네릭을 요구하지 않고 단일 등록만으로 연결할 수 있습니다. 일반적이지 않은쪽으로 기울지 만 주말 동안 많은 실험을 할 것입니다. ILogger<MyClass>ILoggerILogger
Michael Stum

10

ILogger<T>DI 위해 만들어 실제 하나였다. ILogger는 모든 DI 및 Factory 로직을 직접 작성하는 대신 asp.net 코어에서 가장 현명한 결정 중 하나 인 팩토리 패턴을 훨씬 더 쉽게 구현할 수 있도록 돕기 위해 왔습니다.

다음 중에서 선택할 수 있습니다.

ILogger<T>코드에서 공장과 DI 패턴을 사용 할 필요가있는 경우 또는 당신은 사용할 수 ILogger필요하지 DI 간단한 로깅을 구현하기.

따라서 The ILoggerProvider는 등록 된 각 로그의 메시지를 처리하기위한 다리 일뿐입니다. 코드에 개입해야하는 사항에 영향을주지 않으므로 사용할 필요가 없습니다. 등록 된 ILoggerProvider를 수신하고 메시지를 처리합니다. 그게 다예요.


1
ILogger 대 ILogger <T>는 DI와 어떤 관련이 있습니까? 둘 중 하나를 주사하지 않습니까?
Matthew Hostetler

실제로 MS Docs의 ILogger <TCategoryName>입니다. ILogger에서 파생되며 기록 할 클래스 이름을 제외하고는 새로운 기능을 추가하지 않습니다. 이것은 또한 고유 한 유형을 제공하므로 DI가 올바르게 명명 된 로거를 주입합니다. Microsoft 확장은 일반 this ILogger.
Samuel Danielson

8

질문에 집착하면서 ILogger<T>다른 옵션의 단점을 고려할 때 올바른 옵션이라고 생각합니다.

  1. 주입 ILoggerFactory은 사용자가 변경 가능한 글로벌 로거 팩토리의 제어권을 클래스 라이브러리에 넘겨 주도록 강제합니다. 더욱이, ILoggerFactory당신의 클래스 를 받아들임으로써 이제는 CreateLogger메소드로 임의의 카테고리 이름으로 로그에 쓸 수 있습니다 . ILoggerFactory일반적으로 DI 컨테이너에서 단일 항목으로 사용할 수 있지만 사용자로서 나는 라이브러리가 왜 그것을 사용 해야하는지 의심 할 것입니다.
  2. 방법 ILoggerProvider.CreateLogger은 비슷해 보이지만 주사를위한 것이 아닙니다. 함께 사용 ILoggerFactory.AddProvider되므로 팩토리는 등록 된 각 공급자로부터 생성 된 ILogger여러 개에 기록하는 집계 를 생성 할 수 있습니다 ILogger. 이는 구현을 검사 할 때 분명합니다.LoggerFactory.CreateLogger
  3. 수락 ILogger도 갈 길처럼 보이지만 .NET Core DI에서는 불가능합니다. 이것은 실제로 그들이 ILogger<T>처음 에 제공해야하는 이유처럼 들립니다 .

결국, 우리가 ILogger<T>그 클래스에서 선택한다면 우리는.

또 다른 접근법은 non-generic을 감싸는 다른 것을 주입하는 것 ILogger입니다.이 경우에는 non-generic이어야합니다. 아이디어는 자신의 클래스로 래핑하여 사용자가 구성하는 방법을 완전히 제어 할 수 있다는 것입니다.


6

기본 접근 방식은 ILogger<T>입니다. 즉, 전체 클래스 이름을 컨텍스트로 포함하기 때문에 특정 클래스의 로그가 로그에 명확하게 표시됩니다. 예를 들어 클래스의 전체 이름 MyLibrary.MyClass이이 클래스에서 생성 된 로그 항목에 표시됩니다. 예를 들면 :

MyLibrary.MyClass : Information : 내 정보 로그

ILoggerFactory고유 한 컨텍스트를 지정하려면를 사용해야합니다 . 예를 들어 라이브러리의 모든 로그에는 모든 클래스가 아닌 동일한 로그 컨텍스트가 있습니다. 예를 들면 :

loggerFactory.CreateLogger("MyLibrary");

그러면 로그는 다음과 같습니다.

MyLibrary : Information : 내 정보 로그

모든 클래스에서 그렇게하면 컨텍스트는 모든 클래스에 대한 MyLibrary가됩니다. 로그에 내부 클래스 구조를 노출하지 않으려면 라이브러리에 대해 그렇게하고 싶을 것이라고 생각합니다.

선택적 로깅에 관하여. 나는 항상 생성자에서 ILogger 또는 ILoggerFactory를 요구하고 라이브러리 소비자에게 그것을 남겨 두거나 로깅을 원하지 않는 경우 종속성 주입에서 아무것도하지 않는 Logger를 제공해야한다고 생각합니다. 구성에서 특정 컨텍스트에 대한 로깅을 설정하는 것은 매우 쉽습니다. 예를 들면 :

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyLibrary": "None"
    }
  }
}

5

라이브러리 디자인의 경우 좋은 접근 방식은 다음과 같습니다.

1. 소비자가 로거를 클래스에 주입하도록 강요하지 마십시오. NullLoggerFactory를 전달하는 다른 액터를 생성하기 만하면됩니다.

class MyClass
{
    private readonly ILoggerFactory _loggerFactory;

    public MyClass():this(NullLoggerFactory.Instance)
    {

    }
    public MyClass(ILoggerFactory loggerFactory)
    {
      this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
    }
}

2. 소비자가 로그 필터링을 쉽게 구성 할 수 있도록 로거를 만들 때 사용하는 범주 수를 제한합니다. this._loggerFactory.CreateLogger(Consts.CategoryName)

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