1. Giới thiệu
Trong lập trình ghi log không chỉ là "in cái gì đó ra console". Ghi log là hộp đen của bạn, GPS-tracker và indicator trong cùng một chai. Không có logs thì chương trình lớn trở thành một hộp đen bí ẩn: "tại sao service bị treo?", "tại sao người dùng không nhận được mail?", "có ai chạy module này trong 3 tháng qua không?" — những câu hỏi này thường chỉ có thể trả lời được nếu logs được lưu và có thể truy cập.
Tại sao logging cần thiết trong thực tế?
Tìm kiếm và chẩn đoán lỗi
Nếu chương trình crash — logs sẽ cho biết chỗ và lý do. Nếu không crash nhưng chạy bất thường — logs sẽ chỉ ra các bước dẫn đến tình trạng đó.
Giám sát trạng thái
Từ logs có thể biết hệ thống đang chạy hay không, có nhiều request tới server không, có lỗi xuất hiện không, ai đang làm gì.
Bảo mật
Ghi lại các cố gắng truy cập trái phép, hoạt động đáng ngờ, lỗi xác thực.
Audit
Ai đã làm gì và khi nào. Nếu một admin ác ý nghỉ việc và xóa dữ liệu, thông qua logs có thể phục hồi mọi thứ (hoặc ít nhất biết chính xác hành động của họ).
Hỗ trợ phát triển và vận hành
Logs không chỉ cho lập trình viên mà còn cho tester, operator, admin. Sau nửa năm bạn sẽ tự nói với mình: 'Cảm ơn vì đã thêm logs!'
Từ Console.WriteLine đến logging công nghiệp
Phản ứng đầu tiên của người mới: "Không lẽ chỉ cần Console.WriteLine là đủ sao?". Với chương trình học nhỏ đúng là đủ. Nhưng khi ứng dụng chạy trên server, chạy đồng thời, hoặc có hàng chục hàng trăm user sử dụng, console không còn giúp được. Cần:
- Phân tách thông tin quan trọng và debug.
- Gửi logs không chỉ tới console mà còn tới file, database, hệ thống tập trung.
- Thay đổi mức chi tiết của logs (ít nhất — chỉ errors, nhiều nhất — mọi thứ).
- Tự động bổ sung timestamp, thông tin thêm vào bản ghi.
- Cấu hình linh hoạt.
- Và quan trọng — không phải thay đổi code chỉ để chuyển hướng logs sang nơi khác.
Đó là lúc bắt đầu kỷ nguyên của logger "thật sự".
2. Cơ bản về hệ thống logging hiện đại trong .NET
Trong ecosystem .NET có một framework logging tiêu chuẩn, mạnh mẽ và linh hoạt — Microsoft.Extensions.Logging. Đây là một phần của "làn sóng mới" các thư viện .NET ra đời cùng ASP.NET Core, nhưng giờ được dùng khắp nơi: server, desktop, thậm chí mobile apps.
Framework này tốt ở điểm gì?
- Abstraction — không ràng buộc bạn với implementation cụ thể (logger có thể ghi vào file, console, cloud, hoặc cùng lúc nhiều nơi).
- Hỗ trợ các mức logging (Trace, Debug, Information, Warning, Error, Critical).
- Được tích hợp vào DI container và kết hợp với tất cả ứng dụng .NET hiện đại.
- Hệ sinh thái extension phong phú: hỗ trợ structured logging, formatter nâng cao và các integration.
Khái niệm và lớp chính
Hãy làm quen với các đối tượng then chốt cần cho làm việc với Microsoft.Extensions.Logging:
| Class/Interface | Mục đích |
|---|---|
|
Interface để logging, được typed theo class |
|
Interface logger tổng quát |
|
Factory để tạo các instance logger |
|
Cho phép cấu hình logging trong app |
|
Enum các mức log (Trace, Debug, Information, ...) |
Các mức logging
Phân loại message theo mức quan trọng giúp lọc "noise" và tìm thứ cần thiết:
| Mức (LogLevel) | Dùng cho gì? |
|---|---|
|
Debug rất chi tiết, "noise", dữ liệu tạm thời |
|
Thông tin debug chính |
|
Thông báo sự kiện quan trọng cho hoạt động bình thường |
|
Cảnh báo về vấn đề có thể xảy ra, nhưng hệ thống vẫn chạy |
|
Lỗi cần chú ý, nhưng ứng dụng vẫn sống |
|
Sự cố nghiêm trọng, đe dọa toàn bộ hệ thống |
3. Thực hành: thêm logging vào ứng dụng của chúng ta
Đừng viết code trừu tượng, thay vào đó tiếp tục phát triển app demo. Giả sử chúng ta đã có một lớp Calculator đơn giản, sẽ mở rộng dần theo khóa học.
Ví dụ: calculator cơ bản
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
// Các phương thức còn lại...
}
Bây giờ tích hợp logging vào nó. Ta cần interface ILogger<Calculator>, sẽ nhận từ bên ngoài (ví dụ qua Dependency Injection, DI).
using Microsoft.Extensions.Logging;
public class Calculator
{
private readonly ILogger<Calculator> _logger;
public Calculator(ILogger<Calculator> logger)
{
_logger = logger;
}
public int Add(int a, int b)
{
int result = a + b;
_logger.LogInformation("Thực hiện phép cộng: {A} + {B} = {Result}", a, b, result);
return result;
}
}
Sự thật thú vị:
Thay vì nối chuỗi, loggers hỗ trợ templates và named parameters ({A}, {B}, {Result}), cho phép logs có cấu trúc và dễ xử lý/search tự động sau này.
4. Cách tạo và cấu hình logger trong ứng dụng console
1. Thêm NuGet packages
Trong project của bạn cần:
- Microsoft.Extensions.Logging
- Microsoft.Extensions.Logging.Console (nếu muốn ghi ra console)
- (tùy chọn) các providers khác nếu cần
2. Cấu hình logger
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
// Tạo DI container
var serviceProvider = new ServiceCollection()
.AddLogging(builder => {
builder.AddConsole(); // In ra console
builder.SetMinimumLevel(LogLevel.Debug); // Mức log tối thiểu
})
.BuildServiceProvider();
// Lấy instance logger cho kiểu cần thiết
var logger = serviceProvider.GetRequiredService<ILogger<Calculator>>();
var calculator = new Calculator(logger);
calculator.Add(5, 3); // Tin nhắn sẽ vào logs
Lỗi điển hình của người mới
"Tại sao log không xuất hiện trên console?" — kiểm tra xem provider cần thiết đã được thêm (.AddConsole()) và mức tối thiểu đã được set đúng (SetMinimumLevel) chưa. Nếu mức đặt cao hơn, các message có thể bị "lọc" đi!
5. Các chi tiết hữu ích
Ghi log lỗi và tình huống bất thường
Giả sử muốn log phép chia cho 0. Thêm phương thức tương ứng:
public int Divide(int a, int b)
{
if (b == 0)
{
_logger.LogError("Thử chia cho 0! a={A}", a);
throw new DivideByZeroException();
}
int result = a / b;
_logger.LogInformation("Thực hiện phép chia: {A} / {B} = {Result}", a, b, result);
return result;
}
Tại sao cần thế?
Trong app thực tế, khi có chuyện không ổn, logs mức Error thường được chú ý đặc biệt: tự động gửi tới admin, đánh dấu trong monitoring và dùng cho alerts/notifications.
Sử dụng categories và scopes
Logger trong .NET hỗ trợ cái gọi là scopes — metadata bổ sung được thêm tự động vào mọi log trong một block code. Ví dụ khi xử lý web request hoặc session người dùng, có thể thêm id của nó vào scope.
using (_logger.BeginScope("UserId: {UserId}", 42))
{
_logger.LogInformation("Bắt đầu xử lý dữ liệu người dùng");
// ...
}
Mọi message trong block sẽ có tag bổ sung UserId: 42, giúp sau này tìm logs theo user hoặc operation.
Ví dụ: các mức logging trong thực tế
_logger.LogTrace("Đây là Trace — hầu như không ai thấy");
_logger.LogDebug("Đây là Debug — cho dev");
_logger.LogInformation("Đây là Information — cho sự kiện hoạt động bình thường");
_logger.LogWarning("Đây là Warning — cảnh báo về vấn đề có thể xảy ra");
_logger.LogError("Đây là Error — lỗi cần chú ý");
_logger.LogCritical("Đây là Critical — hệ thống đang cháy, cần cứu hỏa!");
Nếu bạn cấu hình SetMinimumLevel(LogLevel.Information), bạn chỉ thấy messages của mức Information, Warning, Error, Critical.
Mẹo:
Giữ Trace và Debug cho giai đoạn dev để debug chi tiết, còn trên production thường bật chỉ từ Information trở lên để không làm logs phình to và không làm mất các thông tin quan trọng trong "noise".
Sơ đồ trực quan: kiến trúc logging hiện đại
graph TD
A[Mã ứng dụng] --ILogger<YourClass>--> B[Microsoft.Extensions.Logging]
B --> C1[Console Provider]
B --> C2[File Provider]
B --> C3[Cloud/Database Provider]
C1 -.-> D1[Logs ra console]
C2 -.-> D2[Logs vào file]
C3 -.-> D3[Logs vào hệ thống monitoring]
subgraph Providers
C1
C2
C3
end
6. Khả năng mở rộng và extensions
Structured logging:
Giá trị của parameters có thể lưu không chỉ trong chuỗi mà dưới dạng key-value, cho phép tìm kiếm và aggregate theo các tham số (ví dụ qua Seq, ELK/ElasticSearch hoặc Application Insights).
Providers:
Có thể thêm hàng tá providers khác nhau: file, Windows EventLog, Azure, v.v.
Cấu hình logging qua appsettings.json
Trong ASP.NET Core logging có thể cấu hình linh hoạt qua file config, không cần recompile app.
Ví dụ cấu hình mức tối thiểu qua config (appsettings.json):
{
"Logging": {
"LogLevel": {
"Default": "Information",
"MyApp.Calculator": "Debug",
"Microsoft": "Warning"
}
}
}
7. Lỗi thường gặp khi làm việc với Microsoft.Extensions.Logging
Lỗi #1: Chọn sai mức logging.
Dùng LogInformation cho lỗi thay vì LogError hoặc LogCritical làm khó tìm lỗi trên production.
Lỗi #2: Bỏ qua structured logging.
Nối chuỗi thay vì dùng templates với placeholders ({Parameter}) làm mất lợi ích của structured logging như search theo parameter.
Lỗi #3: Cấu hình sai mức logging.
Nếu mức tối thiểu được set cao hơn cần thiết (ví dụ Warning thay vì Debug) thì message quan trọng có thể bị lọc.
Lỗi #4: Logging quá nhiều hoặc quá ít.
Logs quá chi tiết (ví dụ Trace trên production) tạo ra "noise", còn thiếu logs thì gây khó khăn cho debug.
GO TO FULL VERSION