7

Objects are rendered as strings, (name of the object), in Application Insights custom dimensions when passed as arguments to ilogger. The actual values are not shown.

Register Application Insights

services.AddApplicationInsightsTelemetry();

New log

public class HealthController : ControllerBase
{
    private readonly ILogger<HealthController> _logger;

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

    [HttpGet]
    public IActionResult Get()
    {
        var health = new HealthViewModel()
        {
             ok = false
        };

        _logger.LogInformation("Hlep me pls {health}", health);

        return Ok(health);
    }
}

Result

enter image description here

I do not want to this this for every log:

var health = new HealthViewModel()
{
     ok = false
};

_logger.LogInformation("Hlep me pls {health}", JsonConvert.SerializeObject(health));

I tried creating a middleware for application insights but the value is still the name of the object..

enter image description here

Why are arguments not rendered as json?

Edit

It seems like

var health = new
{
     ok = false
};

_logger.LogInformation("HEJ2 {health}", health);

works but not

var health = new HealthViewModel
{
     ok = false
};

_logger.LogInformation("HEJ2 {health}", health);
Reft
  • 2,333
  • 5
  • 36
  • 64

2 Answers2

4

Not supported

Quote from https://github.com/microsoft/ApplicationInsights-dotnet/issues/1722

I think you're expecting too much of the logger. It doesn't know about JSON format, it just calls Convert.ToString on properties

Convert.ToString typically calls ToString() and the default ToString implementation for new classes is simply to return the type name

What you can do

Use ToJson() on objects logged to ILogger and create a middleware for application insights and modify the name of the log and the custom dimensions.

Middleware

public class ProcessApiTraceFilter : ITelemetryProcessor
{
    private ITelemetryProcessor Next { get; set; }
    private readonly IIdentity _identity;
    private readonly IHostEnvironment _hostEnvironment;

    public ProcessApiTraceFilter(ITelemetryProcessor next, IHostEnvironment hostEnvironment, IIdentity identity)
    {
        Next = next;
        _identity = identity;
        _hostEnvironment = hostEnvironment;
    }

    public void Process(ITelemetry item)
    {
        item.Process(_hostEnvironment, _identity);

        Next.Process(item);
    }
}

Implementation

public static class ApplicationInsightsExtensions
{
    public static void Process(this ITelemetry item, IHostEnvironment hostEnvironment, IIdentity identity)
    {
        if (item is TraceTelemetry)
        {
            var traceTelemetry = item as TraceTelemetry;
            var originalMessage = traceTelemetry.Properties.FirstOrDefault(x => x.Key == "{OriginalFormat}");

            if (!string.IsNullOrEmpty(originalMessage.Key))
            {
                var reg = new Regex("{([A-z]*)*}", RegexOptions.Compiled);
                var match = reg.Matches(originalMessage.Value);
                var formattedMessage = originalMessage.Value;
                foreach (Match arg in match)
                {
                    var parameterName = arg.Value.Replace("{", "").Replace("}", "");
                    var parameterValue = traceTelemetry.Properties.FirstOrDefault(x => x.Key == parameterName);
                    formattedMessage = formattedMessage.Replace(arg.Value, "");
                }

                traceTelemetry.Message = formattedMessage.Trim();
            }

            if (identity != null)
            {
                var isAuthenticated = identity.IsAuthenticated();
                const string customerKey = "customer";

                if (isAuthenticated && !traceTelemetry.Properties.ContainsKey(customerKey))
                {
                    var customer = identity.Customer();

                    if (customer != null)
                    {
                        traceTelemetry.Properties.Add(customerKey, customer.ToJson());
                    }
                }

                var request = identity.Request();
                const string requestKey = "request";

                if (request != null && !traceTelemetry.Properties.ContainsKey(requestKey))
                {
                    traceTelemetry.Properties.Add(requestKey, request.ToJson());
                }
            }

            var applicationNameKey = "applicationName";

            if (hostEnvironment != null && !string.IsNullOrEmpty(hostEnvironment.ApplicationName) && !traceTelemetry.Properties.ContainsKey(applicationNameKey))
            {
                traceTelemetry.Properties.Add(applicationNameKey, hostEnvironment.ApplicationName);
            }
        }
    }
}

Register application insights and middleware in startup

services.AddApplicationInsightsTelemetry();
services.AddApplicationInsightsTelemetryProcessor<ProcessApiTraceFilter>();

ToJson

public static class ObjectExtensions
{
    private static readonly string Null = "null";
    private static readonly string Exception = "Could not serialize object to json";

    public static string ToJson(this object value, Formatting formatting = Formatting.None)
    {
        if (value == null) return Null;

        try
        {
            string json = JsonConvert.SerializeObject(value, formatting);

            return json;
        }
        catch (Exception ex)
        {
            return $"{Exception} - {ex?.Message}";
        }
    }
}

Log

//Log object? _smtpAppSettings.ToJson()

_logger.LogInformation("Email sent {to} {from} {subject}", to, _smtpAppSettings.From, subject)

Result

enter image description here

Community
  • 1
  • 1
Reft
  • 2,333
  • 5
  • 36
  • 64
-1

from your custom dimensions i can see that it`s not considering the health obj param as an extra data

_logger.LogInformation("Hlep me pls {health}", health);

trying using the jsonConverter within the string itself.

_logger.LogInformation($"Hlep me pls {JsonConvert.SerializeObject(health)}");
Ahmed Magdy
  • 794
  • 4
  • 13
  • Yes converting it to a json string works but i dont want to do this for every log, i mentioned this in the question. Would be ok to do this in a middleware but the value there is already incorrect – Reft Mar 08 '20 at 07:01
  • i think the logged Value which is *.Api.Models.Health ... etc is the Value of .ToString() of your object Try to override .ToString() in your Class and Check. – Ahmed Magdy Mar 08 '20 at 20:18
  • I would not recommend pre-composing the string as suggested here as it is not possible to query for the value of the parameter itself. The log message needs to be parsed, which may not be easy. Better pass the value as a separate parameter to the ILogger methods as suggested in the question. – Palec May 31 '21 at 08:08