在 Function App ILogger (C#) 的 Application Insights 中记录自定义 object
Log custom object in Application Insights from Function App ILogger (C#)
我有一个 C# .NET Core Azure Function App,我正在使用 ILogger 将日志发送到 Application Insights。到目前为止效果很好。
函数header:
public static void Run([TimerTrigger("0 30 * * * *")] TimerInfo myTimer, ILogger log, ExecutionContext context)
ILogger 用法:
log.LogInformation($"MyFunction trigger function executed at: {DateTime.Now}");
在 App Insights 中,我看到日志中有默认信息,例如它来自哪个 Function App,以及包含上述字符串的 message
。
但是,现在我想记录一个自定义日志。我有一个 IEnumerable<IDictionary<string, string>>
并且我希望列表中的每个字典元素都是一个单独的日志。理想情况下,我可以有一个日志,每个字段都是字典中的一个键,它的值是相应的值。或者,我可以在日志中使用某种 customDimensions 字段,它是一个 object ,其中包含列表中 1 个字典中的所有 key-value 对。
目的是使日志易于在 Kusto 中查询。我想避免在 App Insights 中查询它们时必须解析它们。
备注:
- 因为我已经将 ILogger 用于现有的日志记录,有没有办法通过 ILogger 接口执行上述 object-logging?
- 如果没有,我如何使用不同的记录器记录上面提到的 object?
我查看了许多其他类似的帖子,但其中 none 似乎得到了完整的回答。
这是我以前用过的模式:
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);
}
}
}
最重要的是最后一个方法Log(LogLevel logLevel, string message, params object[] args)
。它将 _log.Log()
包装在 using
中,并使用 _log.BeginScope()
将自定义属性添加到日志消息中,这些消息应该在 Application Insights“自定义属性”部分中可见。
详细说明@pinkfloydx33 的评论:您可以通过
_logger.BeginScope( < your state here > )
{
// All log methods here include state, regardless
// of which ILogger object is used.
}
或使用
System.Diagnostics.Activity.Current.AddBaggage()
这无需额外配置即可工作(例如,范围已在 AI 上默认启用)。
例如,这里有一个中间件 class 用于记录显示两种方法的租户信息:
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);
}
}
}
}
我不确定哪个最好。 Activity
对我的吸引力稍微大一点,而且我猜数据可能会在稍后的管道中持续存在。
如果您使用 nlog 并希望能够在那里记录 属性 以获得奖励积分,您可以在上面的 Invoke()
开头添加此行,然后在其中使用 ${mdlc:item=TenantDomain}
你的 nlog.config
文件。
NLog.MappedDiagnosticsLogicalContext.Set("TenantDomain", tenantContext?.Domain);
您或许可以使用 https://github.com/NLog/NLog.DiagnosticSource 作为替代方案,但我没有尝试过。
更新 2022 和 .NET 6
using Newtonsoft.Json;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
Activity.Current?.AddBaggage(nameof(foodMenuALModelCreate), JsonConvert.SerializeObject(foodMenuALModelCreate));
logger.LogInformation(nameof(CreateFoodMenuAutomaticLocalizationsAsync));
Application Insights 查询:
traces
| where message contains "CreateFoodMenuAutomaticLocalizationsAsync"
| order by timestamp desc
结果如下所示:
我有一个 C# .NET Core Azure Function App,我正在使用 ILogger 将日志发送到 Application Insights。到目前为止效果很好。
函数header:
public static void Run([TimerTrigger("0 30 * * * *")] TimerInfo myTimer, ILogger log, ExecutionContext context)
ILogger 用法:
log.LogInformation($"MyFunction trigger function executed at: {DateTime.Now}");
在 App Insights 中,我看到日志中有默认信息,例如它来自哪个 Function App,以及包含上述字符串的 message
。
但是,现在我想记录一个自定义日志。我有一个 IEnumerable<IDictionary<string, string>>
并且我希望列表中的每个字典元素都是一个单独的日志。理想情况下,我可以有一个日志,每个字段都是字典中的一个键,它的值是相应的值。或者,我可以在日志中使用某种 customDimensions 字段,它是一个 object ,其中包含列表中 1 个字典中的所有 key-value 对。
目的是使日志易于在 Kusto 中查询。我想避免在 App Insights 中查询它们时必须解析它们。
备注:
- 因为我已经将 ILogger 用于现有的日志记录,有没有办法通过 ILogger 接口执行上述 object-logging?
- 如果没有,我如何使用不同的记录器记录上面提到的 object?
我查看了许多其他类似的帖子,但其中 none 似乎得到了完整的回答。
这是我以前用过的模式:
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);
}
}
}
最重要的是最后一个方法Log(LogLevel logLevel, string message, params object[] args)
。它将 _log.Log()
包装在 using
中,并使用 _log.BeginScope()
将自定义属性添加到日志消息中,这些消息应该在 Application Insights“自定义属性”部分中可见。
详细说明@pinkfloydx33 的评论:您可以通过
_logger.BeginScope( < your state here > )
{
// All log methods here include state, regardless
// of which ILogger object is used.
}
或使用
System.Diagnostics.Activity.Current.AddBaggage()
这无需额外配置即可工作(例如,范围已在 AI 上默认启用)。
例如,这里有一个中间件 class 用于记录显示两种方法的租户信息:
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);
}
}
}
}
我不确定哪个最好。 Activity
对我的吸引力稍微大一点,而且我猜数据可能会在稍后的管道中持续存在。
如果您使用 nlog 并希望能够在那里记录 属性 以获得奖励积分,您可以在上面的 Invoke()
开头添加此行,然后在其中使用 ${mdlc:item=TenantDomain}
你的 nlog.config
文件。
NLog.MappedDiagnosticsLogicalContext.Set("TenantDomain", tenantContext?.Domain);
您或许可以使用 https://github.com/NLog/NLog.DiagnosticSource 作为替代方案,但我没有尝试过。
更新 2022 和 .NET 6
using Newtonsoft.Json;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
Activity.Current?.AddBaggage(nameof(foodMenuALModelCreate), JsonConvert.SerializeObject(foodMenuALModelCreate));
logger.LogInformation(nameof(CreateFoodMenuAutomaticLocalizationsAsync));
Application Insights 查询:
traces
| where message contains "CreateFoodMenuAutomaticLocalizationsAsync"
| order by timestamp desc
结果如下所示: