我想要在从ForContext获取的ILogger实例中添加自定义属性和值。我的想法是可以为所有语句标记上一些名称,比如“Render”或者“AI”,然后在输出中看到该组名称,并且还可以对其进行过滤。
我在网上看到的所有示例都是使用 ForContext 来指定类名。
我已经这样做了,但是想要增加这个自定义属性。
PushProperty 可以用于每个语句属性的设置,包括使用语句与全局上下文。但我不希望这样做。我不希望调用者需要为每个语句都执行此操作。
以下是我为学习目的编写的代码:
我在网上看到的所有示例都是使用 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