Introduction
One of the things that is really important at Log4Net is the ability to set a custom appender in a simple and easy way, so it would meet your needs. Most of the logs are writing to database so typically it's easy to query them later, but what if you want to obtain the logs from Q/A team in your work directly to your home online over the internet? Imagine that you tell them: "run the section with the bug again" while on the screen you see the log at real time...
Background
When I wanted to write custom appender for WCF service, I thought to myself , "it's simple", and tried to find a simple example of WCF appender on the internet. But to my surprise, I found few samples and they were very complicated and not so easy, so I started to examine how to write custom appender, and I encountered this good post and realized how easy it is. And after I implemented it, I'm here to share this with you...
This post is for beginners. Intentionally, the code is as short as possible and clear. It's not the best way to do it (in the real app, you will write it with more detailed message, formatting message, and use threading for service and more...), but the purpose of this post is to show the base steps and make it as clear as possible.
Also, on purpose I avoided using automatic 'Add Service Reference' way to set proxy to service, because I want to show exactly what is going on and so you can easily change or adjust the code to understand the sample.
So, How to Write Custom Appender?(or What's Really In This Class...)
In order to write custom appender, there are two ways: the first is to implement the log4net.Appender.IAppender
interface, but the second way much more simple and in most cases you'll find it appropriate to your needs. In the second way, you need to derive your custom appender's class from the abstract class log4net.Appender.AppenderSkeleton
and implement one method called Append
. Here is my class:
public class WcfAppender : AppenderSkeleton
{
}
This class is used as Wcf appender and so it will contain the whole infrastructure to communicate with the service. Fortunately, it's only one static
field that holds the channel to the log service:
static IServiceLogger Service;
As I mentioned, this class needs to implement the 'Append
' method:
protected override void Append(LoggingEvent loggingEvent)
{
string message = string.Format("{0}, {1}", loggingEvent.Level.DisplayName,
loggingEvent.RenderedMessage);
Service.NewLog(message);
}
The loggingEvent
contains all the data collected by the loggers, here I choose the data I want and make it to one string
and send it to WCF service (in the real app, it makes more sense to send Log
class or something like that instead of compress all data to one string
).
Now we have the WCF appender class. Not simple??? But the work is not yet done. We need to tell the log4net to instantiate this class as our appender, and that is done through App.config file.
Configure the App.config
We can configure as many loggers as we want, but for this sample we set only one:
<logger name="WcfLogger">
<appender-ref ref="WcfAppender" />
</logger>
The logger is responsible to collect all the messages and pass them to appenders. Appender is responsible to write the messages to a specific location. Logger
can pass the messages to many appenders but we set only one appender for this logger. We can set as many appenders as we want, but in this sample we used only one appender:
<appender name="WcfAppender" type="WcfAppenderLog4net.WcfAppender, WcfAppenderLog4net">
<layout type="log4net.Layout.PatternLayout">
<ConversionPattern value="%m" />
</layout>
</appender>
The 'type
' tells the log4net which class is used as that appender. When logger wants to pass the messages to appender named WcfAppender
, it checks which class needs to instantiate (in this case WcfAppenderLog4net.WcfAppender
) and which assembly holds it (in this case 'WcfAppenderLog4net
') and instantiate that appender.
For some reason, when we using log4net, it didn't know where to look for its default configuration unless we tell it explicitly:
log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo ("..//..//App.config"));
That line tells the log4net where its config file exist. (If we go up two directories from the EXE file, debug -> bin -> we'll get to the directory of App.config.) (There are more ways to do so, like in AssemblyInfo
).
WCF Parts
Without 'add service reference' way, that creates proxy for us, we can simply set our setting for WCF service. In WCF we need three parts, address, binding, and contract to establish the service. When the app starts up, it creates and rises the service of WCF logger:
ServiceHost sh = new ServiceHost(typeof(ServiceLogger));
string address = "http://localhost:8080/WcfAppenderService";
BasicHttpBinding binding = new BasicHttpBinding();
Type contract = typeof(IServiceLogger);
sh.AddServiceEndpoint(contract,binding,address);
sh.Open();
The logger knows that it should pass the messages to appender named WcfAppender
(as configured at App.config) and needs to instantiate this appender class. In the constructor of this class, we initialize and get the channel to the service, so the appender can send data to the service. It's simple:
static IServiceLogger Service;
EndpointAddress address = new EndpointAddress
(new Uri("http://localhost:8080/WcfAppenderService"));
BasicHttpBinding binding = new BasicHttpBinding();
Service = ChannelFactory<IServiceLogger>.CreateChannel(binding,address);
The Sample App
In the sample app, we are trying in the loop to divide numbers and send the result to logger as information. When we try to divide by zero, we get an exception and send it as error. I hope this article will help to understand in writing WCF appender. If you find some way better, please tell me and I'll mark this correction.
History
- 23rd July, 2010: Initial post