简体   繁体   English

在自定义Log4Net appender中使用IoC

[英]Using IoC in a custom Log4Net appender

I have searched quite a while unfortunately when I search for anything involving Unity or IoC with Log4Net the only results I get is how to make ILog automatically populated via IoC. 不幸的是,当我使用Log4Net搜索涉及Unity或IoC的任何内容时,我已经搜索了很长一段时间,我得到的唯一结果是如何通过IoC自动填充ILog This is not what I am trying to do. 不是我想要做的。

I have a custom Appender that transmits data over WCF to a server. 我有一个自定义Appender,通过WCF将数据传输到服务器。 What I would like to do is pass in a factory method, preferably one generated by Unity, that creates the WCF client class for me so I can swap out the real client class for stubs during unit testing. 我想要做的是传递一个工厂方法,最好是由Unity生成的方法,为我创建WCF客户端类,这样我就可以在单元测试期间替换存根的真实客户端类。 The problem is I can not find any explanation anywhere on how to pass a argument in to a custom log4net appender. 问题是我找不到任何关于如何将参数传递给自定义log4net appender的解释。

Here is how I would normally implement this using Unity. 以下是我通常使用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;
    }
}

However I don't call container.Resolve<WcfAppender>() it is in the log4net library that new WcfAppender() gets called. 但是我不调用container.Resolve<WcfAppender>()它在log4net库中调用new WcfAppender() How do I tell the log4net library to use new WcfAppender(factoryGeneratedFromUnity) instead? 如何告诉log4net库使用new WcfAppender(factoryGeneratedFromUnity)呢?

I think samy is right , here is the implementation of how I solved it. 我认为samy是对的 ,这是我如何解决它的实现。

ActivateOptions() was being called before I was initializing my Unity container, so to play it safe I just put the retrieval of the factory method inside the SendBuffer method. 在我初始化Unity容器之前调用了ActivateOptions() ,所以为了安全起见我只是将工厂方法的检索放在SendBuffer方法中。 I ended up using the LogManager.GetRepository().Properties property to store the factory object. 我最终使用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;
    }

}

I then create the factory and store it during the initiation of my program. 然后我创建工厂并在程序启动期间存储它。

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

            //...

From what i see in the code, appenders are created by the XmlHierarchyConfigurator class, in the ParseAppender method: 从我在代码中看到的,appender是由ParseAppender方法中的XmlHierarchyConfigurator类创建的:

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>

The method finishes loading the appender by calling a method implemented by the IOptionHandler interface (you already have it in your appender, because you have the AppenderSkeleton class in your ancestors tree) 该方法通过调用IOptionHandler接口实现的方法完成加载appender(您已经在appender中使用它,因为您的祖先树中有AppenderSkeleton类)

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

Any xml parameter that is not known by log4net is pushed to a property with the same name. log4net未知的任何xml参数都将推送到具有相同名称的属性。 So it is possible to configure your appender entirely from the xml config file and launch any specific behavior you need by having your appender implement IOptionHandler 因此,可以完全从xml配置文件配置appender,并通过让appender实现IOptionHandler启动所需的任何特定行为

The only way (bar rebuilding a custom log4net) you have to pass a Func<ClientLoggerClient> to your appender is to use a static event that you appender will call in the ActivateOptions() method, event that would be picked up by your resolving method of choice; 您必须将Func<ClientLoggerClient>传递给appender的唯一方法(重建自定义log4net)是使用您在Apateder中调用的静态事件, ActivateOptions()方法将由您的解析方法获取的事件选择; the event can contain some customization coming from the xml configuration for the appender, so you can route the resolution based on that. 该事件可以包含来自appender的xml配置的一些自定义,因此您可以基于此来路由解析。 Let the event give back the adequate Func<ClientLoggerClient> to your appender and you are good to go. 让事件给你的appender提供足够的Func<ClientLoggerClient> ,你就可以了。

I was able to run the appender instance through Unity container after its creation: 我能够在创建后通过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() method will populate the injection property in the appender class: BuildUp()方法将填充appender类中的injection属性:

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

    ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM