在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:
由於我使用的是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資料庫,所以再安裝
通過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專案也是如此,但是在控制檯應用卻沒有問題,今天晚些時候我將繼續研究並解決這個問題。