【问题标题】:How does the logging module for Autofac and NLog work?Autofac 和 NLog 的日志记录模块如何工作?
【发布时间】:2019-03-03 06:36:26
【问题描述】:

我对 Autofac 和 Nlog 还很陌生,我需要一些帮助来了解我的 Autofac LoggingModule for Nlog 中发生了什么。由于关注injecting-nlog-with-autofacs-registergeneric,它可以按预期工作。但不仅仅是复制粘贴,我想确保我了解每种方法中发生的情况(Load & AttachToComponentRegistration)。如果您可以查看我的想法并进一步澄清我的任何错误(我很确定),我将不胜感激。提前谢谢!

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

记录模块

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 });
            };
    }

}

我对@9​​87654326@内代码的理解

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

p - 带有传入参数集的 IEnumerable

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

思考:Load方法代码注册了一个新的LogService类(代表“c”),logger的类型(代表“p em>") 作为 LogService 类的构造函数参数

问题:

  • 我上面的想法正确吗?
  • 应该是 SingleInstance 还是应该/它只在调用类范围内存在? (我正在考虑我的工作单元)

我对@9​​87654327@内代码的理解

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

AttachToComponentRegistration 参数:

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

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


var forType = args.Component.Activator.LimitType;

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

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

Activator = 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 - 可用作提供从容器中动态检索的值的一种方式, 例如通过名称解析服务。

想法 on 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


日志服务类

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);
}

【问题讨论】:

    标签: autofac nlog autofac-module


    【解决方案1】:

    这里有很多需要解压。您并没有真正要求对特定问题的答案,而是代码演练和对现有解决方案的解释,因此如果您需要的比我要提供的更多,我可能会建议您发帖到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 并将其作为练习。

    我希望这会有所帮助。我可能不会回来跟进其他问题,所以如果这还不够,我非常非常建议设置一些断点,也许设置一些微小的最小重现单元测试之类的东西,并且做一些更深入的探索以获得清晰。老实说,让别人解释它是一回事,但真正看到它在行动中并深入研究各种项目的来源是另一回事。您将通过后一种方法获得更全面的理解,即使它可能没有那么快。

    【讨论】:

    • Travis,非常感谢您花时间用如此深入的解释回答我的问题!这正是我想要的!我希望文档对他们的一些示例进行深入了解。如果您决定创建 Autofac 课程,请告诉我。我要报名了!
    • 如果有什么需要澄清的,请随时提出问题或提交 PR。文档的一大挑战是平衡“TL;DR”(太多信息)和良好的解释。总是乐于接受输入。
    猜你喜欢
    • 2019-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-29
    • 1970-01-01
    • 2023-03-11
    相关资源
    最近更新 更多