asp.net core

Launching a docker container (Seq + Serilog) automatically as part of Asp.Net Core startup

We are using Serilog along with Seq for logging against one of our Asp.Net Core applications and wanted to be able to stand up the Seq docker image automatically when launching the application, including downloading the docker image if it doesn't already exist.

To demonstrate how to do this I will bootstrap a new Asp.Net Core web application and configure Serilog and a Seq sink. If you wish to add Serilog to your existing app, you can follow the instructions found here   https://nblumhardt.com/2019/10/serilog-in-aspnetcore-3 ) then just patch in the necessary code to your Program.cs

You can also find a minimal sample application here

Create a new empty web application and install the Serilog and Seq packages by running the following commands from your terminal


dotnet new web -o AutoLaunchSeq

cd AutoLaunchSeq

dotnet add package Serilog.AspNetCore

dotnet add package Serilog.Sinks.Seq

dotnet build

If everything builds ok open your project and navigate to the Program.cs file

Replace the contents of this file with the following

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;

namespace AutoLaunchSeq
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .WriteTo.Seq("http://localhost:5341")
                .CreateLogger();

            await StartSeq();

            try
            {
                Log.Information("Starting up");
                CreateHostBuilder(args).Build().Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Application start-up failed");
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

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

        private static async Task StartSeq() =>
            await RunStartupProcess(Environment.CurrentDirectory, "docker", "run --rm -it -e ACCEPT_EULA=Y -p 5341:80 datalust/seq");

            public static async Task ConsumeAsync(StreamReader reader, StringBuilder lines)
        {
            await Task.Yield();

            string line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                lines.AppendLine(line);
                Console.WriteLine(line);
            }
        }

        public static async Task<bool> RunStartupProcess(string workingDirectory, string command, string args)
        {
            var psi = new ProcessStartInfo(command, args)
            {
                WorkingDirectory = workingDirectory,
                UseShellExecute = false,
                CreateNoWindow = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            };

            var p = new Process();
            try
            {
                p.StartInfo = psi;
                p.Start();

                var output = new StringBuilder();
                var errors = new StringBuilder();
                var outputTask = ConsumeAsync(p.StandardOutput, output);
                var errorTask = ConsumeAsync(p.StandardError, errors);

                return true;
            }
            finally
            {
                p.Dispose();
            }
        }
    }
}

To summarise the key things happening in the code above, we first configure Serilog and add logging to Seq under the url http://localhost5341

.WriteTo.Seq("http://localhost:5341")

Register Serilog with Asp.Net Core

.UseSerilog()

We then call the 'StartSeq' method that invokes the docker command to download then run the Seq docker container under the specified port.

 await StartSeq();

The 'StartSeq' method then invokes a 'RunStartupProcess' method with the docker arguments which uses ProcessStartInfo to run the docker process. We have one additional helper method 'ConsumeAsync' to write the output from the docker process back to our terminal

        private static async Task StartSeq() =>
            await RunStartupProcess(Environment.CurrentDirectory, "docker", "run --rm -it -e ACCEPT_EULA=Y -p 5341:80 datalust/seq");

            public static async Task ConsumeAsync(StreamReader reader, StringBuilder lines)
        {
            await Task.Yield();

            string line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                lines.AppendLine(line);
                Console.WriteLine(line);
            }
        }

        public static async Task<bool> RunStartupProcess(string workingDirectory, string command, string args)
        {
            var psi = new ProcessStartInfo(command, args)
            {
                WorkingDirectory = workingDirectory,
                UseShellExecute = false,
                CreateNoWindow = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            };

            var p = new Process();
            try
            {
                p.StartInfo = psi;
                p.Start();

                var output = new StringBuilder();
                var errors = new StringBuilder();
                var outputTask = ConsumeAsync(p.StandardOutput, output);
                var errorTask = ConsumeAsync(p.StandardError, errors);

                return true;
            }
            finally
            {
                p.Dispose();
            }
        }

Next open Startup.cs and add the Serilog using statement to the top then find the following code

await context.Response.WriteAsync("Hello World!");

Wrap this with some logging, for example

Log.Information("Beginning Request: Hello World!");

await context.Response.WriteAsync("Hello World!");

Log.Information("Completed Request: Hello World!");

We are now ready to try out app and see if Seq stands up correctly in docker and starts capturing logs.

Return to your terminal and run the following

dotnet run

If you havent already Seq downloaded as a docker container, this may take a few minutes the first time it runs. Once completed and running, you should be able to access your application under the default port https://localhost:5001

You can also check if Seq has launched correctly by checking your running docker containers

docker container ls

You should hopefully see Seq is running and when you hit your application endpoint in the browser you should see 'Hello World!'

You can now access Seq in your browser via the following url http://localhost:5341 and if everything has worked correctly you should see your application logs

You can find the sample code on my GitHub here