依赖关系在大型应用程序中为语义日志实现 EventSource

Posted

技术标签:

【中文标题】依赖关系在大型应用程序中为语义日志实现 EventSource【英文标题】:Dependency concerns Implementing EventSource for semantic logging in large application 【发布时间】:2014-12-07 08:09:33 【问题描述】:

我正在开发一个包含三个 Windows 服务和几个普通 Windows 应用程序 (.exe) 的大型产品。现在我们要迁移到 ETW 和语义日志记录,并使用 Microsoft.Diagnostics.Tracing.EventSource。

我在某处读到应用程序的所有逻辑连接部分都应该使用相同的事件源。这意味着我们希望我们的服务有一个几乎单一的 EventSource。但是我们如何在不引入产品中几乎所有程序集之间的依赖关系的情况下做到这一点呢?

该应用程序当前包含大约 70 个程序集。并且为了能够在 EventSource 中创建例如接受枚举值的日志方法,包含事件源的程序集必须引用定义枚举的程序集,这意味着需要将枚举定义从使用它的程序集,可能是一个 .exe,到所有程序集引用的东西。

是否有某种方法可以在一个仍使用相同 ETW EventSource 的应用程序中拥有从 EventSource 派生的多个类?或者在这种情况下,当不希望引入一大堆新依赖项来创建日志类时,用 ETW 实现语义日志记录的好方法是什么?

【问题讨论】:

我通常使用一个唯一的 guid、一个唯一的日志记录类、一个唯一的枚举(定义为“日志记录组件”)和一个 TraceLevel(错误、警告、信息等)。然后,Log 方法可以使用很酷的 [CallerMemberName],这样您就可以自动设置跟踪的方法名称。这只会创建一个对公共类的引用(如果您不想要硬程序集引用,您甚至可以与 Visual Studio 中的源链接共享)。 @SimonMourier 这适用于“通用”日志,但它似乎不适用于语义日志? 是的,这更像是一条评论 :) 【参考方案1】:

共有三种策略:

    创建一个仅包含 EventSource 派生类的程序集,该派生类为所有应用程序定义事件。将对该程序集的引用添加到所有必需的项目中。为简单起见,您可以将其包装到 nuget 包中。 仅使用一个 EventSource 派生类创建一个测试项目。仅将其用于验证目的。将此类复制到所有必需的项目中。这基本上是相同的解决方案,但没有二进制依赖关系。 为每个项目创建新的 EventSource 派生类,但为它们指定相同的 Guid 属性。在这种情况下,您需要确保所有这些事件源都具有相同的重叠(具有相同 ID)事件的声明。在这种情况下,您必须编写一些清单合并工具来生成组合清单。

【讨论】:

这些都不是那么吸引人,但我想上面的第三个选项至少回答了我的问题;这是可能的,但并非没有大量工作。 :// 虽然我实际上希望实现上面的解决方案 3,但它似乎不起作用。因为您无法创建具有相同 GUID 的 EventSource 类的两个实例,所以它将生成一个错误事件,指出“错误:EventSource XXXX 的命令处理中的异常:已使用 Guid c64e6d39-ff1f-4620-8041-e3f9cca908c9 的 EventSource 实例存在。 :(【参考方案2】:

小心,EventSource 类必须被密封! 如果你想使用 EventSource 使用依赖注入,有一个解决方法...

定义一个简单的接口:

// A simple interface to log what you need ...
public interface ILog

    void Debug(string message);

    void Info(string message);

    void Warn(string message);

    void Error(string message);

    void Error(string message, Exception exception);

并且实现(你的接口的实现必须用NonEventAttribute装饰:

[EventSource(Name = "MyLogEventsource")]
public class Log : EventSource, ILog

    public Log()
    
        EventSourceAnalyzer.InspectAll(this);
    

    [NonEvent]
    public void Debug(string message)
    
        DebugInternal(message);
    

    [Event(1)]
    private void DebugInternal(string message)
    
        WriteEvent(1, message);
    

    [NonEvent]
    public void Info(string message)
    
        InfoInternal(message);
    

    [Event(2)]
    private void InfoInternal(string message)
    
        WriteEvent(2, message);
    

    [NonEvent]
    public void Warn(string message)
    
        WarnInternal(message);
    

    [Event(3)]
    private void WarnInternal(string message)
    
        WriteEvent(3, message);
    

    [NonEvent]
    public void Error(string message)
    
        ErrorInternal(message, "", "");
    

    [NonEvent]
    public void Error(string message, Exception exception)
    
        ErrorInternal(message, exception.Message, exception.ToString());
    

    [Event(4)]
    private void ErrorInternal(string message, string exceptionMessage, string exceptionDetails)
    
        WriteEvent(4, message, exceptionMessage, exceptionDetails);
    

您现在可以注入您的日志记录类 ^^

【讨论】:

【参考方案3】:

我通常这样做是为了实现接口的隔离,即使它们使用事件源的单个实例。在我的 ioc 中,所有带有 ISingletonDependency 的代码都注册为单例。因此,您可以使用非常具体的方法调用接口,但它们仍然是相同的 EventSource。

希望这会有所帮助。

public MyCompanyEventSource: IMyCompanyEventSource, ISingletonDependency

public IMyCompanyEventSource: IComponentLogger1, IComponentLogger2, IComponentLogger3

public Component1
       public Component1(IComponentLogger logger)
       
    

【讨论】:

以上是关于依赖关系在大型应用程序中为语义日志实现 EventSource的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Rails 中为多对多关系(和中间表)做一个选择字段?

浅谈SpringBoot底层日志文件依赖关系及日志使用

nuget 没有使用 nuget 包中为 Microsoft.Extensions.Logging 指定的内容解决依赖关系

UML语言体系

UML语言体系

c++ 设计模式5 (Observer / Event 观察者模式)