C# Serilog:如何使用字符串插值记录日志并保留消息模板中的参数名称?

11

如何替换这段代码:

string name = "John";
logger.Information("length of name '{name}' is {nameLength}", name, name.Length);

使用类似这样的C# 字符串插值或类似语法
string name = "John";
// :-( lost benefit of structured logging: property names not passed to logger
logger.Information($"length of name '{name}' is {name.Length}");

但保留属性名称以使结构化日志正常工作?

好处包括:

  1. 提高可读性
  2. 您永远不会在参数列表中忘记参数或在消息模板中忘记属性名称,特别是在更改日志记录代码时
  3. 您始终知道此属性名称将打印到日志中
2个回答

6

将此文件添加到您的项目中。它具有ILogger扩展方法VerboseInterpolated()DebugInterpolated()等。这里还有单元测试

使用格式字符串

string name = "John";
// add 'Interpolated' to method name: InformationInterpolated() instead of Information()
// add name of the property after the expression. Name is passed to the logger
logger.InformationInterpolated($"length of name '{name:name}' is {name.Length:Length}");

但要小心:使用错误的方法非常容易。比如意外使用Serilog的方法,例如logger.Debug($"length = {length:propertyNameForLogger}"),它会记录length = propertyNameForLogger,所以没有参数值被记录下来。这是由于propertyNameForLogger是您的值的格式

使用匿名类型

string name = "John";
// add 'Interpolated' to method name: InformationInterpolated() instead of Information()
// create an anonymous object with 'new { propertyName }'. 
// It's much slower because of using Reflection, but allows to write the variable name only once. 
logger.InformationInterpolated($"length of name '{new { name }}' is {new { name.Length }}");
// you can also specify other property names
logger.InformationInterpolated($"length of name '{new { userName = name }}' is {new { lengthOfName = name.Length }}");

1
这使得错误更加容易 - 现在很容易使用错误的扩展方法。EF Core曾经也遇到了FromSql同样的问题 - 很容易运行未参数化的查询,从而导致SQL注入。这就是为什么EF Core 3.1将其替换为FromSqlRawFromSqlInterpolated并向旧方法添加了Obsolete属性的原因。 - Panagiotis Kanavos
好主意,我喜欢这个。你有尝试建议Serilog将其实现到其核心中吗? - Vočko
1
你可以考虑使用LinqExpressions而不是反射和显式的AnonymousType,例如我的函数PropertyNameAndValue https://github.com/MNF/CommonDotNetHelpers/blob/015ac57a8b2850d4af9a4c30d3d0ef8b9327e6f6/src/Diagnostics/PropertyNameExtensions.cs#L38 - Michael Freidgeim
1
我刚刚发布了一个库(带有对Serilog、NLog和Microsoft ILogger的扩展方法),允许使用插值字符串编写日志消息,同时定义属性名称(基于使用匿名对象定义名称的相同思想)。很想听听一些反馈:https://github.com/Drizin/InterpolatedLogging - drizin
就此而言,使用表达式自动确定属性/变量的名称比其他方法(包括匿名对象)要慢得多。在我的库中,我进行了一些基准测试,如果我记得数字,看起来表达式要慢50倍以上。 - drizin
显示剩余3条评论

0

感谢您提供的好主意。我发现一个缺点,就是在使用 DI 时没有支持通用记录器。我已经扩展了 @Artemious 的解决方案。如果这不是正确的方法,请告诉我。

public static void LogInformationInterpolated<T>(this ILogger<T> logger, FormattableString? message) =>
            WriteInterpolated<T>(logger, null, message, Information);

public static void LogInformationInterpolated<T>(this ILogger<T> logger, Exception? ex, FormattableString? message) =>
            WriteInterpolated<T>(logger, ex, message, Information);

public static void WriteInterpolated<T>(this ILogger<T> logger, Exception? ex, FormattableString? message, Serilog.Events.LogEventLevel logEventLevel)
{
    var contextedLogger = Log.ForContext<T>();
    WriteInterpolated(contextedLogger, ex, message, logEventLevel);
}

ILogger来自Microsoft.Extensions.Logging命名空间,Serilog似乎没有这个。但我不是专家,因此愿意听取建议。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接