如何将自定义属性添加到ILogger

时间:2018-11-13 22:21:47

标签: c# serilog

我想向从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 个答案:

答案 0 :(得分:4)

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);