⚙️ Cross-Cutting Concern شماره ۱ - لاگگیری (Logging)
لاگگیری یکی از جنبههای بنیادین توسعهٔ نرمافزار است که به شما اجازه میدهد رفتار یک برنامه را بررسی کنید.
این قابلیت برای دیباگ کردن (Debugging)، پایش سلامت سیستم (Monitoring Application Health) و ردیابی فعالیت کاربران و ناهنجاریهای سیستم (Tracking User Activities and System Anomalies) حیاتی است.
در زمینهٔ Clean Architecture، پیادهسازی لاگگیری باید به گونهای انجام شود که اصل تفکیک وظایف (Separation of Concerns) حفظ شود.
🧠 رویکرد ظریف با MediatR’s IPipelineBehavior
یکی از راههای زیبا برای رسیدن به این هدف، استفاده از IPipelineBehavior در MediatR است.
با قرار دادن منطق لاگگیری درون یک Pipeline Behavior، اطمینان حاصل میکنیم که لاگگیری بهعنوان یک دغدغهٔ مجزا و مستقل از منطق تجاری در نظر گرفته میشود.
این روش به ما امکان میدهد اطلاعات دقیقی از درخواستهایی که در سراسر برنامه جریان دارند ثبت کنیم.
📋 ویژگیهای یک سیستم لاگگیری مؤثر
لاگگیری مؤثر باید یکنواخت (Consistent)، دارای زمینه (Context-Rich) و غیرمزاحم (Non-Intrusive) باشد.
با استفاده از قابلیتهای Structured Logging در Serilog، میتوانیم لاگهایی بسازیم که نهتنها گویا هستند بلکه بهراحتی قابل جستوجو نیز هستند.
این ویژگی برای درک وضعیت برنامه در هر لحظه از زمان ضروری است.
اگر این کار بهدرستی انجام شود، Structured Logging بینشهای بسیار ارزشمندی دربارهٔ وضعیت برنامه ارائه میدهد —
بدون آنکه منطق اصلی سیستم را شلوغ کند.
این کار نوعی تعادل میان جزئیات و شفافیت (Balance of Granularity and Clarity) است که اطمینان میدهد لاگها ابزاری مفید باشند نه منبعی از نویز.
🧩 نمونه کد
using Serilog.Context;
internal sealed class RequestLoggingPipelineBehavior<TRequest, TResponse>(
ILogger<RequestLoggingPipelineBehavior<TRequest, TResponse>> logger)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : class
where TResponse : Result
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
string requestName = typeof(TRequest).Name;
logger.LogInformation(
"Processing request {RequestName}",
requestName);
TResponse result = await next();
if (result.IsSuccess)
{
logger.LogInformation(
"Completed request {RequestName}",
requestName);
}
else
{
using (LogContext.PushProperty("Error", result.Error, true))
{
logger.LogError(
"Completed request {RequestName} with error",
requestName);
}
}
return result;
}
}