Polly and Blazor, Part 2 – Using the Context

Full source code available here.

This post is a short follow up on the one where I used Polly Wait and Retry in Blazor. In that previous post I used variables defined in my C# method to pass information back to the calling code, to display on the screen. But here I’m going to show how the Polly Context could be used instead.

The setup and almost all the code is the same, so please take a look at that post.

Using the Context
The Razor section references the message in the Polly context rather than a defined variable for the message –

<p>Polly Summary : @context["message"]</p>

In the code block add a Polly Context and initialize with the dictionary with a message, this is important because the Razor block references this message.

@code {
    //snip...

    private Context context = new Polly.Context("", new Dictionary<string, object>() {{"message",""}});
    //snip... 

Then in the rest of the code block, use update the message to display to the user inside the context.

private async Task CallRemoteServiceAsync()
{
    IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
        r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3,
            retryAttempt => TimeSpan.FromSeconds(retryAttempt * 2), onRetry: (response, retryDelay, retryCount, context) => {
                context["message"] = $"Recieved: {response.Result.StatusCode}, retryCount: {retryCount}, delaying: {retryDelay.Seconds} seconds\n";
                Console.WriteLine(context["message"]);
                InvokeAsync(StateHasChanged);
    });
    status = "Calling remote service...";
    string requestEndpoint = "faulty/";
    HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(ctx => httpClient.GetAsync(requestEndpoint), context);
    responseCode = httpResponse.StatusCode.ToString();
    number = await httpResponse.Content.ReadAsStringAsync();
    status = "Finished";
    context["message"] = "";
}

That’s it, another way to do the same thing.

Full source code available here.

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.