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專案也是如此,但是在控制檯應用卻沒有問題,今天晚些時候我將繼續研究並解決這個問題。