Praktik terbaik pembungkus logger


91

Saya ingin menggunakan nlogger di aplikasi saya, mungkin di masa mendatang saya perlu mengubah sistem logging. Jadi saya ingin menggunakan fasad kayu.

Apakah Anda tahu ada rekomendasi untuk contoh yang ada bagaimana menulis yang itu? Atau beri saya tautan ke beberapa praktik terbaik di bidang ini.




Jawaban:


207

Saya dulu menggunakan fasad logging seperti Common.Logging (bahkan untuk menyembunyikan library CuttingEdge.Logging saya sendiri ), tetapi saat ini saya menggunakan pola Dependency Injection dan ini memungkinkan saya untuk menyembunyikan logger di balik abstraksi saya sendiri (sederhana) yang menganut kedua Dependency Prinsip Inversi dan Prinsip Segregasi Antarmuka(ISP) karena memiliki satu anggota dan karena antarmuka ditentukan oleh aplikasi saya; bukan perpustakaan eksternal. Meminimalkan pengetahuan yang dimiliki bagian inti aplikasi Anda tentang keberadaan perpustakaan eksternal, semakin baik; bahkan jika Anda tidak berniat untuk mengganti perpustakaan logging Anda. Ketergantungan keras pada pustaka eksternal mempersulit pengujian kode Anda, dan memperumit aplikasi Anda dengan API yang tidak pernah dirancang secara khusus untuk aplikasi Anda.

Ini adalah abstraksi yang sering terlihat dalam aplikasi saya:

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

Secara opsional, abstraksi ini dapat diperluas dengan beberapa metode ekstensi sederhana (memungkinkan antarmuka tetap sempit dan tetap mengikuti ISP). Ini membuat kode untuk konsumen antarmuka ini jauh lebih sederhana:

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

Karena antarmuka hanya berisi satu metode, Anda dapat dengan mudah membuat ILoggerimplementasi yang merupakan proxy ke log4net , untuk Serilog , Microsoft.Extensions.Logging , NLog atau perpustakaan logging lain dan mengkonfigurasi kontainer DI Anda untuk menyuntikkan itu di kelas yang memiliki ILoggerdi mereka konstruktor.

Perhatikan bahwa memiliki metode ekstensi statis di atas antarmuka dengan metode tunggal sangat berbeda dari memiliki antarmuka dengan banyak anggota. Metode ekstensi hanyalah metode pembantu yang membuat LogEntrypesan dan meneruskannya melalui satu-satunya metode pada ILoggerantarmuka. Metode ekstensi menjadi bagian dari kode konsumen; bukan bagian dari abstraksi. Hal ini tidak hanya memungkinkan metode ekstensi berkembang tanpa perlu mengubah abstraksi, metode ekstensi, dan ekstensiLogEntrykonstruktor selalu dieksekusi ketika abstraksi pencatat digunakan, bahkan ketika pencatat tersebut dihentikan / diolok-olok. Ini memberikan lebih banyak kepastian tentang kebenaran panggilan ke logger saat menjalankan rangkaian pengujian. Antarmuka satu anggota membuat pengujian lebih mudah juga; Memiliki abstraksi dengan banyak anggota mempersulit pembuatan implementasi (seperti tiruan, adaptor, dan dekorator).

Saat Anda melakukan ini, hampir tidak pernah ada kebutuhan untuk beberapa abstraksi statis yang mungkin ditawarkan oleh fasad logging (atau pustaka lainnya).


4
@GabrielEspinoza: Itu sepenuhnya tergantung pada namespace tempat Anda menempatkan metode ekstensi masuk Jika Anda menempatkannya di namespace yang sama dengan antarmuka atau di namespace root proyek Anda, masalah tidak akan ada.
Steven

2
@ user1829319 Ini hanya sebuah contoh. Saya yakin Anda dapat menghasilkan implementasi berdasarkan jawaban ini yang sesuai dengan kebutuhan khusus Anda.
Steven

2
Saya masih belum mengerti ... di mana keuntungan memiliki 5 metode Logger sebagai kelanjutan dari ILogger dan tidak menjadi anggota ILogger?
Elisabeth

3
@Elisabeth Manfaatnya adalah Anda dapat menyesuaikan antarmuka fasad ke kerangka kerja logging APA PUN, cukup dengan menerapkan satu fungsi: "ILogger :: Log." Metode ekstensi memastikan bahwa kami memiliki akses ke API 'kenyamanan' (seperti "LogError", "LogWarning", dll.), Terlepas dari kerangka kerja mana yang Anda putuskan untuk digunakan. Ini adalah cara memutar untuk menambahkan fungsionalitas 'kelas dasar' umum, meskipun bekerja dengan antarmuka C #.
BTownTKD

2
Saya perlu menekankan kembali alasan mengapa ini bagus. Mengonversi kode Anda dari DotNetFramework ke DotNetCore. Proyek tempat saya melakukan ini, saya hanya perlu menulis satu beton baru. Yang mana aku tidak .... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! Saya senang saya menemukan "jalan kembali" ini.
granadaCoder


8

Saat ini, taruhan terbaik adalah menggunakan paket Microsoft.Extensions.Logging ( seperti yang ditunjukkan oleh Julian ). Sebagian besar kerangka kerja logging dapat digunakan dengan ini.

Mendefinisikan antarmuka Anda sendiri, seperti yang dijelaskan dalam jawaban Steven tidak masalah untuk kasus-kasus sederhana, tetapi beberapa hal yang saya anggap penting terlewatkan:

  • Objek logging dan de-strukturisasi terstruktur (notasi @ di Serilog dan NLog)
  • Konstruksi / pemformatan string yang tertunda: karena membutuhkan string, ia harus mengevaluasi / memformat semuanya saat dipanggil, meskipun pada akhirnya acara tidak akan dicatat karena di bawah ambang batas (biaya kinerja, lihat poin sebelumnya)
  • Pemeriksaan bersyarat seperti IsEnabled(LogLevel)yang mungkin Anda inginkan, untuk alasan pertunjukan sekali lagi

Anda mungkin dapat menerapkan semua ini dalam abstraksi Anda sendiri, tetapi pada saat itu Anda akan menemukan kembali roda.


4

Umumnya saya lebih suka membuat antarmuka seperti

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

dan dalam runtime saya menyuntikkan kelas beton yang diimplementasikan dari antarmuka ini.


11
Dan jangan lupa LogWarningdan LogCriticalmetode dan semua overloads mereka. Saat melakukan ini, Anda akan melanggar Prinsip Segregasi Antarmuka . Lebih suka menentukan ILoggerantarmuka dengan satu Logmetode.
Steven

2
Saya benar-benar minta maaf, itu bukan niat saya. Tak perlu malu. Saya sering melihat desain ini karena banyak pengembang menggunakan log4net populer (yang menggunakan desain persis seperti ini) sebagai contoh. Sayangnya desain itu tidak terlalu bagus.
Steven

2
Saya lebih suka ini daripada jawaban @ Steven. Dia memperkenalkan ketergantungan ke LogEntry, dan dengan demikian ketergantungan pada LoggingEventType. The ILoggerpelaksanaan harus berurusan dengan ini LoggingEventTypes, mungkin meskipun case/switch, yang merupakan kode bau . Mengapa menyembunyikan LoggingEventTypesketergantungan? Pelaksanaannya harus menangani tingkat penebangan pula , sehingga akan lebih baik untuk eksplisit tentang apa yang harus melakukan implementasi, bukan menyembunyikannya di balik metode tunggal dengan argumen umum.
DharmaTurtle

1
Sebagai contoh ekstrem, bayangkan an ICommandyang memiliki a Handleyang mengambil object. Implementasi harus case/switchmelebihi jenis yang mungkin untuk memenuhi kontrak antarmuka. Ini tidak ideal. Tidak memiliki abstraksi yang menyembunyikan ketergantungan yang tetap harus ditangani. Alih-alih memiliki antarmuka yang menyatakan dengan jelas apa yang diharapkan: "Saya mengharapkan semua penebang untuk menangani Peringatan, Kesalahan, Fatals, dll". Ini lebih disukai daripada "Saya mengharapkan semua logger menangani pesan yang mencakup Peringatan, Kesalahan, Fatals, dll."
DharmaTurtle

Saya agak setuju dengan @Steven dan @DharmaTurtle. Selain itu LoggingEventTypeharus dipanggil LoggingEventLevelkarena tipe adalah kelas dan harus diberi kode seperti itu dalam OOP. Bagi saya tidak ada perbedaan antara tidak menggunakan metode antarmuka vs tidak menggunakan nilai yang sesuai enum. Sebagai gantinya gunakan ErrorLoggger : ILogger, di InformationLogger : ILoggermana setiap logger menentukan levelnya sendiri. Kemudian DI perlu memasukkan logger yang diperlukan, mungkin melalui kunci (enum), tetapi kunci ini tidak lagi menjadi bagian dari antarmuka. (Anda sekarang SOLID).
Wouter

4

Solusi hebat untuk masalah ini telah muncul dalam bentuk proyek LibLog .

LibLog adalah abstraksi logging dengan dukungan built-in untuk logger besar termasuk Serilog, NLog, Log4net dan logger Enterprise. Ini diinstal melalui manajer paket NuGet ke perpustakaan target sebagai file sumber (.cs), bukan referensi .dll. Pendekatan tersebut memungkinkan abstraksi logging disertakan tanpa memaksa perpustakaan untuk mengambil dependensi eksternal. Hal ini juga memungkinkan penulis perpustakaan untuk menyertakan logging tanpa memaksa aplikasi yang memakan untuk secara eksplisit menyediakan logger ke perpustakaan. LibLog menggunakan refleksi untuk mencari tahu logger beton apa yang digunakan dan menghubungkannya tanpa kode pengkabelan eksplisit dalam proyek perpustakaan.

Jadi, LibLog adalah solusi yang bagus untuk masuk dalam proyek perpustakaan. Cukup rujuk dan konfigurasikan logger beton (Serilog untuk kemenangan) di aplikasi atau layanan utama Anda dan tambahkan LibLog ke perpustakaan Anda!


Saya telah menggunakan ini untuk melewati masalah perubahan pemutusan log4net (yuck) ( wiktorzychla.com/2012/03/pathetic-breaking-change-between.html ) Jika Anda mendapatkan ini dari nuget, itu benar-benar akan membuat file .cs dalam kode Anda daripada menambahkan referensi ke dll yang telah dikompilasi sebelumnya. File .cs diberi nama untuk proyek Anda. Jadi jika Anda memiliki lapisan yang berbeda (csprojs), Anda akan memiliki beberapa versi, atau Anda perlu menggabungkannya ke csproj bersama. Anda akan mengetahui hal ini saat Anda mencoba menggunakannya. Tapi seperti yang saya katakan, ini adalah penyelamat dengan masalah perubahan pemecah log4net.
granadaCoder


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.