{"id":9645,"date":"2017-04-26T07:29:02","date_gmt":"2017-04-26T14:29:02","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/webdev\/?p=9645"},"modified":"2017-04-26T07:29:02","modified_gmt":"2017-04-26T14:29:02","slug":"asp-net-core-logging","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/asp-net-core-logging\/","title":{"rendered":"ASP.NET Core Logging with Azure App Service and Serilog"},"content":{"rendered":"<h5 class=\"code-line\">\n  <em>This guest post was written by Mike Rousos<\/em>\n<\/h5>\n<p class=\"code-line\">\n  ASP.NET Core supports diagnostic logging through the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging\/\">Microsoft.Extensions.Logging<\/a> package. This logging solution (which is used throughout ASP.NET Core, including internally by the Kestrel host) is highly extensible. There&#8217;s already <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/logging\">documentation<\/a> available to help developers get started with ASP.NET Core logging, so I&#8217;d like to use this post to highlight how custom log providers (like <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging.AzureAppServices\/\">Microsoft.Extensions.Logging.AzureAppServices<\/a> and <a href=\"https:\/\/www.nuget.org\/packages\/Serilog.Extensions.Logging\/1.4.0-dev-10138\">Serilog<\/a>) make it easy to log to a wide variety of destinations.\n<\/p>\n<p class=\"code-line\">\n  It&#8217;s also worth mentioning that nothing in Microsoft.Extensions.Logging requires ASP.NET Core, so these same logging solutions can work in any <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/articles\/standard\/library\">.NET Standard<\/a> environment.\n<\/p>\n<h2 class=\"code-line\" id=\"a-quick-overview\">\n  A Quick Overview\n<\/h2>\n<p class=\"code-line\">\n  Setting up logging in an ASP.NET Core app doesn&#8217;t require much code. ASP.NET Core new project templates already setup some basic logging providers with this code in the <code>Startup.Configure<\/code> method:\n<\/p>\n<pre>loggerFactory.AddConsole(Configuration.GetSection(\"Logging\")); \nloggerFactory.AddDebug();<\/pre>\n<p class=\"code-line\">\n  These methods register logging providers on an instance of the <code>ILoggerFactory<\/code> interface which is provided to the <code>Startup.Configure<\/code> method via dependency injection. The <code>AddConsole<\/code> and <code>AddDebug<\/code> methods are just extension methods which <a href=\"https:\/\/github.com\/aspnet\/Logging\/blob\/dev\/src\/Microsoft.Extensions.Logging.Console\/ConsoleLoggerFactoryExtensions.cs#L79\">wrap calls<\/a> to <code>ILoggerFactory.AddProvider<\/code>.\n<\/p>\n<p class=\"code-line\">\n  Once these providers are registered, the application can log to them using an <code>ILogger&lt;T&gt;<\/code> (retrieved, again, via dependency injection). The generic parameter in <code>ILogger&lt;T&gt;<\/code> will be used as the logger&#8217;s <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/logging#log-category\">category<\/a>. By convention, ASP.NET Core apps use the class name of the code logging an event as the event&#8217;s category. This makes it easy to know where events came from when reviewing them later.\n<\/p>\n<p class=\"code-line\">\n  It&#8217;s also possible to retrieve an <code>ILoggerFactory<\/code> and use the <code>CreateLogger<\/code> method to generate an <code>ILogger<\/code> with a custom category.\n<\/p>\n<p class=\"code-line\">\n  <code>ILogger<\/code>&#8216;s log APIs send diagnostic messages to the logging providers you have registered.\n<\/p>\n<h2 class=\"code-line\" id=\"structured-logging\">\n  Structured Logging\n<\/h2>\n<p class=\"code-line\">\n  One useful characteristic of <code>ILogger<\/code> logging APIs (<code>LogInformation<\/code>, <code>LogWarning<\/code>, etc.) is that they take both a message string and an <code>object[]<\/code> of arguments to be formatted into the message. This is useful because, in addition to passing the formatted message to logging providers, the individual arguments are also made available so that logging providers can record them in a structured format. This makes it easy to query for events based on those arguments.\n<\/p>\n<p class=\"code-line\">\n  So, make sure to take advantage of the args parameter when logging messages with an <code>ILogger<\/code>. Instead of calling\n<\/p>\n<p class=\"code-line\">\n  <code>Logger.LogInformation(\"Retrieved \" + records.Count + \" records for user \" + user.Id)<\/code>\n<\/p>\n<p class=\"code-line\">\n  Consider calling\n<\/p>\n<p class=\"code-line\">\n  <code>Logger.LogInformation(\"Retrieved {recordsCount} records for user {user}\", records.Count, user.Id)<\/code>\n<\/p>\n<p class=\"code-line\">\n  so that later you can easily query to see how many records are returned on average, or query only for events relating to a particular user or with more than a specific number of records.\n<\/p>\n<h2 class=\"code-line\" id=\"azure-app-service-logging\">\n  Azure App Service Logging\n<\/h2>\n<p class=\"code-line\">\n  If you will be deploying your ASP.NET Core app as an <a href=\"https:\/\/azure.microsoft.com\/en-us\/services\/app-service\/\">Azure app service<\/a> web app or API, make sure to try out the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging.AzureAppServices\/\">Microsoft.Extensions.Logging.AzureAppServices<\/a> logging provider.\n<\/p>\n<p class=\"code-line\">\n  Like other logging providers, the Azure app service provider can be registered on an <code>ILoggerFactory<\/code> instance:\n<\/p>\n<pre>loggerFactory.AddAzureWebAppDiagnostics( \n  new AzureAppServicesDiagnosticsSettings \n  {\n    OutputTemplate = \"{Timestamp:yyyy-MM-dd HH:mm:ss zzz} [{Level}] {RequestId}-{SourceContext}: {Message}{NewLine}{Exception}\" \n  } \n);<\/pre>\n<p class=\"code-line\">\n  The <code>AzureAppServicesDiagnosticsSettings<\/code> argument is optional, but allows you to specify the format of logged messages (as shown in the sample, above), or customize how Azure will store diagnostic messages.\n<\/p>\n<p class=\"code-line\">\n  Notice that the output format string can include common <code>Microsoft.Extensions.Logging<\/code> parameters (like Level and Message) or ASP.NET Core-specific scopes like RequestId and SourceContext.\n<\/p>\n<p class=\"code-line\">\n  To log messages, application logging must be enabled for the Azure app service. Application logging can be enabled in the Azure portal under the app service&#8217;s &#8216;Diagnostic logs&#8217; page. Logging can be sent either to the file system or blob storage. Blob storage is a better option for longer-term diagnostic storage, but logging to the file system allows logs to be streamed. Note that file system application logging should only be turned on temporarily, as needed. The setting will automatically turn itself back off after 12 hours.\n<\/p>\n<p class=\"code-line\">\n  \u00a0 <img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/EnableAzureAppServiceLogging-1.png\" alt=\"Enable Azure App Service Logging\" class=\"aligncenter wp-image-9665 size-full\" title=\"Enable Azure App Service Logging\" width=\"799\" height=\"262\" \/>\n<\/p>\n<p class=\"code-line\">\n  Logging can also be enabled with the Azure CLI:\n<\/p>\n<pre>az appservice web log config --application-logging true --level information -n [Web App Name] -g [Resource Group]<\/pre>\n<p class=\"code-line\">\n  Once logging has been enabled, the Azure app service logging provider will automatically begin recording messages. Logs can be downloaded via FTP (see information in the diagnostics log pane in the Azure portal) or streamed live to a console. This can be done either through the Azure portal or with the Azure CLI. Notice the streamed messages use the output format specified in the code snippet above.\n<\/p>\n<p class=\"code-line\">\n  <a href=\"http:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/AppServiceLogStreaming.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/AppServiceLogStreaming-1024x671-1.png\" alt=\"AppService Log Streaming\" class=\"aligncenter wp-image-9655 size-large\" width=\"879\" height=\"576\" \/><\/a>\n<\/p>\n<p class=\"code-line\">\n  The Azure app service logging provider is one example of a useful logging extension available for ASP.NET Core. Of course, if your app is not run as an Azure app service (perhaps it&#8217;s run as a microservice in Azure Container Service, for example), you will need other logging providers. Fortunately, ASP.NET Core has many to choose from.\n<\/p>\n<h2 class=\"code-line\" id=\"serilog\">\n  Serilog\n<\/h2>\n<p class=\"code-line\">\n  ASP.NET Core logging documentation lists the many <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/logging#built-in-logging-providers\">built-in providers<\/a> available. In addition to the providers already seen (console, debug, and Azure app service), these include useful providers for writing to <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging.EventSource\">ETW<\/a>, the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging.EventLog\">Windows EventLog<\/a>, or <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging.TraceSource\">.NET trace sources<\/a>.\n<\/p>\n<p class=\"code-line\">\n  There are also many <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/logging#third-party-logging-providers\">third-party providers<\/a> available. One of these is the <a href=\"https:\/\/serilog.net\/\">Serilog<\/a> provider. Serilog is a notable logging technology both because it is a structured logging solution and because of the <a href=\"https:\/\/github.com\/serilog\/serilog\/wiki\/Provided-Sinks\">wide variety<\/a> of custom sinks it supports. Most Serilog sinks now support .NET Standard.\n<\/p>\n<p class=\"code-line\">\n  I&#8217;ve recently worked with customers interested in logging diagnostic information to custom data stores like <a href=\"https:\/\/azure.microsoft.com\/en-us\/services\/storage\/tables\/\">Azure Table Storage<\/a>, <a href=\"https:\/\/azure.microsoft.com\/en-us\/services\/application-insights\/\">Application Insights<\/a>, <a href=\"https:\/\/aws.amazon.com\/cloudwatch\/\">Amazon CloudWatch<\/a>, or <a href=\"https:\/\/www.elastic.co\/\">Elasticsearch<\/a>. One approach might be to just use the default console logger or another built-in provider and capture the events from those output streams and redirect them. The problem with that approach is that it&#8217;s not suitable for production environments since the console log provider is slow and redirecting from other destinations involves unnecessary extra work. It would be much better to log batches of messages to the desired data store directly.\n<\/p>\n<p class=\"code-line\">\n  Fortunately, Serilog sinks exist for all of these data stores that do exactly that. Let&#8217;s take a quick look at how to set those up.\n<\/p>\n<p class=\"code-line\">\n  First, we need to reference the <code>Serilog.Extensions.Logging<\/code> package. Then, register the Serilog provider in <code>Startup.Configure<\/code>:\n<\/p>\n<pre>loggerFactory.AddSerilog();<\/pre>\n<p class=\"code-line\">\n  <code>AddSerilog<\/code> registers a Serilog <code>ILogger<\/code> to receive logging events. There are two different overloads of <code>AddSerilog <\/code>that you may call depending on how you want to provide an <code>ILogger<\/code>. If no parameters are passed, then the global <code>Log.Logger<\/code> Serilog instance will be registered to receive events. Alternatively, if you wish to provide the <code>ILogger<\/code> via dependency injection, you can use the <code>AddSerilog<\/code> overload which takes an <code>ILogger<\/code> as a parameter. Regardless of which <code>AddSerilog<\/code> overload you choose, you&#8217;ll need to make sure that your <code>ILogger<\/code> is setup (typically in the <code>Startup.ConfigureServices<\/code> method).\n<\/p>\n<p class=\"code-line\">\n  To create an <code>ILogger<\/code>, you will first create a new <code>LoggerConfiguration<\/code> object, then configure it (more on this below), and call <code>LoggerConfiguration.CreateLogger()<\/code>. If you will be registering the static <code>Log.Logger<\/code>, then just assign the logger you have created to that property. If, on the other hand, you will be retrieving an <code>ILogger<\/code> via dependency injection, then you can use <code>services.AddSingleton&lt;Serilog.ILogger&gt;<\/code> to register it.\n<\/p>\n<h3 class=\"code-line\" id=\"configuring-serilog-sinks\">\n  Configuring Serilog Sinks\n<\/h3>\n<p class=\"code-line\">\n  There are a few ways to configure Serilog sinks. One good approach is to use the <code>LoggerConfiguration.ReadFrom.Configuration<\/code> method which accepts an <code>IConfiguration<\/code> as an input parameter and reads sink information from the configuration. This <code>IConfiguration<\/code> is the same configuration interface that is used elsewhere for <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\">ASP.NET Core configuration<\/a>, so your app&#8217;s Startup.cs probably already creates one.\n<\/p>\n<p class=\"code-line\">\n  The ability to configure Serilog from <code>IConfiguration<\/code> is contained in the <code>Serilog.Settings.Configuration <\/code>package. Make sure to add a reference to that package (as well as any packages containing sinks you intend to use).\n<\/p>\n<p class=\"code-line\">\n  The complete call to create an <code>ILogger<\/code> from configuration would look like this:\n<\/p>\n<pre>var logger = new LoggerConfiguration() \n  .ReadFrom.Configuration(Configuration) \n  .CreateLogger(); \n\nLog.Logger = logger; \n\/\/ or: services.AddSingleton&lt;Serilog.ILogger&gt;(logger);<\/pre>\n<p class=\"code-line\">\n  Then, in a configuration file (like appsettings.json), you can specify your desired Serilog configuration. Serilog expects to find a configuration element named &#8216;Serilog&#8217;. In that configuration property, you can specify a minimum event level to log and a &#8216;writeto&#8217; element that is an array of sinks. Each sink needs a &#8216;name&#8217; property to identify the kind of sink it is and, optionally, can take an args object to configure the sink.\n<\/p>\n<p class=\"code-line\">\n  As an example, here is an appsettings.json file that sets the minimum logging level to &#8216;Information&#8217; and adds two sinks &#8211; one for Elasticsearch and one for LiterateConsole (a nifty color-coded structured logging sink that writes to the console):\n<\/p>\n<p class=\"code-line\">\n  Another option for configuring Serilog sinks is to add them programmatically when creating the <code>ILogger<\/code>. For example, here is an updated version of our previous <code>ILogger<\/code> creation logic which loads Serilog settings from configuration <em>and<\/em> adds additional sinks programmatically using the <code>WriteTo<\/code> property:\n<\/p>\n<pre><span>var logger = new LoggerConfiguration() \n  .ReadFrom.Configuration(Configuration) \n  .WriteTo.AzureTableStorage(connectionString, LogEventLevel.Information) \n  .WriteTo.AmazonCloudWatch(new CloudWatchSinkOptions \n    { \n      LogGroupName = \"MyLogGroupName\",   \n      MinimumLogEventLevel = LogEventLevel.Warning \n    }, new AmazonCloudWatchLogsClient(new InstanceProfileAWSCredentials(), RegionEndpoint.APNortheast1))\n  .CreateLogger();<\/span><\/pre>\n<p class=\"code-line\">\n  In this example, we&#8217;re using Azure credentials from a connection string and AWS credentials from the current instance profile (assuming that this code will run on an EC2 instance). We could just as easily use a different <code>AWSCredentials <\/code>class if we wanted to load credentials in some other way.\n<\/p>\n<p class=\"code-line\">\n  Once Serilog is setup and registered with your application&#8217;s ILoggerFactory, you will start seeing events (both those you log with an <code>ILogger<\/code> and those internally logged by Kestrel) in all appropriate sinks!\n<\/p>\n<p class=\"code-line\">\n  Here is a screenshot of events logged by the LiterateConsole sink and a screenshot of an Elasticsearch event viewed in Kibana:\n<\/p>\n<p class=\"code-line\">\n  <a href=\"http:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/LiterateConsoleOutput.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/LiterateConsoleOutput-1024x473-1.png\" alt=\"Literate Console Output\" class=\"aligncenter size-large wp-image-9666\" width=\"879\" height=\"406\" \/><\/a><a href=\"http:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/ElasticsearchLogEntry.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2017\/04\/ElasticsearchLogEntry-1.png\" alt=\"Elastic Search Log Entry\" class=\"aligncenter size-full wp-image-9656\" width=\"750\" height=\"475\" \/><\/a>\n<\/p>\n<p class=\"code-line\">\n  Notice in the Elasticsearch event, there are a number of fields available besides the message. Some (like taskID), I defined through my message format string. Others (like RequestPath or RequestId) are automatically included by ASP.NET Core. This is the power of structured logging &#8211; in addition to searching just on the message, I can also query based on these fields. The Azure table storage sink preserves these additional data points as well in a json blob in its &#8216;data&#8217; column:\n<\/p>\n<h2 class=\"code-line\" id=\"conclusion\">\n  Conclusion\n<\/h2>\n<p class=\"code-line\">\n  Thanks to the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging\/\">Microsoft.Extensions.Logging<\/a> package, ASP.NET Core apps can easily log to a wide variety of endpoints. Built-in logging providers cover many scenarios, and thid-party providers like <a href=\"https:\/\/serilog.net\/\">Serilog<\/a> add even more options. Hopefully this post has helped give an overview of the ASP.NET Core (and .NET Standard) logging ecosystem. Please check out the <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/logging\">official docs<\/a> and <a href=\"https:\/\/github.com\/serilog\/serilog-extensions-logging\">Serilog.Extensions.Logging readme<\/a> for more information.\n<\/p>\n<h2 class=\"code-line\" id=\"resources\">\n  Resources\n<\/h2>\n<p><li class=\"code-line\">\n  <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/logging\">ASP.NET Core Logging Documentation<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Logging.AzureAppServices\">Azure App Services Logging Provider<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/serilog.net\/\">Serilog<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/github.com\/serilog\/serilog-extensions-logging\">Serilog.Extensions.Logging<\/a> (the Microsoft.Extensions.Logging provider for Serilog)\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/github.com\/serilog\/serilog-sinks-literate\">Serilog Literate Console Sink<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/github.com\/serilog\/serilog-sinks-applicationinsights\">Serilog Application Insights Sink<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/github.com\/serilog\/serilog-sinks-azuretablestorage\">Serilog Azure Table Storage Sink<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/www.nuget.org\/packages\/Serilog.Sinks.Elasticsearch\/\">Serilog Elasticsearch Sink<\/a>\n<\/li>\n<li class=\"code-line\">\n  <a href=\"https:\/\/github.com\/Cimpress-MCP\/serilog-sinks-awscloudwatch\">Serilog AWS CloudWatch Sink<\/a>\n<\/li><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This guest post was written by Mike Rousos ASP.NET Core supports diagnostic logging through the Microsoft.Extensions.Logging package. This logging solution (which is used throughout ASP.NET Core, including internally by the Kestrel host) is highly extensible. There&#8217;s already documentation available to help developers get started with ASP.NET Core logging, so I&#8217;d like to use this post [&hellip;]<\/p>\n","protected":false},"author":405,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[197,7509],"tags":[32,7530],"class_list":["post-9645","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspnet","category-aspnetcore","tag-asp-net-core","tag-logging"],"acf":[],"blog_post_summary":"<p>This guest post was written by Mike Rousos ASP.NET Core supports diagnostic logging through the Microsoft.Extensions.Logging package. This logging solution (which is used throughout ASP.NET Core, including internally by the Kestrel host) is highly extensible. There&#8217;s already documentation available to help developers get started with ASP.NET Core logging, so I&#8217;d like to use this post [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/9645","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/405"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=9645"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/9645\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=9645"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=9645"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=9645"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}