حسین احمدی
بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب

آموزش لاگ (Log) برداری در ASP.NET Core (استفاده از nlog)

در این قسمت از مطالب مرتبط با مبحث Logging در ASP.NET Core با nlog آشنا خواهیم شد. nlog یک کتابخانه جانبی است که بوسیله Nuget به پروژه اضافه میشه. ابتدا در Package Manager Console دستور زیر رو اجرا می کنیم که nlog به پروژه اضافه بشه:

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران
Install-Package NLog.Web.AspNetCore

بعد از اضافه شدن nlog به پروژه باید فایل config رو برای nlog ایجاد کنیم. داخل پوشه اصلی پروژه فایلی با نام nlog.config با محتویات زیر ایجاد می کنیم:

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="info"
      internalLogFile="c:\temp\internal-nlog.txt">

  <!-- enable asp.net core layout renderings -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!-- the targets to write to -->
  <targets>
    <!-- write logs to file  -->
    <target xsi:type="File" name="allfile" fileName="c:\temp\nlog-all-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

    <!-- another file log, only own logs. Uses some ASP.NET core renderers -->
    <target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />

    <target name="database" xsi:type="Database">

      <connectionString>Data Source=.;Initial Catalog=TosinsoDb;Integrated Security=False;User ID=sa;Password=1;MultipleActiveResultSets=True</connectionString>

      <commandText>
        INSERT INTO [dbo].[ApplicationLogs]
        ([Date],[Level],[Message],[UserName],
        [ServerName],[IP],[Controller],
        [Action],[Host],[Url],[UserAuthenticated],
        [UserAgent],[ThreadName],[Logger],
        [Callsite],[Exception])
        VALUES
        (@date,@level,@message,@username,
        @servername,@ip,@controller,
        @action,@host,@url,@userAuthenticated,
        @userAgent,@threadName,@logger,
        @callsite,@exception)
      </commandText>

      <parameter name="@date" layout="${date}" />
      <parameter name="@level" layout="${level}" />
      <parameter name="@message" layout="${message}" />
      <parameter name="@username" layout="${aspnet-user-identity}" />
      <parameter name="@servername" layout="${machinename}" />
      <parameter name="@ip" layout="${aspnet-request-ip}" />
      <parameter name="@controller" layout="${aspnet-mvc-controller}" />
      <parameter name="@action" layout="${aspnet-mvc-action}" />
      <parameter name="@host" layout="${aspnet-request-host}" />
      <parameter name="@url" layout="${aspnet-request-url}" />
      <parameter name="@userAuthenticated" layout="${aspnet-user-isauthenticated}" />
      <parameter name="@userAgent" layout="${aspnet-request-useragent}" />
      <parameter name="@threadName" layout="${threadname}" />
      <parameter name="@logger" layout="${logger}" />
      <parameter name="@callsite" layout="${callsite}" />
      <parameter name="@callsiteLineNumber" layout="${callsite-linenumber}" />
      <parameter name="@exception" layout="${exception:format=tostring}" />
    </target>
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <!--All logs, including from Microsoft-->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <logger name="Tosinso.*" minlevel="Trace" writeTo="database" />

    <!--Skip non-critical Microsoft logs and so log only own logs-->
    <logger name="Microsoft.*" maxLevel="Info" final="true" />
    <!-- BlackHole without writeTo -->
    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
    <logger name="*" minlevel="Trace" writeTo="database" />
  </rules>
</nlog>

داخل این فایل یکسری تنظیمات مورد نیاز برای نوشتن Log ها وجود دارد، به عنوان مثال اینکه Log ما داخل بانک اطلاعاتی نوشته بشه یا فایل، تنظیمات مربوط به مسیر نوشتن فایل ها، اطلاعات بانک اطلاعاتی که Log ها داخلش نوشته میشه و اینکه چه نوع Log هایی در کجا نوشته بشه. بحش های مهم این فایل رو با هم بررسی می کنیم. در ابتدا به این دو خط توجه کنید:

    <target xsi:type="File" name="allfile" fileName="c:\temp\nlog-all-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

    <!-- another file log, only own logs. Uses some ASP.NET core renderers -->
    <target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />

در خطوط بالا دو Target با نام های allFile و ownFile-web تعریف شده که بخش all فایل شامل تمامی Log های Application است و بخش ownFile لاگ های داخلی nlog هست. هر کدوم از این Target ها دو بخش مهم دارن، یکی filename که مسیر فایل رو مشخص می کنه که در اینجا پوشه temp در درایو C هست، نام فایل هم ترکیبی از یک نام ثابت و یک پارامتر هست، در فایل config ما پارامتر ها را بوسیله ${} مشخص می کنیم، برای مثال پارامتر shortdate با تاریخ جاری جایگزین میشه، در زیر لیستی از فایل های نمونه ایجاد شده رو می بینید:

09/06/2019  06:48 PM           163,988 nlog-all-2019-09-06.log
09/07/2019  05:26 PM           279,774 nlog-all-2019-09-07.log
09/14/2019  12:30 PM           127,482 nlog-all-2019-09-14.log
09/15/2019  01:21 PM           114,692 nlog-all-2019-09-15.log
09/12/2018  04:10 PM             2,712 nlog-own-2018-09-12.log
09/13/2018  09:40 PM            29,741 nlog-own-2018-09-13.log
09/14/2018  11:57 PM           233,223 nlog-own-2018-09-14.log
09/15/2018  08:50 PM           294,272 nlog-own-2018-09-15.log
09/16/2018  09:07 PM           364,832 nlog-own-2018-09-16.log

خصوصیت بعدی layout هست که ساختار نوشتن log رو مشخص می کنه، مثل longdate و message یا exception که هر کدوم از این پارامترها با اطلاعات log مثل متن log یا خطایی که اتفاق افتاده جایگزین میشه.تا اینجا بخش نوشتن لاگ در فایل رو داشتیم، بخش بعدی این فایل تنظیمات مربوط به database یا بانک اطلاعاتی هست. تو قدم اول یک جدول با اسکریپت زیر داخل بانک مورد نظرتون ایجاد کنید:

CREATE TABLE [dbo].[ApplicationLogs](
 [Id] [bigint] IDENTITY(1,1) NOT NULL,
 [Date] [datetime] NOT NULL,
 [Level] [nvarchar](50) NOT NULL,
 [Message] [nvarchar](max) NOT NULL,
 [UserName] [nvarchar](250) NULL,
 [ServerName] [nvarchar](max) NULL,
 [IP] [nvarchar](max) NULL,
 [Controller] [nvarchar](max) NULL,
 [Action] [nvarchar](max) NULL,
 [Host] [nvarchar](max) NULL,
 [Url] [nvarchar](max) NULL,
 [UserAuthenticated] [bit] NULL,
 [UserAgent] [nvarchar](max) NULL,
 [ThreadName] [nvarchar](max) NULL,
 [Logger] [nvarchar](250) NULL,
 [Callsite] [nvarchar](max) NULL,
 [Exception] [nvarchar](max) NULL,
 CONSTRAINT [PK_dbo.Log] PRIMARY KEY CLUSTERED 
(
 [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

جدول رو که ایجاد کردید، داخل فایل config باید تنظیمات مورد نیاز برای نوشتن log رو بنویسید، خطوط زیر در فایل config مربوط به این کار هستند:

    <target name="database" xsi:type="Database">

      <connectionString>Data Source=.;Initial Catalog=MyWebsiteDb;Integrated Security=False;User ID=sa;Password=samplepass;MultipleActiveResultSets=True</connectionString>

      <commandText>
        INSERT INTO [dbo].[ApplicationLogs]
        ([Date],[Level],[Message],[UserName],
        [ServerName],[IP],[Controller],
        [Action],[Host],[Url],[UserAuthenticated],
        [UserAgent],[ThreadName],[Logger],
        [Callsite],[Exception])
        VALUES
        (@date,@level,@message,@username,
        @servername,@ip,@controller,
        @action,@host,@url,@userAuthenticated,
        @userAgent,@threadName,@logger,
        @callsite,@exception)
      </commandText>

      <parameter name="@date" layout="${date}" />
      <parameter name="@level" layout="${level}" />
      <parameter name="@message" layout="${message}" />
      <parameter name="@username" layout="${aspnet-user-identity}" />
      <parameter name="@servername" layout="${machinename}" />
      <parameter name="@ip" layout="${aspnet-request-ip}" />
      <parameter name="@controller" layout="${aspnet-mvc-controller}" />
      <parameter name="@action" layout="${aspnet-mvc-action}" />
      <parameter name="@host" layout="${aspnet-request-host}" />
      <parameter name="@url" layout="${aspnet-request-url}" />
      <parameter name="@userAuthenticated" layout="${aspnet-user-isauthenticated}" />
      <parameter name="@userAgent" layout="${aspnet-request-useragent}" />
      <parameter name="@threadName" layout="${threadname}" />
      <parameter name="@logger" layout="${logger}" />
      <parameter name="@callsite" layout="${callsite}" />
      <parameter name="@callsiteLineNumber" layout="${callsite-linenumber}" />
      <parameter name="@exception" layout="${exception:format=tostring}" />
    </target>

نام target ایجاد شده رو database گذاشتیم و type هم برابر Database هست، داخل بخش connectionString تنظیمات ارتباط با بانک اطلاعاتی رو مشخص می کنیم. در بخش commandText دستوری که برای درج رکورد های log در دیتابیس استفاده میشه رو می نویسیم که در اینجا یک دستور INSERT ساده هست. پارامترهایی که در قسمت VALUES استفاده شده، در بخش های بعدی که بوسیله تگ parameter مشخص شده تعریف می کنیم. برای مثال پارامتر controller مقدارش برابر با aspnet-mvc-controller هست که با نام Controller ای که عملیات Log گیری داخل اون انجام میشه جایگزین میشه و سایر پارامتر هایی که اینجا مشخص شده، مثل پیام خطا، نام کاربری، آدرس آی پی و ...

مورد آخر در فایل config مشخص کردن نوع Log ها و target هایی که باید نوشته بشن که به صورت زیر و در بخش rules مشخص میشن:

  <rules>
    <!--All logs, including from Microsoft-->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <logger name="Tosinso.*" minlevel="Trace" writeTo="database" />

    <!--Skip non-critical Microsoft logs and so log only own logs-->
    <logger name="Microsoft.*" maxLevel="Info" final="true" />
    <!-- BlackHole without writeTo -->
    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
    <logger name="*" minlevel="Trace" writeTo="database" />
  </rules>

به عنوان مثال، rule اول، تمامی log ها با حداقل درجه Trace در target ای که با نام allFile تعریف کردیم نوشته میشه یا Log هایی که با Tosinso. شروع میشن با حداقل درجه Trace در database نوشته میشن. 
موردی با نام Microsoft.* خصوصیتی با نام final برابر true مشخص می کنه که کلیه Log هایی که با Microsoft. شروع میشن نباید نوشته بشن.

بعد از اینکه موارد مورد نظر رو مشخص کردیم، تو قدم بعدی باید فایل Program.cs رو به صورت زیر تغییر بدیم تا عملیات Log گیری بوسیله nlog انجام بشه:

public class Program
{
    public static void Main(string[] args)
    {
        var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
        try
        {
            logger.Debug("Init main");
            CreateWebHostBuilder(args).Build().Run();
        }
        catch (Exception e)
        {
            logger.Error(e, "Program stopped because of an exception.");
            throw;
        }
        finally
        {
            LogManager.Shutdown();
        }
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .ConfigureLogging(builder =>
            {
                builder.ClearProviders();
                builder.SetMinimumLevel(LogLevel.Trace);
            }).UseNLog();
}

در داخل متد Main و اولین خط، بوسیله متد ConfigureNLog نام فایل config ای که برای nlog ایجاد کردیم رو مشخص می کنیم. در قدم عای بعدی بوسیله Logger ای که ایجاد شده Log های ابتدایی برای اجرای برنامه نوشته میشه. تو قسمت CreateWebHostBuilder و متد ConfigureLogging ابتدا کلیه Provider های پیش فرض رو حذف می کنیم و در نهایت بوسیله متد UseNLog مشخص می کنیم که از NLog Provider برای Log گیری استفاده بشه.

تنظیمات ما انجام شد، حالا کافیه از کلاس ILogger داخل بخشی که میخواییم استفاده کنیم. برای مثال در Controller زیر یک Log گیری ساده انجام دادیم:

public class HomeController : Controller
{
  private readonly ILogger<HomeController> logger;

    public HomeController(ILogger<HomeController> logger)
    {
        this.logger = logger;
    }

    [HttpGet("")]
    public IActionResult Index()
    {
        logger.LogInformation("Processing request for home page...");
        return Content("OK");
    }
}

اگر موارد گفته شده رو به درستی انجام داده باشید، داخل جدول ApplicationLogs باید رکوردهای جدیدی به همراه Log ای که بالا نوشتیم ایجاد شده باشه. کتابخانه های زیادی علاوه بر nlog برای Log گیری در ASP.NET Core وجود دارن که در سایر مطالب به بررسی اون ها خواهیم پرداخت.


حسین احمدی
حسین احمدی

بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب

حسین احمدی ، بنیانگذار TOSINSO ، توسعه دهنده وب و برنامه نویس ، بیش از 12 سال سابقه فعالیت حرفه ای در سطح کلان ، مشاور ، مدیر پروژه و مدرس نهادهای مالی و اعتباری ، تخصص در پلتفرم دات نت و زبان سی شارپ ، طراحی و توسعه وب ، امنیت نرم افزار ، تحلیل سیستم های اطلاعاتی و داده کاوی ...

نظرات