我应该如何在我的应用程序中构建日志记录?

发布于 2024-11-24 03:36:20 字数 2083 浏览 3 评论 0 原文

所以我对此做了很多研究,但没有找到任何我说的“是的,那就是”的答案。我希望博学的 StackOverflow 人群能够帮助我。

我在几个不同的场景中遇到过这个问题。假设我有一个 C# 应用程序,并且有一些我想要记录的重要内容。

public class MyClass
{
    ... 

    public void ImportantMethod()
    {
        DoInterestingThing();

        var result = SomethingElseImportant();
        if (result == null)
        {
            logger.Log("I wasn't expecting that. No biggie.");
            return;
        }

        MoreInterestingStuff(); 
}

我感兴趣的是从哪里获取记录器

据我所知,我有几个选择。

  1. 将其注入构造函数中的 MyClass 中。
  2. 使用全局可用的服务定位器检索它。
  3. 使用方法装饰器和 AOP 为我完成日志记录。

这些似乎都不是很好的选择。 #3 看起来是不可能的,因为我是在业务逻辑中间进行日志记录,而不仅仅是对方法调用、输入参数和/或抛出的异常进行简单的跟踪。 #2,虽然简单看起来单元测试确实很困难。当然,我想对所有内容进行单元测试。 #1,虽然它可以正常工作,但使用与业务对象本身无关的日志记录对象弄乱了我的所有业务逻辑。

对于上述选项之一,还有其他想法或想法吗? 非常感谢!

编辑:澄清一下,我已经知道如何进行 DI(我使用 Unity),并且我已经知道一个好的日志框架(我使用 log4net)。只是想知道如何以最智能的方式在应用程序中使用架构意义上的日志记录。


* 编辑*

我将 Mark Seeman 的答案标记为解决方案。我检查了我的应用程序,发现我的大多数日志记录调用都在执行装饰器可以执行的相同操作。即,记录方法的入口、抛出的任何异常以及退出返回值。

在某些情况下,我仍然需要直接在方法内登录。一个例子是,我想在一个不返回任何内容但不返回 throw 的方法中快速失败异常。在这些情况下,我有一个单例,它保存对 LogProvider 的引用,该引用将依次检索命名日志实例。代码看起来与此类似:

private ILog logger = LogProviderFactory.Instance.GetLogger(typeof(Foo));

LogProviderFactory 有一个方法 SetProvider,它允许您交换单例。所以在单元测试中我可以这样做:

// LogProviderFactory.Instance now is our mock
LogProviderFactory.SetProvider(MockLogProvider);

日志记录装饰器使用与单例相同的LogProvider(它通过注入获得),因此日志记录在整个系统中是统一的。

所以实际上最终的解决方案主要是选项 #3 和混合选项 #2(其中它是服务定位器模式,但服务被“注入”到定位器中)。

AOP

就“面向方面编程”而言,我对该语言的局限性感到有点失望。希望 AOP 在未来的版本中能够被视为一等公民。

  • 我尝试了 PostSharp 但无法让它在我的机器上正确运行。此外,它是一个很大的限制,您必须在系统上安装 PostSharp 才能使用它(而不是仅仅调用解决方案附带的 dll 或类似的东西)。
  • 我使用了LinFu并且能够让它部分工作。然而,它在某些情况下爆炸了。新的 2.0 版本几乎没有记录,所以这是一个障碍。
  • 然而,Unity 的界面拦截似乎开箱即用。我很幸运,我想要记录的大多数内容都在实现接口的类中。

So I've done a lot of research on this and haven't found any answers where I said, "yes, THAT". I'm hoping the ever-erudite StackOverflow crowd can help me out.

I've run into this issue in a couple of different scenarios. Say I have a C# app, and there are important things that I want to log.

public class MyClass
{
    ... 

    public void ImportantMethod()
    {
        DoInterestingThing();

        var result = SomethingElseImportant();
        if (result == null)
        {
            logger.Log("I wasn't expecting that. No biggie.");
            return;
        }

        MoreInterestingStuff(); 
}

The thing I'm interested in is where do I get logger from.

As I see it I have a few options.

  1. Have it injected into MyClass in the constructor.
  2. Retrieve it using a globally available service locator.
  3. Use method decorators and AOP to have my logging done for me.

None of these seem like great options. #3 looks to be impossible since I'm logging right in the middle of my business logic and not just doing a simple trace of my method calls, input parameters and/or exceptions thrown. #2, though simple seems like it would be really difficult to unit test. Of course, I would want to unit test everything. #1, though it would work fine, clutters up all my business logic with logging objects that have nothing to do with the business objects themselves.

Any alternative ideas, or thoughts on one of the options above?
Much thanks!

EDIT: just to be clear, I already know how to do DI (I use Unity), and I already know a good logging framework (I use log4net). Just wondering a how to use logging in an architecture sense across an application in the smartest way.


* EDIT *

I marked Mark Seeman's answer as the solution. I went through my application and found that most of my logging calls were doing the same thing a decorator could do. Namely, log the entry to the method, any exceptions thrown, and exit return values.

Some cases I still needed to log directly inside a method. An example would be where I want to fail fast in a method which doesn't return anything but not throw an Exception. In those cases I have a singleton which holds a reference a LogProvider which will in turn retrieve a named log instance. The code looks similar to this:

private ILog logger = LogProviderFactory.Instance.GetLogger(typeof(Foo));

LogProviderFactory has a method SetProvider which allows you to swap out the singleton. So in unit testing I can do:

// LogProviderFactory.Instance now is our mock
LogProviderFactory.SetProvider(MockLogProvider);

The logging decorator uses the same LogProvider as the singleton (which it obtains through injection), so logging is unified throughout the system.

So really the end solution was mostly option #3, and a hybrid #2 (where it's the service locator pattern but the service is 'injected' into the locator).

AOP

As far as "aspect oriented programming" goes, I was a bit disappointed in the limitations of the language. Hoping AOP will be treated as a first-class citizen in future releases.

  • I tried PostSharp but couldn't get it running on my machine correctly. Additionally it was a big restriction that you had to have PostSharp installed on your system to use it (as opposed to just calling a dll which comes with the solution or something similar).
  • I used LinFu and was able to get it partially working. It blew up in a few instances however. The new 2.0 release is barely documented, so that was a hurdle.
  • Interface interception with Unity however seems to work well out of the box. I got lucky that most things I wanted to log were in classes implementing interfaces.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

噩梦成真你也成魔 2024-12-01 03:36:21

两位:

(1) - 预构建的日志框架。

有些人喜欢 Log4Net,但我是 EntLibs 的粉丝。这在实际日志记录方面完成了繁重的工作。像 EntLibs 这样的工具可以让您记录到不同类型的日志存储库(数据库、消息队列、滚动文本文件等)。它们还允许您根据类别等登录到不同的实例。它们通常是高度可配置的。

(2) - 包装日志框架的自定义类。

因此,“logger”是您编写的内容,它调用日志框架来执行实际的日志记录。

我喜欢这种方法有几个原因:

  • 当自定义包装器进入单独的程序集时,您可以将日志记录框架 (#1) 与应用程序的其余部分分离。
  • 通过编写自己的日志记录 API,您可以定义适合您需求的方法签名,并且可以对其进行扩展。
  • 如果您在团队中工作,您可以使方法签名非常易于使用,这样就没有人有理由说使用日志记录太难了。
  • 它保持日志记录的一致性。它还可以轻松地对写入文件、控制台或事件日志的“非法”代码进行代码搜索,因为日志记录中不会有任何代码(都在框架中)。
  • 通过为每一层编写特定的自定义类,您可以在幕后预先填充大量数据,从而使编写实际应用程序代码的人的工作变得更轻松。您可以设置严重性、优先级、默认事件 ID、类别等。
  • 它在应用程序复杂性和增长方面具有良好的扩展性;对于较小的应用程序来说,它可能看起来很笨重,但如果它随着时间的推移开始增长,你会有足够的空间。

这是我参与的项目中的信息日志记录类的示例。它有一堆易于调用的公共方法,以及一个调用框架的私有方法(ConcreteLogInformation)。

public static void LogInformation(string title, string message)

public static void LogInformation(string title, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId, string category)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

private static void ConcreteLogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

Two bits:

(1) - A pre-built logging framework.

Some people like Log4Net but I'm a EntLibs fan. This does the heavy lifting in terms of actual logging. Tools like EntLibs will let you log to different types of logging repositories (database, message queue, rolling text file, etc). They'll also let you log to different instances based on categories and so forth. They are usually highly configurable.

(2) - Custom class(es) that wrap the logging framework.

So "logger" is something you write, and it calls the logging framework to do the actual logging.

I like this approach for several reasons:

  • You decouple the logging framework (#1) from the rest of your application as the custom wrappers go into a separate assembly.
  • By writing your own Logging API you can define method signatures that suit your needs, and you can expand on them.
  • If your working on a team, you can make the method signatures very easy to use so that no-one has grounds to say that using the logging was too hard.
  • It keeps the logging consistent. It also makes it easy to do code searches for "illegal" code that writes to files, console or event log easy as there won't be any as part of your logging (it's all in the framework).
  • By writing specific custom classes for each tier you can pre-populate a lot data behind the scenes making life easier for whoever's writing the actual application code. You can set severity, priority, default event Ids, categories and more.
  • It scales well in terms of application complexity and growth; it might seem heavy handed for smaller apps but you'd have plenty of head-room if it starts to grow on you over time.

Here's an example of a Informational logging class in a project I've worked on. It has a bunch of easy to call public methods, and one private method that calls the framework (ConcreteLogInformation).

public static void LogInformation(string title, string message)

public static void LogInformation(string title, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties)

public static void LogInformation(string title, string message, int eventId, string category)

public static void LogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)

private static void ConcreteLogInformation(string title, string message, int eventId, Dictionary<string, object> extendedProperties, string category)
复古式 2024-12-01 03:36:21

在工厂或工厂方法中使用请求上下文进行上下文绑定部分="nofollow">Ninject Contetual Binding 文档 我有一个利用容器为你的类注入适当的记录器的示例(用 Ninjectese 语言):

Bind<ILog>().ToMethod( context => LogFactory.CreateLog( context.Request.Target.Type ) );

对于跟踪类型内容,Mark 的拦截文章描述了最好的方法 方法。

我能否再问一次,您可以先深入阅读 @Mark Seemann 引用的文章,然后再不投票就将其丢弃。

In the Using the request context in a Factory or Factory Method to do contextual binding section of the Ninject Contetual Binding docs I have an example of leveraging your container to inject an appropriate logger for your class by doing (in Ninjectese):

Bind<ILog>().ToMethod( context => LogFactory.CreateLog( context.Request.Target.Type ) );

For tracing type stuff, Mark's interception article describes the best approach.

And can I ask again that you read @Mark Seemann's cited articles in depth before just discarding them without an upvote.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文