Logging to DataDog with Serliog and .Net 5

Full source code available here.

This is a quick post to showing how to setup a .Net 5 Web Api application with logging to DataDog from your local computer.

Getting Datadog up and running

First step is to create an account with Datadog and then install the Datadog agent on your computer, in my case I am using Ubuntu.

There are a few small configuration steps to get the agent up and running.

In the datadog.yaml file look for an entry called logs_enabled, make sure this is set to true.

##################################
## Log collection Configuration ##
##################################

## @param logs_enabled - boolean - optional - default: false
## Enable Datadog Agent log collection by setting logs_enabled to true.
#
logs_enabled: true

In the /etc/datadog-agent/conf.d/ create a directory called mydotnet5app.d.

Inside that directory create a file called conf.yaml and paste in the below.

#Log section
logs:

    # - type : (mandatory) type of log input source (tcp / udp / file)
    #   port / path : (mandatory) Set port if type is tcp or udp. Set path if type is file
    #   service : (mandatory) name of the service owning the log
    #   source : (mandatory) attribute that defines which integration is sending the log
    #   sourcecategory : (optional) Multiple value attribute. Can be used to refine the source attribute
    #   tags: (optional) add tags to each log collected

  - type: file
    path: /path/to/my/logfile/application.log
    service: csharp
    source: csharp

The .Net 5 Application

I have a simple .Net 5 Web Api application. I added Serilog to log to a file at the location specified in the conf.yaml above.

In Program.cs I add the below. Note the JsonFormatter on line 4, that is important for structured logging in DataDog –

public static void Main(string[] args)
{
    Log.Logger = new LoggerConfiguration()
        .WriteTo.File(new JsonFormatter(renderMessage: true), "webapi.log")
        .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(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

That’s all you need to configure logging, all that is left is to add a few logging statements.

Here is a simple controller that lets you do division from, giving the opportunity to generate and log divide by zero exceptions.

[ApiController]
[Route("[controller]")]
public class MathController : ControllerBase
{
    private readonly ILogger<MathController> _logger;

    public MathController(ILogger<MathController> logger)
    {
        _logger = logger;
    }

    [HttpGet("division")]
    public ActionResult Get(int a, int b)
    {
        _logger.LogInformation("Dividing {numerator} by {denominator}", a, b); // note how I can change the names of the attributes that DataDog logs to numerator and denominator.

        int answer;
        try
        {
            answer = a / b;
        }
        catch (DivideByZeroException ex)
        {
            _logger.LogError(ex, "SomeId: {someId}. ErrorCode: {errorCode} Division didn't work, was dividing {numerator} by {denominator}", Guid.NewGuid(), -99, a, b);

            return BadRequest("Dividing by zero is not allowed");
        }
        _logger.LogInformation("Returning {result}", answer); // again the name of the attribute logged is different from the variable name in the code.
        return Ok(answer);
    }
}

You can also log more complex objects in DataDog, like a Person with an id, first name, last name and date of birth. The full code for this is in the attached zip in the PersonController.cs file, but it is as simple as –

public ActionResult Post(Person person)
{
    _logger.LogDebug("Received new person: {@person}",person);

And here is what it looks like in DataDog.

A successful request –

And one that generates an exception –

And one that logs a complex object –

Now, there is a huge variety of things you can do within DataDog to search, alert, monitor, etc on the data but I’m not going into that here.

Full source code available here.