【问题标题】:How to conditionally render an attribute with NLog and structured logging?如何使用 NLog 和结构化日志有条件地呈现属性?
【发布时间】:2021-01-15 18:18:12
【问题描述】:

我继承了一个使用 NLog 进行日志记录的 ASP.NET 应用程序。记录的属性之一是命名模块,它被硬编码为“核心”。这根本没用,因为应用程序的所有部分(例如健康检查、各种业务上下文)都推送相同的值。

在某些情况下,我尝试使用结构化日志记录来覆盖此值(我现在无法重构整个日志记录)。

扩展方法

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(和here)建议的解决方案,但${mdlc:item=module} 不包含任何内容。

【问题讨论】:

    标签: c# asp.net-core logging nlog structured-logging


    【解决方案1】:

    简单的方法,但会影响性能(通知${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);
    }
    

    【讨论】:

    • 不知何故我错过了这个答案的通知。 WithProperty 看起来非常好。我会尝试并提供反馈。否则${event-properties:module} 是一个不错的选择。
    • 我通过使用 WithProperty 包找到了快乐,并且我在您的回答中包含了我使用它的方式。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-24
    • 2019-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多