Custom AzureDevOps Serilog Sink
The other day I was wondering how I can add warnings and error indicators to pipeline summaries of Azure DevOps. It turns out that you can write simple echo statements to trigger all sorts of cool commands. I was even more stoked when I realized that I could write a custom sink for my beloved logging framework Serilog to send pipeline error/warning commands right from a C# application. It's nothing fancy, but for some reason this little code snippet brought me joy:
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog;
namespace Example;
public static class Program
{
public static void Main()
{
Log.Logger = new LoggerConfiguration()
.WriteTo.AzureDevOpsOr(l => l.Console())
.MinimumLevel.Debug()
.CreateLogger();
try
{
Log.Warning("A warning message");
Log.Error("An error message");
throw new NotSupportedException("An example exception");
}
catch (Exception e)
{
Log.Error(e, "Unexpected error");
Environment.ExitCode = 1;
}
finally
{
Log.CloseAndFlush();
}
}
}
public sealed class AzureDevOpsSink : ILogEventSink
{
public void Emit(LogEvent logEvent)
{
var rendered = logEvent.RenderMessage();
var annotated = logEvent.Level switch
{
LogEventLevel.Verbose => $"##[debug]{rendered}",
LogEventLevel.Debug => $"##[debug]{rendered}",
LogEventLevel.Information => rendered,
LogEventLevel.Warning => $"##vso[task.logissue type=warning]{rendered}",
LogEventLevel.Error => $"##vso[task.logissue type=error]{rendered}",
LogEventLevel.Fatal => $"##vso[task.logissue type=error]{rendered}",
_ => throw new NotImplementedException()
};
Console.WriteLine(annotated);
if (logEvent.Exception != null)
{
Console.WriteLine($"##[debug]{logEvent.Exception}");
}
}
}
public static class AzureDevOpsSinkExtensions
{
public static LoggerConfiguration AzureDevOpsOr(
this LoggerSinkConfiguration config,
Func<LoggerSinkConfiguration, LoggerConfiguration> orFunc)
{
var runningOnAzureDevops = !string.IsNullOrEmpty(
Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER"));
return runningOnAzureDevops
? config.Sink(new AzureDevOpsSink())
: orFunc(config);
}
}