代码之家  ›  专栏  ›  技术社区  ›  Christopher Pisz

如何向ILogger添加自定义属性

  •  3
  • Christopher Pisz  · 技术社区  · 6 年前

    我想向从ForContext获得的ILogger实例添加一个自定义属性和值。我的想法是,我可以用一些名称标记所有语句,比如“Render”或“AI”,然后在输出中看到该组名称,并对其进行过滤。

    我在网上看到的所有例子:

    • 将ForContext用作类名

    • PushProperty,对每个语句属性使用语句和全局上下文

    我不想那样。我不希望打电话的人每次都要做任何事情。


    以下是我迄今为止为学习目的编写的一些代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using Serilog;
    using Serilog.Core;
    using Serilog.Events;
    
    namespace SerilogExtensions {
    
        public static class SerilogForContextExtension {
    
            /// <summary>
            /// Extension method that adds the class name to the source context associated with the logger interface
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="that"></param>
            /// <returns></returns>
            public static ILogger ForMyContext<T>(this ILogger that) =>
                that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
    
            public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) {
                // TODO - What goes here?
                that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
                return that;
            }
    
            /// <summary>
            /// Extension method that adds the class name to the source context associated with the logger interface
            /// For use with static classes
            /// </summary>
            /// <param name="that"></param>
            /// <param name="t"></param>
            /// <returns></returns>
            public static ILogger ForMyContextWithExplicitType(this ILogger that, Type t) =>
                that.ForContext(Constants.SourceContextPropertyName, t.Name);
        }
    
        /// <summary>
        /// POD Class, for serialization to and from file, that contains configurable option we will pass to Serilog
        /// </summary>
        public class LogConfiguration {
    
            public LogConfiguration() {
                DefaultLevel = LogEventLevel.Verbose;
                Enabled = new List<string>();
                Disabled = new List<string>();
                LogLevelsBySource = new Dictionary<string, LogEventLevel>() { {"SomeClass", 0 }, { "OtherClass", 0 }  };
                OutputTemplate = "[{ThreadId} {Level:u3} {SourceContext} {Tag}] {Message:lj}{NewLine}{Exception}";
            }
    
            /// <summary>
            /// The default logging level
            /// </summary>
            public LogEventLevel DefaultLevel { get; set; }
    
            /// <summary>
            /// Enable logging by source context class name
            /// </summary>
            public List<string> Enabled { get; set; }
    
            /// <summary>
            /// Disable logging by source context class name
            /// </summary>
            public List<string> Disabled { get; set; }
    
            /// <summary>
            /// Configure logging level by source context class name
            /// </summary>
            public Dictionary<string, LogEventLevel> LogLevelsBySource;
    
            /// <summary>
            /// Determines what each log message will look like. 
            /// Uses Serilog's rules
            /// </summary>
            public string OutputTemplate { get; set; }
    
            /// <summary>
            /// Overides any previous configuration Serilog is using with one dictated by this class' state
            /// </summary>
            public void ConfigureSerilog() {
    
                var configuration = new LoggerConfiguration()
                    .MinimumLevel.ControlledBy(new Serilog.Core.LoggingLevelSwitch(DefaultLevel))
                    .Enrich.WithThreadId()
                    .Enrich.FromLogContext()
                    .WriteTo.TextWriter(Console.Out, outputTemplate: OutputTemplate);
    
                var filterExpression = new StringBuilder();
    
                if(Enabled.Count > 0) {
    
                    filterExpression.Append($"@Properties['{Serilog.Core.Constants.SourceContextPropertyName}'] in ['{Enabled[0]}'");
                    for(int index = 1; index < Enabled.Count; ++index) {
                        filterExpression.Append($",'{Enabled[index]}'");
                    }
                    filterExpression.Append("]");
    
                    configuration.Filter.ByIncludingOnly(filterExpression.ToString());
                }
                else if(Disabled.Count > 0) {
    
                    filterExpression.Append($"@Properties['{Serilog.Core.Constants.SourceContextPropertyName}'] in ['{Disabled[0]}'");
                    for (int index = 1; index < Disabled.Count; ++index) {
                        filterExpression.Append($",'{Disabled[index]}'");
                    }
                    filterExpression.Append("]");
    
                    configuration.Filter.ByExcluding(filterExpression.ToString());
                }
    
                foreach(var logLevelForSource in LogLevelsBySource) {
                    configuration.MinimumLevel.Override(logLevelForSource.Key, logLevelForSource.Value);
                }
    
                Log.Logger = configuration.CreateLogger();
            }
        }
    }
    

    using System;
    using System.IO;
    
    using Newtonsoft.Json;
    using Serilog;
    using SerilogExtensions;
    
    namespace SeriConfigurable {
        public static class MyOptions {
            private static readonly object __lock = new object();
            private static FileSystemWatcher __logLevelWatcher;
    
            /// <summary>
            /// Allows us to configure Serilog from option in a file
            /// </summary>
            /// <param name="file"></param>
            private static void ReadLogLevel(string file) {
    
                LogConfiguration configuration = null;
                if (!File.Exists(file)) {
                    configuration = new LogConfiguration();
                    var jsonAsText = JsonConvert.SerializeObject(configuration);
    
                    using (StreamWriter writer = new StreamWriter(file)) {
                        writer.Write(jsonAsText);
                    }
                }
                else {
                    using (StreamReader reader = new StreamReader(file)) {
                        var jsonAsText = reader.ReadToEnd();
                        configuration = JsonConvert.DeserializeObject<LogConfiguration>(jsonAsText);
                    }
                }
    
                configuration.ConfigureSerilog();
            }
    
            public static void SetOptionsPath(string path) {
                lock (__lock) {
                    string logLevelFile = Path.Combine(path, "logLevel");
    
                    ReadLogLevel(logLevelFile);
    
                    if (__logLevelWatcher != null) {
                        __logLevelWatcher.EnableRaisingEvents = false;
                        __logLevelWatcher.Dispose();
                    }
    
                    __logLevelWatcher = new FileSystemWatcher {
                        Path = Path.GetDirectoryName(logLevelFile),
                        Filter = Path.GetFileName(logLevelFile),
                        NotifyFilter = NotifyFilters.LastWrite
                    };
    
                    __logLevelWatcher.Changed += (sender, e) => { ReadLogLevel(e.FullPath); };
                    __logLevelWatcher.EnableRaisingEvents = true;
                }
            }
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    
    using Serilog;
    using SerilogExtensions;
    using Serilog.Sinks;
    
    namespace SeriConfigurable {
    
        public class SomeClass {
            private static Serilog.ILogger _log = Serilog.Log.Logger.ForMyContext<SomeClass>();
    
            public SomeClass() {
                _log.Debug("Constructed");
            }
    
            public virtual void Foo() {
                _log.Verbose("Doing Verbose Stuff");
                _log.Information("Doing Information Stuff");
                _log.Debug("Doing Debug Stuff");
                _log.Warning("Doing Warning Stuff");
                _log.Error("Doing Error Stuff");
                _log.Fatal("Doing Fatal Stuff");
    
                var dummyData = new byte[] { 0x01, 0x03, 0xFF, 0x6E, 0xFF };
                StringBuilder hex = new StringBuilder(dummyData.Length * 6);
                foreach (byte oneByte in dummyData)
                    hex.AppendFormat("0x{0:x2}, ", oneByte);
    
                _log.Verbose(string.Format("Received {0} bytes of data: {1}", dummyData.Length, hex.ToString()));
            }
        }
    
        public class OtherClass {
            private static Serilog.ILogger _log = Serilog.Log.Logger.ForMyContext<OtherClass>();
    
            public OtherClass() {
                _log.Debug("Constructed");
            }
    
            public void Foo() {
                _log.Verbose("Doing Verbose Stuff");
                _log.Information("Doing Information Stuff");
                _log.Debug("Doing Debug Stuff");
                _log.Warning("Doing Warning Stuff");
                _log.Error("Doing Error Stuff");
                _log.Fatal("Doing Fatal Stuff");
            }
        }
    
        public class DerivedClass : SomeClass {
            private static Serilog.ILogger _log = Serilog.Log.Logger.ForMyContextWithTag<DerivedClass>("Poop");
    
            public DerivedClass() {
                _log.Debug("Constructed");
            }
    
            public override void Foo() {
    
                _log.Verbose("Doing Verbose Stuff");
                _log.Information("Doing Information Stuff");
                _log.Debug("Doing Debug Stuff");
                _log.Warning("Doing Warning Stuff");
                _log.Error("Doing Error Stuff");
                _log.Fatal("Doing Fatal Stuff");
    
                try {
                    MakeExceptions();
                }
                catch(Exception e) {
                    _log.Error(e, "Bad Things");
                }
            }
    
            public void MakeExceptions() {
                var inner = new BadImageFormatException("You made us look at x86 things");
                var e = new ArgumentException("All of your arguments are bad. You skipped philosophy class", inner);
                throw e;
            }
        }
    
        class Program {
            static void Main(string[] args) {
    
                MyOptions.SetOptionsPath(System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
    
                var someClass = new SomeClass();
                someClass.Foo();
    
                var otherClass = new OtherClass();
                otherClass.Foo();
    
                var derived = new DerivedClass();
                derived.Foo();
            }
        }
    }
    

    [1 DBG OtherClass] Constructed
    [1 INF OtherClass] Doing Information Stuff
    [1 DBG OtherClass] Doing Debug Stuff
    [1 WRN OtherClass] Doing Warning Stuff
    [1 ERR OtherClass] Doing Error Stuff
    [1 FTL OtherClass] Doing Fatal Stuff
    [1 DBG DerivedClass Poop] Constructed
    [1 VRB DerivedClass Poop] Doing Verbose Stuff
    [1 INF DerivedClass Poop] Doing Information Stuff
    [1 DBG DerivedClass Poop] Doing Debug Stuff
    [1 WRN DerivedClass Poop] Doing Warning Stuff
    [1 ERR DerivedClass Poop] Doing Error Stuff
    [1 FTL DerivedClass Poop] Doing Fatal Stuff
    [1 ERR DerivedClass Poop] Bad Things
    System.ArgumentException: All of your arguments are bad. You skipped philosophy class ---> System.BadImageFormatException: You made us look at x86 things
       --- End of inner exception stack trace ---
       at SeriConfigurable.DerivedClass.MakeExceptions() in Program.cs:line 82
       at SeriConfigurable.DerivedClass.Foo() in Program.cs:line 72
    
    1 回复  |  直到 6 年前
        1
  •  4
  •   Ruben Bartelink    3 年前
    public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) {
        // TODO - What goes here?
        that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name);
        return that;
    }
    

    需要成为:

    public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) {
        return that
             .ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
             .ForContext("Tag", tag);
    }
    

    哪个是

    public static ILogger ForMyContextWithTag<T>(this ILogger that, string tag) =>
       that.ForContext(Constants.SourceContextPropertyName, typeof(T).Name)
           .ForContext("Tag", tag);