استفاده از Elmah در ASP.NET Core

جمعه، 21 دی 1397

Logging
Elmah
ASP.NET Core
Photo by Fredy Jacob on Unsplash
Photo by Fredy Jacob on Unsplash

مقدمه

پیاده سازیه Logging توی برنامه می تونه خیلی مهم باشه. حتی برنامه های کوچیک. به هر حال هر برنامه ای باگ داره، اگرم نداشته باشه کاربرش کرم داره! یعنی بالاخره یه وقتی پیش میاد تا برنامه مون نشیمن گاهِ مبارکشو به یه دلیلی می ذاره زمین و با یه پیغامِ تکراری و منفور بسته می شه. پس پیاده سازیه Logging توی برنامه مهمه، واسه همینم شاید تا حالا یه همچین کدهایی نوشته باشید، یا جاهای دیگه خونده باشید:

1 2 3 4 5 6 7 8 9 10// ASP.NET Core Controller public class FooController : Controller { private readonly ILogger _logger; protected override void OnException(ExceptionContext context) { _logger.Error(context.Exception); } }
1 2 3 4 5 6// React Component class BarComponent extends React.Component{ componentDidCatch(error, info){ logError(error, info); } }

بنابراین اگه بخوایم Logging رو تعریف کنیم (به صورت اجمالی) می شه:

Logging به ساز و کارِ مربوط به ثبتِ اطلاعات در برنامه که چگونگیِ کارکردِ آن را تشریح می کند، گفته می شود. در حقیقت این ساز و کار به شما اجازه می دهد تا روحیات برنامه تان را در هر لحظه از زمان در جایی ثبت کنید.

جایی که این اطلاعات قراره ذخیره بشه می تونه متفاوت باشه، مثلا یه TextFile ِ ساده یا یه دیتابیسِ بزرگ و پیچیده. به هر حال آنچه که اهمیت داره، اینه که به وقت نیازش آقا یا خانمِ پشتیبان یا برنامه نویس بتونه این منبع اطلاعاتی رو باز کنه و از وضعیت برنامه مطلع بشه. باید توجه داشت که همیشه هم اطلاعات ثبت شده قرار نیست مربوط به خاطاها و وضعیتای بحرانی باشه. خیلی وقتا هم می تونه اطلاعاتِ کارکردِ سیستم باشه. مثلا "فاکتور شماره 123 در سیستم ثبت شد".

مبحثِ Logging هم مثل خیلی مباحثِ دیگه ی صنعت ما توی این سال ها کلی تغیر کرده. یکی از این تغیرا اینه که سر و کله ی کلی ابزار جدید پیدا شده که پروسه ی خوندنِ Logها رو متفاوت کردن. یعنی به نوعی راحت تر کردن. بنابراین دیگه مجبور نیستیم با یه سری فایل و متنِ چغر کلنجار بریم. به جاش یه سری نمودارِ رنگی و جذاب رو تماشا می کنیم! کی بدش میاد؟!

توی این مقاله یکی از این ابزارا به نام Elmah رو خیلی مختصر معرفی می کنم و یه مثال کوچیک باهاش اجرا می کنم.

در حال حاضر همه ی Planهای این سرویس پولی هستن و تنها راه استفاده ازش همون دوره ی رایگانشه (Free Trial). با این شرایطی که داریم (از نظر سیاسی و اقتصادی) شاید استفاده از این چیزا به نظر مسخره میاد. موضوع اینجاس که قرار نیست حتما از این ابزار خاص استفاده کنیم. اون بیرون کلی ابزار و سرویس دیگه هست که می شه از اونا استفاده کرد. به نظرم وقت صرفِ این چیزا کردن اصلا اتلاف وقت نیست چون در نهایت چیزی که مهمه اینه که آشنا شدن با همچین چیزایی باعث ارتقای تجربه و از اون مهم تر توقع ما می شه.

Elmah چیست؟

Elmah یه ابزاره مخصوصِ مانیتور کردنِ Logهای ثبت شده. و به خاطر اینکه مبتنی بر Cloud کار می کنه آدم رو از شر راه اندازی و نگهداری خلاص می کنه. تنها کاری که آدم باید بکنه اینه که توی سایتش ثبت نام کنه...

استفاده از Elmah در ASP.NET Core

اول از همه توی سایت Elmah عضو بشید. بعدش سایت به شما دو تا شناسه می ده (API Key و Log ID) که یکیش برای API هست و اون یکیش واسه منبعِ Log. (چون می تونید چندین منبعِ Log توی Elmah درست کنید). باید این دو تا شناسه رو توی برنامه تون استفاده کنید، که جلوتر توضیح دادم.

کدهای زیر برای ASP.NET Core 2.x نوشته شدن.

بعد از عضویت یه پروژه ی ASP.NET Core بسازید و پکیجِ Elmah رو نصب کنید:

Install-Package Elmah.Io.Extensions.Logging

تنظیمات مربوط به Logging هم توی متدِ سازنده ی Host تو فایلِ Program.cs نوشته می شه:

1 2 3 4 5 6 7 8 9 10 11public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureLogging((context, builder) => { builder.AddElmahIo(options => { options.ApiKey = "API-Key"; options.LogId = Guid.Parse("LOG-ID"); }); }) .UseStartup

رشته های API Key و Log ID رو تو جاهای مشخص شده وارد کنید.

کلاسِ Product

فرض بر اینه که داریم یه برنامه واسه پردازشِ سفارشاتِ مشتریا می نویسیم. کلاسِ Product مدلیه (Input Model) که از سمتِ Client به برنامه ی ما ارسال می شه. برای این کلاس چند تا قانونِ Validation با کمک Attributeها مشخص کردیم. یکیش اینه که نامِ محصول حتما باید مقداردهی شده باشه و اون یکیش هم اینه که تعدادِ اون باید حتما بین 10 تا 20 باشه. شرایطِ Validation ِ محصول ارسالی رو متدِ Order1 بررسی می کنه. که در ادامه توضیح دادم.

1 2 3 4 5 6 7 8public class Product { [Required] public string Name { get; set; } [Range(10, 20)] public int Quantity { get; set; } }

کلاسِ OrderController

این کلاس یه APIController ِ خیلی سادست که از ILogger برای ثبت Logهاش استفاده کرده. وظیفه ی این Controller مثلا پردازشِ سفارشاته.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55public class OrderController : ControllerBase { public OrderController(IService service, ILogger logger) { this.service = service; this.logger = logger; } private readonly IService service; private readonly ILogger logger; [HttpPost] public IActionResult Order1([FromBody]Product product) { if (!ModelState.IsValid) { var errors = new List(); errors.Add("Bad Request for order ({Product}, {Qty})"); errors.AddRange(ModelState .Values .SelectMany(x => x.Errors) .Select(x => x.ErrorMessage)); var errorsMsg = string.Join(" | ", errors); logger.LogWarning(errorsMsg, product.Name, product.Quantity); return BadRequest(ModelState); } service.ProcessWithNoError(product); logger.LogInformation("Successful Order for ({Product}, {Qty}!", product.Name, product.Quantity); return Ok(); } [HttpPost] public IActionResult Order2([FromBody]Product product) { try { service.ProcessWithError(product); } catch (Exception ex) { logger.LogError(ex, ex.Message); return StatusCode(500, new { Error = ex.Message }); } return Ok(); } [HttpPost] public IActionResult Order3([FromBody] Product product) { service.ProcessWithError(product); return Ok(); } }

دو تا Dependency هم به این کلاس Inject شده:

توی این کلاس سه تا ActionMethod وجود داره که هر کدوم شون یه موقعیت خاص برای Logging رو شبیه سازی می کنه. به ترتیب این سه تا متد رو بررسی می کنیم.

Order1

تو این متد اول Validation ِ محصول ارسال شده بررسی می شه. اگه Valid نبود از تمام خطاها یه پیغام ساخته می شه و با حساسیتِ Warning توی سیستم Log می شه. در نهایت هم کدِ 400 یعنی BadRequest به سمتِ Client ارسال می شه.

ولی اگه محصول مشکلی نداشت و Valid بود، اول واسه پردازش تحویل متدِ ProcessWithNoError می شه و در نهایت هم یه Log با حساسیتِ Information ثبت می شه. همون طوری که از اسمِ متدِ ProcessWithNoError معلومه، این متد قرار نیست توی طول اجراش خطایی داشته باشه. بنابراین متدِ Order1 دو تا سناریو رو شبیه سازی می کنه، یکی حالتی که مدل Valid نیست و تراکنش انجام نمی شه و دومی هم حالتی که همه چیز مرتبه و تراکنش با موفقیت اجرا می شه.

یه توضیح کوچیک هم واسه حساسیتِ Warning: این حساسیت معنی اینو می ده که شرایط واسه بروزِ یه خطای بالقوه فراهم بوده. از این حساسیت معمولا واسه ثبتِ شرایطی که اجرای برنامه رو تهدید می کنه، استفاده می کنن. به کلمه ی "تهدید" دقت کنید. یعنی عملا آسیبی به برنامه وارد نشده ولی می تونسته بشه! اگه Client اطلاعات غلطی واسه API ارسال کنه می تونه اجرای برنامه رو به صورت بالقوه مختل کنه! فرض کنید تعداد رو یه عدد منفی بفرسته. یا صفر بفرسته. همون طور که توی کد مشخص شده با انجام Validation می تونیم از آسیب شدن این تهدیدا جلوگیری کنیم. ثبت کردنِ این اطلاعات با حساسیتِ Warning می تونه بعدا گزارشات خوبی بهمون بده.

سناریوی بعدی اینه که IService نمی تونه کارش رو درست انجام بده. یعنی باعث بروزِ خطا می شه. متدِ Order2 این شرایط رو بررسی می کنه.

Order2

تو این متد محصول واسه پردازش تحویلِ متدِ ProcessWithError می شه. این متد هم یه Exception تولید می کنه. متدِ Order2 با بلوکِ try/catch از بروزِ خطا توی برنامه جلوگیری می کنه و در عین حال اطلاعات خطا رو Log می کنه.

این متد برای ثبتِ Log از حساسیتِ Error استفاده می کنه. هر وقت توی برنامه خطایی رخ بده که بتونیم کنترلش کنیم (مثلا اینجا با try/catch این کارو کرده) از این حساسیت استفاده می شه. فرق Error با Warning اینه که Warning واسه شرایطِ بالقوه است ولی Error واسه شرایطِ بالفعله.

سناریوی آخر هم واسه وقتاییه که یه خطا باعث می شه کلا برنامه از کار بیافته. متدِ Order3 این شرایط رو نشون می ده.

Order3

تمام برنامه هایی که اصولی ساخته شدن با بروزِ Exceptionهای کنترل نشده (یا اصطلاحا همون Unhandled Exceptionها) بسته می شن. این چیز خوبیه. چون قطع شدن اجرای برنامه خیلی بهتر از ادامه ی اجرا با یه وضعیتِ غیر قابل اعتماده.

توی این شرایط بهتره یک Log با حساسیتِ Critical (یا Fatal) ثبت کنیم و بزاریم برنامه بسته بشه. دیگه حساسیتِ Critical هم نیاز به توضیح زیادی نداره. از این حساسیت برای شرایطی استفاده می شه که بروز خطا اجرای برنامه رو مختل می کنه.

گزارشات Elmah

حالا با چند بار اجرای هر کدوم از این متدا سراغ داشبودِ Elmah می ریم تا ببینیم چه اطلاعاتی می شه ازش گرفت.

داشبوردِ اصلی یه نمودار داره که تعداد Logها رو با حساسیت هاشون نشون می ده. دیدن این نمودار به تنهایی می تونه به ما بگه که برنامه مون چند چنده. یعنی حالش خوبه یا نه. اصولا هرچقدر رنگای قرمز و نارنجی کمتر باشه، بهتره!

به جز اینا خیلی اطلاعات دیگه هم توی این صفحه وجود داره. مثل مرورگری که Client ازش استفاده کرده. یا متداول ترین Logها یا آخرین Logها و غیره.

پنل اصلیِ داشتبور
پنل اصلیِ داشتبور

اطلاعاتِ دقیقی که این نمودار ازشون ساخته شده توی تبِ Search قرار گرفته. تو این تب یه جست و جوی (به نوع خودش) خیلی قوی هم هست که وجودش نعمتیه!

لیست Logها
لیست Logها
جزئیات یک Log
جزئیات یک Log

یکی از ویژگی های خوب سیستمای مدرنِ Logging اینه که به آدم قالبیتِ ضمیمه اطلاعات اضافی هم می دن. مثلا توی نمونه ای که ما ساختیم به همراه پیغامِ Log، نام و تعداد محصول هم ثبت می شه. این اطلاعات اصولا به صورت Key/Value Pair هستن. واسه اطلاعات بیشتر تو این زمینه درباره ی Structured Logging مطالعه کنید.

Key/ValuePairهای ثبت شده
Key/ValuePairهای ثبت شده

ختم کلام

پیاده کردنِ یه راهکارِ Logging ِ شسته رفته واسه هر سیستمی هم مهمه، هم مفید. این مسئله وقتی اهمیتِ خودش رو نشون می ده که تو تنگا هستیم و دستمون به هیج جا بند نیست! امیدوارم توی این پست با معرفیِ یه ابزارِ پر زرق و برق این وسوسه ایجاد شده باشه تا سریع تر یه فکر اساسی واسه پیاده سازیِ Logging تو برنامه هاتون بکنید. Elmah تنها ابزار واسه این کار نیست، کلی برنامه و سرویسِ دیگه پیدا می شه. چیزی که در نهایت مهمه اینه که یه چیزی داشته باشیم تا باهاش کارکردِ برنامه مون رو خوب و راحت مانیتور کنیم.

کد کامل پروژه ی پیاده شده در این پست رو می تونید از اینجا بگیرید.