Introduction
Log4net is an excellent logging framework, and is widely used in various applications. I enjoyed very much its capability to decided where to send logging info, one of them is the SmtpAppender, which will send emails base on your logging item, so you don't have to compose email seperately, which is nice to a lazy guy like me. But right now there is no way to change the email subject on the fly, and this make it painful to the email readers. I googled around to look for solutions without one to my satisfaction. So I decide to try my way. (I saw mail archives saying log4j, the origin of log4net is planning such a capability, but I did not see any info on this to log4net. )
Background
The reason behind log4net's limitation to customizing subject is, SmtpAppender is base on the idea that it is buffered appender, with possibly multiple logging items in buffer when email is about to be sent. So there is no better way of deciding how the subject should look like. But to me, I would like to see the most important info in the events as the subject. Also the buffering stragegey of SmtpAppender is not satisfying me, it will always wait for the buffer to be full before it sends out mails, which make the buffer almost useless: the reason why we want emails to be sent out is, when there is something emergent happening, we get immediate alert! But without buffering, you will receive too many emails with 1 logging item in each message, which is also not so nice.
On the other hand, the SmtpAppender is a powerful appender which has a lot of features to customize, and I don't feel good to loss them or create the wheel again. So a good way is to extend it. A good design should be open to extension, so let's see if there is a good design here:).
The Subject property is not virtual, so we have to figure out another way. Luckily the SendBuffer method is virtual, which allowes us to prepare the Subject before sending. Thus the code:
public class NoBufferSmtpAppender : SmtpAppender
{
protected override void SendBuffer(LoggingEvent[] events)
{
prepareSubject(events);
base.SendBuffer(events);
}
protected virtual void prepareSubject(ICollection<LoggingEvent> events)
{
Subject = null;
foreach (LoggingEvent evt in events)
{
if (evt.Level >= _TriggerSendingLevel)
{
string msg = evt.ExceptionObject == null ? evt.RenderedMessage : evt.ExceptionObject.Message;
Subject = string.Format("[{0}] {1}", evt.Level, msg);
break;
}
}
if (Subject == null)
Subject = string.Format("{0} logging items...(beleow [{1}] level)", events.Count, _TriggerSendingLevel);
}
}
I need email to be sent out when some message with "tiggering level" coming in, so a property with reasonable intial value is necessary:
private Level _TriggerSendingLevel = Level.Warn;
public Level TriggerSendingLevel { get { return _TriggerSendingLevel; } set { _TriggerSendingLevel = value; } }
So if there is a logging item with Level higher than the trigger level, the email will be sent out immediately.
Now is the question: how to decide the subject? I hard coded a strategy I personally found useful: if sending message contains an item with higher level than the trigger level, it's message will be used as subject, or a simple summary like "[16] logging items... (below WARN level)" is used as subject. As the extension point is opened, feel free to customize it by your self.
Using the code
I favor config log4net per xml file, so here is how I used the code in my config file:
<appender name="SmtpAppender" type="Ifx.Mit.MapService.Server.Util.log4net.NoBufferSmtpAppender">
<to href="%22mailto:value=someone@somecompany.com%22/%3E%22">value="%22mailto:value=someone@somecompany.com%22">someone@somecompany.com" />
<from value="%22mailto:someapp@somecompany.com%22">someapp@somecompany.com" />
<smtpHost value="your.host" />
<bufferSize value="10" />
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="[email]"/>
<acceptOnMatch value="true"/>
</filter>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO"/>
<levelMax value="FATAL"/>
<acceptOnMatch value="true"/>
</filter>
<filter type="log4net.Filter.DenyAllFilter"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%newline%date [%thread] %-5level %logger [%property{NDC}] - %newline ===> %message%newline%newline" />
</layout>
</appender>
enjoy!
History
20/10/2009, Initial post