在WebApi2上使用Serilog的正确方法

时间:2019-03-25 10:39:51

标签: asp.net-web-api2 serilog

我正在寻找将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 }
        );
    }

有更清洁的方法吗?我想知道,如果我必须对控制器进行一些测试,这是否可能是个问题。

1 个答案:

答案 0 :(得分:2)

对于具有Web API(和/或MVC)的应用程序,我已经大量使用以下代码组织。 (请注意,由于它是基于某些软件包的旧版本,因此可能有点过时了,但是您无需过多的工作就可以对其进行修改...)

您将需要一些软件包,您应该能够从名称空间中猜测它们,但最重要的是,安装WebActivatorEx软件包,该软件包提供了一种使代码在应用程序生命周期的不同时刻运行的方法< / p>

我们的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):

App_Start folder structure

  • 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中读取Log配置的可变部分,该部分在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转换,将其转换为用于生产的“令牌化”文件

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();,以确保所有日志事件均已存储/推送
  • 添加Serilog的Enrich.FromLogContext(),以及SerilogWeb.ClassicSerilogWeb.WebApi的一些浓缩器,结果证明它们非常有用。
  • 登录到正确支持结构化日志记录的日志服务器(写入文件有很多缺点)……我们使用Seq并对此感到非常高兴(在每台开发机上本地安装,然后生产中的集中实例)。它支持搜索/查询和仪表板以及动态日志级别控制。