[英]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.