
Ghi nhật ký là một công cụ rất quan trọng dành cho nhà phát triển, nhưng khi tạo hệ thống phân tán, nó sẽ trở thành nền tảng cần được đặt ngay vào nền tảng ứng dụng của bạn, nếu không, sự phức tạp của việc phát triển các dịch vụ vi mô sẽ nhanh chóng bị ảnh hưởng.
.Net Core 3 đã bổ sung thêm một tính năng tuyệt vời , vì vậy, nếu ứng dụng của bạn sử dụng lệnh gọi HTTP trực tiếp để liên lạc giữa các dịch vụ thì bạn có thể tận dụng chức năng có sẵn này. Tuy nhiên, nếu kiến trúc phụ trợ của bạn liên quan đến sự tương tác thông qua trình trung chuyển tin nhắn (RabbitMQ, Kafka, v.v.), thì bạn vẫn cần lo lắng về việc tự mình chuyển ngữ cảnh tương quan qua các tin nhắn này.
Trong bài viết này, chúng ta sẽ sử dụng một ứng dụng web-api đơn giản và tổ chức ghi nhật ký, việc này sẽ
duy trì mối tương quan từ đầu đến cuối giữa nhật ký của các dịch vụ độc lập để bạn có thể dễ dàng xem tất cả các hoạt động do một yêu cầu cụ thể từ khách hàng gây ra
có một điểm truy cập duy nhất với khả năng phân tích thuận tiện, để ngay cả bộ phận Hỗ trợ cũng có thể sử dụng công cụ ghi nhật ký để trả lời những câu hỏi như “Tôi gặp lỗi trong ứng dụng với ID yêu cầu như vậy” có thể được sử dụng
Đầu tiên, chúng ta cần quyết định nhà cung cấp dịch vụ ghi nhật ký cho ứng dụng của mình. Yêu cầu chính cho việc ghi nhật ký hiện đại là cấu trúc, tức là chúng ta không nên làm việc với các tin nhắn văn bản phẳng mà với các đồ vật. Nhờ những nhật ký như vậy, chúng tôi có thể dễ dàng xây dựng chế độ xem tin nhắn của mình từ các góc độ khác nhau và tiến hành phân tích.
Đối với ứng dụng của mình, chúng tôi sẽ sử dụng gói Serilog, gói này có hỗ trợ tuyệt vời cho việc ghi nhật ký có cấu trúc và hệ thống tiện ích bổ sung phong phú. Tôi sẽ bỏ qua các bước thiết lập cơ bản (bạn có thể tìm thấy rất nhiều bài viết về chủ đề này) và giả định rằng
Serilog đã được định cấu hình và là trình ghi nhật ký mặc định cho nhà cung cấp nội dung phụ thuộc của bạn
cấu hình của nó bao gồm việc làm phong phú các thông điệp với các thuộc tính ngữ cảnh (Enrich.FromLogContext)
Bước tiếp theo là chọn hệ thống thu thập nhật ký tập trung nào để gửi tin nhắn từ Serilog tới. Có lẽ tùy chọn nguồn mở phổ biến nhất hiện nay là ngăn xếp ELK (Elasticsearch, Logstash và Kibana), vì vậy hãy sử dụng nó. Để làm điều này, chúng tôi sẽ sử dụng ưu đãi từ — sau khi đăng ký gói miễn phí, chúng tôi có trong tay toàn bộ sức mạnh của công cụ tìm kiếm Lucene.
Tất cả những gì chúng ta phải làm là thêm gói vào dự án của mình
Install-Package Serilog.Sinks.Logzio
Và thêm bộ làm giàu tương ứng vào cấu hình của trình ghi nhật ký của chúng tôi, cung cấp cho nó mã thông báo truy cập
LoggerConfiguration loggerConfig = new LoggerConfiguration();
loggerConfig.WriteTo.Logzio(secrets.LogzioToken, 10, TimeSpan.FromSeconds(10), null, LogEventLevel.Debug);
Bằng cách khởi chạy ứng dụng, chúng ta sẽ có thể quan sát các tin nhắn của mình không chỉ trong bảng điều khiển mà còn ở Kibana.

Giao diện

Trong một ứng dụng loại dịch vụ, chúng ta có thể phân biệt hai giao diện chính để tương tác với thế giới bên ngoài, hãy chỉ định chúng là dọc và ngang. Giao diện dọc là một API web thông qua đó các cuộc gọi từ ứng dụng khách sẽ đến. Horizontal là một nhà môi giới tin nhắn được sử dụng để trao đổi dữ liệu với các dịch vụ nội bộ khác.
Hãy xem xét các giai đoạn giới thiệu mối tương quan trên từng giao diện này.
Mối tương quan trong các yêu cầu HTTP
Để có được càng nhiều thông tin càng tốt, chúng tôi cần tạo một mã định danh tương quan càng gần thời điểm bắt đầu hoạt động càng tốt, tức là. trên cổng hoặc trực tiếp trên máy khách (di động hoặc web). Vì hôm nay chúng ta đang xử lý một ứng dụng phụ trợ nên chúng ta sẽ chỉ nêu trên đó yêu cầu về tiêu đề “X-Correlation-ID” bắt buộc trong tất cả các yêu cầu đối với API web.
Thêm một gói , chức năng của nó là lấy giá trị từ tiêu đề mà chúng ta cần
Install-Package CorrelationID
Hãy thêm nó vào quy trình xử lý yêu cầu
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application
.UseCorrelationId(new CorrelationIdOptions
{
Header = "X-Correlation-ID",
IncludeInResponse = false,
UpdateTraceIdentifier = false,
UseGuidForCorrelationId = false
});
}
}
Bây giờ chúng ta sẽ sử dụng nó để tạo bộ lọc hành động đơn giản:
public sealed class ApiRequestFilter : ActionFilterAttribute
{
public ApiRequestFilter(IApiRequestTracker apiRequestTracker, ICorrelationContextAccessor correlationContextAccessor)
{
_correlationContextAccessor = correlationContextAccessor ?? throw new ArgumentNullException(nameof(correlationContextAccessor));
}
private readonly ICorrelationContextAccessor _correlationContextAccessor;
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!Guid.TryParse(_correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
context.Result = new BadRequestResult();
return;
}
await next.Invoke();
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await next.Invoke();
}
}
Và thêm nó vào bộ điều khiển
[Route("[controller]")]
[ApiController]
[ServiceFilter(typeof(ApiRequestFilter))]
public class CarsController : ControllerBase
{
}
Kết quả, bộ điều khiển sẽ hiển thị 400 Yêu cầu xấu cho tất cả các yêu cầu không có tiêu đề với mã định danh tương ứng.
Sau khi chúng tôi bắt đầu nhận được mã định danh từ máy khách, chúng tôi phải thêm nó vào ngữ cảnh ghi nhật ký, chúng tôi sẽ tạo một lớp khung cho việc này:
public class CorrelationIdContextLogger
{
public CorrelationIdContextLogger(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}
readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext httpContext, ILogger<CorrelationIdContextLogger> logger, ICorrelationContextAccessor correlationContextAccessor)
{
if (Guid.TryParse(correlationContextAccessor.CorrelationContext.CorrelationId, out Guid correlationId))
{
using (logger.BeginScopeWith(("CorrelationId", correlationId)))
{
await _next(context);
}
}
else
{
await _next(context);
}
}
}
Trong ứng dụng của mình, chúng tôi sử dụng ILogger tiêu chuẩn từ gói Microsoft.Extensions.Logging.Abstractions, vì vậy chúng tôi sẽ thêm giá trị bằng cách sử dụng một tiện ích mở rộng đơn giản cho nó.
public static IDisposable BeginScopeWith(this ILogger logger, params (string key, object value)[] keys)
{
return logger.BeginScope(keys.ToDictionary(x => x.key, x => x.value));
}
Chúng tôi thêm một lớp vào quy trình xử lý yêu cầu và nhận được kết quả mong muốn.
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseMiddleware<CorrelationIdContextLogger>();
}
}
Giờ đây, tất cả các hoạt động được tạo bởi các yêu cầu tới API web của chúng tôi đều chứa mã định danh tương quan để chúng có thể được liên kết dễ dàng.

Mối tương quan trong tin nhắn của người môi giới
Bước tiếp theo là thiết lập việc truyền và nhận mã định danh tương quan thông qua nhà môi giới tin nhắn. Trong ví dụ của chúng tôi, chúng tôi sẽ sử dụng RabbitMQ và lấy khung MassTransit làm ứng dụng khách. Một lần nữa, hãy bỏ qua bước thiết lập ban đầu khi làm việc với MassTransit và chuyển thẳng sang thiết lập ghi nhật ký.
Để bắt đầu, chúng tôi có thể bao gồm nhật ký của chính MassTransit; để làm được điều này, chúng tôi sẽ thêm một gói vào ứng dụng của mình
Install-Package MassTransit.SerilogIntegration
Bây giờ sau khi thêm trình ghi nhật ký vào cài đặt MassTransit, chúng ta sẽ có thể xem nhật ký khung.
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
});
});
Hãy để ứng dụng của chúng tôi phản hồi yêu cầu POST bằng cách gửi sự kiện SomethingDoneMessage với giá trị “xong”. Hợp đồng cho một tin nhắn như vậy có thể được mô tả như sau:
namespace MbMessages
{
public interface ISomethingDoneMessageV1
{
string Value { get; }
}
}
Các tin nhắn MassTransit về cơ bản là một phong bì chứa các tin nhắn của nhà môi giới. Phong bì trông giống như thế này:
{
"messageId": "59020000-5dba-0015-10b8-08d77ec28593",
"requestId": "59020000-5dba-0015-5674-08d77ec28592",
"conversationId": "59020000-5dba-0015-bca8-08d77ec28594",
"destinationAddress": "rabbitmq://bear.rmq.cloudamqp.com/aelzlsta/ya.servicetemplate.receiveendpoint",
"headers": {},
"messageType": [
"urn:message:MbMessages:ISomethingDoneMessageV1"
],
"message": {
"value": "done"
}
}
Thông báo hiển thị các trường dịch vụ cần thiết cho hoạt động của khung, nhưng chúng tôi có khả năng thêm các thuộc tính bổ sung của riêng mình vào phong bì này. Hơn nữa, MassTransit có các công cụ tích hợp sẵn để làm việc với một số trường tùy chọn, trong đó thú vị nhất là mã định danh tương quan CorrelationId.
Hãy thêm giao diện CoreledBy vào hợp đồng tin nhắn:
namespace MbMessages
{
public interface ISomethingDoneMessageV1 : CorrelatedBy<Guid>
{
string Value { get; }
}
}
Hãy triển khai nó và gán giá trị cho thuộc tính CorrelationId khi tạo thông báo:
internal class SomethingDoneMessageV1 : ISomethingDoneMessageV1
{
internal SomethingDoneMessageV1(Guid correlationId, string value)
{
CorrelationId = correlationId;
Value = value;
}
public Guid CorrelationId { get; private set; }
public string Value { get; private set; }
}
Nếu chúng ta xem thông báo được cập nhật, chúng ta sẽ thấy rằng mã định danh tương quan không chỉ trở thành một phần của thông báo của chúng ta mà còn là một phần của phong bì - mã định danh này giờ đây cũng sẽ được sử dụng trong tất cả nhật ký MassTransit, điều đó có nghĩa là nó sẽ dễ dàng hơn nhiều để chúng tôi giải quyết các vấn đề ở cấp độ người môi giới tin nhắn.
{
"messageId": "59020000-5dba-0015-10b8-08d77ec28593",
"requestId": "59020000-5dba-0015-5674-08d77ec28592",
"conversationId": "59020000-5dba-0015-bca8-08d77ec28594",
"correlationId": "c7ff562a-b639-415b-9add-c9e524a727cc",
"destinationAddress": "rabbitmq://bear.rmq.cloudamqp.com/aelzlsta/ya.servicetemplate.receiveendpoint",
"headers": {},
"messageType": [
"urn:message:MbMessages:ISomethingDoneMessageV1"
],
"message": {
"correlationId": "c7ff562a-b639-415b-9add-c9e524a727cc",
"value": "Hello"
}
}
Tất cả những gì chúng ta phải làm là định cấu hình ghi nhật ký các thuộc tính dịch vụ này của tin nhắn; để làm được điều này, chúng ta sẽ thêm một gói vào dự án . Gói này thêm một bộ lọc vào đường dẫn xử lý tin nhắn MassTransit để sắp xếp bối cảnh tin nhắn vào một ngăn xếp an toàn theo luồng. Serilog đọc ngữ cảnh từ ngăn xếp và thêm các thuộc tính bổ sung này vào đối tượng nhật ký của chúng tôi.
Install-Package Serilog.Enrichers.MassTransitMessage
Trong MassTransit, chúng tôi chèn một bộ lọc
services
.AddSingleton(provider =>
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseSerilog();
cfg.UseSerilogMessagePropertiesEnricher();
});
});
Và trong cấu hình Serilog, chúng tôi thêm bộ làm giàu
Log.Logger = new LoggerConfiguration()
.Enrich.FromMassTransitMessage()
.CreateLogger();
Vì ứng dụng nhận tin nhắn từ hàng đợi RabbitMQ có quyền truy cập vào tất cả các thuộc tính của đường bao MassTransit nên chúng tôi có thể sử dụng mã định danh tương quan thu được trong ứng dụng sử dụng cũng như chuyển nó đi xa hơn dọc theo chuỗi cuộc gọi.
Do đó, nhật ký của chúng tôi bắt đầu chứa CorrelationId không chỉ trong một dịch vụ mà còn khi tương tác với các ứng dụng khác.

Vì vậy, hệ thống ghi nhật ký thu được trong các ứng dụng .Net cho phép chúng tôi tương quan các nhật ký từ các dịch vụ vi mô hoàn toàn khác nhau mà không gặp bất kỳ sự cố nào - ngay cả những dịch vụ hoạt động thông qua nhà môi giới tin nhắn. Và với sự trợ giúp của Elaticsearch, chúng ta có thể phân tích nhật ký một cách nhanh chóng và thuận tiện bằng cách xây dựng các trang tổng quan mà chúng ta cần trong Kibana (một ví dụ được hiển thị trong hình cho bài đăng).
Tất nhiên, việc đăng nhập vào biểu mẫu này sẽ không bao gồm các tương tác phức tạp giữa các dịch vụ của bạn và các hệ thống bên ngoài khác nhau, nhưng việc thiết lập trật tự như vậy ngay khi bắt đầu phát triển dự án là một trong những điều mà bạn sẽ phải cảm ơn bản thân nhiều lần.
Bạn có thể hiểu mã nguồn của hệ thống kết quả trong dự án:
Nguồn: www.habr.com
