Using IoC in a custom Log4Net appender

我是研究僧i 提交于 2019-12-02 00:56:49

问题


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. This is not what I am trying to do.

I have a custom Appender that transmits data over WCF to a server. 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. The problem is I can not find any explanation anywhere on how to pass a argument in to a custom log4net appender.

Here is how I would normally implement this using 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. How do I tell the log4net library to use new WcfAppender(factoryGeneratedFromUnity) instead?


回答1:


I was able to run the appender instance through Unity container after its creation:

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:

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

    ...
}



回答2:


I think samy is right, here is the implementation of how I solved it.

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. I ended up using the LogManager.GetRepository().Properties property to store the factory object.

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

            //...



回答3:


From what i see in the code, appenders are created by the XmlHierarchyConfigurator class, in the ParseAppender method:

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

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; the event can contain some customization coming from the xml configuration for the appender, so you can route the resolution based on that. Let the event give back the adequate Func<ClientLoggerClient> to your appender and you are good to go.



来源:https://stackoverflow.com/questions/23634472/using-ioc-in-a-custom-log4net-appender

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!