Logging adalah praktik mencatat informasi penting tentang apa yang terjadi di dalam aplikasi saat berjalan, sangat krusial untuk debugging masalah di production di mana developer tidak bisa langsung menempelkan debugger ke proses yang sedang berjalan secara live.

Serilog adalah salah satu library logging paling populer di ekosistem .NET, menawarkan konsep structured logging yang memungkinkan data log disimpan dalam format terstruktur (bukan sekadar teks biasa), memudahkan pencarian dan analisis log di kemudian hari menggunakan tool seperti Seq atau Elasticsearch.

Artikel ini membahas cara mengintegrasikan Serilog ke ASP.NET Core, konsep structured logging dengan parameter bernama, serta pengaturan sink (tujuan output log) seperti file dan console secara bersamaan.

Mengonfigurasi Serilog di Program.cs

Serilog dikonfigurasi dengan beberapa sink output sekaligus

Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); builder.Host.UseSerilog();
Konfigurasi ini mendaftarkan dua sink sekaligus: Console untuk melihat log secara real-time selama development, dan File dengan rolling interval harian yang membuat file log baru setiap hari, menghindari satu file log tunggal yang terus membesar tanpa batas seiring waktu.

Structured Logging dengan Parameter Bernama

Parameter bernama disimpan sebagai field terstruktur, bukan string biasa

_logger.LogInformation("Produk {ProdukId} berhasil ditambahkan dengan harga {Harga}", produk.Id, produk.Harga); -- Expected: log tersimpan dengan ProdukId dan Harga -- sebagai FIELD terpisah, bukan hanya teks tergabung, -- sehingga bisa DICARI dan DIFILTER berdasarkan nilai tersebut
Berbeda dengan string interpolation biasa yang menghasilkan teks log statis, structured logging dengan parameter bernama seperti {ProdukId} menyimpan nilai tersebut sebagai field terpisah dalam data log, memungkinkan query seperti 'tampilkan semua log dengan ProdukId = 5' pada tool analisis log yang mendukung structured data.

Level Logging dan Kapan Memakainya

Berbagai level log sesuai tingkat urgensi informasi

_logger.LogDebug("Memulai proses validasi produk"); // detail teknis development _logger.LogInformation("Produk berhasil disimpan"); // informasi alur normal _logger.LogWarning("Stok produk hampir habis"); // perlu perhatian, belum error _logger.LogError(ex, "Gagal menyimpan produk ke database"); // error yang perlu ditindaklanjuti
Memilih level logging yang tepat penting agar log production tidak dibanjiri informasi debug yang tidak relevan, sementara error yang benar-benar kritikal tetap mudah ditemukan. Umumnya, production hanya menampilkan Information ke atas, sementara Debug hanya diaktifkan saat development atau troubleshooting intensif.

Menambahkan Context Enrichment

Enrichment menambahkan informasi kontekstual ke setiap log otomatis

Log.Logger = new LoggerConfiguration() .Enrich.WithMachineName() .Enrich.WithProperty("Application", "TokoOnlineApi") .WriteTo.Console(outputTemplate: "{Timestamp} [{Level}] {Message} {MachineName}{NewLine}") .CreateLogger();
Enrichment memungkinkan informasi tambahan seperti nama mesin atau nama aplikasi otomatis disertakan pada setiap baris log tanpa perlu menuliskannya berulang di setiap pemanggilan logger, sangat berguna saat log dari beberapa instance aplikasi digabung dan perlu dibedakan sumbernya.

Serilog juga mendukung pencatatan request HTTP secara otomatis lewat middleware UseSerilogRequestLogging, yang mencatat satu baris log ringkas untuk setiap request masuk lengkap dengan path, status code, dan durasi pemrosesan, jauh lebih efisien dibanding mencatat log detail secara manual di setiap controller untuk kebutuhan monitoring dasar semacam ini.

Structured Logging dan Enrichment dengan Serilog

Structured logging: parameter log menjadi field JSON, bukan string tergabung

// Program.cs builder.Host.UseSerilog((context, config) => config .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .Enrich.WithProperty("Application", "JMOrder-API") .WriteTo.Console(new Serilog.Formatting.Json.JsonFormatter())); // Dipakai di controller/service _logger.LogInformation("Order {OrderId} dibuat oleh {UserId} senilai {Total}", order.Id, userId, order.Total); // Output JSON: {"OrderId":1042,"UserId":"riki","Total":150000, ...} // Properti BUKAN disisipkan sebagai string biasa, tapi sebagai field terpisah

Perbedaan mendasar antara logging biasa dan structured logging terletak pada bagaimana data tambahan pada pesan log diperlakukan. Menulis _logger.LogInformation($"Order {order.Id} dibuat") menggabungkan nilai menjadi satu string utuh yang sulit difilter nantinya, sedangkan menulis _logger.LogInformation("Order {OrderId} dibuat", order.Id) membuat Serilog menyimpan OrderId sebagai properti terstruktur terpisah yang bisa langsung difilter atau diquery di log viewer seperti Seq atau Elasticsearch.

Enrich.FromLogContext() memungkinkan penambahan properti kontekstual yang otomatis menempel ke semua log dalam satu scope, misalnya CorrelationId yang sama untuk melacak satu request dari controller hingga ke lapisan repository paling dalam. Kombinasi ini sangat membantu saat debugging masalah di production, karena seluruh log yang berkaitan dengan satu request bisa ditelusuri lewat satu identifier yang sama tanpa perlu menebak-nebak urutan kejadian dari timestamp saja.

Memilih format output JSON lewat JsonFormatter, dibandingkan format teks biasa, menjadi penting ketika log dikirim ke sistem agregasi terpusat, karena struktur field yang konsisten memudahkan pembuatan dashboard dan alert otomatis berdasarkan properti tertentu, misalnya menghitung jumlah error per OrderId atau per endpoint dalam rentang waktu tertentu tanpa perlu melakukan parsing string log secara manual.

Mengarahkan Log ke File Rolling dan Seq dengan Multiple Sinks

Satu baris log terdistribusi otomatis ke semua sink yang terdaftar

// Program.cs - Serilog menulis ke tiga tujuan sekaligus builder.Host.UseSerilog((context, config) => config .ReadFrom.Configuration(context.Configuration) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.File("logs/jmorder-.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 14) .WriteTo.Seq("http://seq-server:5341")); // Log error otomatis masuk ke ketiga sink: console, file harian, dan Seq _logger.LogError(ex, "Gagal memproses pembayaran untuk order {OrderId}", order.Id); // File hasil: logs/jmorder-20261003.log, otomatis terhapus setelah 14 hari

Serilog dirancang dengan konsep sink, yaitu tujuan akhir ke mana log dikirim, dan satu aplikasi bisa mendaftarkan lebih dari satu sink sekaligus tanpa mengubah cara pemanggilan _logger.LogInformation di kode aplikasi. Pada contoh di atas, satu baris log yang sama otomatis terkirim ke console untuk kebutuhan debugging langsung, ke file untuk arsip lokal, dan ke server Seq untuk kebutuhan pencarian dan analisis terpusat. Setiap sink juga bisa diberi level minimum berbeda, misalnya file hanya mencatat Warning ke atas sedangkan console mencatat semua level termasuk Debug.

WriteTo.File dengan rollingInterval: RollingInterval.Day membuat file log baru setiap hari secara otomatis dengan nama yang mengandung tanggal, sementara retainedFileCountLimit membatasi jumlah file lama yang disimpan agar disk tidak penuh oleh arsip log yang menumpuk tanpa batas. Pengaturan rolling ini penting untuk aplikasi yang berjalan terus-menerus di server production dalam jangka waktu lama. Tanpa batasan retensi semacam ini, volume file log historis bisa terus bertambah sampai akhirnya mengganggu kapasitas penyimpanan server.

Seq sebagai log viewer terpusat memberi antarmuka pencarian berbasis query terhadap properti terstruktur yang sudah dibahas sebelumnya, seperti mencari seluruh log dengan OrderId tertentu dalam hitungan detik tanpa perlu membuka file log satu per satu di server yang berbeda-beda. Pada arsitektur microservice dengan banyak instance aplikasi berjalan bersamaan, sink terpusat seperti Seq menjadi jauh lebih praktis dibanding mengandalkan file log lokal di tiap server. Dashboard dan alert otomatis di Seq juga bisa dikonfigurasi untuk memberi notifikasi ketika jumlah error melebihi ambang batas tertentu dalam rentang waktu singkat.

Mengaktifkan Request Logging Otomatis dengan Serilog

var app = builder.Build(); app.UseSerilogRequestLogging(options => { options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} selesai {StatusCode} dalam {Elapsed:0.0000} ms"; }); // Satu baris log ringkas per request, menggantikan // banyak baris log default dari komponen internal ASP.NET Core

Tanpa memanggil UseSerilogRequestLogging, log bawaan ASP.NET Core untuk satu request yang sama bisa terpecah menjadi beberapa baris terpisah dari komponen internal yang berbeda, seperti routing dan MVC, sehingga cukup menyulitkan saat perlu mencari log yang terkait dengan satu request tertentu di tengah banyaknya baris log lain. Middleware ini merangkum seluruh informasi penting tersebut menjadi satu baris log terstruktur per request.

MessageTemplate pada middleware ini dapat disesuaikan untuk menyertakan property tambahan seperti elapsed time atau status code, yang kemudian tetap bisa difilter dengan mudah di sink seperti Seq atau file log berformat JSON. Penempatan middleware ini sebaiknya diletakkan di awal pipeline agar durasi yang dicatat benar-benar mencerminkan keseluruhan waktu pemrosesan request dari awal sampai akhir, bukan hanya sebagian prosesnya saja.

Kesalahan Umum yang Sering Terjadi

Mencatat Data Sensitif ke dalam Log

Mencatat password, token, atau data kartu kredit secara langsung ke dalam log adalah risiko keamanan serius, karena log seringkali disimpan lebih lama dan diakses oleh lebih banyak orang dibanding database utama aplikasi itu sendiri.

Memakai String Interpolation Alih-alih Parameter Bernama

Menulis _logger.LogInformation($"Produk {produk.Id}") dengan string interpolation C# menghilangkan manfaat structured logging karena nilai sudah digabung menjadi string sebelum sempat diproses sebagai field terpisah oleh Serilog.

Logging Berlebihan pada Level Information di Production

Mencatat terlalu banyak informasi detail pada level Information untuk setiap request bisa membanjiri sistem log dan menyulitkan menemukan informasi yang benar-benar penting di antara volume log yang sangat besar.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Structured logging dengan Serilog membutuhkan sedikit penyesuaian cara menulis pesan log dibanding string interpolation biasa, namun manfaatnya dalam kemampuan pencarian dan analisis log jangka panjang jauh lebih besar, terutama pada aplikasi production dengan volume traffic tinggi.

Menyimpan log ke file lokal sederhana dan cepat untuk setup awal, namun untuk aplikasi yang di-deploy sebagai banyak instance (seperti di Kubernetes), sink terpusat seperti Elasticsearch atau Seq jauh lebih praktis untuk melihat log gabungan dari seluruh instance di satu tempat.

Ringkasan

Serilog dengan structured logging memberikan kemampuan pencatatan yang jauh lebih kaya dibanding logging teks biasa, memudahkan pencarian dan analisis masalah di production. Pilih level logging yang tepat sesuai urgensi, manfaatkan parameter bernama untuk structured data, dan selalu hindari mencatat informasi sensitif ke dalam log.

Seri Belajar .NET Core:

← Sebelumnya: Authorization dan Role Based Access Control

→ Selanjutnya: Exception Handling Global Middleware