DEV Community

Wallism
Wallism

Posted on

Pulumi with Serilog

I use Serilog everywhere and I want to do the same in Pulumi projects. It provides all the flexibility I want/need for logging, so I try and bake it into any new project (unless it's going to be a shared library, in which case I want minimal dependencies.)

When doing this, we want to ensure we don't lose any of the console logging that Pulumi does.

First thing, add the Serilog nuget package. Then what I do is create a method somewhere to configure it. (this can usually go into the config file however with the way Pulumi runs, that doesn't work out so well, didn't for me anyway).

Config method:

    public static void ConfigureLogger()
    {

        Log.Logger = new LoggerConfiguration()
            .Enrich.FromLogContext()
            .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
                .WithDefaultDestructurers()
                .WithDestructuringDepth(8)) // essential to limit the depth
            .WriteTo.PulumiLogSink()
            .WriteTo.File(path: $"{Environment.CurrentDirectory}\\log\\log.txt")
            .CreateLogger();

        Log.Information("Logging configured");

    }
Enter fullscreen mode Exit fullscreen mode

Either remove "WithExceptionDetails" or add that package. Same with WriteTo.File, I recommend adding, then you have a history.

Then what we need to do, is implement PulumiLogSink...so we don't lose any of the normal Pulumi output.

Info on building a custom sink is here. Following those instructions our Pulumi sink looks like this:

    public class PulumiLogSink : ILogEventSink
    {
        private readonly IFormatProvider _formatProvider;

        public PulumiLogSink(IFormatProvider formatProvider)
        {
            _formatProvider = formatProvider;
        }

        public void Emit(LogEvent logEvent)
        {
            var message = logEvent.RenderMessage(_formatProvider);
            if (logEvent.Level == LogEventLevel.Debug || logEvent.Level == LogEventLevel.Verbose)
                Pulumi.Log.Debug(message);
            if (logEvent.Level == LogEventLevel.Information)
                Pulumi.Log.Info(message);
            if (logEvent.Level == LogEventLevel.Warning)
                Pulumi.Log.Warn(message);
            if (logEvent.Level == LogEventLevel.Error || logEvent.Level == LogEventLevel.Fatal)
                Pulumi.Log.Error(message);
        }
    }

    public static class MySinkExtensions
    {
        public static LoggerConfiguration PulumiLogSink(
            this LoggerSinkConfiguration loggerConfiguration,
            IFormatProvider formatProvider = null)
        {
            return loggerConfiguration.Sink(new PulumiLogSink(formatProvider));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, the first line of our Pulumi should call ConfigureLogger() and voila, serilog logging hooked up so you can add whatever sinks you like.

Tip, you probably want to add this using: using Log = Serilog.Log; to ensure 'Log.' uses serilog and not the Pulumi logger.

Top comments (0)