简体   繁体   English

从 Function App ILogger (C#) 在 Application Insights 中记录自定义对象

[英]Log custom object in Application Insights from Function App ILogger (C#)

I have a C# .NET Core Azure Function App, and I am using the ILogger to send logs to Application Insights.我有一个 C# .NET Core Azure Function App,我正在使用 ILogger 将日志发送到 Application Insights。 This is working well so far.到目前为止,这运行良好。

Function header:函数头:

public static void Run([TimerTrigger("0 30 * * * *")] TimerInfo myTimer, ILogger log, ExecutionContext context)

ILogger usage: ILogger 用法:

log.LogInformation($"MyFunction trigger function executed at: {DateTime.Now}");

In App Insights, I see the log which has default information like which Function App it came from, as well as the message which contains the above string.在 App Insights 中,我看到日志包含默认信息,例如它来自哪个函数应用程序,以及包含上述字符串的message

However, now I want to log a custom log.但是,现在我想记录自定义日志。 I have an IEnumerable<IDictionary<string, string>> and I want each dictionary element of the list to be a separate log.我有一个IEnumerable<IDictionary<string, string>>并且我希望列表的每个字典元素都是一个单独的日志。 Ideally, I could have a log with each field being a key from the dictionary, and its value to be the corresponding value.理想情况下,我可以有一个日志,其中每个字段都是字典中的一个键,其值是相应的值。 Alternatively, I would be fine with some sort of customDimensions field in the log, which would be an object containing all the key-value pairs from 1 dictionary in the list.或者,我可以在日志中使用某种 customDimensions 字段,该字段将是一个包含列表中 1 个字典中的所有键值对的对象。

The intent is to make the logs simple to query in Kusto.目的是使日志易于在 Kusto 中查询。 I want to avoid having to parse them when querying them in App Insights.我想避免在 App Insights 中查询它们时解析它们。

Notes:笔记:

  • since I already use the ILogger for existing logging, is there a way to do the above object-logging with the ILogger interface?由于我已经将 ILogger 用于现有日志记录,有没有办法使用 ILogger 接口执行上述对象日志记录?
  • if not, how can I log an object like mentioned above with a different logger?如果没有,我如何使用不同的记录器记录上面提到的对象?

I looked at numerous other similar posts, but none of them seemed to be fully answered.我查看了许多其他类似的帖子,但似乎没有一个得到完整的回答。

Here is a pattern I have used previously:这是我以前使用过的模式:

public class LogService : ILogService
{
    private readonly ILogger<LogService> _log;
    
    private readonly Dictionary<string, object> _customProperties = new Dictionary<string, object>();

    public LogService(ILogger<LogService> log)
    {
        _log = log;
    }
    
    public void SetCustomProperty(string key, object value)
    {
        _customProperties.Add(key, value);
    }

    public void LogInformation(string message, params object[] args)
    {
        Log(LogLevel.Information, message, args);
    }

    public void LogWarning(string message, params object[] args)
    {
        Log(LogLevel.Warning, message, args);
    }
    
    ...etc 

    private void Log(LogLevel logLevel, string message, params object[] args)
    {
        using (_log.BeginScope(_customProperties))
        {
            _log.Log(logLevel, message, args);
        }
    }
}

The important bit is the last method Log(LogLevel logLevel, string message, params object[] args) .重要的一点是最后一个方法Log(LogLevel logLevel, string message, params object[] args) It wraps the _log.Log() in a using and uses the _log.BeginScope() to add the custom properties to the log message which should be visible in the Application Insights "Custom Properties" section.它将_log.Log()包装在using并使用_log.BeginScope()将自定义属性添加到日志消息中,日志消息应该在 Application Insights“自定义属性”部分中可见。

To elaborate on the comment from @pinkfloydx33: you can do it via详细说明@pinkfloydx33 的评论:您可以通过

_logger.BeginScope( < your state here > )
{
    // All log methods here include state, regardless 
    // of which ILogger object is used. 
}

or by using或通过使用

System.Diagnostics.Activity.Current.AddBaggage()

This works without additional configuration (eg scopes are already enabled by default on AI).这无需额外配置即可工作(例如,默认情况下 AI 已启用范围)。

For example, here's a middleware class to log tenant information that shows both methods:例如,这是一个中间件类,用于记录显示两种方法的租户信息:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace MyApp
{

    public static class StoreTenantForLoggingMiddlewareExtensions
    {
        /// <summary>
        /// Register StoreTenantForLoggingMiddleware as middleware.
        /// Call this from Configure() in Startup, as: 
        /// app.UseStoreTenantForLogging()
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseStoreTenantForLogging(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<StoreTenantForLoggingMiddleware>();
        }
    }

    /// <summary>
    /// Middleware to log the Tenant's domain to Application 
    /// Insights as a customDimension
    /// </summary>
    public class StoreTenantForLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<StoreTenantForLoggingMiddleware> _logger;

        public StoreTenantForLoggingMiddleware(RequestDelegate next,
                    ILogger<StoreTenantForLoggingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        // Here TenantContext is my own class that gets the state
        // I want to be logged. You'd replace with your own object 
        // or just call a method on httpContext.
        public async Task InvokeAsync(HttpContext httpContext, TenantContext tenantContext)
        {
            // Example 1: Add data to current activity. AI will pick this
            // up and add as a customDimension in traces logs.
            var currentActivity = System.Diagnostics.Activity.Current;
            if (currentActivity != null)
            {
                currentActivity.AddBaggage("TenantDomain1", tenantContext?.Domain);
            }

            // Example 2: Use a scope. 
            // If you go with option 1, remove this 'using' but still  
            // call await _next(httpContext);
            using ( var scope = _logger.BeginScope(new Dictionary<string, object>() 
                                 { { "TenantDomain2", tenantContext?.Domain } }))
            {    
                await _next(httpContext);
            }
        }
    }
}

I'm not sure which is best.我不确定哪个最好。 The Activity one appeals slightly more to me, plus I'm guessing the data might persist a bit later in the pipeline. Activity one 对我更有吸引力,而且我猜数据可能会在管道中保留一段时间。

For bonus points if you use nlog and want to be able to log the property there you can add this line at the start of Invoke() above and then use ${mdlc:item=TenantDomain} within your nlog.config file.如果您使用 nlog 并希望能够在那里记录属性,则可以在上面的Invoke()开头添加此行,然后在nlog.config文件中使用${mdlc:item=TenantDomain} nlog.config

NLog.MappedDiagnosticsLogicalContext.Set("TenantDomain", tenantContext?.Domain);

You can probably use https://github.com/NLog/NLog.DiagnosticSource as an alternative but I've not tried.您可能可以使用https://github.com/NLog/NLog.DiagnosticSource作为替代,但我还没有尝试过。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM