5

所以我对此做了很多研究,但没有找到任何答案,我说“是的,那个”。我希望博学多才的 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(); 
}

我感兴趣的是我哪里得到logger

正如我所看到的,我有几个选择。

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

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

任何替代想法或对上述选项之一的想法?非常感谢!

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


* 编辑 *

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.
4

3 回答 3

4

Use a logging Decorator.

于 2011-07-15T00:12:03.387 回答
2

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)
于 2011-07-15T01:17:48.053 回答
1

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.

于 2011-07-15T06:34:15.630 回答