Author : HASSAN MD TAREQ

Logging using Serilog

Serilog / Logging in ASP.Net Core

Azure App Service Logging

Enable App Service Logs

  • Azure Portal > App Service > App Service logs
  • Select On for either Application Logging (Filesystem) or Application Logging (Blob), or both.

File System Logging

Blog Storage Logging

App Service Logging with Serilog

appsettings.json

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Warning"
    },
    "Enrich": [
      "FromLogContext"
    ],
    "Properties": {
      "Application": "HovermindApp"
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u10}] {Message:lj}",
          "theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console"
        }
      },
      {
        "Name": "Async",
        "Args": {
          "configure": [
            {
              "Name": "File",
              "Args": {
                "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
                "path": "%HOME%/LogFiles/Application/FooApp.ndjson",
                "shared": "true",
                "fileSizeLimitBytes": "1000000",
                "rollOnFileSizeLimit": true,
                "flushToDiskInterval": "1"
              }
            }
          ]
        }
      }
    ]
  }
}

Program.cs

using System;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;

namespace BlazorAppDefaultScaffoling
{
    public class Program
    {
        public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
            .AddEnvironmentVariables()
            .Build();


        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration).CreateLogger();

            try
            {
                Log.Warning("Running Blazor App");

                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            }).UseSerilog();
    }
}

Using Fluent API (instead of appsettings.json)

loggerConfiguration.WriteTo.File(
    @"D:\home\LogFiles\Application\trace.log",
    fileSizeLimitBytes: 5_000_000,
    rollOnFileSizeLimit: true,
    shared: true,
    flushToDiskInterval: TimeSpan.FromSeconds(1));

Log.Logger = loggerConfiguration
    .Enrich.WithMachineName()
    .Enrich.WithProcessId()
    .Enrich.FromLogContext()
    .CreateLogger();

Viewing App Service Logs

Console Logs

  • Azure portal > App services
  • Then select your app service i.e. Foo App Service > Log Steram

File logs

Logging to Azure Blob Storage using Serilog

Installation

Install-Package Serilog.Sinks.AzureBlobStorage

JSON Configuration: https://github.com/chriswill/serilog-sinks-azureblobstorage#json-configuration

{
  "Serilog": {
  
    "Using": [ "Serilog.Sinks.AzureBlobStorage" ],
    "WriteTo": [
      {
        "Name": "AzureBlobStorage",
        "Args": {
          "connectionString": "",
          "storageContainerName": "",
          "storageFileName": "",
          "outputTemplate": "",
          "blobSizeLimitBytes": ""
        }
      }
    ]
	
  }
}

Example JSON Configuration: appsettings.Production.json

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.AzureBlobStorage" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "AzureBlobStorage",
        "Args": {
          "connectionString": "DefaultEndpointsProtocol=https;AccountName=blobstorageloggingpoc;AccountKey=ceF/WJzzXoZYOWB/z22L5IZS2dtLHLFMwcJ/JhgoyYfHd8xn2eWn7p2nsNPus2VlvCPMYVf8BKDh++k1tIOlUQ==;EndpointSuffix=core.windows.net",
          "storageContainerName": "foocontainer",
          "storageFileName": "{dd-MM-yyyy}-test.log",
          "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level:u10}] {Message:lj}{NewLine}{NewLine}{Exception}{NewLine}",
          "blobSizeLimitBytes": "4000000"
        }
      }
    ]
  }
}

Get Connection String

Get Connection String in Azure Blob Storage

Logging to blob storage with batch posting

  • Serilog.Sinks.AzureBlobStorage has dependency to Serilog.Sinks.PeriodicBatching
  • No need to install extra package for batch posting (AzureBlobStorage sink uses PeriodicBatching for batch posting)
{
  "Serilog": {
    "Using": [ "Serilog.Sinks.AzureBlobStorage" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "AzureBlobStorage",
        "Args": {
          "connectionString": "",
          "storageContainerName": "",
          "storageFileName": "",
          "outputTemplate": "",
          "blobSizeLimitBytes": "4000000",
          "writeInBatches": "true",
          "period ": "15",
          "batchPostingLimit": "50"
        }
      }
    ]
  }
}

Working example: appsettings.Production.json

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.AzureBlobStorage" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "AzureBlobStorage",
        "Args": {
          "connectionString": "DefaultEndpointsProtocol=https;AccountName=blobstorageloggingpoc;AccountKey=ceF/WJzzXoZYOWB/z22L5IZS2dtLHLFMwcJ/JhgoyYfHd8xn2eWn7p2nsNPus2VlvCPMYVf8BKDh++k1tIOlUQ==;EndpointSuffix=core.windows.net",
          "storageContainerName": "foocontainer",
          "storageFileName": "{dd-MM-yyyy}-test.log",
          "outputTemplate": "[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level:u10}] {Message:lj}{NewLine}{NewLine}{Exception}{NewLine}",
          "blobSizeLimitBytes": "4000000",
          "writeInBatches": "true",
          "period ": "15",
          "batchPostingLimit": "50"
        }
      }
    ]
  }
}

Logging to blob storage with connectionString in Azure KeyVault

  • connectionString contains AccountKey and therefore should be in KeyVault
  • appsettings.json
    • Will contain KeyVaultBaseUrl and BlobStorageContainerName (values will be empty)
    • Both KeyVaultBaseUrl and BlobStorageContainerName will be overriden by DevOps pipeline (pipeline will provide actual values)
  • appsettings.Development.json will contain development-time specific settings only (will be ignored in Production by convention)

appsettings.json

{
  "KeyVaultBaseUrl": "",
  "BlobStorageContainerName": ""
}

Constants.cs

public static class Azure
{
	public const string KeyVaultIdentifier = "KeyVaultBaseUrl";
	public const string BlobStorageConnectionStringKey = "BlobStorageAccountConnectionString";
	public const string BlobStorageContainerNameIndentifier = "BlobStorageContainerName";
	public const string BlobStorageFileNamePattern = @"{yyyyMMdd}_hoverapp.ndjson";
	public const string BlobStorageLogFormat = @"[{Timestamp:yyyy/MM/dd HH:mm:ss} {Level:u10}] {LineNumber} {Method} {SourceContext:l} {Message:lj}{NewLine}{Exception}{NewLine}";
	public const int BatchPeriodInMilliSeconds = 15;
	public const int BatchEventLimit = 30;
}

Program.cs

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Serilog;
using static Serilog.Events.LogEventLevel;


namespace Hovermind.HoverApp
{
    public class Program
    {
        private const string MicrosoftLog = "Microsoft";
        private const string SystemtLog = "System";

        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
             Host.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, configBuilder) =>
             {
                 var Configuration = configBuilder.Build();

                 var isProduction = context.HostingEnvironment.IsProduction();
                 if (isProduction)
                 {
                     var keyVaultUri = Configuration[Azure.KeyVaultIdentifier];
                     configBuilder.AddAzureKeyVault(keyVaultUri);

                     Configuration = configBuilder.Build();

                     var blogStorageConnectionString = Configuration[Azure.BlobStorageConnectionStringKey];
                     var blogStorageContainerName = Configuration[Azure.BlobStorageContainerNameIndentifier];

                     if (!string.IsNullOrEmpty(blogStorageConnectionString))
                     {
                         Log.Logger = new LoggerConfiguration()
                            .MinimumLevel.Information()
                            .MinimumLevel.Override(MicrosoftLog, Warning)
                            .MinimumLevel.Override(SystemtLog, Warning)
                            .WriteTo.AzureBlobStorage(connectionString: blogStorageConnectionString,
                                restrictedToMinimumLevel: Information,
                                storageContainerName: blogStorageContainerName,
                                storageFileName: Azure.BlobStorageFileNamePattern,
                                outputTemplate: Azure.BlobStorageLogFormat,
                                writeInBatches: true,
                                period: TimeSpan.FromSeconds(Azure.BatchPeriodInMilliSeconds),
                                batchPostingLimit: Azure.BatchEventLimit)
                            .CreateLogger();

                         Log.Information("AzureBlobStorage logger created");
                     }
                 }
                 else
                 {
                     //
                     // DEV: create logger from appsettings.json
                     //
                     Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration).CreateLogger();
					 
                     Log.Information("Development logger created");
                 }
             })
             .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
             .UseSerilog();
    }
}

Log as a Service - Azure Log Analytics

https://www.vivienfabing.com/aspnetcore/2019/02/21/how-to-add-logging-on-azure-with-aspnetcore-and-serilog.html