Giới thiệu
Hướng dẫn này trình bày cách triển khai Serilog - một thư viện ghi nhật ký cấu trúc (structured logging) mạnh mẽ - trong Optimizely CMS 12.
Những gì bạn sẽ học:
- Cấu hình Serilog với đầu ra file và console
- Sử dụng ghi nhật ký cấu trúc trong mã nguồn của bạn
- Xem và quản lý các file nhật ký
- Các phương pháp hay nhất và khắc phục sự cố
Điều kiện tiên quyết:
- Optimizely CMS 12.x trên .NET 6.0+
- Kiến thức cơ bản về ASP.NET Core
Tại sao nên dùng Serilog?
✅ Ghi nhật ký Cấu trúc (Structured Logging) - Nhật ký là dữ liệu có cấu trúc, không chỉ là văn bản thuần
✅ Nhiều Đầu ra (Multiple Outputs) - Console, files, cơ sở dữ liệu đồng thời
✅ Hiệu suất Cao (High Performance) - Ghi nhật ký bất đồng bộ
✅ Cấu hình Dễ dàng (Easy Configuration) - Cài đặt dựa trên JSON
Bước 1: Cài đặt Packages
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File
Bước 2: Khởi tạo trong Program.cs
using Serilog;
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build())
.CreateLogger();
try
{
Log.Information("Starting Optimizely CMS");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application start-up failed");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Bước 3: Cấu hình appsettings.json
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "logs/app-log-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 30,
"fileSizeLimitBytes": 10485760,
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
"restrictedToMinimumLevel": "Error"
}
},
{
"Name": "Console",
"Args": {
"restrictedToMinimumLevel": "Information"
}
}
]
}
}
Cài đặt Chính:
| Cài đặt (Setting) | Giá trị (Value) | Mô tả (Description) |
|---|---|---|
rollingInterval |
Day |
Tạo file mới mỗi ngày |
retainedFileCountLimit |
30 |
Giữ lại trong 30 ngày |
fileSizeLimitBytes |
10485760 (10MB) |
Kích thước file tối đa |
| File Level | Error |
Chỉ ghi lỗi vào file |
| Console Level | Information |
Ghi tất cả thông tin vào console |
Bước 4: Sử dụng trong Mã nguồn
Ghi nhật ký Cơ bản
public class ProductController : Controller
{
private readonly ILogger<ProductController> _logger;
public ProductController(ILogger<ProductController> logger)
{
_logger = logger;
}
public IActionResult Index(int productId)
{
_logger.LogInformation("Loading product {ProductId}", productId);
try
{
var product = LoadProduct(productId);
return View(product);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load product {ProductId}", productId);
return NotFound();
}
}
}
Ghi nhật ký Cấu trúc
// ❌ KHÔNG TỐT: String interpolation
_logger.LogInformation($"User {userId} created order {orderId}");
// ✅ TỐT: Thuộc tính cấu trúc
_logger.LogInformation(
"User {UserId} created order {OrderId} with total {Total:C}",
userId, orderId, total);
Các Cấp độ Log
_logger.LogTrace("Detailed tracing"); // Chỉ dành cho Phát triển
_logger.LogDebug("Debug information"); // Phát triển
_logger.LogInformation("Normal events"); // Sản xuất
_logger.LogWarning("Potential issues"); // Sản xuất
_logger.LogError(ex, "Errors"); // Sản xuất
_logger.LogCritical("Critical failures"); // Sản xuất
Xem Nhật ký
Tùy chọn 1: Console (Môi trường Phát triển)
Khi chạy dotnet run, tất cả nhật ký sẽ hiển thị theo thời gian thực trong console.
Ưu điểm: ✅ Thời gian thực, ✅ Tất cả cấp độ
Nhược điểm: ❌ Không lưu trữ dai dẳng
Tùy chọn 2: Log Files (Môi trường Sản xuất)
Files được lưu tại: /logs/app-log-20260120.txt
Ưu điểm: ✅ Lưu trữ dai dẳng, ✅ Chỉ ghi lỗi
Nhược điểm: ❌ Không theo thời gian thực
Tùy chọn 3: Công cụ Quản trị Tùy chỉnh
Tạo một trình xem nhật ký đơn giản trong trang quản trị CMS:
[Authorize(Roles = "CmsAdmins")]
public class LogViewerController : Controller
{
private readonly IWebHostEnvironment _environment;
public LogViewerController(IWebHostEnvironment environment)
{
_environment = environment;
}
[Route("admin/logs")]
public IActionResult Index()
{
var logDir = Path.Combine(_environment.ContentRootPath, "logs");
var files = Directory.GetFiles(logDir, "*.txt")
.Select(f => new FileInfo(f))
.OrderByDescending(f => f.LastWriteTime)
.Select(f => new {
Name = f.Name,
Size = $"{(f.Length / 1024)} KB",
Modified = f.LastWriteTime
});
return View(files);
}
[Route("admin/logs/download/{fileName}")]
public IActionResult Download(string fileName)
{
var logDir = Path.Combine(_environment.ContentRootPath, "logs");
var filePath = Path.Combine(logDir, fileName);
if (!System.IO.File.Exists(filePath))
return NotFound();
var content = System.IO.File.ReadAllBytes(filePath);
return File(content, "text/plain", fileName);
}
}
Các Phương pháp Hay nhất
1. Sử dụng Ghi nhật ký Cấu trúc
// ✅ Luôn sử dụng các thuộc tính có tên
_logger.LogInformation("Processing {ItemCount} items", items.Count);
2. Không bao giờ ghi lại Dữ liệu Nhạy cảm
Không bao giờ ghi lại:
- ❌ Mật khẩu (Passwords)
- ❌ Thẻ tín dụng (Credit cards)
- ❌ Khóa API (API keys)
- ❌ Thông tin cá nhân (Personal information)
3. Chọn Cấp độ Thích hợp
- Debug/Trace → Chỉ Phát triển
- Information → Sự kiện nghiệp vụ
- Warning → Các vấn đề tiềm ẩn
- Error → Ngoại lệ
- Critical → Lỗi ứng dụng nghiêm trọng
4. Sử dụng Ghi nhật ký Có điều kiện
if (_logger.IsEnabled(LogLevel.Debug))
{
var expensiveData = ComputeExpensiveData();
_logger.LogDebug("Debug: {Data}", expensiveData);
}
5. Thêm Ngữ cảnh với Scopes
using (_logger.BeginScope("OrderId={OrderId}", orderId))
{
_logger.LogInformation("Validating order");
_logger.LogInformation("Processing payment");
// Tất cả nhật ký sẽ bao gồm OrderId
}
Các Vấn đề Thường gặp
Vấn đề 1: Không có Nhật ký trong Files
Sự cố: Console hiển thị nhật ký nhưng các file trống
Giải pháp:
// Kiểm tra restrictedToMinimumLevel không quá cao
{
"Args": {
"restrictedToMinimumLevel": "Information" // Giảm mức này
}
}
Vấn đề 2: Kích thước File quá lớn
Sự cố: Thư mục log chiếm quá nhiều dung lượng đĩa
Giải pháp:
{
"Serilog": {
"WriteTo": [{
"Args": {
"rollingInterval": "Day",
"retainedFileCountLimit": 7, // Chỉ giữ 7 ngày
"fileSizeLimitBytes": 5242880, // Tối đa 5MB
"restrictedToMinimumLevel": "Warning" // Chỉ cảnh báo trở lên
}
}]
}
}
Vấn đề 3: Công cụ Quản trị không hiển thị Files
Sự cố: Đường dẫn không khớp giữa Serilog và công cụ quản trị
Giải pháp:
// Đảm bảo đường dẫn khớp nhau
{
"Serilog": {
"WriteTo": [{
"Args": {
"path": "logs/app-log-.txt" // ← Phải khớp với đường dẫn công cụ
}
}]
}
}
Xác minh trong code:
var logPath = Path.Combine(_environment.ContentRootPath, "logs");
if (!Directory.Exists(logPath))
{
_logger.LogWarning("Log directory not found: {Path}", logPath);
}
Vấn đề 4: Ảnh hưởng đến Hiệu suất
Sự cố: Ứng dụng chạy chậm do việc ghi nhật ký
Giải pháp:
// 1. Giới hạn cấp độ log trong môi trường sản xuất
"MinimumLevel": { "Default": "Information" }
// 2. Sử dụng sink file bất đồng bộ (async file sink)
services.AddSerilog(config =>
config.WriteTo.Async(a => a.File("logs/app.txt")));
// 3. Ghi nhật ký có điều kiện cho các hoạt động tốn kém
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Expensive: {Data}", ComputeData());
}
Cấu hình Cụ thể theo Môi trường
appsettings.Development.json:
{
"Serilog": {
"MinimumLevel": {
"Default": "Verbose"
}
}
}
appsettings.Production.json:
{
"Serilog": {
"MinimumLevel": {
"Default": "Warning"
}
}
}
Tham khảo Nhanh
Hướng dẫn Cấp độ Log
| Cấp độ (Level) | Khi nào (When) | Ví dụ (Example) |
|---|---|---|
| Trace | Chi tiết theo dõi | Cache lookup |
| Debug | Sự kiện nội bộ | Query results |
| Information | Luồng bình thường | Order created |
| Warning | Vấn đề tiềm ẩn | Slow response |
| Error | Ngoại lệ | Payment failed |
| Critical | Ứng dụng sập | Database down |
Các Mẫu thường dùng
// Ghi nhật ký đơn giản
_logger.LogInformation("User logged in");
// Với thuộc tính
_logger.LogInformation("Order {OrderId} total {Total:C}", id, total);
// Với ngoại lệ
_logger.LogError(ex, "Failed to process {OrderId}", id);
// Với scope
using (_logger.BeginScope("UserId={UserId}", userId))
{
// Tất cả nhật ký bao gồm UserId
}
// Có điều kiện
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Data: {Data}", expensiveData);
}
Kết luận
Serilog trong Optimizely CMS 12 cung cấp:
✅ Ghi nhật ký cấu trúc để phân tích tốt hơn
✅ Nhiều đầu ra (console + files)
✅ Cấu hình linh hoạt thông qua JSON
✅ Sẵn sàng cho sản xuất với tính năng xoay file (file rotation)
Các điểm chính cần nhớ
- Khởi tạo Serilog trước khi ứng dụng khởi động
- Sử dụng ghi nhật ký cấu trúc với các thuộc tính có tên
- Không bao giờ ghi lại dữ liệu nhạy cảm
- Đặt cấp độ log thích hợp cho từng môi trường
- Triển khai chính sách lưu giữ để quản lý dung lượng đĩa