Giới thiệu
Logging không chỉ đơn thuần là việc ghi lại các thông điệp vào tệp, mà còn là hệ thống thần kinh của ứng dụng của bạn. Nó cho bạn biết điều gì đã sai, khi nào và thường là lý do tại sao. Trong các hệ thống sản xuất, logging trở thành một cứu cánh để chẩn đoán sự cố, theo dõi hành vi người dùng và duy trì sự rõ ràng trong hoạt động.
Tuy nhiên, logging không chỉ về khối lượng, mà còn về kiểm soát. Quá ít thì bạn như đang bay mù, còn quá nhiều thì bạn lại bị ngợp trong tiếng ồn.
Trong bài viết này, chúng ta sẽ khám phá Serilog, một thư viện logging có cấu trúc cho .NET, mang lại sự rõ ràng, linh hoạt và sức mạnh cho chiến lược ghi chép của bạn. Khác với các logger truyền thống, Serilog coi các log như dữ liệu phong phú — không chỉ là chuỗi — giúp dễ dàng lọc, truy vấn và định tuyến log đến nhiều đích đến khác nhau.
Tính năng của Serilog
Serilog cung cấp:
- Logging có cấu trúc với các thuộc tính đã đặt tên
- Các sink linh hoạt để ghi log vào tệp, console, cơ sở dữ liệu, dịch vụ đám mây và hơn thế nữa
- Kiểm soát thời gian chạy đối với các cấp độ log
- Lọc tùy chỉnh để tránh làm ngập các hệ thống bên ngoài
- Mở rộng để tích hợp các sink của riêng bạn — các đích ghi log
Cho dù bạn đang gỡ lỗi một tính năng không ổn định hay theo dõi các lỗi quan trọng trong sản xuất, bài viết này sẽ khám phá cách kiểm soát Serilog — mà không cần triển khai lại hoặc khởi động lại ứng dụng của bạn.
Kiểm soát cấp độ log tại thời gian chạy
Cấu hình Serilog trong ứng dụng
Đầu tiên, chúng ta cần cấu hình Serilog trong ứng dụng:
csharp
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
Thêm một switch cấp độ và đăng ký nó (Dependency Injection)
csharp
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Error);
builder.Services.AddSingleton(levelSwitch);
Cấu hình Serilog với switch cấp độ
csharp
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.Console()
.WriteTo.File("logs/log.txt")
.CreateLogger();
Sử dụng switch cấp độ trong ứng dụng
csharp
levelSwitch.MinimumLevel = LogEventLevel.Information;
Exposing Log Level Control via API
Bây giờ chúng ta đã có một switch, chúng ta có thể sử dụng hoặc cung cấp nó qua một API nào đó để có thể gọi trực tiếp hoặc từ một bảng điều khiển quản trị.
Định nghĩa controller mới
csharp
[ApiController]
[Route("api/logging")]
public class LoggingController : ControllerBase
{
private readonly LoggingLevelSwitch _levelSwitch;
public LoggingController(LoggingLevelSwitch levelSwitch)
{
_levelSwitch = levelSwitch;
}
[HttpPost("set-level")]
public IActionResult SetLevel([FromQuery] string level)
{
_levelSwitch.MinimumLevel = Enum.Parse<LogEventLevel>(level, true);
return Ok($"Log level set to {level}");
}
}
Kiểm soát cấp độ log từ bảng điều khiển quản trị
API trên có thể được gọi từ một frontend / bảng điều khiển.
javascript
<select onChange={e => setLogLevel(e.target.value)}>
<option value="Error">Error</option>
<option value="Information">Information</option>
<option value="Debug">Debug</option>
</select>
Gọi API
javascript
const setLogLevel = async (level) => {
await fetch(`/api/logging/set-level?level=${level}`, { method: "POST" });
};
Điều này sẽ cho phép nhóm của bạn kiểm soát mức độ ghi chép theo thời gian thực mà không cần triển khai lại.
Tăng cường ghi chép tự động trong trường hợp ngoại lệ
Không chỉ qua API, chúng ta cũng có thể sử dụng switch cấp độ một cách âm thầm trong ứng dụng, ví dụ trong trường hợp xảy ra lỗi nghiêm trọng. Hãy sử dụng switch này trong bộ xử lý ngoại lệ toàn cục của ứng dụng:
csharp
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly LoggingLevelSwitch _levelSwitch;
public GlobalExceptionMiddleware(RequestDelegate next, LoggingLevelSwitch levelSwitch)
{
_next = next;
_levelSwitch = levelSwitch;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_levelSwitch.MinimumLevel = LogEventLevel.Information;
Log.Error(ex, "Unhandled exception occurred");
Log.Information("Log level elevated to Info");
_ = Task.Delay(TimeSpan.FromMinutes(5)).ContinueWith(_ =>
{
Log.Information("Log level reverted to Error");
_levelSwitch.MinimumLevel = LogEventLevel.Error;
});
context.Response.StatusCode = 500;
await context.Response.WriteAsync("Có gì đó đã sai.");
}
}
}
Lưu ý
Log.Information(...)
phải được gọi trước khi thiết lập lại cấp độ về 'Error', nếu không nó sẽ không được phát ra.
Đừng quên đăng ký middleware:
csharp
app.UseMiddleware<GlobalExceptionMiddleware>();
Thiết lập và sử dụng một Custom Sink
Sink là bất kỳ đích nào mà bạn muốn Serilog gửi logs đến, có thể là một tệp, một hàng đợi tin nhắn hoặc một dịch vụ thông báo đám mây nào đó. Trong ví dụ của chúng ta, chúng ta sẽ tạo một sink gửi logs đến chủ đề AWS SNS.
csharp
public class AwsSnsSink : ILogEventSink
{
private readonly IAmazonSimpleNotificationService _snsClient;
private readonly string _topicArn;
public AwsSnsSink(IAmazonSimpleNotificationService snsClient, string topicArn)
{
_snsClient = snsClient;
_topicArn = topicArn;
}
public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage();
_snsClient.PublishAsync(new PublishRequest
{
TopicArn = _topicArn,
Message = $"{logEvent.Timestamp}: {logEvent.Level} - {message}"
});
}
}
Đăng ký sink trong Program.cs
csharp
builder.Services.AddAWSService<IAmazonSimpleNotificationService>();
builder.Services.AddSingleton(levelSwitch);
var snsClient = builder.Services.BuildServiceProvider().GetRequiredService<IAmazonSimpleNotificationService>();
var topicArn = builder.Configuration["AWS:SnsTopicArn"];
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.WriteTo.Console()
.WriteTo.File("logs/log.txt")
.WriteTo.Sink(new AwsSnsSink(snsClient, topicArn))
.CreateLogger();
Lọc để tránh làm ngập SNS
Bây giờ chúng ta đã có một sink tùy chỉnh để nhận logs, chúng ta phải cẩn thận về chi phí của dịch vụ đám mây đó và không làm ngập chủ đề. Quá nhiều tiếng ồn cũng sẽ tốn kém.
Sửa đổi phương thức Emit của sink class
csharp
public void Emit(LogEvent logEvent)
{
if (logEvent.Level < LogEventLevel.Warning) return;
}
Lọc trong cấu hình Serilog
csharp
...
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(le => le.Level >= LogEventLevel.Warning)
WriteTo.Sink(new AwsSnsSink(snsClient, topicArn))
...
Điều này đảm bảo chỉ những log Warning, Error và Fatal mới đến SNS — giữ cho tùy chọn này tiết kiệm chi phí và đường ống đám mây gọn gàng và có ý nghĩa.
Kết luận
Với LoggingLevelSwitch
của Serilog, bạn có thể kiểm soát cấp độ ghi chép theo thời gian thực. Kết hợp điều đó với các sink tùy chỉnh và lọc thông minh, bạn có một đường ống ghi chép vừa thân thiện với nhà phát triển vừa an toàn trong sản xuất.
Cho dù bạn đang gửi logs đến AWS SNS, Slack hay một bảng điều khiển tùy chỉnh, kiến trúc này cho bạn sự linh hoạt để quan sát, gỡ lỗi và mở rộng — mà không bị ngợp trong tiếng ồn log.