Pengelogan Sederhana

Petunjuk / Saran

Anda dapat mengunduh sampel artikel ini dari GitHub.

Pengelogan sederhana Entity Framework Core (EF Core) dapat digunakan untuk dengan mudah mendapatkan log saat mengembangkan dan men-debug aplikasi. Bentuk pengelogan ini membutuhkan konfigurasi minimal dan tidak ada paket NuGet tambahan.

Petunjuk / Saran

EF Core juga terintegrasi dengan Microsoft.Extensions.Logging, yang membutuhkan lebih banyak konfigurasi, tetapi seringkali lebih cocok untuk pengelogan dalam aplikasi produksi.

Konfigurasi

Log EF Core dapat diakses dari semua jenis aplikasi melalui penggunaan LogTo saat mengonfigurasi instans DbContext. Konfigurasi ini biasanya dilakukan dalam override DbContext.OnConfiguring. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

Alternatifnya, LogTo dapat dipanggil sebagai bagian dari AddDbContext atau saat membuat instance DbContextOptions untuk diteruskan ke konstruktor DbContext.

Petunjuk / Saran

OnConfiguring masih dipanggil ketika AddDbContext digunakan atau instans DbContextOptions diteruskan ke konstruktor DbContext. Ini menjadikannya tempat yang ideal untuk menerapkan konfigurasi konteks terlepas dari bagaimana DbContext dibangun.

Mengarahkan catatan

Pengelogan ke konsol

LogTo Action<T> memerlukan delegasi yang menerima string. EF Core akan memanggil delegasi ini dengan string untuk setiap pesan log yang dihasilkan. Kemudian terserah delegasi untuk melakukan sesuatu dengan pesan yang diberikan.

Metode Console.WriteLine ini sering digunakan untuk delegasi ini, seperti yang ditunjukkan di atas. Ini menghasilkan setiap pesan log yang ditulis ke konsol.

Pengelogan ke jendela debug

Debug.WriteLine dapat digunakan untuk mengirim output ke jendela Debug di Visual Studio atau IDE lainnya. Sintaks lambda harus digunakan dalam kasus ini karena kelas tidak termasuk dalam pengembangan versi rilis. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(message => Debug.WriteLine(message));

Pengelogan ke file

Menulis ke file memerlukan pembuatan sebuah StreamWriter atau objek serupa untuk file. Metode ini WriteLine kemudian dapat digunakan seperti pada contoh lain di atas. Ingatlah untuk memastikan file ditutup dengan bersih dengan membuang penulis ketika konteks dibuang. Contohnya:

private readonly StreamWriter _logStream = new StreamWriter("mylog.txt", append: true);

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(_logStream.WriteLine);

public override void Dispose()
{
    base.Dispose();
    _logStream.Dispose();
}

public override async ValueTask DisposeAsync()
{
    await base.DisposeAsync();
    await _logStream.DisposeAsync();
}

Petunjuk / Saran

Pertimbangkan untuk menggunakan Microsoft.Extensions.Logging untuk pengelogan ke file dalam aplikasi produksi.

Mendapatkan pesan terperinci

Data sensitif

Secara default, EF Core tidak akan menyertakan nilai data apa pun dalam pesan pengecualian. Ini karena data tersebut mungkin bersifat rahasia, dan dapat diungkapkan dalam penggunaan produksi jika pengecualian tidak ditangani.

Namun, mengetahui nilai data, terutama untuk kunci, bisa sangat membantu saat men-debug. Ini dapat diaktifkan di EF Core dengan memanggil EnableSensitiveDataLogging(). Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableSensitiveDataLogging();

Pengecualian kueri terperinci

Untuk alasan performa, EF Core tidak membungkus setiap panggilan untuk membaca nilai dari penyedia database dalam blok try-catch. Namun, ini terkadang menghasilkan pengecualian yang sulit didiagnosis, terutama ketika database mengembalikan NULL ketika tidak diizinkan oleh model.

Mengaktifkan EnableDetailedErrors akan menyebabkan EF mengimplementasikan blok try-catch ini sehingga memberikan kesalahan yang lebih rinci. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine)
        .EnableDetailedErrors();

Penyaringan

Tingkat log

Setiap pesan log EF Core ditetapkan ke tingkat yang ditentukan oleh LogLevel enum. Secara default, pencatatan log sederhana EF Core mencakup setiap pesan pada tingkat Debug atau lebih. LogTo dapat diberikan tingkat minimum yang lebih tinggi untuk memfilter beberapa pesan. Misalnya, meneruskan Information menghasilkan sekumpulan log minimal yang terbatas pada akses database dan beberapa pesan housekeeping.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

Pesan tertentu

Setiap pesan log diberi EventId. ID ini dapat diakses dari CoreEventId kelas atau RelationalEventId kelas untuk pesan khusus relasional. Penyedia database mungkin juga memiliki ID khusus penyedia di kelas serupa. Misalnya, SqlServerEventId untuk penyedia SQL Server.

LogTo dapat dikonfigurasi untuk hanya mencatat pesan yang terkait dengan satu atau beberapa ID peristiwa. Misalnya, untuk mencatat hanya pesan pada konteks yang diinisialisasi atau dihapus:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { CoreEventId.ContextDisposed, CoreEventId.ContextInitialized });

Kategori pesan

Setiap pesan log ditetapkan ke kategori pencatat hierarkis bernama. Kategorinya adalah:

Kategori Pesan
Microsoft.EntityFrameworkCore Semua pesan EF Core
Microsoft.EntityFrameworkCore.Database Semua interaksi database
Microsoft.EntityFrameworkCore.Database.Connection Penggunaan koneksi database
Microsoft.EntityFrameworkCore.Database.Command Penggunaan perintah database
Microsoft.EntityFrameworkCore.Database.Transaction Penggunaan transaksi database
Microsoft.EntityFrameworkCore.Update Menyimpan entitas, tidak termasuk interaksi database
Microsoft.EntityFrameworkCore.Model Semua interaksi model dan metadata
Microsoft.EntityFrameworkCore.Model.Validation Validasi model
Microsoft.EntityFrameworkCore.Query Kueri, tidak termasuk interaksi database
Microsoft.EntityFrameworkCore.Infrastructure Peristiwa umum, seperti pembuatan konteks
Microsoft.EntityFrameworkCore.Scaffolding Rekayasa terbalik database
Microsoft.EntityFrameworkCore.Migrations Migrasi
Microsoft.EntityFrameworkCore.ChangeTracking Pelacakan interaksi perubahan

LogTo dapat dikonfigurasi untuk hanya mencatat pesan dari satu atau beberapa kategori. Misalnya, untuk mencatat hanya interaksi database:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name });

Perhatikan bahwa DbLoggerCategory kelas menyediakan API hierarkis untuk menemukan kategori dan menghindari kebutuhan untuk string hard-code.

Karena kategori bersifat hierarkis, contoh ini menggunakan Database kategori akan menyertakan semua pesan untuk subkategori Database.Connection, , Database.Commanddan Database.Transaction.

Filter khusus

LogTo memungkinkan filter kustom digunakan untuk kasus di mana tidak ada opsi pemfilteran di atas yang cukup. Misalnya, untuk mencatat pesan apa pun pada tingkat Information atau di atasnya, serta pesan untuk membuka dan menutup koneksi:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .LogTo(
            Console.WriteLine,
            (eventId, logLevel) => logLevel >= LogLevel.Information
                                   || eventId == RelationalEventId.ConnectionOpened
                                   || eventId == RelationalEventId.ConnectionClosed);

Petunjuk / Saran

Pemfilteran menggunakan filter kustom atau menggunakan salah satu opsi lain yang terlihat di sini lebih efisien daripada pemfilteran di LogTo delegate. Ini karena jika filter menentukan pesan tidak boleh dicatat, maka pesan log bahkan tidak dibuat.

Konfigurasi untuk pesan tertentu

EF Core ConfigureWarnings API memungkinkan aplikasi untuk mengubah apa yang terjadi ketika peristiwa tertentu ditemui. Ini dapat digunakan untuk:

  • Mengubah tingkat log tempat peristiwa dicatat
  • Lewati pencatatan peristiwa sepenuhnya
  • Melemparkan pengecualian saat peristiwa terjadi

Mengubah tingkat log untuk suatu peristiwa

Contoh sebelumnya menggunakan filter kustom untuk mencatat setiap pesan di LogLevel.Information serta dua peristiwa yang ditentukan untuk LogLevel.Debug. Hal yang sama dapat dicapai dengan mengubah tingkat log dari dua Debug peristiwa menjadi Information:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(
            b => b.Log(
                (RelationalEventId.ConnectionOpened, LogLevel.Information),
                (RelationalEventId.ConnectionClosed, LogLevel.Information)))
        .LogTo(Console.WriteLine, LogLevel.Information);

Menyembunyikan pengelogan peristiwa

Dengan cara yang sama, peristiwa individu dapat dicegah dari pencatatan. Ini sangat berguna untuk mengabaikan peringatan yang telah ditinjau dan dipahami. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(CoreEventId.DetachedLazyLoadingWarning))
        .LogTo(Console.WriteLine);

Melemparkan untuk suatu peristiwa

Terakhir, EF Core dapat dikonfigurasi untuk dilemparkan untuk peristiwa tertentu. Ini sangat berguna untuk mengubah peringatan menjadi kesalahan. (Memang, ini adalah tujuan ConfigureWarnings asli metode, oleh karena itu namanya.) Misalnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Throw(RelationalEventId.MultipleCollectionIncludeWarning))
        .LogTo(Console.WriteLine);

Isi dan pemformatan pesan

Konten default dari LogTo diformat di beberapa baris. Baris pertama berisi metadata pesan:

  • LogLevel sebagai awalan empat karakter
  • Tanda waktu lokal, diformat untuk budaya saat ini
  • EventId dalam formulir yang dapat disalin/ditempelkan untuk mendapatkan anggota dari CoreEventId atau salah satu kelas lainnyaEventId, ditambah nilai ID mentah
  • Kategori peristiwa, seperti yang dijelaskan di atas.

Contohnya:

info: 10/6/2020 10:52:45.581 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Blogs" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
          "Name" INTEGER NOT NULL
      );
dbug: 10/6/2020 10:52:45.582 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committing transaction.
dbug: 10/6/2020 10:52:45.585 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committed transaction.

Konten ini dapat dikustomisasi dengan meneruskan nilai dari DbContextLoggerOptions, seperti yang ditunjukkan di bagian berikut.

Petunjuk / Saran

Pertimbangkan untuk menggunakan Microsoft.Extensions.Logging untuk kontrol lebih besar atas pemformatan log.

Menggunakan waktu UTC

Secara bawaan, tanda waktu dirancang untuk penggunaan lokal ketika melakukan debugging. Gunakan DbContextLoggerOptions.DefaultWithUtcTime untuk menggunakan tanda waktu UTC budaya-agnostik sebagai gantinya, tetapi menjaga segala sesuatu yang lain tetap sama. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithUtcTime);

Contoh ini menghasilkan pemformatan log berikut:

info: 2020-10-06T17:55:39.0333701Z RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE "Blogs" (
          "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,
          "Name" INTEGER NOT NULL
      );
dbug: 2020-10-06T17:55:39.0333892Z RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committing transaction.
dbug: 2020-10-06T17:55:39.0351684Z RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction)
      Committed transaction.

Pencatatan log satu baris

Kadang-kadang penting untuk mendapatkan tepat satu baris untuk setiap pesan log. Ini dapat diaktifkan oleh DbContextLoggerOptions.SingleLine. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.DefaultWithLocalTime | DbContextLoggerOptions.SingleLine);

Contoh ini menghasilkan pemformatan log berikut:

info: 10/6/2020 10:52:45.723 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" (    "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,    "Name" INTEGER NOT NULL);
dbug: 10/6/2020 10:52:45.723 RelationalEventId.TransactionCommitting[20210] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committing transaction.
dbug: 10/6/2020 10:52:45.725 RelationalEventId.TransactionCommitted[20202] (Microsoft.EntityFrameworkCore.Database.Transaction) -> Committed transaction.

Opsi konten lainnya

Bendera lain di DbContextLoggerOptions dapat digunakan untuk memangkas jumlah metadata yang disertakan dalam log. Ini dapat berguna bersama dengan pengelogan baris tunggal. Contohnya:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(
        Console.WriteLine,
        LogLevel.Debug,
        DbContextLoggerOptions.UtcTime | DbContextLoggerOptions.SingleLine);

Contoh ini menghasilkan pemformatan log berikut:

2020-10-06T17:52:45.7320362Z -> Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']CREATE TABLE "Blogs" (    "Id" INTEGER NOT NULL CONSTRAINT "PK_Blogs" PRIMARY KEY AUTOINCREMENT,    "Name" INTEGER NOT NULL);
2020-10-06T17:52:45.7320531Z -> Committing transaction.
2020-10-06T17:52:45.7339441Z -> Committed transaction.

Beralih dari EF6

Pengelogan sederhana EF Core berbeda dari Database.Log di EF6 dengan dua cara penting:

  • Pesan log tidak terbatas hanya pada interaksi database
  • Pengelogan harus dikonfigurasi pada waktu inisialisasi konteks

Untuk perbedaan pertama, pemfilteran yang dijelaskan di atas dapat digunakan untuk membatasi pesan mana yang dicatat.

Perbedaan kedua adalah perubahan yang disengaja untuk meningkatkan performa dengan tidak menghasilkan pesan log saat tidak diperlukan. Namun, masih mungkin untuk mendapatkan perilaku yang mirip dengan EF6 dengan membuat Log properti pada DbContext Anda dan menggunakannya ketika sudah diatur. Contohnya:

public Action<string> Log { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(s => Log?.Invoke(s));