代码之家  ›  专栏  ›  技术社区  ›  Alexei - check Codidact

如何使用NLog和结构化日志有条件地呈现属性?

  •  0
  • Alexei - check Codidact  · 技术社区  · 3 年前

    我继承了一个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 (以及 here )但是 ${mdlc:item=module} 不包含任何内容。

    0 回复  |  直到 3 年前
        1
  •  2
  •   Alexei - check Codidact    3 年前

    简单的方法,但会对性能产生影响(注意 ${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);
    }
    
    推荐文章