Tôi có nên sử dụng ILogger, ILogger <T>, ILoggerFactory hoặc ILoggerProvider cho thư viện không?


113

Điều này có thể phần nào liên quan đến Pass ILogger hoặc ILoggerFactory tới các hàm tạo trong AspNet Core? , tuy nhiên đây là vấn đề cụ thể về Thiết kế Thư viện , không phải về cách ứng dụng thực tế sử dụng các thư viện đó thực hiện ghi nhật ký của nó.

Tôi đang viết Thư viện .net Standard 2.0 sẽ được cài đặt qua Nuget và để cho phép những người sử dụng Thư viện đó nhận được một số thông tin gỡ lỗi, tôi phụ thuộc vào Microsoft.Extensions.Logging.Abstraction để cho phép đưa vào Logger chuẩn hóa.

Tuy nhiên, tôi đang thấy nhiều giao diện và mã mẫu trên web đôi khi sử dụng ILoggerFactoryvà tạo trình ghi nhật ký trong ctor của lớp. Cũng ILoggerProvidercó phiên bản giống như phiên bản chỉ đọc của Nhà máy, nhưng việc triển khai có thể hoặc không thể triển khai cả hai giao diện, vì vậy tôi phải chọn. (Nhà máy có vẻ phổ biến hơn Nhà cung cấp).

Một số mã mà tôi đã thấy sử dụng ILoggergiao diện không chung chung và thậm chí có thể chia sẻ một phiên bản của cùng một trình ghi nhật ký, và một số sử dụng ILogger<T>ctor của chúng và mong đợi vùng chứa DI hỗ trợ các loại chung mở hoặc đăng ký rõ ràng từng ILogger<T>biến thể trong thư viện của tôi sử dụng.

Ngay bây giờ, tôi nghĩ đó ILogger<T>là cách tiếp cận đúng và có thể một ctor không sử dụng đối số đó và thay vào đó chỉ chuyển Null Logger. Bằng cách đó, nếu không cần ghi nhật ký, không cần ghi nhật ký nào được sử dụng. Tuy nhiên, một số container DI chọn ctor lớn nhất và do đó sẽ không thành công.

Tôi tò mò về những gì tôi phải làm ở đây để ít gây đau đầu nhất cho người dùng, trong khi vẫn cho phép hỗ trợ ghi nhật ký thích hợp nếu muốn.

Câu trả lời:


113

Định nghĩa

Chúng tôi có 3 giao diện: ILogger, ILoggerProviderILoggerFactory. Hãy xem mã nguồn để tìm ra trách nhiệm của chúng:

ILogger : chịu trách nhiệm viết thông báo nhật ký của một Cấp độ nhật ký nhất định .

ILoggerProvider : chịu trách nhiệm tạo một phiên bản của ILogger(bạn không được phép sử dụng ILoggerProvidertrực tiếp để tạo trình ghi nhật ký)

ILoggerFactory : bạn có thể đăng ký một hoặc nhiều ILoggerProviders với nhà máy, lần lượt sử dụng tất cả chúng để tạo một phiên bản của ILogger. ILoggerFactorygiữ một bộ sưu tập của ILoggerProviders.

Trong ví dụ dưới đây, chúng tôi đang đăng ký 2 nhà cung cấp (bảng điều khiển và tệp) với nhà máy. Khi chúng tôi tạo một trình ghi nhật ký, nhà máy sử dụng cả hai nhà cung cấp này để tạo một phiên bản của trình ghi nhật ký:

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

Vì vậy, bản thân trình ghi nhật ký đang duy trì một tập hợp các ILoggers, và nó viết thông điệp nhật ký cho tất cả chúng. Nhìn vào mã nguồn Logger, chúng ta có thể xác nhận rằng Loggercó một mảng ILoggers(tức là LoggerInformation[]), đồng thời nó đang triển khai ILoggergiao diện.


Tiêm phụ thuộc

Tài liệu MS cung cấp 2 phương pháp để đưa vào trình ghi nhật ký:

1. Tiêm cho nhà máy:

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

tạo Logger với Category = TodoApi.Controllers.TodoController .

2. Tiêm thuốc chung ILogger<T>:

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

tạo một trình ghi nhật ký với Category = tên loại đủ điều kiện của TodoController


Theo tôi, điều làm cho tài liệu khó hiểu là nó không đề cập bất cứ điều gì về việc tiêm thuốc không chung chung , ILogger. Trong cùng một ví dụ ở trên, chúng ta đang tiêm một loại không chung chung ITodoRepositoryvà nó không giải thích tại sao chúng ta không làm như vậy đối với ILogger.

Theo Mark Seemann :

Một Injection Constructor không nên làm gì nhiều hơn là nhận các phụ thuộc.

Đưa một nhà máy vào Bộ điều khiển không phải là một cách tiếp cận tốt, vì Bộ điều khiển không có trách nhiệm khởi tạo Bộ ghi (vi phạm SRP). Đồng thời việc tiêm một loại thuốc chung ILogger<T>làm tăng thêm tiếng ồn không cần thiết. Xem blog của Simple Injector để biết thêm chi tiết: Có gì sai với sự trừu tượng của ASP.NET Core DI?

Những gì nên được đưa vào (ít nhất là theo bài viết trên) là một thứ không chung chung ILogger, nhưng sau đó, đó không phải là thứ mà Bộ chứa DI tích hợp của Microsoft có thể làm và bạn cần sử dụng Thư viện DI của bên thứ ba. Những hai tài liệu giải thích làm thế nào bạn có thể sử dụng thư viện của bên thứ 3 với NET Core.


Đây là một bài báo khác của Nikola Malovic, trong đó ông giải thích 5 định luật IoC của mình.

Định luật IoC thứ 4 của Nikola

Mọi phương thức khởi tạo của một lớp đang được giải quyết không được có bất kỳ triển khai nào khác ngoài việc chấp nhận một tập các phụ thuộc của chính nó.


3
Câu trả lời của bạn và bài viết của Steven là đúng nhất và đồng thời cũng khiến bạn chán nản nhất.
bokibeg

30

Tất cả đều hợp lệ ngoại trừ ILoggerProvider. ILoggerILogger<T>là những gì bạn phải sử dụng để ghi nhật ký. Để có được một ILogger, bạn sử dụng một ILoggerFactory. ILogger<T>là một phím tắt để lấy một trình ghi nhật ký cho một danh mục cụ thể (phím tắt cho loại là danh mục).

Khi bạn sử dụng ILoggerđể thực hiện ghi nhật ký, mỗi người đã đăng ký ILoggerProvidersẽ có cơ hội xử lý thông báo nhật ký đó. Nó không thực sự hợp lệ cho việc sử dụng mã để gọi vào ILoggerProvidertrực tiếp.


Cảm ơn. Điều này khiến tôi nghĩ rằng an ILoggerFactorysẽ là một cách dễ dàng để nạp tiền DI cho người tiêu dùng bằng cách chỉ có 1 người phụ thuộc ( "Chỉ cần cung cấp cho tôi một Nhà máy và tôi sẽ tạo bộ ghi nhật ký của riêng mình" ), nhưng sẽ ngăn cản việc sử dụng một bộ ghi hiện có ( trừ khi người tiêu dùng sử dụng một số trình bao bọc). Lấy một ILogger- chung chung hoặc không - cho phép người tiêu dùng cung cấp cho tôi bộ ghi nhật ký mà họ đã chuẩn bị đặc biệt, nhưng làm cho việc thiết lập DI có khả năng phức tạp hơn rất nhiều (trừ khi sử dụng bộ chứa DI hỗ trợ Open Generics). Điều đó có chính xác? Trong trường hợp đó, tôi nghĩ tôi sẽ đi cùng nhà máy.
Michael Stum

3
@MichaelStum - Tôi không chắc mình tuân theo logic của bạn ở đây. Bạn đang mong đợi người tiêu dùng của mình sử dụng hệ thống DI nhưng sau đó lại muốn tiếp quản và quản lý thủ công các phần phụ thuộc trong thư viện của bạn? Tại sao đó có vẻ là cách tiếp cận đúng?
Damien_The_Un Believer

@Damien_The_Un Believer Đó là một điểm tốt. Lấy một nhà máy có vẻ kỳ quặc. Tôi nghĩ rằng thay vì lấy một ILogger<T>, tôi sẽ lấy một ILogger<MyClass>hoặc thậm chí chỉ ILoggerthay vào đó - theo cách đó, người dùng có thể kết nối nó chỉ với một đăng ký duy nhất và không yêu cầu các generic mở trong vùng chứa DI của họ. Nghiêng về những thứ không chung chung ILogger, nhưng sẽ thử nghiệm nhiều vào cuối tuần.
Michael Stum

10

Đó ILogger<T>là cái thực sự được tạo ra cho DI. ILogger ra đời để giúp triển khai mô hình nhà máy dễ dàng hơn nhiều, thay vì bạn phải tự viết tất cả logic DI và Nhà máy, đó là một trong những quyết định thông minh nhất trong lõi asp.net.

Bạn có thể chọn giữa:

ILogger<T>nếu bạn có nhu cầu sử dụng các mẫu nhà máy và DI trong mã của mình hoặc bạn có thể sử dụng ILogger, để thực hiện ghi nhật ký đơn giản mà không cần DI.

cho rằng, The ILoggerProviderchỉ là một cầu nối để xử lý từng thông điệp của nhật ký đã đăng ký. Không cần phải sử dụng nó, vì nó không ảnh hưởng đến bất cứ điều gì mà bạn nên can thiệp vào mã, Nó sẽ lắng nghe ILoggerProvider đã đăng ký và xử lý các thông báo. Đó là về nó.


1
ILogger vs ILogger <T> phải làm gì với DI? Hoặc sẽ được tiêm, không?
Matthew Hostetler

Nó thực sự là ILogger <TCategoryName> trong MS Docs. Nó bắt nguồn từ ILogger và không thêm chức năng mới ngoại trừ tên lớp để ghi nhật ký. Điều này cũng cung cấp một kiểu duy nhất để DI đưa vào trình ghi nhật ký được đặt tên chính xác. Phần mở rộng của Microsoft mở rộng không chung chung this ILogger.
Samuel Danielson

8

Bám sát câu hỏi, tôi tin rằng đó ILogger<T>là lựa chọn đúng đắn, xem xét mặt trái của các lựa chọn khác:

  1. Việc tiêm ILoggerFactorybuộc người dùng của bạn phải trao quyền kiểm soát nhà máy ghi nhật ký toàn cầu có thể thay đổi cho thư viện lớp của bạn. Hơn nữa, bằng cách chấp nhận ILoggerFactorylớp của bạn bây giờ có thể ghi vào nhật ký với bất kỳ tên danh mục tùy ý nào với CreateLoggerphương thức. Mặc dù ILoggerFactorythường có sẵn dưới dạng singleton trong DI container, tôi với tư cách là người dùng sẽ nghi ngờ tại sao bất kỳ thư viện nào cũng cần sử dụng nó.
  2. Mặc dù phương pháp ILoggerProvider.CreateLoggertrông giống như vậy nhưng nó không được dùng để tiêm. Nó được sử dụng ILoggerFactory.AddProviderđể nhà máy có thể tạo tổng hợp ILoggerghi vào nhiều ILoggerđược tạo từ mỗi nhà cung cấp đã đăng ký. Điều này rõ ràng khi bạn kiểm tra việc triển khaiLoggerFactory.CreateLogger
  3. Chấp nhận ILoggercũng giống như một cách để thực hiện, nhưng với .NET Core DI thì không thể. Điều này thực sự giống như lý do tại sao họ cần cung cấp ILogger<T>ngay từ đầu.

Vì vậy, suy cho cùng, chúng ta không có lựa chọn nào tốt hơn ILogger<T>, nếu chúng ta được chọn từ những lớp học đó.

Một cách tiếp cận khác sẽ là đưa một thứ gì đó khác bao bọc không chung chung ILogger, trong trường hợp này phải là một thứ không chung chung. Ý tưởng là bằng cách gói nó bằng lớp của riêng bạn, bạn có toàn quyền kiểm soát cách người dùng có thể cấu hình nó.


6

Phương pháp tiếp cận mặc định có nghĩa là ILogger<T>. Điều này có nghĩa là trong nhật ký, các bản ghi từ lớp cụ thể sẽ được hiển thị rõ ràng vì chúng sẽ bao gồm tên lớp đầy đủ làm ngữ cảnh. Ví dụ: nếu tên đầy đủ của lớp của bạn là MyLibrary.MyClassbạn sẽ nhận được điều này trong các mục nhật ký được tạo bởi lớp này. Ví dụ:

MyLibrary.MyClass: Thông tin: Nhật ký thông tin của tôi

Bạn nên sử dụng ILoggerFactorynếu bạn muốn chỉ định ngữ cảnh của riêng bạn. Ví dụ: tất cả nhật ký từ thư viện của bạn có cùng ngữ cảnh nhật ký thay vì mọi lớp. Ví dụ:

loggerFactory.CreateLogger("MyLibrary");

Và sau đó nhật ký sẽ trông như thế này:

MyLibrary: Thông tin: Nhật ký thông tin của tôi

Nếu bạn làm điều đó trong tất cả các lớp thì ngữ cảnh sẽ chỉ là MyLibrary cho tất cả các lớp. Tôi tưởng tượng bạn sẽ muốn làm điều đó cho một thư viện nếu bạn không muốn để lộ cấu trúc lớp bên trong trong nhật ký.

Về việc ghi nhật ký tùy chọn. Tôi nghĩ rằng bạn nên luôn yêu cầu ILogger hoặc ILoggerFactory trong phương thức khởi tạo và để nó cho người tiêu dùng của thư viện tắt nó đi hoặc cung cấp Trình ghi nhật ký không thực hiện gì trong việc tiêm phụ thuộc nếu họ không muốn ghi nhật ký. Rất dễ dàng chuyển đổi nhật ký cho một ngữ cảnh cụ thể trong cấu hình. Ví dụ:

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

5

Đối với thiết kế thư viện, cách tiếp cận tốt sẽ là:

1.Không buộc người tiêu dùng đưa trình ghi nhật ký vào các lớp của bạn. Đơn giản chỉ cần tạo một ctor khác truyền NullLoggerFactory.

class MyClass
{
    private readonly ILoggerFactory _loggerFactory;

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

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

2. Giới hạn số lượng danh mục mà bạn sử dụng khi tạo nhật ký để cho phép người tiêu dùng định cấu hình lọc nhật ký dễ dàng. this._loggerFactory.CreateLogger(Consts.CategoryName)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.