首页 > 解决方案 > 通过 ApplicationInsights 中的 ILogger 对大型对象进行结构化跟踪日志记录

问题描述

我们正在尝试通过 记录大型数据对象ILogger.Log,但正在修剪我们发送到 ApplicationInsights 的数据。我们尝试记录的数据对象是来自集成源的请求/响应,用于在开发期间进行诊断。不用说,请求/响应会变得非常大。

我们的第一次尝试是从请求/响应中读取内容作为字符串,然后通过ILogger.Log. 但是,我们很快意识到内容被修剪了。经过进一步调查,我们发现 ApplicationInsights 属性值字符串长度限制为 8192 字节 ( https://github.com/MicrosoftDocs/azure-docs/blob/master/includes/application-insights-limits.md )。

我们的下一个尝试是反序列化请求/响应并将其作为对象 (JToken) 通过ILogger.Log. 我们希望 ApplicationInsightsLoggingProvider 能够以更结构化的方式记录对象,从而完全跳过 8192 字节的属性值字符串长度限制。然而,不幸的是,这让事情变得更糟。请求/响应仍以字符串形式记录在 ApplicationInsights 中,但现在采用每个字符占用两个字节而不是一个字节的格式。基本上将我们的日志记录能力减半。

附加代码是我们当前的实现。我们正在通过DelegatingHandler. 它HttpClient在 ASP 之后LogicalHandler通过自定义插入到传出管道中IHttpMessageHandlerBuilderFilter。我们的DelegatingHandler使用LoggerMessage模式 ( https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/loggermessage?view=aspnetcore-3.0 ),并且仅在日志级别设置为跟踪时才会记录。当前的实现是第二版,我们尝试将请求/响应提取的 JSON 字符串反序列化为 JToken(第 107 行)并通过ILogger.Log. 如果反序列化失败,它将回退到我们的版本一等效实现,原始字符串将被发送到ILogger.Log.

我们正在使用来自Microsoft.ApplicationInsights.AspNetCoreversion的 ApplicationInsightsLoggingProvider 2.8.2

所以问题就变成了:我们如何通过 ILogger 将大型对象记录到 ApplicationInsights,最好是结构化格式,以便我们可以使用 kusto 对其进行查询?

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Shared.Extensions.IntegrationPolicies
{
    public class CustomLoggingFilter : IHttpMessageHandlerBuilderFilter
    {
        private readonly ILoggerFactory _loggerFactory;

        public CustomLoggingFilter(ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
        }

        public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
        {
            if (next == null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            return builder =>
            {
                next(builder);
                var logger = _loggerFactory.CreateLogger($"System.Net.Http.HttpClient.{builder.Name}.CustomLogger");
                builder.AdditionalHandlers.Insert(1, new CustomLoggingHandler(logger));
            };
        }
    }

    internal class CustomLoggingHandler : DelegatingHandler
    {
        private readonly ILogger _logger;

        public CustomLoggingHandler(ILogger logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            await Log.LogRequest(_logger, request);
            var response = await base.SendAsync(request, cancellationToken);
            await Log.LogResponse(_logger, response);
            return response;
        }

        private static class Log
        {
            private static readonly LogLevel LogLevel = LogLevel.Trace;

            private static readonly Action<ILogger, HttpMethod, Uri, object, Exception> LogRequestAction =
                LoggerMessage.Define<HttpMethod, Uri, object>(
                    LogLevel,
                    EventIds.PipelineStart,
                    $"Start processing HTTP request {{HttpMethod}} {{Uri}}{Environment.NewLine}{{Body}}");

            private static readonly Action<ILogger, HttpStatusCode, object, Exception> LogResponseAction =
                LoggerMessage.Define<HttpStatusCode, object>(
                    LogLevel,
                    EventIds.PipelineEnd,
                    $"End processing HTTP request - {{StatusCode}}{Environment.NewLine}{{Body}}");

            public static async Task LogRequest(ILogger logger, HttpRequestMessage request)
            {
                if (!logger.IsEnabled(LogLevel))
                {
                    return;
                }

                var content = await GetContent(request.Content);
                LogRequestAction(logger, request.Method, request.RequestUri, content, null);
            }

            public static async Task LogResponse(ILogger logger, HttpResponseMessage response)
            {
                if (!logger.IsEnabled(LogLevel))
                {
                    return;
                }

                var content = await GetContent(response.Content);
                LogResponseAction(logger, response.StatusCode, content, null);
            }

            private static async Task<object> GetContent(HttpContent content)
            {
                if (content == null)
                {
                    return null;
                }

                await content.LoadIntoBufferAsync();
                var contentAsString = await content.ReadAsStringAsync();

                return TryParseJson<object>(contentAsString, out var contentAsObject) 
                    ? contentAsObject 
                    : contentAsString;
            }

            private static bool TryParseJson<T>(string value, out T result)
            {
                var success = true;
                var settings = new JsonSerializerSettings
                {
                    Error = (sender, args) =>
                    {
                        success = false; 
                        args.ErrorContext.Handled = true;
                    }
                };
                result = JsonConvert.DeserializeObject<T>(value, settings);
                return success;
            }

            private static class EventIds
            {
                public static readonly EventId PipelineStart = new EventId(100, "RequestPipelineStart");
                public static readonly EventId PipelineEnd = new EventId(101, "RequestPipelineEnd");
            }
        }
    }
}

标签: c#asp.net-coreazure-application-insightsilogger

解决方案


推荐阅读