Phương pháp hay nhất về trình bao bọc trình ghi nhật ký


91

Tôi muốn sử dụng một nlogger trong ứng dụng của mình, có thể trong tương lai tôi sẽ cần thay đổi hệ thống ghi nhật ký. Vì vậy, tôi muốn sử dụng một mặt tiền khai thác gỗ.

Bạn có biết bất kỳ khuyến nghị nào cho các ví dụ hiện có về cách viết những ví dụ đó không? Hoặc chỉ cho tôi liên kết đến một số phương pháp hay nhất trong lĩnh vực này.




Câu trả lời:


207

Tôi đã từng sử dụng các mặt tiền ghi nhật ký như Common.Logging (thậm chí để ẩn thư viện CutEdge.Logging của riêng mình ), nhưng ngày nay tôi sử dụng mẫu Dependency Injection và điều này cho phép tôi ẩn các log đằng sau phần trừu tượng (đơn giản) của riêng tôi tuân theo cả Dependency Nguyên tắc nghịch đảonguyên tắc phân tách giao diện(ISP) vì nó có một thành viên và vì giao diện được xác định bởi ứng dụng của tôi; không phải là một thư viện bên ngoài. Giảm thiểu kiến ​​thức mà các phần cốt lõi của ứng dụng của bạn có về sự tồn tại của các thư viện bên ngoài, thì càng tốt; ngay cả khi bạn không có ý định thay thế thư viện ghi nhật ký của mình. Việc phụ thuộc nhiều vào thư viện bên ngoài khiến việc kiểm tra mã của bạn trở nên khó khăn hơn và làm phức tạp ứng dụng của bạn với một API chưa bao giờ được thiết kế riêng cho ứng dụng của bạn.

Đây là những gì trừu tượng thường trông giống như trong các ứng dụng của tôi:

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;
    }
}

Theo tùy chọn, phần trừu tượng này có thể được mở rộng bằng một số phương pháp mở rộng đơn giản (cho phép giao diện thu hẹp và tiếp tục tuân thủ ISP). Điều này làm cho mã cho người tiêu dùng của giao diện này đơn giản hơn nhiều:

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.
}

Vì giao diện chỉ chứa một phương thức duy nhất, bạn có thể dễ dàng tạo một ILoggertriển khai proxy tới log4net , tới Serilog , Microsoft.Extensions.Logging , NLog hoặc bất kỳ thư viện ghi nhật ký nào khác và định cấu hình vùng chứa DI của bạn để đưa nó vào các lớp cóILogger trong chúng constructor.

Xin lưu ý rằng việc có các phương thức mở rộng tĩnh trên đầu giao diện với một phương thức duy nhất khá khác với việc có một giao diện có nhiều thành viên. Các phương thức mở rộng chỉ là các phương thức trợ giúp tạo LogEntrythông báo và chuyển nó qua phương thức duy nhất trên ILoggergiao diện. Các phương pháp mở rộng trở thành một phần của mã của người tiêu dùng; không phải là một phần của trừu tượng. Điều này không chỉ cho phép các phương thức mở rộng phát triển mà không cần thay đổi phần trừu tượng, các phương thức mở rộng vàLogEntryhàm tạo luôn được thực thi khi tính trừu tượng của trình ghi nhật ký được sử dụng, ngay cả khi trình ghi nhật ký đó được khai thác / chế nhạo. Điều này mang lại sự chắc chắn hơn về tính đúng đắn của các lệnh gọi đến trình ghi nhật ký khi chạy trong bộ thử nghiệm. Giao diện một ghi nhớ cũng làm cho việc kiểm tra dễ dàng hơn nhiều; Có một sự trừu tượng với nhiều thành viên khiến cho việc tạo triển khai (chẳng hạn như mocks, adapter và decorator) trở nên khó khăn.

Khi bạn làm điều này, hầu như không cần đến một số trừu tượng tĩnh mà các mặt đăng nhập (hoặc bất kỳ thư viện nào khác) có thể cung cấp.


4
@GabrielEspinoza: Điều đó hoàn toàn phụ thuộc vào không gian tên mà bạn đặt các phương thức mở rộng vào. Nếu bạn đặt nó trong cùng không gian tên với giao diện hoặc trong không gian tên gốc của dự án thì vấn đề sẽ không tồn tại.
Steven

2
@ user1829319 Đó chỉ là một ví dụ. Tôi chắc rằng bạn có thể đưa ra cách triển khai dựa trên câu trả lời này phù hợp với nhu cầu cụ thể của bạn.
Steven

2
Tôi vẫn không hiểu ... lợi thế của việc có 5 phương thức Logger là phần mở rộng của ILogger và không phải là thành viên của ILogger là ở đâu?
Elisabeth

3
@Elisabeth Lợi ích là bạn có thể điều chỉnh giao diện mặt tiền cho BẤT KỲ khung ghi nhật ký nào, chỉ đơn giản bằng cách triển khai một chức năng duy nhất: "ILogger :: Log." Các phương thức mở rộng đảm bảo rằng chúng tôi có quyền truy cập vào các API 'tiện lợi' (chẳng hạn như "LogError", "LogWarning," v.v.), bất kể bạn quyết định sử dụng khuôn khổ nào. Đó là một cách vòng vo để thêm chức năng 'lớp cơ sở' phổ biến, mặc dù làm việc với giao diện C #.
BTownTKD

2
Tôi cần phải nhấn mạnh lại một lần nữa lý do tại sao điều này là tuyệt vời. Nâng cấp chuyển đổi mã của bạn từ DotNetFramework sang DotNetCore. Những dự án mà tôi đã làm điều này, tôi chỉ phải viết một bê tông duy nhất. Những cái mà tôi không .... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! Tôi rất vui vì tôi đã tìm thấy "con đường trở lại" này.
granadaCoder


8

Hiện tại, cách tốt nhất là sử dụng gói Microsoft.Extensions.Logging ( như Julian đã chỉ ra ). Hầu hết các khung ghi nhật ký có thể được sử dụng với điều này.

Xác định giao diện của riêng bạn, như đã giải thích trong câu trả lời của Steven là OK đối với các trường hợp đơn giản, nhưng nó thiếu một số điều mà tôi cho là quan trọng:

  • Ghi nhật ký có cấu trúc và đối tượng hủy cấu trúc (ký hiệu @ trong Serilog và NLog)
  • Cấu trúc / định dạng chuỗi bị trì hoãn: vì nó có một chuỗi, nó phải đánh giá / định dạng mọi thứ khi được gọi, ngay cả khi cuối cùng thì sự kiện sẽ không được ghi lại vì nó ở dưới ngưỡng (chi phí hiệu suất, xem phần trước)
  • Kiểm tra có điều kiện như IsEnabled(LogLevel)bạn có thể muốn, vì lý do biểu diễn một lần nữa

Bạn có thể thực hiện tất cả những điều này theo cách trừu tượng của riêng bạn, nhưng tại thời điểm đó, bạn sẽ phải phát minh lại bánh xe.


4

Nói chung tôi thích tạo một giao diện như

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

và trong thời gian chạy, tôi chèn một lớp cụ thể được triển khai từ giao diện này.


11
Và đừng quên LogWarningLogCriticalphương pháp và tất cả quá tải của họ. Khi làm điều này, bạn sẽ vi phạm Nguyên tắc phân tách giao diện . Thích xác định ILoggergiao diện bằng một Logphương pháp duy nhất .
Steven

2
Tôi thực sự xin lỗi, đó không phải là ý định của tôi. Không cần phải xấu hổ. Tôi thấy thiết kế này thực sự thường xuyên vì nhiều nhà phát triển sử dụng log4net phổ biến (sử dụng thiết kế chính xác này) làm ví dụ. Thật không may, thiết kế đó không thực sự tốt.
Steven

2
Tôi thích câu trả lời này hơn câu trả lời của @ Steven. Anh ta giới thiệu một sự phụ thuộc vào LogEntryvà do đó sự phụ thuộc vàoLoggingEventType . Tuy nhiên, ILoggerviệc triển khai phải giải quyết những điều này LoggingEventTypes, có thể case/switchmùi mã . Tại sao phải ẩn sự LoggingEventTypesphụ thuộc? Dù sao thì việc triển khai cũng phải xử lý các mức ghi nhật ký , vì vậy tốt hơn là bạn nên trình bày rõ ràng về những gì một triển khai nên làm, thay vì ẩn nó sau một phương thức duy nhất với một đối số chung chung.
DharmaTurtle,

1
Như một ví dụ cực đoan, hãy tưởng tượng một ICommandcái cóHandle cái nhận một cái object. Việc triển khai phải case/switchvượt quá các loại có thể để hoàn thành hợp đồng của giao diện. Điều này không lý tưởng. Đừng có một phần trừu tượng ẩn một phần phụ thuộc mà dù sao cũng phải xử lý. Thay vào đó, có một giao diện nói rõ ràng những gì được mong đợi: "Tôi mong đợi tất cả các trình ghi nhật ký xử lý Cảnh báo, Lỗi, Tử vong, v.v.". Điều này thích hợp hơn là "Tôi mong đợi tất cả các trình ghi nhật ký xử lý các thông báo bao gồm Cảnh báo, Lỗi, Tử vong, v.v."
DharmaTurtle,

Tôi đồng ý với cả @Steven và @DharmaTurtle. ngoài raLoggingEventType nên được gọi LoggingEventLevellà các loại là các lớp và nên được mã hóa như vậy trong OOP. Đối với tôi không có sự khác biệt giữa việc không sử dụng phương thức giao diện và không sử dụng enumgiá trị tương ứng . Thay vào đó ErrorLoggger : ILogger, hãy sử dụng , InformationLogger : ILoggernơi mọi trình ghi nhật ký xác định cấp độ riêng của nó. Sau đó DI cần đưa các bộ ghi cần thiết vào, có thể thông qua một khóa (enum), nhưng khóa này không còn là một phần của giao diện. (Bây giờ bạn là RẮN).
Wouter

4

Một giải pháp tuyệt vời cho vấn đề này đã xuất hiện dưới dạng dự án LibLog .

LibLog là một bản tóm tắt ghi nhật ký với hỗ trợ tích hợp cho các bộ ghi chính bao gồm Serilog, NLog, Log4net và Enterprise logger. Nó được cài đặt thông qua trình quản lý gói NuGet vào thư viện đích dưới dạng tệp nguồn (.cs) thay vì tham chiếu .dll. Cách tiếp cận đó cho phép tính toán tóm tắt ghi nhật ký mà không buộc thư viện phải phụ thuộc vào bên ngoài. Nó cũng cho phép tác giả thư viện bao gồm ghi nhật ký mà không buộc ứng dụng tiêu thụ phải cung cấp rõ ràng trình ghi nhật ký cho thư viện. LibLog sử dụng sự phản chiếu để tìm ra trình ghi cụ thể nào đang được sử dụng và kết nối với nó mà không cần bất kỳ mã dây rõ ràng nào trong (các) dự án thư viện.

Vì vậy, LibLog là một giải pháp tuyệt vời để đăng nhập vào các dự án thư viện. Chỉ cần tham khảo và định cấu hình một trình ghi nhật ký cụ thể (Serilog cho win) trong ứng dụng hoặc dịch vụ chính của bạn và thêm LibLog vào thư viện của bạn!


Tôi đã sử dụng điều này để khắc phục sự cố thay đổi phá vỡ log4net (yuck) ( wiktorzychla.com/2012/03/pathetic-break-change-between.html ) Nếu bạn nhận được điều này từ nuget, nó sẽ thực sự tạo tệp .cs trong mã của bạn thay vì thêm các tham chiếu đến các dll được biên dịch trước. Tệp .cs được đặt ở vị trí chung cho dự án của bạn. Vì vậy, nếu bạn có các lớp khác nhau (csproj), bạn sẽ có nhiều phiên bản hoặc bạn cần hợp nhất thành một csproj được chia sẻ. Bạn sẽ tìm ra điều này khi bạn cố gắng sử dụng nó. Nhưng như tôi đã nói, đây là một cứu cánh với vấn đề thay đổi phá vỡ log4net.
granadaCoder


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.