Günlükçü sarmalayıcı için en iyi uygulama


91

Uygulamamda bir nlogger kullanmak istiyorum, belki gelecekte kayıt sistemini değiştirmem gerekecek. Bu yüzden bir ağaç kesme cephesi kullanmak istiyorum.

Bunları nasıl yazacağınıza dair mevcut örnekler için herhangi bir öneri biliyor musunuz? Ya da bana bu alandaki en iyi uygulamalara bağlantı verin.




Yanıtlar:


207

Ben gibi cephe günlüğü kullanmak için kullanılan Common.Logging (hatta kendi gizlemek için CuttingEdge.Logging kütüphane), ancak günümüzde kullandığım Bağımlılık Enjeksiyon kalıbı ve bu benim kendi (basit) soyutlama arkasında gizlemek loggers beni izin verdiğini hem yapıştığı Bağımlılık Ters Çevirme İlkesi ve Arayüz Ayrım İlkesi(ISP) bir üyeye sahip olduğu ve arayüz uygulamam tarafından tanımlandığı için; harici bir kitaplık değil. Uygulamanızın temel bölümlerinin harici kitaplıkların varlığıyla ilgili sahip olduğu bilgileri en aza indirgemek daha iyidir; günlük kitaplığınızı değiştirmek gibi bir niyetiniz olmasa bile. Harici kitaplığa olan sıkı bağımlılık, kodunuzu test etmeyi zorlaştırır ve uygulamanızı, asla uygulamanız için özel olarak tasarlanmamış bir API ile karmaşık hale getirir.

Uygulamalarımda soyutlama genellikle böyle görünüyor:

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

İsteğe bağlı olarak, bu soyutlama bazı basit genişletme yöntemleriyle genişletilebilir (arayüzün dar kalmasına ve ISP'ye bağlı kalmasına izin verir). Bu, bu arayüzün tüketicileri için kodu çok daha basit hale getirir:

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

Arabirim yalnızca tek bir yöntem içerdiğinden , log4net'e , Serilog , Microsoft.Extensions.Logging , NLog veya diğer herhangi bir günlük kitaplığına proxy oluşturan bir ILoggeruygulamayı kolayca oluşturabilir ve DI kapsayıcınızı, içinde a bulunan sınıflara enjekte edecek şekilde yapılandırabilirsiniz. yapıcı.ILogger

Tek bir yöntemle bir arabirimin üstünde statik uzantı yöntemlerine sahip olmanın, birçok üyeli bir arabirime sahip olmaktan oldukça farklı olduğunu unutmayın. Uzantı yöntemleri, bir LogEntrymesaj oluşturan ve bunu ILoggerarayüzdeki tek yöntemden geçiren yardımcı yöntemlerdir . Uzantı yöntemleri, tüketicinin kodunun bir parçası haline gelir; soyutlamanın bir parçası değil. Bu, yalnızca uzantı yöntemlerinin soyutlamayı, uzantı yöntemlerini ve genişletme yöntemlerini değiştirmeye gerek kalmadan gelişmesine izin vermez.LogEntryyapıcı, günlükçü soyutlaması kullanıldığında, bu günlükçü stubbe edilmiş / alay edilmiş olsa bile her zaman çalıştırılır. Bu, bir test paketinde çalışırken kaydediciye yapılan çağrıların doğruluğu hakkında daha fazla kesinlik sağlar. Tek üyeli arayüz testi çok daha kolay hale getirir; Çok sayıda üyeyle bir soyutlamaya sahip olmak, uygulamalar (taklitler, bağdaştırıcılar ve dekoratörler gibi) oluşturmayı zorlaştırır.

Bunu yaptığınızda, günlüğe kaydetme cephelerinin (veya başka herhangi bir kütüphanenin) sunabileceği bazı statik soyutlamalara neredeyse hiç gerek kalmaz.


4
@GabrielEspinoza: Bu tamamen isim alanına bağlıdır. Uzantı yöntemlerini yerleştirebilirsiniz. Eğer yu arayüzle aynı isim alanına veya projenizin bir kök isim alanına yerleştirirseniz sorun olmayacaktır.
Steven

2
@ user1829319 Bu sadece bir örnek. Eminim bu yanıta dayalı olarak, özel ihtiyaçlarınıza uygun bir uygulama geliştirebilirsiniz.
Steven

2
Hala anlamadım ... ILogger'ın uzantısı olarak 5 Logger yöntemine sahip olmanın ve ILogger üyesi olmamanın avantajı nerede?
Elisabeth

3
@Elisabeth Yararı, sadece tek bir işlevi uygulayarak cephe arayüzünü HERHANGİ bir kayıt çerçevesine uyarlayabilmenizdir: "ILogger :: Log." Uzantı yöntemleri, hangi çerçeveyi kullanmaya karar verirseniz verin, 'kolaylık' API'lerine ("LogError", "LogWarning" vb.) Erişmemizi sağlar. Bu, bir C # arabirimiyle çalışmaya rağmen, ortak 'temel sınıf' işlevselliği eklemenin dolambaçlı bir yoludur.
BTownTKD

2
Bunun neden harika olduğunu tekrar vurgulamalıyım. Kodunuzu DotNetFramework'dan DotNetCore'a dönüştürme. Bunu yaptığım projelerde sadece tek bir yeni beton yazmak zorunda kaldım. Yapmadığım şeyler .... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! Bu "dönüş yolunu" bulduğuma sevindim.
granadaCoder


8

Şu an itibariyle, en iyi bahis Microsoft.Extensions.Logging paketini kullanmaktır ( Julian'ın belirttiği gibi ). Çoğu günlüğe kaydetme çerçevesi bununla kullanılabilir.

Steven'ın cevabında açıklandığı gibi kendi arayüzünüzü tanımlamak basit durumlar için uygundur, ancak önemli olduğunu düşündüğüm birkaç şeyi gözden kaçırır:

  • Yapısal günlüğe kaydetme ve nesneleri yeniden yapılandırma (Serilog ve NLog'daki @ notasyonu)
  • Gecikmeli dizi oluşturma / biçimlendirme: bir dizge aldığından, çağrıldığında her şeyi değerlendirmesi / biçimlendirmesi gerekir, sonunda etkinlik eşiğin altında olduğu için günlüğe kaydedilmeyecektir (performans maliyeti, önceki noktaya bakın)
  • IsEnabled(LogLevel)Performans nedenleriyle bir kez daha isteyebileceğiniz gibi koşullu kontroller

Muhtemelen tüm bunları kendi soyutlamanızda uygulayabilirsiniz, ancak bu noktada tekerleği yeniden icat edeceksiniz.


4

Genellikle şöyle bir arayüz oluşturmayı tercih ederim

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

ve çalışma zamanında bu arayüzden uygulanan somut bir sınıf enjekte ediyorum.


11
Ve unutma LogWarningve LogCriticalyöntemlerini ve tüm bunların aşırı yüklenmeleri. Bunu yaparken Arayüz Ayrımı Prensibini ihlal edeceksiniz . ILoggerArayüzü tek bir Logyöntemle tanımlamayı tercih edin .
Steven

2
Gerçekten üzgünüm, niyetim bu değildi. Utanmana gerek yok. Bu tasarımı gerçekten çok sık görüyorum çünkü birçok geliştirici örnek olarak popüler log4net'i (bu tam tasarımı kullanan) kullanıyor. Maalesef bu tasarım pek iyi değil.
Steven

2
Bunu @ Steven'ın cevabına tercih ederim. Bir bağımlılık LogEntryve dolayısıyla bir bağımlılık getirir LoggingEventType. Gerçekleştirme , muhtemelen , bir kod kokusu olan ILoggerbunlarla ilgilenmelidir . Bağımlılığı neden gizleyelim ? Uygulama, günlük kaydı düzeylerini yine de ele almalıdır , bu nedenle , genel bir argümanla tek bir yöntemin arkasına gizlemek yerine, bir uygulamanın ne yapması gerektiğini açıklamak daha iyi olacaktır . LoggingEventTypescase/switchLoggingEventTypes
DharmaTurtle

1
Aşırı bir örnek olarak, bir hayal ICommandbir olan Handlebir aldığı object. case/switchArayüzün sözleşmesini yerine getirmek için uygulamalar olası türlerin üzerinde olmalıdır . Bu ideal değil. Yine de ele alınması gereken bir bağımlılığı gizleyen bir soyutlamanız yok. Bunun yerine, bekleneni açıkça belirten bir arayüze sahip olun: "Tüm kaydedicilerin Uyarıları, Hataları, Hatalıları vb. İşlemesini bekliyorum". Bu, "Tüm kaydedicilerin Uyarılar, Hatalar, Hatalılar vb. İçeren iletileri işlemesini bekliyorum" yerine tercih edilir .
DharmaTurtle

Hem @Steven hem de @DharmaTurtle ile aynı fikirdeyim. Ayrıca , türler sınıflar LoggingEventTypeolarak adlandırılmalı LoggingEventLevelve OOP'de olduğu gibi kodlanmalıdır. Benim için bir arayüz yöntemi kullanmama ile karşılık gelen enumdeğeri kullanmama arasında bir fark yok . Bunun yerine ErrorLoggger : ILogger, InformationLogger : ILoggerher kaydedicinin kendi seviyesini tanımladığı yerde kullanın . Daha sonra DI, gerekli kaydedicileri muhtemelen bir anahtar (enum) yoluyla enjekte etmelidir, ancak bu anahtar artık arayüzün bir parçası değildir. (Artık SAĞLAMSINIZ).
Wouter

4

LibLog projesi şeklinde bu soruna harika bir çözüm ortaya çıktı .

LibLog, Serilog, NLog, Log4net ve Enterprise logger dahil olmak üzere büyük kaydediciler için yerleşik desteğe sahip bir günlük özetidir. NuGet paket yöneticisi aracılığıyla bir .dll başvurusu yerine kaynak (.cs) dosyası olarak bir hedef kitaplığa yüklenir. Bu yaklaşım, kütüphaneyi harici bir bağımlılık almaya zorlamadan loglama soyutlamasının dahil edilmesine izin verir. Ayrıca, bir kitaplık yazarının, tüketen uygulamayı kitaplığa açıkça bir günlük kaydedici sağlamaya zorlamadan günlüğe kaydetmeyi dahil etmesine olanak tanır. LibLog, hangi somut kaydedicinin kullanımda olduğunu anlamak için yansıma kullanır ve kütüphane projelerinde / projelerinde herhangi bir açık kablolama kodu olmadan ona bağlanır.

Dolayısıyla, LibLog, kütüphane projelerinde oturum açmak için harika bir çözümdür. Sadece ana uygulamanızda veya hizmetinizde somut bir kaydediciye (kazanç için Serilog) referans verin ve yapılandırın ve kütüphanelerinize LibLog ekleyin!


Bunu log4net bozma değişiklik sorununu (yuck) geçmek için kullandım ( wiktorzychla.com/2012/03/pathetic-breaking-change-between.html ) Bunu nuget'ten alırsanız, aslında bir .cs dosyası oluşturacaktır. önceden derlenmiş dll'lere başvurular eklemek yerine kodunuzda. .Cs dosyası, projenizin ad alanına sahiptir. Dolayısıyla, farklı katmanlarınız (csprojs) varsa, ya birden çok sürümünüz olur ya da paylaşılan bir csproj ile birleştirmeniz gerekir. Bunu kullanmaya çalıştığınızda anlayacaksınız. Ama dediğim gibi, bu log4net kırılma değişikliği sorunu ile bir cankurtaran oldu.
granadaCoder


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.