Autofac和NLog的日志记录模块如何工作?

时间:2019-03-01 23:25:12

标签: autofac nlog autofac-module

我对Autofac和Nlog还是很陌生,我需要一些帮助来了解我的Nlog Autofac LoggingModule中发生的情况。由于遵循了injecting-nlog-with-autofacs-registergeneric,它可以正常工作。但是,我不只是复制粘贴,而是要确保我了解每种方法( Load AttachToComponentRegistration < / strong>)。如果您可以回顾我的想法并进一步澄清我的任何不正确之处(我敢肯定,我很确定),我将不胜感激。预先谢谢你!

  • 使用Nlog的数据库目标
  • 使用Autofac进行依赖注入
  • 用于学习的ASP.NET MVC Web应用程序
  • DVD库应用程序(DvdAdd,DvdEdit,DvdDelete,DvdList)

LoggingModule

public class LoggingModule : Module
{

    protected override void Load(ContainerBuilder builder)
    {
        builder
            .Register((c, p) => new LogService(p.TypedAs<Type>()))
            .AsImplementedInterfaces();
    }

    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
    {
        registration.Preparing +=
            (sender, args) =>
            {
                var forType = args.Component.Activator.LimitType;

                var logParameter = new ResolvedParameter(
                    (p, c) => p.ParameterType == typeof(ILog),
                    (p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));

                args.Parameters = args.Parameters.Union(new[] { logParameter });
            };
    }

}

我对Load()中的代码的理解

c -提供给表达式的参数 c 是组件所在的组件上下文(一个IComponentContext对象)。创建。可以访问服务或解决组件依赖关系的上下文。

p -具有传入参数集的IEnumerable

AsImplementedInterfaces -Autofac允许其用户显式或隐式注册类型。 “ As ”用于显式注册,而“ AsImplementedInterfaces ”和“ AsSelf ”则用于隐式注册。换句话说,容器会根据其实现的所有接口自动注册实现。

思路::Load方法代码注册了一个新的LogService类(代表“ c ”),该类具有记录器的类型(代表“ p “)作为LogService类的构造函数参数

问题:

  • 我上面的想法正确吗?
  • 应该是SingleInstance还是仅在调用类作用域内才能存在? (我正在考虑我的工作单位)

我对AttachToComponentRegistration()中的代码的理解

AttachToComponentRegistration 方法-重写以将特定于模块的功能附加到组件注册。

AttachToComponentRegistration 参数:

  • IComponentRegistry componentRegistry -根据组件提供的服务提供组件注册。
  • IComponentRegistration 注册 -描述容器中的逻辑组件。

注册。准备 -在需要新实例时触发。通过在提供的事件参数中设置Instance属性,可以提供实例以跳过常规激活器。


var forType = args.Component.Activator.LimitType;

args = Autofac.Core.PreparingEventArgs -在激活过程之前触发以允许更改参数或要提供的替代实例。

组件 = PreparingEventArgs.Component属性 -获取提供激活实例的组件

激活器 = IComponentRegistration.Activator属性-获取用于创建实例的激活器。

LimitType = IInstanceActivator.LimitType属性-获取已知可以实例化为组件实例的最具体类型。

关于forType

思路-据我了解,此变量保存了调用日志服务所在的调用类的NameFullName

forType Debugger Image

问题:

  • 我的想法forType是否正确?

var logParameter = new ResolvedParameter(
                    (p, c) => p.ParameterType == typeof(ILog),
                    (p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));

ResolvedParameter -可以用作提供从容器动态检索的值的方法, 例如通过名称解析服务。

logParameter上的

想法-这就是我开始迷路的地方。如此,它会检查Parameter是否为ILog类型,如果是,它将使用构造函数参数将其解析并传递forType变量?

问题:

  • 我对以上logParameter的想法正确吗?

args.Parameters = args.Parameters.Union(new[] { logParameter });

args.Parameters = PreparingEventArgs.Parameters 属性-获取或设置提供给激活器的参数。

args.Parameters.Union =使用默认的相等比较器生成两个序列的集合并集。返回一个 System.Collections.Generic.IEnumerable`1 ,其中包含两个输入序列中的元素,但不包括重复项。

关于args.Parameters的想法-我现在真的不知道,只能猜测它会返回一组参数并删除重复项?

问题:

  • 您能帮我谈谈args.Parameters中发生的情况吗?

logParameter Debugger Image Nlog Database Table Image


LogService类

public class LogService : ILog
{
    private readonly ILogger _log;

    public LogService(Type type)
    {
        _log = LogManager.GetLogger(type.FullName);
    }

    public void Debug(string message, params object[] args)
    {
        Log(LogLevel.Debug, message, args);
    }

    public void Info(string message, params object[] args)
    {
        Log(LogLevel.Info, message, args);
    }

    public void Warn(string message, params object[] args)
    {
        Log(LogLevel.Warn, message, args);
    }

    public void Error(string message, params object[] args)
    {
        Log(LogLevel.Error, message, args);
    }

    public void Error(Exception ex)
    {
        Log(LogLevel.Error, null, null, ex);
    }

    public void Error(Exception ex, string message, params object[] args)
    {
        Log(LogLevel.Error, message, args, ex);
    }

    public void Fatal(Exception ex, string message, params object[] args)
    {
        Log(LogLevel.Fatal, message, args, ex);
    }

    private void Log(LogLevel level, string message, object[] args)
    {
        _log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args));
    }

    private void Log(LogLevel level, string message, object[] args, Exception ex)
    {
        _log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args, ex));
    }

}

ILog界面

public interface ILog
{
    void Debug(string message, params object[] args);

    void Info(string message, params object[] args);

    void Warn(string message, params object[] args);


    void Error(string message, params object[] args);
    void Error(Exception ex);

    void Error(Exception ex, string message, params object[] args);

    void Fatal(Exception ex, string message, params object[] args);
}

1 个答案:

答案 0 :(得分:3)

这里有很多 要解压。您并不是真正要针对特定​​问题寻求答案,而不仅仅是代码演练和对现有解决方案的解释,因此,如果您需要的内容远远超出我的打算,我建议您将其发布到StackExchange Code Review给你在这里并不是想变得无益,而是,例如,如果您的问题是“我的想法正确吗?”答案是“排序”,每个点都有很多讨论,以解释为什么“排序”是答案(视情况而定,“否”或“是”)。它可能会变成一个冗长的答案,接着是需要澄清的其他问题,这些问题还需要其他答案...而且StackOverflow并不是一个真正具有此类功能的讨论论坛。

[即,我可能要花一个小时在这里写下答案...但是我不能保证我会回来继续跟进任何事情,因为还有其他问题要回答,我还有其他事情需要分配时间。 StackOverflow实际上更多地是关于“我如何...?”或其他具有单个合理答案的事物。]

首先,我建议您在一些断点处使用调试器进行调试,以实际了解正在发生的情况。例如,您问一个区域LimitType中的内容-您只需在该行上插入一个断点并查看其值,就可以很容易地回答该问题。这将是您自己进行进一步澄清的好方法-获胜的断点。

第二,我建议花些时间with the Autofac docs那里有很多可以回答问题的文档。

鉴于文档可以解决一些可能不清楚的问题,而不是尝试解决每个“我的想法是正确的”项目,而是让我对模块进行大量注释,并希望能够澄清这些问题

// General module documentation is here:
// https://autofac.readthedocs.io/en/latest/configuration/modules.html
public class LoggingModule : Module
{
  // Load basically registers types with the container just like
  // if you were doing it yourself on the ContainerBuilder. It's
  // just a nice way of packaging up a set of registrations so
  // they're not all in your program's "Main" method or whatever.
  protected override void Load(ContainerBuilder builder)
  {
    // This is a lambda registration. Docs here:
    // https://autofac.readthedocs.io/en/latest/register/registration.html#lambda-expression-components
    // This one uses both the component context (c) and the incoming
    // set of parameters (p). In this lambda, the parameters are NOT the set of constructor
    // parameters that Autofac has resolved - they're ONLY things that
    // were MANUALLY specified. In this case, it's assuming a TypedParameter
    // with a System.Type value is being provided manually. It's not going
    // to try resolving that value from the container. This is going hand-in-hand
    // with the logParameter you see in AttachToComponentRegistration.
    // Parameter docs are here:
    // https://autofac.readthedocs.io/en/latest/resolve/parameters.html
    // In general if you resolve something that has both manually specified parameters
    // and things that can be resolved by Autofac, the manually specified parameters
    // will take precedence. However, in this lambda it's very specifically looking
    // for a manually specified parameter.
    // You'll want to keep this as a default InstancePerDependency because you probably
    // want this to live as long as the thing using it and no longer. Likely
    // NLog already has object pooling and caching built in so this isn't as
    // expensive as you think, but I'm no NLog expert. log4net does handle
    // that for you.
    builder
      .Register((c, p) => new LogService(p.TypedAs<Type>()))
      .AsImplementedInterfaces();
  }

  // This method attaches a behavior (in this case, an event handler) to every
  // component registered in the container. Think of it as a way to run a sort
  // of "global foreach" over everything registered.
  protected override void AttachToComponentRegistration(
    IComponentRegistry componentRegistry,
    IComponentRegistration registration)
  {
    // The Preparing event is called any time a new instance is needed. There
    // are docs for the lifetime events but Preparing isn't on there. Here are the
    // docs and the issue I filed on your behalf to get Preparing documented.
    // https://autofac.readthedocs.io/en/latest/lifetime/events.html
    // https://github.com/autofac/Documentation/issues/69
    // You can see the Preparing event here:
    // https://github.com/autofac/Autofac/blob/6dde84e5b0a3f82136a0567a84da498b04e1fa2d/src/Autofac/Core/IComponentRegistration.cs#L83
    // and the event args here:
    // https://github.com/autofac/Autofac/blob/6dde84e5b0/src/Autofac/Core/PreparingEventArgs.cs
    registration.Preparing +=
      (sender, args) =>
        {
          // The Component is the thing being resolved - the thing that
          // needs a LogService injected. The Component.Activator is the
          // thing that is actually going to execute to "new up" an instance
          // of the Component. The Component.Activator.LimitType is the actual
          // System.Type of the thing being resolved.
          var forType = args.Component.Activator.LimitType;

          // The docs above explain ResolvedParameter - basically a manually
          // passed in parameter that can execute some logic to determine if
          // it satisfies a constructor or property dependency. The point of
          // this particular parameter is to provide an ILog to anything being
          // resolved that happens to have an ILog constructor parameter.
          var logParameter = new ResolvedParameter(

            // p is the System.Reflection.ParameterInfo that describes the
            // constructor parameter that needs injecting. c is the IComponentContext
            // in which the resolution is being done (not used here). If this
            // method evaluates to true then this parameter will be used; if not,
            // it will refuse to provide a value. In this case, if the parameter
            // being injected is an ILog, this ResolvedParameter will tell Autofac
            // it can provide a value.
            (p, c) => p.ParameterType == typeof(ILog),

            // p and c are the same here, but this time they're used to actually
            // generate the value of the parameter - the ILog instance that should
            // be injected. Again, this will only run if the above predicate evaluates
            // to true. This creates an ILog by manually resolving from the same
            // component context (the same lifetime scope) as the thing that
            // needs the ILog. Remember earlier that call to p.AsTyped<Type>()
            // to get a parameter? The TypedParameter thing here is how that
            // value gets poked in up there. This Resolve call will effectively
            // end up calling the lambda registration.
            (p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));

          // The thing being resolved (the component that consumes ILog) now
          // needs to be told to make use of the log parameter, so add it into
          // the list of parameters that can be used when resolving that thing.
          // If there's an ILog, Autofac will use this specified parameter to
          // fulfill the requirement.
          args.Parameters = args.Parameters.Union(new[] { logParameter });
        };
    }
}

log4net module example中存在的某些缺陷是可以为记录器进行属性注入。但是,我不会在这里解决这个问题。您可以查看示例right in the documentation,并在需要此功能时将此作为练习。

我希望能有所帮助。我可能不会再跟进其他问题,因此,如果这还不够的话,我非常非常建议您设置一些断点,也许要设置一些微小的最小重复单元测试之类的东西,并且做一些更深入的探索以获得清晰感。老实说,让别人来解释它是一回事,而实际上看到并深入研究各个项目的源头则是另一回事。即使后一种方法可能不那么快,您也会对后一种方法有更全面的了解。