1. 程式人生 > 其它 >在asp.net web api 2 (ioc autofac) 使用 Serilog 記錄日誌

在asp.net web api 2 (ioc autofac) 使用 Serilog 記錄日誌

Serilog是.net裡面非常不錯的記錄日誌的庫,另外一個我認為比較好的Log庫是NLog。

在我個人的asp.net web api 2 基礎框架(Github地址)裡,我原來使用的是NLog,但是由於好奇心,我決定使用Serilog代替Nlog。

安裝:

首先安裝 Serilog,通過Package Manager Console或者Nuget管理視窗進行安裝:

PM> Install-Package Serilog

然後安裝 Serilog的Sinks,所謂Sink就是記錄Log的途徑,比如在控制檯輸出,在Debug視窗輸出,輸出到檔案,輸出到資料庫等等。

這裡有一個列表,列出了所有的Sink:

https://github.com/serilog/serilog/wiki/Provided-Sinks

由於我使用的是asp.net web api 2.2 (.Net Framework 4.6+),所以我的專案裡面暫時不需要用到Console,所以不安裝官方教程的Serilog.Sinks.Literate。

但是我需要在VS的Debug視窗顯示Log,所以安裝Serilog.Sinks.Debug

通過Package Manager Console或者Nuget管理視窗進行安裝:

PM> Install-Package Serilog.Sinks.Debug

我還需要輸出到檔案和Sql Server資料庫,所以再安裝

Serilog.Sinks.RollingFile 和 Serilog.Sinks.MSSqlServer

通過Package Manager Console或者Nuget管理視窗進行安裝:

PM> Install-Package Serilog.Sinks.RollingFile
PM> Install-Package Serilog.Sinks.MSSqlServer

這些都安裝完了之後,我們開始配置。

配置:

在Web專案裡,我建立了一個配置類:

   public class SerilogConfiguration
    {
        public static void CreateLogger()
        {
        // 這一部分是配置Sql Server的Sink
            const string connectionString = AppSettings.DefaultConnection; // 資料庫連線字串
            const string tableName = "Logs"; // 表名
            var columnOptions = new ColumnOptions // 自定義欄位
            {
                AdditionalDataColumns = new Collection<DataColumn>
                {
                    new DataColumn {DataType = typeof (string), ColumnName = "User"},
                    new DataColumn {DataType = typeof (string), ColumnName = "Class"},
                }
            };
        // Sql Server的表中加入Json格式Log Event的資料欄位
            columnOptions.Store.Add(StandardColumn.LogEvent);
        // 輸出模板,Sql Server不能用這個
            const string outputTemplate = "[{Timestamp:HH:mm:ss.FFF} {Level}] {Message} ({SourceContext:l}){NewLine}{Exception}";
            Serilog.Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Verbose() // 所有Sink的最小記錄級別
                .Enrich.WithProperty("SourceContext", null) //加入屬性SourceContext,也就執行時是呼叫Logger的具體類
                .Enrich.FromLogContext() //動態加入屬性,主要是針對上面的自定義欄位User和Class,當然也可以隨時加入別的屬性。
                .WriteTo.Debug(
                    outputTemplate: outputTemplate) // 寫到VS Output 視窗
                .WriteTo.RollingFile("logs\{Date}.log", shared: true, restrictedToMinimumLevel: LogEventLevel.Debug,
                    outputTemplate: outputTemplate) // 寫到檔案,每天一個,最小記錄級別是Debug,檔案格式是 yyyyMMdd.log
           // 記錄到Sql Server,最小級別是Information
                .WriteTo.MSSqlServer(connectionString, tableName, columnOptions: columnOptions, autoCreateSqlTable: true, restrictedToMinimumLevel: LogEventLevel.Information)
                .CreateLogger();
        }
    }

配置建立完之後賦值給Serilog.Log.Logger,它是一個靜態變數。

要在進行IOC配置之前呼叫這個配置類。

注意,記錄到Sql server那行配置,我設定的是自動建立表autoCreateSqlTable: true,但是如果建立後,這部分配置(Sql Server Sink)有更改,就需要把生成的表刪掉,再讓它重新自動建立一個,否則就無法再記錄到Sql Server裡面了。

Serilog的級別一共有6個,Verbose - Debug - Information - Warning - Error - Fatal,詳見其文件。

配置IOC

因為我的框架都是使用依賴注入模式的,所以Serilog配置完之後,我們要進行IOC的配置,我使用的是Autofac(非常好的庫),它可以自動Dispose配置的類,如果這個類實現了IDisposable介面的話,例如Serilog。

首先安裝Serilog的Autofac整合庫:

PM> Install-Package AutofacSerilogIntegration

然後到AutofacWebapiConfig.cs進行配置:

builder.RegisterLogger(autowireProperties: true);

非常的簡單,就一句話。

依賴注入

配置完IOC,我們可以注入Serilog的ILogger進行使用,我們把它注入到Service層的CommonService裡而不是所有的Controller裡,這樣就不用改太多程式碼。

namespace LegacyApplication.Services.Core
{
    public interface ICommonService
    {
        IUploadedFileRepository UploadedFileRepository { get; }
        IDepartmentRepository DepartmentRepository { get; }
        ILogger Log { get; }
    }

    public class CommonService : ICommonService
    {
        public IUploadedFileRepository UploadedFileRepository { get; }
        public IDepartmentRepository DepartmentRepository { get; }
        public ILogger Log { get; }

        public CommonService(
            IUploadedFileRepository uploadedFileRepository,
            IDepartmentRepository departmentRepository,
            ILogger log)
        {
            UploadedFileRepository = uploadedFileRepository;
            DepartmentRepository = departmentRepository;
            Log = log;
        }
    }
}

然後在所有Controller的父類裡,就可以獲取到ILogger了。

public abstract class ApiControllerBase : ApiController
    {
        protected readonly ICommonService CommonService;
        protected readonly IUnitOfWork UnitOfWork;
        protected readonly IDepartmentRepository DepartmentRepository;
        protected readonly IUploadedFileRepository UploadedFileRepository;
        protected readonly ILogger Log;

        protected ApiControllerBase(
            ICommonService commonService,
            IUnitOfWork untOfWork)
        {
            CommonService = commonService;
            UnitOfWork = untOfWork;
            DepartmentRepository = commonService.DepartmentRepository;
            UploadedFileRepository = commonService.UploadedFileRepository;
            Log = commonService.Log;
        }

在這個Controller父類(ApiControllerBase.cs)裡,繼續封裝一些Log的方法,以便所有的派生Controller可以簡單的使用:

#region Logging

        [NonAction]
        protected void LogByLevel(LogEventLevel level, string msg)
        {
            using (LogContext.PushProperty("Class", GetType().FullName)) // 對應於自定義的欄位,對Sql server起作用, IDisposable
            using (LogContext.PushProperty("User", CurrentUserName))
            {
                Log.Write(level, $"{msg} (by {CurrentUserName}, at {Now:yyyy-MM-dd HH:mm:ss.FFF})");
            }
        }

        [NonAction]
        protected void LogVerbose(string msg)
        {
            LogByLevel(LogEventLevel.Verbose, msg);
        }

        [NonAction]
        protected void LogDebug(string msg)
        {
            LogByLevel(LogEventLevel.Debug, msg);
        }

        [NonAction]
        protected void LogInformation(string msg)
        {
            LogByLevel(LogEventLevel.Information, msg);
        }

        [NonAction]
        protected void LogWarning(string msg)
        {
            LogByLevel(LogEventLevel.Warning, msg);
        }

        [NonAction]
        protected void LogError(string msg)
        {
            LogByLevel(LogEventLevel.Error, msg);
        }

        [NonAction]
        protected void LogFatal(string msg)
        {
            LogByLevel(LogEventLevel.Fatal, msg);
        }

        #endregion

其中:

using (LogContext.PushProperty("Class", GetType().FullName))
using (LogContext.PushProperty("User", CurrentUserName))

這部分是針對Serilog的Sql Server配置的自定義欄位部分。

全域性異常記錄

針對asp.net web api 2,我使用了自定義的全域性異常記錄類:MyExceptionLogger.cs

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new MyExceptionLogger());
GlobalConfiguration.Configuration.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());
namespace LegacyStandalone.Web.MyConfigurations.Exceptions
{
    public class MyExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
#if DEBUG
            Trace.TraceError(context.ExceptionContext.Exception.ToString());
#endif
            using (LogContext.PushProperty("Class",
                context.ExceptionContext.ControllerContext.ControllerDescriptor.ControllerType))
            using (LogContext.PushProperty("User",
                context.RequestContext.Principal.Identity.Name))
            {
                LogException(context.ExceptionContext.Exception);
            }
        }

        private void LogException(Exception ex)
        {
            if (ex != null)
            {
                LogException(ex.InnerException);
                Serilog.Log.Logger.Error(ex.ToString());
            }
        }
    }
}

在這裡我使用的是靜態版本的Serilog的Logger。

問題

經使用測試,輸出到Debug視窗和Sql Server資料庫是沒有問題的,但是在asp.net web api 2專案的開發環境裡一直無法輸出到檔案,我新建立了一個web api專案也是如此,但是在控制檯應用卻沒有問題,今天晚些時候我將繼續研究並解決這個問題。