Serilog proper configuration with LogManager.LogFactory

I have a ServiceStack project using the default web template that I’m trying to reconfigure to use Serilog.

https://docs.servicestack.net/logging

The doc says to register at startup in AppHost.cs

I’ve tried a few variations and the console output is always showing a mix of logs in the correct Serilog format and the default microsoft format.

So I reverted back to the default on the Serilog website.

I’m not using

LogManager.LogFactory =  new SerilogFactory();

At any point and everything works fine I can use:

public static ILog Log = LogManager.GetLogger(typeof(MyServices));

Without any issue.

The advantage is that the logger is accessing in Startup and in AppHost something that some people seems to recommend:

https://mariomucalo.com/configure-serilog-in-asp-net-core-few-practical-tips/

Is this setup problematic?

Am I missing something…?

Should I add LogManager.LogFactory somewhere?

Here are my classes:

public class Program
{
    public static void Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .WriteTo.Console()
            .CreateLogger();

        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(
                builder => { builder.UseModularStartup<Startup>(); });
}
}

My AppHost.cs

public class AppHost : AppHostBase
{
    private static ILog Log = LogManager.GetLogger(typeof(AppHost));
    public AppHost() : base("Api", typeof(MyServices).Assembly) { }

    // Configure your AppHost with the necessary configuration and dependencies your App needs
    public override void Configure(Container container)
    {
        SetConfig(new HostConfig
        {
            DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false),
        });
        
        Log.Info("Warning in AppHost.");
    }
}

And my StartUp.cs

public class Startup : ModularStartup
{
    private static ILog Log = LogManager.GetLogger(typeof(Startup));
    
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public new void ConfigureServices(IServiceCollection services)
    {
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseServiceStack(new AppHost
        {
            AppSettings = new NetCoreAppSettings(Configuration)
        });

        app.UseSerilogRequestLogging();

        Log.Info("Warning in Startup.Configure");
    }
}

Hi @Math,

It looks like what is happening here is that while you are using the ServiceStack to resolve the logger, it is falling back to the one registered in ASPNET Core. Could you confirm ServiceStack version and if it is running on .NET Core?

Your code above, specifically UseSerilog() registers an ILoggerFactory into the default ASPNET IOC.

Since you’re not setting the LogManager.LogFactory it is defaulting to the NetCoreLogFactory.

The NetCoreLogFactory has a static FallbackLoggerFactory which pulls from the ASPNET IOC, which in your case would also resolve to Serilog since it was registered with UseSerilog in the CreateHostBuilder.

If you could create a minimal example of your setup that ended up mixing Serilog format with default MS format, I might be able to help more. I can’t see the using statements, but looking at your Program class though it looks like you are writing to console but that looks separate to the CreateHostBuilder setup.

If you want to use the ServiceStack LogManager before the AppHost, I think you should be able to use it directly before the AppHost is initialized.

Sure thing (and thanks for the help).

Attached are 2 sample projects (basically the web template + 2 different trials with Serilog). I also added a screenshot of the mixed output.

First project with Serilog without the LogManager (what you saw in the previous post with the using included):

Project file

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
    <PackageReference Include="Rollbar" Version="4.0.3" />
    <PackageReference Include="Rollbar.PlugIns.Serilog" Version="4.0.3" />
    <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
    <PackageReference Include="ServiceStack" Version="5.*" />
    <PackageReference Include="ServiceStack.Logging.Serilog" Version="5.11.1" />
  </ItemGroup>

  <ItemGroup>

    <ProjectReference Include="..\AgApi.ServiceInterface\AgApi.ServiceInterface.csproj" />
    <ProjectReference Include="..\AgApi.ServiceModel\AgApi.ServiceModel.csproj" />
  </ItemGroup>
  
</Project>

Program.cs

using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using ServiceStack;
using ServiceStack.Logging;
using ServiceStack.Logging.Serilog;

namespace AgApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .Enrich.FromLogContext()
                .WriteTo.Console()
                .CreateLogger();

            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(
                    builder => { builder.UseModularStartup<Startup>(); });
    }
}

Startup and AppHost


using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Funq;
using ServiceStack;
using ServiceStack.Logging;
using ServiceStack.Logging.Serilog;
using Serilog;
using AgApi.ServiceInterface;
using Rollbar;
using Rollbar.DTOs;
using Rollbar.PlugIns.Serilog;
using RollbarBase;
using Serilog.Events;

namespace AgApi
{
    public class Startup : ModularStartup
    {
        private static ILog Log = LogManager.GetLogger(typeof(Startup));
        
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public new void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseServiceStack(new AppHost
            {
                AppSettings = new NetCoreAppSettings(Configuration)
            });

            app.UseSerilogRequestLogging();

            Log.Info("Warning in Startup.Configure");
        }
    }

    public class AppHost : AppHostBase
    {
        private static ILog Log = LogManager.GetLogger(typeof(AppHost));
        public AppHost() : base("AgApi", typeof(MyServices).Assembly) { }

        // Configure your AppHost with the necessary configuration and dependencies your App needs
        public override void Configure(Container container)
        {
            SetConfig(new HostConfig
            {
                DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false),
            });
            
            Log.Info("Warning in AppHost.");
        }
    }
}

Settings


{
  "DebugMode": false,
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Information",
        "System": "Information",
        "Microsoft": "Information"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug",
        "System": "Information",
        "Microsoft": "Information"
      }
    }
  }
}


Second project giving the mixed output (and a screenshot showing the mixed ouput):

Project file

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
    <PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
    <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
    <PackageReference Include="ServiceStack" Version="5.*" />
    <PackageReference Include="ServiceStack.Logging.Serilog" Version="5.11.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\..\ceesharp-library\ServiceStackBase\ServiceStackBase\ServiceStackBase.csproj" />
    <ProjectReference Include="..\AgApi.ServiceInterface\AgApi.ServiceInterface.csproj" />
    <ProjectReference Include="..\AgApi.ServiceModel\AgApi.ServiceModel.csproj" />
  </ItemGroup>

</Project>

Program.cs

using System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ServiceStack;

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

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

Startup and AppHost

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Funq;
using ServiceStack;
using ServiceStack.Logging;
using ServiceStack.Logging.Serilog;
using Serilog;
using AgApi.ServiceInterface;

namespace AgApi
{
    public class Startup : ModularStartup
    {
        private static ILog Log = LogManager.GetLogger(typeof(Startup));
        
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public new void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseServiceStack(new AppHost
            {
                AppSettings = new NetCoreAppSettings(Configuration)
                
            });
            
            Log.Info("In Startup.Configure");
        }
    }

    public class AppHost : AppHostBase
    {
        private static ILog Log = LogManager.GetLogger(typeof(AppHost));
        public AppHost() : base("AgApi", typeof(MyServices).Assembly) { }

        // Configure your AppHost with the necessary configuration and dependencies your App needs
        public override void Configure(Container container)
        {
            SetConfig(new HostConfig
            {
                DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false)
            });
            
            var logger = new LoggerConfiguration()
                .WriteTo.Console()
                .WriteTo.File("log-.txt", rollingInterval: RollingInterval.Day)
                .CreateLogger();
            LogManager.LogFactory = new SerilogFactory(logger);
            
            Log.Info("In AppHost.Configure");
        }
    }
}

Settings

{
  "DebugMode": false,
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Information",
        "System": "Information",
        "Microsoft": "Information"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug",
        "System": "Information",
        "Microsoft": "Information"
      }
    }
  }
}

Output:

Mixed output – Startup.cs

Is there a way to use x to mix in the right log provider when a new project is created?

Thanks!

Hi @Math,

Thanks for posting. I think it comes down a mixture of logging methods combined with plug-able implementations. For example, in the working project under Program.cs you are using Log.Information("Starting up"); which is actually Serilog’s global Log static vs in the Startup.cs you have created your own ILog Log which is ServiceStack’s logger via LogManager.GetLogger. This happens to using the FallbackLoggingFactory as I described in my previous post so you happen to get a consistent format in that case, and with static’s order of operations matters here which can add additional confusion.

If you want to use Serilog Log, I’d use it consistently rather than switching between loggers. For request logging you’ll need to assign the global static Serilog.Log.Logger and/or pass your logger instance to UseSerilog before called app.UseSerilogRequestLogging();.

I like the idea of a mix template, but with logging occurring before CreateHostBuilder, it will be possible for inconsistent formatting regardless.

If you want to keep using the LogManager.GetLogger and logging early, I’d suggest moving the LogManager.LogFactory = new SerilogFactory(logger); to early on, before you do any logging. Otherwise, I would just follow Serilogs guides on usage.

I’ve taken your files and created a GitHub repository showing the early use of LogManager for ease of sharing. Rather than pasting files, is generally easier to use a GitHub repository or even a Gist since they can handle multiple files as well.

Hope that helps :+1:

Excellent! Thanks. Works perfectly.

Noted for the repository and Gist.

1 Like