【问题标题】:Flexible Logging interface design in C#C#中灵活的日志接口设计
【发布时间】:2012-12-13 14:14:31
【问题描述】:

我想编写自己的日志记录类(在 C# 中),它实现了一个标准接口,我可以从代码的任何部分调用它。

我的想法是让多个 Log 类实现 Logger 接口,每个类都有其特定的日志目标,例如,FileLogger 将实现对文件的日志记录,TextBox 记录器将实现对 Form 中的 Multi Line TextBox 的日志记录, DBLogger 将实现记录到数据库表等。

此外,每个记录器类都可以有一个嵌套记录器或链式记录器类,这样应用程序代码中对 Log() 方法的一次调用就可以在多个目的地记录消息;示例在一次调用中记录到 Form 上的文件和文本框。

我面临的困难是这样的:

通常我会记录到一个正在运行的日志文件(其中将包含调试所需的所有日志消息)、一个查看日志文件(其中将仅包含要由用户查看或需要用户操作的日志消息)、一个 Multi屏幕上的行文本框(将复制所有日志消息以向用户提供进度指示)和另一个多行文本框(将仅记录用户查看所需的消息)。

当我调用 logger.Log(message) 时,某些消息可能不适用于特定的日志目标。例如,某些消息可能只记录在运行日志文件或进度文本框中,而不是用户评论文本框中,反之亦然。

由于记录器将被链接起来,以便单个函数调用可以登录到所有必需的目的地,特定记录器如何识别日志消息不是针对它的,从而忽略日志消息?

我的示例日志界面是:

public interface Logger
{
    public void Log(string msg);
    public void Log(string msgType, string msg);
    public void InitLogSession();
    public void EndLogSession();
    public void AddLogger(Logger chainedLogger);
    public void RemoveLogger(Logger chainedLogger);
}

public class FileLogger : Logger
{
      //implement methods
}

public class TextBoxLogger : Logger
{
      //implement methods
}

public class DBLogger : Logger
{
      //implement methods
}

编辑 1:

更准确地说,可能有 4 个记录器:2 个文件记录器和 2 个文本框记录器。假设一条特定消息适用于 1 个文本框记录器和 1 个文件记录器;我的设计应该如何处理?

编辑 2: 请不要建议现有的日志框架。我只想自己写!

编辑 3: 好的。我有一个设计。请提供您的反馈,并可能填补空白。

修改后的界面:

public interface Logger
{
    public void Log(string msg);
    public void Log(string msgType, string msg);
    public void Log(int loggerIds, string msg);
    public void Log(int loggerIds, string msgType, string msg);
    public void InitLogSession();
    public void EndLogSession();
    public int getLoggerId();
}

public enum LoggerType
{
    File,
    TextBox
};

public class LoggerFactory
{
    public Logger getLogger(LoggerType loggerType)
    {

    }
}

LoggerFactory 类将是实例化记录器的唯一方法。此类将为记录器的每个实例分配一个唯一的 ID。这个唯一的 id 将是 2 的幂。例如,第一个记录器将获得 id 1,第二个将获得 id 2,第三个将获得 4,第四个将获得 8,依此类推。

返回的记录器对象可以被类型转换为特定的类,调用者可以设置更多的值,如文件路径、文本框等,或者我可以在 LoggerFactory 中有多个方法:一种用于每种类型的记录器,这将接受特定参数。

所以,假设我们有 4 个记录器,ID 分别为 1、2、4、8。 必须使用以下函数记录必须由第一个和第三个记录器(即记录器 ID 1 和 4)处理的特定消息:

    public void Log(int loggerIds, string msg);

要传递给 loggerIds 的值应该是“0101”。每个记录器将检查其记录器 ID 位是否为 ON。如果是,那么它才会记录消息。

现在在函数签名中,我已经提到了 int 类型,但是执行位操作和比较的具体优化类型是什么?

在这种方法中,最大数量可能存在限制。记录器,但这对我来说很好。请提供您的反馈。

注意:目前我仍在使用 .NET 2.0。如果可能,建议 .NET 2.0 中的解决方案,否则可以,我可以升级到更高版本。

这种设计的缺点:每个需要记录的类都需要知道应用程序实例化的所有可用记录器,并相应地设置位模式。任何想法如何进行松散耦合的设计?

【问题讨论】:

  • 嗯...用来决定什么记录器记录什么消息的逻辑是什么?
  • 我认为这是他的问题。
  • 你想把它当作练习吗?因为有些框架已经做得很好(比如log4net)。这些框架使用配置文件来定义单个记录器的行为方式(它们是否以及它们将发送输出的位置)。然后,代码中的每个记录器都需要有一个唯一的名称,根据该名称确定输出(或多个输出)。
  • 我想自己做这件事。我现在不想学习第三方库。但是,唯一名称将如何帮助记录器决定是否应该记录特定消息?
  • 我想您需要尝试一下您修改后的想法。如果您正在使用单元测试,那么您应该能够模拟工厂和其他类并测试它们的工作方式

标签: c# logging


【解决方案1】:

您为什么不查看(或确实使用)现有的日志记录框架,例如 log4netNLog

它们具有日志级别的概念(例如跟踪、信息、错误等),并且能够按日志名称(通常是调用日志记录调用的完全限定类型名称)进行过滤。然后,您可以将它们映射到一个或多个“目标”。

【讨论】:

  • 不要重新装轮子。选择 NLog 或 log4net(这两个框架都相当成熟,在企业中我们使用 log4net)和 AOP:)
  • 我想自己做这件事。我现在不想学习第三方库。日志级别无济于事,因为相同日志级别的不同消息可能只需要在选定的目的地登录。
  • 如果它们共享相同的日志记录级别和记录器名称,您必须区分需要不同目标的每条消息。例如,您可以为每条消息添加一个“类别”类型。如果您想使用现有框架,您可以构建自己的记录器名称,而不是使用类型名称。例如。一个类可以使用多个 ILogger,每个实现使用不同的记录器名称。
  • 但是当一条消息是针对 4 个记录器中的 2 个时,单个类别字段将如何帮助 4 个记录器确定消息是否针对他们?能举个伪代码例子吗?
  • 您可以 (l4ndash.com/…) 但最好使用记录器名称来区分。要输出到 TextBox,您需要编写一个自定义附加程序,例如triagile.blogspot.co.uk/2010/12/…
【解决方案2】:

正如 devdigital 所写,这些框架通常通过提供指定的 Logging 方法来做到这一点,例如:Warn("...")、Fail("...")...

你也可以寻找城堡项目的logging facilityILogger接口。 (也许可以尝试谷歌 ILogger.cs 源代码)

如果您仍然坚持使用通用接口链接记录器的方法(您还必须为此实现链接机制),则必须为 Log() 方法提供一种日志记录级别。这可能只是一个整数或枚举。

像这样:

    public interface Logger
    {
        public void Log(LogLevel level, string msg);
        public void Log(LogLevel level, string msgType, string msg);
        public void InitLogSession();
        public void EndLogSession();
        public void AddLogger(Logger chainedLogger);
        public void RemoveLogger(Logger chainedLogger);
    }

使用这样的日志记录级别枚举:

public enum LogLevel
{
    Info,
    Warn,
    Debug,
    Error,
    Fail
}

然后将在责任链中选择要使用的记录器。

【讨论】:

  • 这是真的,关于日志级别。但 OP 的问题似乎与将记录器配置为具有不同目标(或 log4net 中的“附加器”,例如)更相关。
  • 记录级别或消息类型在我的设计中的参数 msgType 中捕获。但特定类型的消息也可能仅用于登录少数目的地。那么如何处理呢?例如,一些 INFO 类型的消息可能需要记录到进度文本框和审阅文本框,而另一条 INFO 类型的消息可能只需要记录在进度文本框中。
【解决方案3】:

我前段时间写了自己的记录器。老实说,它不如免费提供的那么好,我意识到我正在尝试重新发明一个已经圆的轮子!

我知道您想编写自己的代码,但查看开源解决方案并可能根据您自己的特定需求使用或修改它们可能仍然是一个想法

我现在使用 TracerX:http://www.codeproject.com/Articles/23424/TracerX-Logger-and-Viewer-for-NET 这是一个开源项目,因此可以轻松修改所需的源代码。提到的其他记录器当然也不错。

编辑

这是基于我在此处接受的问题的答案:How to pass status information to the GUI in a loosely coupled application 所以我声称这没有独创性。目前我认为您的日志消息很简单

我建议的答案是,您使用的消息类型可以处理(例如)根据运行时传递给它的某些逻辑将自身发送到不同的记录器,或者使用工厂根据运行时条件创建不同的消息类型.

所以

  1. 创建具有处理方法的抽象消息类或接口。
  2. 创建许多从抽象类或接口继承的消息类型,它们代表您要执行的不同类型的日志记录。处理方法可以确定将它们发送到哪里。
  3. 考虑使用工厂来创建运行时所需的消息类型,这样您就无需提前决定需要哪些类型
  4. 当您生成日志消息时,使用进程消息将消息路由到您希望它转到的记录器

【讨论】:

  • 这里的每个人都建议使用现有的日志框架。但是现有的日志框架能满足我提出的要求吗?假设 1 条日志消息用于 4 个记录器中的 2 个(而不是基于日志记录级别) - 现有框架可以处理这种情况吗?
  • 我可以看到你有一个特定的问题。我有一个类似的问题,我想将状态消息与应用程序日志分开发送给用户。我很可能会选择一个单独的类来处理用户数据。我可以建议您看看我在上面发布的 TracerX 文章吗?它确实使用日志记录级别来区分去哪里,但查看器的代码确实允许一系列搜索。我正在拦截日志请求并使用一个小类将它们发送到正确的位置。同样,这可能不是您想要的,因为它与代码紧密耦合。
  • 我的空间用完了。我认为我们都在说的是查看用于基本无聊管道工作的现有框架,并根据您的需要扩展它,而不是从头开始。但是我可以看到这可能行不通。
  • 我刚刚通过 TracerX。这似乎是一个很棒的产品,但只是缺少我想要的 1 功能。即在每个消息级别单独控制日志记录目标!有什么想法吗?
  • 我不知道这是否会有所帮助,但是我正在做的是将我的日志信息发送到一个小的静态包装类。然后这个类将信息发送到不同的记录器。一个是 TracerX,另一个是我的用户状态报告,它向 GUI 报告。它会更改日志级别并根据消息的内容过滤消息。我有一个包含日志信息的小类 - 日志级别、消息等。另一个可能的选择是打开 TracerX 代码并添加您的要求。我改变了它以供我使用。
【解决方案4】:

这似乎是一个使用扩展方法的好地方。

创建您的基类,然后为其创建扩展方法

BaseLogger(LogMessage).toTextBoxLog().toFileLog().toDatabaseLog().

这样,您总是调用BaseLogger,然后只在需要的地方调用扩展方法

【讨论】:

    【解决方案5】:

    “请不要推荐现有的日志框架。我只想自己写!”

    接受的答案:不要重新发明轮子!使用这个现有的日志框架!

    捂脸

    最好的答案,也就是我的答案,是这样的。 如果您想要即插即用功能,请使用接口。您可以轻松配置它。这是高级别的运行。

    1. 使用配置文件指明您想要的记录器类型 它实现了你的日志接口

    2. 使用反射来实例化您从中提取的类型 您的配置文件在运行时。

    3. 传入您刚刚通过类中的构造函数注入创建的记录器接口。

    您不会通过界面设计来重新发明轮子。 如果您使您的界面足够通用,那么它是非特定的实现(理想情况下)。 这意味着如果 log4net 出现问题或不再受支持,您不必删除并修改所有呼叫代码。这就像将一盏灯直接连接到你的房子里以打开它。看在上帝的份上,请不要那样做。 接口定义了组件交互的契约,而不是实现。

    我唯一能想到的就是,看看现有的日志框架,找到共同的元素,把你的界面写成共同特征的交集。显然,您会错过一些功能。这取决于你想要多大的灵活性。您可以使用 Log4net 或 Microsoft Event Viewer 记录器,或两者都使用!没有重新实施实施细节。与将代码中的所有内容都绑定到一个技术/框架相比,它是一个耦合度更低的系统。

    【讨论】:

      【解决方案6】:

      .NET 现在提供 ILogger 接口,可通过依赖注入与各种 .NET 或 3rd 方日志记录工具一起使用。

      使用此功能,您可以将代码中的日志记录功能与架构中的具体实现分开,并且以后可以在不对业务代码进行重大修改的情况下更换记录器。

      https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger?view=dotnet-plat-ext-6.0

      https://docs.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line

      【讨论】:

        猜你喜欢
        • 2013-01-17
        • 1970-01-01
        • 1970-01-01
        • 2011-12-15
        • 1970-01-01
        • 1970-01-01
        • 2016-08-21
        • 1970-01-01
        • 2012-10-06
        相关资源
        最近更新 更多