Chcę używać nloggera w mojej aplikacji, być może w przyszłości będę musiał zmienić system logowania. Dlatego chcę użyć elewacji z drewna.
Czy znasz jakieś zalecenia dotyczące istniejących przykładów, jak je napisać? Lub po prostu podaj mi link do najlepszych praktyk w tej dziedzinie.
Odpowiedzi:
Kiedyś używałem fasad rejestrowania, takich jak Common.Logging (nawet w celu ukrycia mojej własnej biblioteki CuttingEdge.Logging ), ale obecnie używam wzorca Dependency Injection, co pozwala mi ukryć rejestratory za własną (prostą) abstrakcją, która jest zgodna z obydwoma Zależnościami Zasada inwersji i zasada segregacji interfejsów(ISP), ponieważ ma jednego członka i ponieważ interfejs jest zdefiniowany przez moją aplikację; nie jest to biblioteka zewnętrzna. Im mniej wiedzy na temat istnienia bibliotek zewnętrznych, które mają podstawowe części aplikacji, tym lepiej; nawet jeśli nie masz zamiaru kiedykolwiek zastępować swojej biblioteki rejestrowania. Twarda zależność od biblioteki zewnętrznej utrudnia testowanie kodu i komplikuje aplikację za pomocą interfejsu API, który nigdy nie został zaprojektowany specjalnie dla tej aplikacji.
Tak często wygląda abstrakcja w moich aplikacjach:
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; } }
Opcjonalnie tę abstrakcję można rozszerzyć za pomocą kilku prostych metod rozszerzających (pozwalając interfejsowi pozostać wąskim i zachować zgodność z dostawcą usług internetowych). To sprawia, że kod dla użytkowników tego interfejsu jest znacznie prostszy:
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. }
Ponieważ interfejs zawiera tylko jedną metodę, możesz łatwo utworzyć
ILogger
implementację, która będzie pośredniczyła w log4net , Serilog , Microsoft.Extensions.Logging , NLog lub dowolnej innej bibliotece rejestrowania i skonfiguruj swój kontener DI tak, aby wstrzykiwał go w klasach, które mająILogger
w swoim konstruktor.Zwróć uwagę, że posiadanie statycznych metod rozszerzających na interfejsie z pojedynczą metodą różni się od posiadania interfejsu z wieloma składowymi. Metody rozszerzające to tylko metody pomocnicze, które tworzą
LogEntry
wiadomość i przekazują ją przez jedyną metodę wILogger
interfejsie. Metody rozszerzające stają się częścią kodu konsumenta; nie jest częścią abstrakcji. Nie tylko umożliwia to ewolucję metod rozszerzających bez konieczności zmiany abstrakcji, metod rozszerzających i rozszerzeniaLogEntry
konstruktor są zawsze wykonywane, gdy używana jest abstrakcja rejestratora, nawet jeśli ten rejestrator jest ukryty / mockowany. Daje to większą pewność co do poprawności wywołań programu rejestrującego podczas uruchamiania w zestawie testów. Jednoczłonowy interfejs również znacznie ułatwia testowanie; Posiadanie abstrakcji z wieloma członkami utrudnia tworzenie implementacji (takich jak makiety, adaptery i dekoratory).Kiedy to robisz, prawie nigdy nie ma potrzeby stosowania statycznej abstrakcji, jaką mogą oferować fasady logowania (lub jakakolwiek inna biblioteka).
źródło
Użyłem małej otoki interfejsu + adaptera z https://github.com/uhaciogullari/NLog.Interface, który jest również dostępny przez NuGet :
źródło
Jak na razie najlepiej jest użyć pakietu Microsoft.Extensions.Logging ( jak wskazał Julian ). Do tego można użyć większości struktur rejestrowania.
Zdefiniowanie własnego interfejsu, jak wyjaśniono w odpowiedzi Stevena, jest w porządku w prostych przypadkach, ale pomija kilka rzeczy, które uważam za ważne:
IsEnabled(LogLevel)
te, które możesz chcieć, jeszcze raz ze względu na wydajnośćPrawdopodobnie możesz zaimplementować to wszystko we własnej abstrakcji, ale w tym momencie wymyślisz koło na nowo.
źródło
Generalnie wolę stworzyć interfejs taki jak
public interface ILogger { void LogInformation(string msg); void LogError(string error); }
aw środowisku wykonawczym wstrzykuję konkretną klasę, która jest implementowana z tego interfejsu.
źródło
LogWarning
iLogCritical
metod i wszystkie ich przeciążenia. W ten sposób naruszysz zasadę segregacji interfejsów . Preferuj definiowanieILogger
interfejsu za pomocą jednejLog
metody.LogEntry
, a tym samym zależność odLoggingEventType
.ILogger
Realizacja musi radzić sobie z nimiLoggingEventTypes
, choć zapewnecase/switch
, co to zapach kod . Po co ukrywaćLoggingEventTypes
zależność? Realizacja musi obsługiwać poziomy rejestrowania w każdym razie , więc byłoby lepiej, aby jednoznacznie o tym, co należy zrobić, implementacja, zamiast ukrywać je za pomocą jednej metody z ogólnym argumentem.ICommand
który ma aHandle
który przyjmujeobject
. Implementacje muszącase/switch
przekraczać możliwe typy, aby spełnić kontrakt interfejsu. To nie jest idealne. Nie używaj abstrakcji, która ukrywa zależność, którą i tak trzeba obsłużyć. Zamiast tego należy mieć interfejs, który jasno określa, czego się oczekuje: „Oczekuję, że wszystkie programy rejestrujące będą obsługiwać ostrzeżenia, błędy, błędy krytyczne itp.”. Jest to lepsze niż „Oczekuję, że wszystkie programy rejestrujące będą obsługiwać komunikaty zawierające ostrzeżenia, błędy, błędy krytyczne itp.”LoggingEventType
powinny być nazywane,LoggingEventLevel
ponieważ typy są klasami i powinny być zakodowane jako takie w OOP. Dla mnie nie ma różnicy między niestosowaniem metody interfejsu a niestosowaniem odpowiedniejenum
wartości. Zamiast tego użyjErrorLoggger : ILogger
,InformationLogger : ILogger
gdzie każdy rejestrator definiuje swój własny poziom. Następnie DI musi wstrzyknąć potrzebne rejestratory, prawdopodobnie za pomocą klucza (wyliczenia), ale ten klucz nie jest już częścią interfejsu. (Jesteś teraz SOLIDNY).Świetne rozwiązanie tego problemu pojawiło się w postaci projektu LibLog .
LibLog to abstrakcja rejestrowania z wbudowaną obsługą głównych rejestratorów, w tym Serilog, NLog, Log4net i Enterprise logger. Jest instalowany za pośrednictwem menedżera pakietów NuGet w bibliotece docelowej jako plik źródłowy (.cs) zamiast odwołania .dll. Takie podejście umożliwia uwzględnienie abstrakcji rejestrowania bez zmuszania biblioteki do przyjęcia zewnętrznej zależności. Pozwala również autorowi biblioteki na dołączenie rejestrowania bez zmuszania używającej aplikacji do jawnego dostarczania programu rejestrującego do biblioteki. LibLog używa refleksji, aby dowiedzieć się, jaki konkretny rejestrator jest używany i połączyć się z nim bez żadnego jawnego kodu okablowania w projektach biblioteki.
Tak więc LibLog to świetne rozwiązanie do logowania w projektach bibliotek. Po prostu odwołaj się i skonfiguruj konkretny rejestrator (Serilog dla wygranej) w swojej głównej aplikacji lub usłudze i dodaj LibLog do swoich bibliotek!
źródło
Zamiast pisać własną fasadę, możesz skorzystać z usług Castle Logging Services lub Simple Logging Fasade .
Oba zawierają adaptery do NLog i Log4net.
źródło
Od 2015 roku możesz również korzystać z rejestrowania .NET Core, jeśli tworzysz aplikacje .NET Core.
Pakiet, do którego można podłączyć się NLog, to:
źródło