将 Serilog 与 WebApi2 一起使用的正确方法
Correct way to use Serilog with WebApi2
我正在寻找将 serilog 与 aspnet webapi2 一起使用的正确方法。
至于现在我像这样初始化全局 Log.Logger 属性 :
public static void Register(HttpConfiguration config)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
IndexFormat = IndexFormat,
BufferBaseFilename = outputLogPath,
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6,
CustomFormatter = new ElasticsearchJsonFormatter(renderMessageTemplate: false),
BufferFileCountLimit = NbDaysRetention
})
.MinimumLevel.ControlledBy(new LoggingLevelSwitch() { MinimumLevel = LogEventLevel.Information})
.Enrich.FromLogContext()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiActionName()
.CreateLogger();
//Trace all requests
SerilogWebClassic.Configure(cfg => cfg.LogAtLevel(LogEventLevel.Information));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
有没有更简洁的方法呢?我想知道,如果我必须为我的控制器进行一些测试,这是否会成为一个问题。
对于带有 Web API (and/or MVC) 的应用程序,我经常使用以下代码组织。 (请注意,它可能有点过时,因为它基于某些软件包的旧版本,但你应该能够在不做太多工作的情况下适应它......)
您将需要相当多的包,您应该能够从命名空间中猜出这些包,但最重要的是,安装 WebActivatorEx
包,它提供了一种在不同位置使用代码 运行 的方法应用程序生命周期的时刻
我们的 Global.asax.cs
最终看起来像这样:
public class WebApiApplication : System.Web.HttpApplication
{
// rely on the fact that AppPreStart is called before Application_Start
private static readonly ILogger Logger = Log.ForContext<WebApiApplication>();
public override void Init()
{
base.Init();
}
protected void Application_Start()
{
// WARNING : Some code runs even before this method ... see AppPreStart
Logger.Debug("In Application_Start");
// Mvc (must be before)
AreaRegistration.RegisterAllAreas();
// Web API
// ... snip ...
// some DependencyInjection config ...
// ... snip ...
// MVC
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Logger.Information("App started !");
}
protected void Application_End(object sender, EventArgs e)
{
Logger.Debug("In Application_End");
ApplicationShutdownReason shutdownReason = System.Web.Hosting.HostingEnvironment.ShutdownReason;
Logger.Information("App is shutting down (reason = {@shutdownReason})", shutdownReason);
// WARNING : Some code runs AFTER Application_End ... see AppPostShutDown
}
}
然后 App_Start
文件夹下的几个 类(其中一些你可以忽略 :P ):
AppPreStart.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(AppPreStart), nameof(AppPreStart.PreApplicationStart))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPreStart
{
public static void PreApplicationStart()
{
LogConfig.Configure();
var logger = Log.ForContext<AppPreStart>();
logger.Information("App is starting ...");
// ... snip ...
// very early things like IoC config, AutoMapper config ...
// ... snip ...
logger.Debug("Done with AppPreStart");
}
}
}
AppPostShutDown.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(AppPostShutDown), nameof(AppPostShutDown.PostApplicationShutDown))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPostShutDown
{
private static ILogger Logger = Log.ForContext<AppPostShutDown>();
public static void PostApplicationShutDown()
{
Logger.Debug("PostApplicationShutDown");
// ... snip ...
// very late things like IoC dispose ....
// ... snip ...
// force flushing the last "not logged" events
Logger.Debug("Closing the logger! ");
Log.CloseAndFlush();
}
}
}
LogConfig.cs
using Serilog;
using Serilog.Events;
using SerilogWeb.Classic;
using SerilogWeb.Classic.Enrichers;
using SerilogWeb.Classic.WebApi.Enrichers;
namespace XXX
{
public class LogConfig
{
static public void Configure()
{
ApplicationLifecycleModule.LogPostedFormData = LogPostedFormDataOption.OnlyOnError;
ApplicationLifecycleModule.FormDataLoggingLevel = LogEventLevel.Debug;
ApplicationLifecycleModule.RequestLoggingLevel = LogEventLevel.Debug;
var loggerConfiguration = new LoggerConfiguration().ReadFrom.AppSettings()
.Enrich.FromLogContext()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<UserNameEnricher>()
.Enrich.With<HttpRequestUrlEnricher>()
.Enrich.With<WebApiRouteTemplateEnricher>()
.Enrich.With<WebApiControllerNameEnricher>()
.Enrich.With<WebApiRouteDataEnricher>()
.Enrich.With<WebApiActionNameEnricher>()
;
Log.Logger = loggerConfiguration.CreateLogger();
}
}
}
然后从 Web.config
中读取日志配置的可变部分,在 AppSettings
中具有以下键:
<!-- SeriLog-->
<add key="serilog:level-switch:$controlSwitch" value="Information" />
<add key="serilog:minimum-level:controlled-by" value="$controlSwitch" />
<add key="serilog:enrich:with-property:AppName" value="XXXApp" />
<add key="serilog:enrich:with-property:AppComponent" value="XXXComponent" />
<add key="serilog:enrich:with-property:Environment" value="Dev" />
<add key="serilog:enrich:with-property:MachineName" value="%COMPUTERNAME%" />
<add key="serilog:using:Seq" value="Serilog.Sinks.Seq" />
<add key="serilog:write-to:Seq.serverUrl" value="http://localhost:5341" />
<add key="serilog:write-to:Seq.apiKey" value="xxxxxxxxxxx" />
<add key="serilog:write-to:Seq.controlLevelSwitch" value="$controlSwitch" />
(然后我们进行了 Web.config
转换,将其转换为 "tokenized" 生产文件
Web.Release.config
<!-- SeriLog-->
<add key="serilog:enrich:with-property:Environment" value="__Release_EnvironmentName__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.serverUrl" value="__app_serilogSeqUrl__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.apiKey" value="__app_serilogApiKey__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
此配置的一些最重要的部分是:
- 尽快配置您的记录器
- 在最后调用
Log.CloseAndFlush();
以确保您的所有日志事件都是 stored/pushed
- 添加来自 Serilog 的
Enrich.FromLogContext()
,以及来自 SerilogWeb.Classic
和 SerilogWeb.WebApi
的一些 enrichers,它们会变得非常有用。
- 记录到正确支持结构化日志记录的日志服务器(写入文件有太多缺点)......我们使用 Seq 并且对此非常满意(在每台开发机器上本地安装,然后是生产中的集中实例)。它支持 searching/querying 和仪表板以及动态日志级别控制。
我正在寻找将 serilog 与 aspnet webapi2 一起使用的正确方法。 至于现在我像这样初始化全局 Log.Logger 属性 :
public static void Register(HttpConfiguration config)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
IndexFormat = IndexFormat,
BufferBaseFilename = outputLogPath,
AutoRegisterTemplate = true,
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6,
CustomFormatter = new ElasticsearchJsonFormatter(renderMessageTemplate: false),
BufferFileCountLimit = NbDaysRetention
})
.MinimumLevel.ControlledBy(new LoggingLevelSwitch() { MinimumLevel = LogEventLevel.Information})
.Enrich.FromLogContext()
.Enrich.WithWebApiRouteTemplate()
.Enrich.WithWebApiActionName()
.CreateLogger();
//Trace all requests
SerilogWebClassic.Configure(cfg => cfg.LogAtLevel(LogEventLevel.Information));
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
有没有更简洁的方法呢?我想知道,如果我必须为我的控制器进行一些测试,这是否会成为一个问题。
对于带有 Web API (and/or MVC) 的应用程序,我经常使用以下代码组织。 (请注意,它可能有点过时,因为它基于某些软件包的旧版本,但你应该能够在不做太多工作的情况下适应它......)
您将需要相当多的包,您应该能够从命名空间中猜出这些包,但最重要的是,安装 WebActivatorEx
包,它提供了一种在不同位置使用代码 运行 的方法应用程序生命周期的时刻
我们的 Global.asax.cs
最终看起来像这样:
public class WebApiApplication : System.Web.HttpApplication
{
// rely on the fact that AppPreStart is called before Application_Start
private static readonly ILogger Logger = Log.ForContext<WebApiApplication>();
public override void Init()
{
base.Init();
}
protected void Application_Start()
{
// WARNING : Some code runs even before this method ... see AppPreStart
Logger.Debug("In Application_Start");
// Mvc (must be before)
AreaRegistration.RegisterAllAreas();
// Web API
// ... snip ...
// some DependencyInjection config ...
// ... snip ...
// MVC
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
Logger.Information("App started !");
}
protected void Application_End(object sender, EventArgs e)
{
Logger.Debug("In Application_End");
ApplicationShutdownReason shutdownReason = System.Web.Hosting.HostingEnvironment.ShutdownReason;
Logger.Information("App is shutting down (reason = {@shutdownReason})", shutdownReason);
// WARNING : Some code runs AFTER Application_End ... see AppPostShutDown
}
}
然后 App_Start
文件夹下的几个 类(其中一些你可以忽略 :P ):
AppPreStart.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(AppPreStart), nameof(AppPreStart.PreApplicationStart))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPreStart
{
public static void PreApplicationStart()
{
LogConfig.Configure();
var logger = Log.ForContext<AppPreStart>();
logger.Information("App is starting ...");
// ... snip ...
// very early things like IoC config, AutoMapper config ...
// ... snip ...
logger.Debug("Done with AppPreStart");
}
}
}
AppPostShutDown.cs
using XXX;
using Serilog;
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(AppPostShutDown), nameof(AppPostShutDown.PostApplicationShutDown))]
namespace XXX
{
/// <summary>
/// This runs even before global.asax Application_Start (see WebActivatorConfig)
/// </summary>
public class AppPostShutDown
{
private static ILogger Logger = Log.ForContext<AppPostShutDown>();
public static void PostApplicationShutDown()
{
Logger.Debug("PostApplicationShutDown");
// ... snip ...
// very late things like IoC dispose ....
// ... snip ...
// force flushing the last "not logged" events
Logger.Debug("Closing the logger! ");
Log.CloseAndFlush();
}
}
}
LogConfig.cs
using Serilog;
using Serilog.Events;
using SerilogWeb.Classic;
using SerilogWeb.Classic.Enrichers;
using SerilogWeb.Classic.WebApi.Enrichers;
namespace XXX
{
public class LogConfig
{
static public void Configure()
{
ApplicationLifecycleModule.LogPostedFormData = LogPostedFormDataOption.OnlyOnError;
ApplicationLifecycleModule.FormDataLoggingLevel = LogEventLevel.Debug;
ApplicationLifecycleModule.RequestLoggingLevel = LogEventLevel.Debug;
var loggerConfiguration = new LoggerConfiguration().ReadFrom.AppSettings()
.Enrich.FromLogContext()
.Enrich.With<HttpRequestIdEnricher>()
.Enrich.With<UserNameEnricher>()
.Enrich.With<HttpRequestUrlEnricher>()
.Enrich.With<WebApiRouteTemplateEnricher>()
.Enrich.With<WebApiControllerNameEnricher>()
.Enrich.With<WebApiRouteDataEnricher>()
.Enrich.With<WebApiActionNameEnricher>()
;
Log.Logger = loggerConfiguration.CreateLogger();
}
}
}
然后从 Web.config
中读取日志配置的可变部分,在 AppSettings
中具有以下键:
<!-- SeriLog-->
<add key="serilog:level-switch:$controlSwitch" value="Information" />
<add key="serilog:minimum-level:controlled-by" value="$controlSwitch" />
<add key="serilog:enrich:with-property:AppName" value="XXXApp" />
<add key="serilog:enrich:with-property:AppComponent" value="XXXComponent" />
<add key="serilog:enrich:with-property:Environment" value="Dev" />
<add key="serilog:enrich:with-property:MachineName" value="%COMPUTERNAME%" />
<add key="serilog:using:Seq" value="Serilog.Sinks.Seq" />
<add key="serilog:write-to:Seq.serverUrl" value="http://localhost:5341" />
<add key="serilog:write-to:Seq.apiKey" value="xxxxxxxxxxx" />
<add key="serilog:write-to:Seq.controlLevelSwitch" value="$controlSwitch" />
(然后我们进行了 Web.config
转换,将其转换为 "tokenized" 生产文件
Web.Release.config
<!-- SeriLog-->
<add key="serilog:enrich:with-property:Environment" value="__Release_EnvironmentName__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.serverUrl" value="__app_serilogSeqUrl__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="serilog:write-to:Seq.apiKey" value="__app_serilogApiKey__"
xdt:Transform="Replace" xdt:Locator="Match(key)"/>
此配置的一些最重要的部分是:
- 尽快配置您的记录器
- 在最后调用
Log.CloseAndFlush();
以确保您的所有日志事件都是 stored/pushed - 添加来自 Serilog 的
Enrich.FromLogContext()
,以及来自SerilogWeb.Classic
和SerilogWeb.WebApi
的一些 enrichers,它们会变得非常有用。 - 记录到正确支持结构化日志记录的日志服务器(写入文件有太多缺点)......我们使用 Seq 并且对此非常满意(在每台开发机器上本地安装,然后是生产中的集中实例)。它支持 searching/querying 和仪表板以及动态日志级别控制。