質問

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?

役に立ちましたか?

解決 2

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

    ...
}

他のヒント

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

            //...

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top