【问题标题】:Can NLog preserve callsite information through c# extension methods?NLog 可以通过 c# 扩展方法保存调用点信息吗?
【发布时间】:2013-05-03 20:00:43
【问题描述】:

编辑:虽然类似,但这与使用 NLog 包装器的问题不同。扩展方法添加了另一层间接性,即使是适当的包装器也会报告错误的调用站点

我目前在 NLog 周围使用日志包装器,并且我使用他们在源代码中示例中显示的技巧来获取准确的调用站点信息。对于我开始的一个新项目,我想创建一个更简单的类,所以我只是实现了如下接口:

public interface ILogger
{
    void Log( LogEntry entry );
}

然后我创建了一个扩展方法类,例如:

public static class LoggerExtensions
{
    public static void Debug( this ILogger logger, string format, params object[] args)
    {
        logger.Log( new LogEntry( LogLevel.Debug, format, args ) );
    }

    ...
}

问题在于 NLog 然后将调用站点显示为扩展方法,而不是扩展方法的调用者。我做了一些搜索,但找不到任何关于 NLog 和扩展方法的信息。

在这种情况下是否可以修复呼叫站点信息?或者是在接口本身中包含Debug、Info等功能的唯一方法?

【问题讨论】:

标签: c# nlog


【解决方案1】:

聚会迟到了,但我对这个解决方案有疑问,因为我对包装器使用了扩展方法。通过将程序集传递给 LogManager,我能够让 NLog 忽略我的扩展类。

    /// <summary>
    /// Bootstrap the wrapper class
    /// </summary>
    static Logger()
    {
        LogManager.AddHiddenAssembly(typeof(LoggingExtensions).Assembly);
    }

除了

之外,文档中没有太多细节

添加给定程序集,当 NLog 试图在堆栈跟踪上查找调用方法时将跳过该程序集。

来自NLog Documentation

通过这个设置,我什至设法让扩展方法 + DI 与 SimpleInjector 一起工作。

为了表明您仍然可以使用此方法在同一个程序集中拥有一个调用点

我的 Logger() 与 SettingsHelper() 一起存在于 Utilities 项目中 我使用 SettingsHelper 的输出设置了一个测试:

2015-08-18 20:44:07.5352 | vstest.executionengine.x86 |调试 |实用程序.Settings.SettingsHelper |来自与 Logger() 相同程序集的测试 | ActionTests.LoggingTest+LogTest.RunLogTest

粗体部分是 ${callsite}

我的 SettingsHelper() 测试:

ILogger logger = new Logger(typeof(SettingsHelper));
logger.Debug("A test from the same assembly as Logger()");

不要忘记使用 LogEventInfo() 的重载

_logger.Log(typeof(Logger), logEvent);

【讨论】:

  • @bj0 我的答案已被删除 b/c 我将其发布到其他相关问题。但是这里又是给你的;它对我有用,所以它应该适合你。我什至测试了从与 LoggerExtensions 相同的程序集中调用 Logger()(我的 AddHiddenAssembly
  • 您的呼叫站点信息似乎来自ActionTests.LoggingTest+LogTest.RunLogTest,而不是来自SettingsHelper。我的经验是一样的,如果它在程序集中,它会跳过调用点并显示程序集的第一个调用者。
  • 德普。我错过了。你说的对!也许我们需要破解源代码并提出请求来解决这个问题..或者,将您的 Logger() 完全分开在一个差异程序集中 =/
  • 更好的是,我正在考虑将 loggerType 转换为参数,然后我可以在 NLog 方法中传递 .Log(logEvent, typeof(Logger), typeof(LoggingExtensions)) private static bool IsNonUserStackFrame([NotNull] MethodBase method, [NotNull] Type loggerType)
  • 这可能比全局设置更干净。它更加明确,让您可以更好地控制何时忽略类。不确定它会如何影响性能
【解决方案2】:

编辑:不幸的是,这个答案不再有效。 NLog 在 3.2.0 中破坏了这个功能,看起来他们不打算修复它:https://github.com/NLog/NLog/issues/696

我找到了解决方法。虽然 不完全相同 与仅用于 NLog 包装器的解决方案,但结果是相似的。

我没有让 ILogger 实现者(NLog 包装器)简单地将它自己的类型传递给 NLog,而是创建了一个允许从调用者传递一个类型的重载:

public void Log( LogEntry entry )
{
    this.Log( this.GetType(), entry );
}

public void Log( Type type, LogEntry entry)
{
    NLogLogger.Log( type, new NLog.LogEventInfo( ... ) );
}

这需要向接口添加重载,这使得它有点难看(并且特定于 NLog):

public interface ILogger
{
    void Log( LogEntry entry );
    void Log( Type type, LogEntry entry );
}

然后可以修改扩展方法:

public static class LoggerExtensions
{
    public static void Debug( this ILogger logger, string format params object[] args )
    {
        logger.Log( typeof(LoggerExtensions), LogLevel.Debug, format, args ) );
    }

    ...
}

虽然不像简单地使用包装器那样干净,但这确实允许使用扩展方法,同时保留调用点信息。如果有人有更清洁的方式,我想看看。

【讨论】:

    【解决方案3】:

    LogEntry 是您自己的类/结构吗?也许您可以向它添加一个字段/属性来保存记录器类型。在您的扩展方法中,当您创建要发送到 ILogger 的 LogEntry 时,使用 tyepof(LoggerExtensions) 填充 LogEntry.LoggerType。

    因此,您的 LoggerExtensions 类可能看起来像这样(未编译且未经测试):

    public static class LoggerExtensions
    {
        public static void Debug( this ILogger logger, string format, params object[] args)
        {
            logger.Log( new LogEntry( typeof(LoggerExtensions), LogLevel.Debug, format, args ) );
        }
    
        ...
    }
    

    由于 log4net 使用类似的方案(查看记录器类型以判断哪个堆栈帧对应于实际调用站点),如果您愿意,可以编写 log4net 包装器和相应的 log4net 包装器扩展方法。

    【讨论】:

    • 这不是一个坏主意,它将消除界面中的过载,同时提供相同的解决方案。我想不利的一面是您将信息放入 LogEntry 中,而这并不是严格意义上的日志条目的一部分,从而激怒了 OO 之神。
    猜你喜欢
    • 2014-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多