Oleg Sh
Oleg Sh

Reputation: 9013

Application insights and custom properties of Exception

I have the following exception:

public class InvalidStatusCodeException : Exception
{
    public HttpStatusCode ReceivedStatusCode { get; set; }
    public string ApiUrl { get; set; }

    public InvalidStatusCodeException(HttpStatusCode receivedStatusCode, string apiUrl)
    {
        ReceivedStatusCode = receivedStatusCode;
        ApiUrl = apiUrl;
    }
}

throw it in some cases:

        string url = "api/group/1/getAll";
        var response = await _client.GetAsync(url);
        if (!response.IsSuccessStatusCode)
            throw new InvalidStatusCodeException(response.StatusCode, url);

if I catch and log this exception:

        catch(InvalidStatusCodeException status_ex)
        {
            string error = $"Invalid Status code for request: '{status_ex.ApiUrl}', received code: {status_ex.ReceivedStatusCode}";
            log.LogError(status_ex, error, "InvalidStatusCode");
        }

I don't see values of my custom properties (ReceivedStatusCode, ApiUrl) and can see details only in error message.

enter image description here

If I don't catch this exception at all and exception is being logged automatically then no way to see details at all.

Any way to see these custom properties without additional catching of exception?

Upvotes: 1

Views: 2076

Answers (3)

June Lau
June Lau

Reputation: 191

You can write a custom middleware for this. There are 3 steps:

  • create your custom telemetry middleware, which specifies what you want to capture from the request
  • register it as a middleware extension
  • register your middleware to be used by your server.

Step 1: Customise what telemetry you want to capture:

public class CustomTelemetryMiddleware
{
    private readonly RequestDelegate _next;

    public CustomTelemetryMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/686#issuecomment-530353448
    public async Task Invoke(HttpContext context)
    {
        var requestTelemetry = context.Features.Get<RequestTelemetry>();
        if (requestTelemetry != null)
        {
            var request = context.Request;

            // add items to show up in Request Properties:
            requestTelemetry.Context.User.Id = context.User.Identity.Name;

            // add items to show up in Custom Properties:
            requestTelemetry.Properties.Add("PayingCustomer", "Yes");

            /* Capture the request body as a Custom Property
             */
            if (request?.Body?.CanRead == true)
            {
                request.EnableBuffering();

                var bodySize = (int)(request.ContentLength ?? request.Body.Length);
                if (bodySize > 0)
                {
                    request.Body.Position = 0;
                    byte[] body;
                    using (var ms = new MemoryStream(bodySize))
                    {
                        await request.Body.CopyToAsync(ms);
                        body = ms.ToArray();
                    }
                    request.Body.Position = 0;

                    var requestBodyString = Encoding.UTF8.GetString(body);
                    requestTelemetry.Properties.Add("RequestBody", requestBodyString);
                }
            }
        }
        await _next(context);
    }
}

Step 2, register it:

public static class CustomTelemetryMiddlewareExtensions
{
    public static IApplicationBuilder UseCustomTelemetry(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CustomTelemetryMiddleware>();
    }
}

And finally, Step 3, register it in your Startup.cs

public async void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
{
  // put this towards the end of your pipeline
  app.UseCustomTelemetry();
}

Upvotes: 0

user1672994
user1672994

Reputation: 10839

You can use the concept of structure logging which logs the named constant as custom properties.

catch(InvalidStatusCodeException status_ex)
{
     log.LogError(status_ex, "Invalid Status code for request: '{ApiUrl}', received code: {ReceivedStatusCode}", status_ex.ApiUrl, status_ex.ReceivedStatusCode);
 }

The above log will add ApiUrl and ReceivedStatusCode as custom properties in application insights log.

Update

You don't need to throw and catch the exception. You can log in the else block of if (response.IsSuccessStatusCode) like shown below:

 if (response.IsSuccessStatusCode)
 {
     
 }
 else
 {
     log.LogError(status_ex, "Invalid Status code for request: '{ApiUrl}', received code: {ReceivedStatusCode}",
         apiUrl, response.StatusCode);
 }

Other way of logging custom properties is via Logging Scope (check this) and via TelemetryInitializer (check this)

Upvotes: 1

David Specht
David Specht

Reputation: 9044

Add a message to the base Exception

public class InvalidStatusCodeException : Exception
{
    public HttpStatusCode ReceivedStatusCode { get; set; }
    public string ApiUrl { get; set; }

    public InvalidStatusCodeException(HttpStatusCode receivedStatusCode, string apiUrl) 
        : base($"Invalid Status code for request: '{apiUrl}', received code: {receivedStatusCode}")
    {
        ReceivedStatusCode = receivedStatusCode;
        ApiUrl = apiUrl;
    }
}

Upvotes: 1

Related Questions