I've been working with Log4Net on and off now for several years. I've used most of the existing appenders, tweaked an appender here or there, but I'd not done any heavy lifting. That was until I started checking out the Gelf4Net appender.
Gelf4Net supports multiple transport mechanisms (currently UDP and AMQP), in the constructor the appender creates up an instance of each transport class and assigns these to two private fields, so that when it comes to appending log events, either can be used. This isn't ideal, and I believe it's been done because Log4Net doesn't configure the appender (which transport to use) until after the class is constructed. So I've been scratching my head and Googling around to work out a solution, and I've uncovered the ActivateOptions method, which appears to exist exactly for this purpose. Let's see how this works out.
Let's take a look at some code. The following is a simplified version of the code in the existing Gelf4Net appender.
public class Gelf4NetAppender : AppenderSkeleton
{
private UdpGelfTransport _udpGelfTransport;
private AmqpGelfTransport _amqpGelfTransport;
public bool UseUdpTransport { get; set; } // Log4Net populates this
public Gelf4NetAppender() : base()
{
_udpGelfTransport = new UdpGelfTransport();
_amqpGelfTransport = new AmqpGelfTransport();
}
protected override void Append(log4net.Core.LoggingEvent loggingEvent)
{
if (UseUdpTransport)
_udpGelfTransport.Send(loggingEvent);
else
_amqpGelfTransport.Send(loggingEvent);
}
}
See those two private members being assigned in the constructor? And see how the append method only ever uses one of these? It feels wasteful to construct two classes like this, which both inherit from a common base class, when we are only ever going to use one of them. Also what happens if we decide to include a third or fourth transport type?
After my little Googling session, I now suspect that when you use a custom appender with Log4Net, I believe the flow is something like:
- Log4Net at start-up constructs an instance of each appender in the configuration.
- After constructing the appender, Log4Net reads any configuration data and assigns public properties on the appender.
- When the appender has been fully configured, Log4Net in the books of the activate options method.
- The appender can override this method to perform logic that is dependent on the configuration data.
Taking the above into consideration, the code after my modification now looks like the following:
public class Gelf4NetAppender : AppenderSkeleton
{
private GelfTransport _gelfTransport;
public bool UseUdpTransport { get; set; } // Log4Net populates this
public Gelf4NetAppender() : base()
{ }
public override void ActivateOptions()
{
_transport = UseUdpTransport
? (GelfTransport)new UdpGelfTransport()
: (GelfTransport)new AmqpGelfTransport();
}
protected override void Append(log4net.Core.LoggingEvent loggingEvent)
{
_gelfTransport.Send(loggingEvent);
}
}
See how I only have one private member, and that I only construct this member in the newly overridden ActivateOptions method? Incidentally, I'm not surprised the original author of this code was unaware of this method, as I mentioned above I've struggled to find clear documentation. If anyone has any feedback to confirm or correct any of this, comments would be much appreciated.