如何使用 NLog 和结构化日志记录有条件地呈现属性?
How to conditionally render an attribute with NLog and structured logging?
我继承了一个 ASP.NET 应用程序,它使用 NLog 进行日志记录。记录的属性之一被命名为模块,它被硬编码为“核心”。这一点用处都没有,因为应用程序的所有部分(例如健康检查、各种业务上下文)都会推送相同的值。
在某些情况下,我正在尝试使用结构化日志记录来覆盖此值(我现在负担不起重构整个日志记录)。
扩展方法
public static void LogInfoWithModule<T>(this ILogger<T> logger, string message, string module)
{
var escapedMessage = message.Replace("{", "{{");
escapedMessage = escapedMessage.Replace("}", "}}");
logger.Log(LogLevel.Information, "[{module}] " + escapedMessage, module);
}
用法
logger.LogInfoWithModule($"Health check response = {response}", Constants.LoggingModules.Health);
NLog 布局(为简洁起见删除了大部分属性)
<target name="logJson" xsi:type="File">
<layout xsi:type="JsonLayout" includeAllProperties="true">
<attribute name="module" layout="${when:when='${event-properties:item=module}'=='':Core" />
</layout>
</target>
我得到了想要的东西,即所有现有调用都不会提供该模块,它们将像以前一样工作。通过 LogInfoWithModule
的任何日志记录都将覆盖默认值。
但是,我发现这个解决方案非常混乱,因为:
- 实际消息包含模块名称
- 无法使用结构化日志记录
- 不能延迟字符串格式化(通过为 Log 函数提供参数)
我已经尝试了建议的解决方案 here (and here),但是 ${mdlc:item=module}
不包含任何内容。
简单的方法,但会影响性能(注意 ${mdlc:item=module}
区分大小写)
public static void LogInfoWithModule<T>(this ILogger<T> logger, string message, string module)
{
using (logger.BeginScope(new [] { new KeyValuePair<string,object>("module", module) }))
{
logger.Log(LogLevel.Information, message);
}
}
advanced way for injecting properties(注意${event-properties:module}
区分大小写):
public static void LogInfoWithModule<T>(this ILogger<T> logger, string message, string module)
{
logger.Log(LogLevel.Information, default(EventId), new ModuleLogEvent(message, module), default(Exception), ModuleLogEvent.Formatter);
}
class ModuleLogEvent : IReadOnlyList<KeyValuePair<string, object>>
{
public static Func<ModuleLogEvent, Exception, string> Formatter { get; } = (l, ex) => l.Message;
public string Message { get; }
public string Module { get; }
public MyLogEvent(string message, string module)
{
Message = message;
Module = module;
}
public override string ToString() => Message;
// IReadOnlyList-interface
public int Count => 1;
public KeyValuePair<string, object> this[int index] => new KeyValuePair<string,object>("module", Module);
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => yield return new KeyValuePair<string,object>("module", Module);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
注意上面的示例代码中可能包含拼写错误和编译错误。
或者你可能会在这里找到幸福:https://github.com/viktor-nikolaev/XeonApps.Extensions.Logging.WithProperty:
public static class LoggingExtensions
{
public static NLog.Logger WithModule(this NLog.Logger logger, object propertyValue) =>
logger.WithProperty("module", propertyValue);
public static ILogger WithModule(this ILogger logger, object propertyValue) =>
logger.WithProperty("module", propertyValue);
}
我继承了一个 ASP.NET 应用程序,它使用 NLog 进行日志记录。记录的属性之一被命名为模块,它被硬编码为“核心”。这一点用处都没有,因为应用程序的所有部分(例如健康检查、各种业务上下文)都会推送相同的值。
在某些情况下,我正在尝试使用结构化日志记录来覆盖此值(我现在负担不起重构整个日志记录)。
扩展方法
public static void LogInfoWithModule<T>(this ILogger<T> logger, string message, string module)
{
var escapedMessage = message.Replace("{", "{{");
escapedMessage = escapedMessage.Replace("}", "}}");
logger.Log(LogLevel.Information, "[{module}] " + escapedMessage, module);
}
用法
logger.LogInfoWithModule($"Health check response = {response}", Constants.LoggingModules.Health);
NLog 布局(为简洁起见删除了大部分属性)
<target name="logJson" xsi:type="File">
<layout xsi:type="JsonLayout" includeAllProperties="true">
<attribute name="module" layout="${when:when='${event-properties:item=module}'=='':Core" />
</layout>
</target>
我得到了想要的东西,即所有现有调用都不会提供该模块,它们将像以前一样工作。通过 LogInfoWithModule
的任何日志记录都将覆盖默认值。
但是,我发现这个解决方案非常混乱,因为:
- 实际消息包含模块名称
- 无法使用结构化日志记录
- 不能延迟字符串格式化(通过为 Log 函数提供参数)
我已经尝试了建议的解决方案 here (and here),但是 ${mdlc:item=module}
不包含任何内容。
简单的方法,但会影响性能(注意 ${mdlc:item=module}
区分大小写)
public static void LogInfoWithModule<T>(this ILogger<T> logger, string message, string module)
{
using (logger.BeginScope(new [] { new KeyValuePair<string,object>("module", module) }))
{
logger.Log(LogLevel.Information, message);
}
}
advanced way for injecting properties(注意${event-properties:module}
区分大小写):
public static void LogInfoWithModule<T>(this ILogger<T> logger, string message, string module)
{
logger.Log(LogLevel.Information, default(EventId), new ModuleLogEvent(message, module), default(Exception), ModuleLogEvent.Formatter);
}
class ModuleLogEvent : IReadOnlyList<KeyValuePair<string, object>>
{
public static Func<ModuleLogEvent, Exception, string> Formatter { get; } = (l, ex) => l.Message;
public string Message { get; }
public string Module { get; }
public MyLogEvent(string message, string module)
{
Message = message;
Module = module;
}
public override string ToString() => Message;
// IReadOnlyList-interface
public int Count => 1;
public KeyValuePair<string, object> this[int index] => new KeyValuePair<string,object>("module", Module);
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => yield return new KeyValuePair<string,object>("module", Module);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
注意上面的示例代码中可能包含拼写错误和编译错误。
或者你可能会在这里找到幸福:https://github.com/viktor-nikolaev/XeonApps.Extensions.Logging.WithProperty:
public static class LoggingExtensions
{
public static NLog.Logger WithModule(this NLog.Logger logger, object propertyValue) =>
logger.WithProperty("module", propertyValue);
public static ILogger WithModule(this ILogger logger, object propertyValue) =>
logger.WithProperty("module", propertyValue);
}