【问题标题】:Using IoC in a custom Log4Net appender在自定义 Log4Net 附加程序中使用 IoC
【发布时间】:2014-05-13 14:44:06
【问题描述】:

不幸的是,当我使用 Log4Net 搜索涉及 Unity 或 IoC 的任何内容时,我已经搜索了很长时间,得到的唯一结果是如何通过 IoC 自动填充 ILog。这不是我想要做的。

我有一个自定义 Appender,它通过 WCF 将数据传输到服务器。我想做的是传入一个工厂方法,最好是由 Unity 生成的,它为我创建 WCF 客户端类,这样我就可以在单元测试期间将真实的客户端类换成存根。问题是我在任何地方都找不到关于如何将参数传递给自定义 log4net appender 的任何解释。

这是我通常使用 Unity 实现此功能的方式。

public class WcfAppender : BufferingAppenderSkeleton
{
    public WcfAppender(Func<ClientLoggerClient> loggerClientFactory) //How do I get this Func passed in?
    {
        LoggerClientFactory = loggerClientFactory;
    }


    private readonly Func<ClientLoggerClient> LoggerClientFactory; 
    private static readonly ILog Logger = LogManager.GetLogger(typeof(WcfAppender));
    private static readonly string LoggerName = typeof(WcfAppender).FullName;

    public override void ActivateOptions()
    {
        base.ActivateOptions();
        this.Fix = FixFlags.All;
    }

    protected override bool FilterEvent(LoggingEvent loggingEvent)
    {
        if (loggingEvent.LoggerName.Equals(LoggerName))
            return false;
        else
            return base.FilterEvent(loggingEvent);
    }


    protected override void SendBuffer(LoggingEvent[] events)
    {
        try
        {
            var client = LoggerClientFactory();
            try
            {
                client.LogRecord(events.Select(CreateWrapper).ToArray());
            }
            finally
            {
                client.CloseConnection();
            }
        }
        catch (Exception ex)
        {
            Logger.Error("Error sending error log to server", ex);
        }
    }

    private ErrorMessageWrapper CreateWrapper(LoggingEvent arg)
    {
        var wrapper = new ErrorMessageWrapper();

        //(Snip)

        return wrapper;
    }
}

但是我不调用container.Resolve&lt;WcfAppender&gt;(),它是在log4net 库中调用new WcfAppender()。如何让 log4net 库改用new WcfAppender(factoryGeneratedFromUnity)

【问题讨论】:

    标签: c# inversion-of-control log4net unity-container


    【解决方案1】:

    我认为samy is right,这是我如何解决它的实现。

    ActivateOptions() 在我初始化 Unity 容器之前被调用,所以为了安全起见,我只是将工厂方法的检索放在 SendBuffer 方法中。我最终使用了LogManager.GetRepository().Properties 属性来存储工厂对象。

    public class WcfAppender : BufferingAppenderSkeleton
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(WcfAppender));
        private static readonly string LoggerName = typeof(WcfAppender).FullName;
    
        public override void ActivateOptions()
        {
            base.ActivateOptions();
            this.Fix = FixFlags.All;
        }
    
        protected override bool FilterEvent(LoggingEvent loggingEvent)
        {
            if (loggingEvent.LoggerName.Equals(LoggerName))
                return false;
            else
                return base.FilterEvent(loggingEvent);
        }
    
    
        protected override void SendBuffer(LoggingEvent[] events)
        {
            try
            {
                var clientFactory = log4net.LogManager.GetRepository().Properties["ClientLoggerFactory"] as Func<ClientLoggerClient>;
                if (clientFactory != null)
                {
                    var client = clientFactory();
                    try
                    {
                        client.LogRecord(events.Select(CreateWrapper).ToArray());
                    }
                    finally
                    {
                        client.CloseConnection();
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Error("Error sending error log to server", ex);
            }
        }
    
        private ErrorMessageWrapper CreateWrapper(LoggingEvent arg)
        {
            var wrapper = new ErrorMessageWrapper();
    
            //SNIP...
    
            return wrapper;
        }
    
    }
    

    然后我创建工厂并在我的程序启动期间存储它。

    static class Program
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(Program));
    
        static void Main(string[] args)
        {
            log4net.Util.SystemInfo.NullText = String.Empty;
    
            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    
            Logger.Debug("Service Starting");
            using (var container = new UnityContainer())
            {
                var debugMode = args.Contains("--debug", StringComparer.InvariantCultureIgnoreCase);
    
                BaseUnityConfiguration.Configure(container, debugMode);
                LogManager.GetRepository().Properties["ClientLoggerFactory"] = container.Resolve<Func<ClientLoggerClient>>();
    
                //...
    

    【讨论】:

      【解决方案2】:

      我能够在创建后通过 Unity 容器运行 appender 实例:

      ILog logger = LogManager.GetLogger("My Logger");
      IAppender[] appenders = logger.Logger.Repository.GetAppenders();
      foreach (IAppender appender in appenders)
      {
          container.BuildUp(appender.GetType(), appender);
      }
      
      container.RegisterInstance(logger);
      

      BuildUp() 方法将填充 appender 类中的注入属性:

      public class LogDatabaseSaverAppender : AppenderSkeleton
      {
          [Dependency]
          public IContextCreator ContextCreator { get; set; }
      
          ...
      }
      

      【讨论】:

        【解决方案3】:

        从我在代码中看到的,附加程序是由XmlHierarchyConfigurator 类在ParseAppender 方法中创建的:

        protected IAppender ParseAppender(XmlElement appenderElement)
        {
            string attribute = appenderElement.GetAttribute("name");
            string attribute2 = appenderElement.GetAttribute("type");
            // <snip>
            try
            {
                IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(attribute2, true, true));
                appender.Name = attribute;
                // <snip>
        

        该方法通过调用由IOptionHandler 接口实现的方法来完成加载appender(您已经在appender 中拥有它,因为您的祖先树中有AppenderSkeleton 类)

        IOptionHandler optionHandler = appender as IOptionHandler;
        if (optionHandler != null)
        {
            optionHandler.ActivateOptions();
        }
        

        log4net 不知道的任何 xml 参数都被推送到同名的属性。因此,可以完全从 xml 配置文件配置您的 appender,并通过让您的 appender 实现 IOptionHandler

        来启动您需要的任何特定行为

        您必须将Func&lt;ClientLoggerClient&gt; 传递给附加程序的唯一方法(重建自定义log4net)是使用附加程序将在ActivateOptions() 方法中调用的静态事件,该事件将由您的选择的解决方法;该事件可以包含来自附加程序的 xml 配置的一些自定义,因此您可以基于此路由解析。让事件将足够的Func&lt;ClientLoggerClient&gt; 返还给您的附加程序,您就可以开始了。

        【讨论】:

        • 我没有使用静态事件,而是使用 log4net 的ILoggerRepository.Properties 系统来存储委托。详情见我的回答。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多