Log4Net + Twitter = Awesome

Unfortunately, application logging tends to be one of those things that doesn’t become a priority until something goes horribly wrong. Once you decide to implement logging, the next question becomes where you want to log your events to. You might decide that you should log events to the database. You might decide that you should notify a select set of users by e-mail whenever there is an application error. While both of these are great options, there is no silver bullet. Logging events to a database is a great idea since it basically guarantees persistence, but how often will it really be checked? Ideally, this option should be used in conjunction with another mechanism that provides more immediate feedback when something important has happened. While the most common choice is to automatically send out e-mail notifications, this can often become annoying very quickly to those that are receiving the e-mails.

Twitter is a wildly popular social networking service that allows users to share what they’re up to, in 140 characters or less, by posting “tweets”.  I have been using Twitter for years and find it to be not only addictive, but a great way to keep updated on what those in my social circle are working on in an unobtrusive fashion.  I wondered if it would be possible to leverage this platform to allow my applications to share their status in an equally unobtrusive way.  Fortunately, Twitter exposes almost all of it’s functionality through an easy-to-use REST-ful API.

Originally, I thought that I would roll my own logging framework to support this new model, however, I eventually decided agasint reinventing the wheel.  I have been using Log4Net, a .NET port of the popular Java Log4J platform, for years.  Log4Net is not only incredibly flexible, allowing you to customize nearly every facet of your logging strategy via XML configuration, but it is also very extensible.  The Log4Net platform uses “appenders” to post logging events to a variety of destinations, including databases, files and even e-mail recipients.  Fortunately, Log4Net makes it simple to create your own custom appenders.

Log4Net recommends that when building a custom appender, you inherit from the abstract AppenderSkeleton class, located within the log4net.Appender namespace.  The AppenderSkeleton class exposes some useful functionality via protected methods and requires that your custom appender override the abstract Append method.  This method is the primary entry point for the Log4Net platform and is where you should implement your custom logic for handling a Log4Net event, represented internally by the LoggingEvent class.

I’ve posted the code for my TwitterAppender class below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;

using log4net.Appender;
using log4net.Core;

namespace TwitterAppender
{
public class TwitterAppender : AppenderSkeleton
{
#region Private Members

private const string REQUEST_CONTENT_TYPE = "application/x-www-form-urlencoded";
private const string REQUEST_METHOD = "POST";

// The source attribute has been removed from the Twitter API,
// unless you're using OAuth.
// Even if you are using OAuth, there's still an approval process.
// Not worth it; "API" will work for now!
// private const string TWITTER_SOURCE_NAME = "Log4Net";

private const string TWITTER_UPDATE_URL_FORMAT = "http://twitter.com/statuses/update.xml?status={0}";

private string _twitterUserName;
private string _twitterPassword;

/// <summary>
/// This is where we're posting the actual event as a tweet.
/// I was originally thinking of using REST-ful WCF, but, to keep things simple
/// and to reduce dependencies on other technologies, I decided to keep it simple.
/// </summary>
/// <param name="eventToPost_">The event to post.</param>
private void PostLoggingEvent(LoggingEvent eventToPost_)
{
// The base RenderLoggingEvent method converts the event to a string
// based on the layout specified in the configuration file.
// It's important to encode the tweet so that the request URL remains valid.

var updateRequest = HttpWebRequest.Create(String.Format(TWITTER_UPDATE_URL_FORMAT,
HttpUtility.UrlEncode(RenderLoggingEvent(eventToPost_).ToTweet()))) as HttpWebRequest;

updateRequest.ContentLength = 0;
updateRequest.ContentType = REQUEST_CONTENT_TYPE;
updateRequest.Credentials = new NetworkCredential(TwitterUserName, TwitterPassword);
updateRequest.Method = REQUEST_METHOD;

// This is in idiosyncracy with the Twitter API.
updateRequest.ServicePoint.Expect100Continue = true;

var updateResponse = updateRequest.GetResponse() as HttpWebResponse;

if (updateResponse.StatusCode != HttpStatusCode.OK && updateResponse.StatusCode != HttpStatusCode.Continue)
throw new ApplicationException(String.Format("An error occured while invoking the Twitter REST API [Response Code: {0}]; unable to proceed.", updateResponse.StatusCode));
}

#endregion

#region Public Members

/// <summary>
/// This is the password for the Twitter account that is to be used.
/// Fortunately, the Log4Net framework automatically populates
/// this property for us based on the configuration.
/// </summary>
public string TwitterPassword
{
get
{
// We need a password.
if (String.IsNullOrEmpty(_twitterPassword))
throw new ApplicationException("Twitter account password not specified; unable to proceed.");

return _twitterPassword;
}
set
{
_twitterPassword = value;
}
}

/// <summary>
/// This is the user name for the Twitter account that is to be used.
/// Fortunately, the Log4Net framework automatically populates
/// this property for us based on the configuration.
/// </summary>
public string TwitterUserName
{
get
{
// We need a user name.
if (String.IsNullOrEmpty(_twitterUserName))
throw new ApplicationException("Twitter account user name not specified; unable to proceed.");

return _twitterUserName;
}
set
{
_twitterUserName = value;
}
}

#endregion

#region Protected Members

/// <summary>
/// This is the main entry point for the Log4Net framework.
/// </summary>
/// <param name="loggingEvent_">The event to append.</param>
protected override void Append(LoggingEvent loggingEvent_)
{
try
{
if (loggingEvent_ == null)
throw new ArgumentNullException("loggingEvent_");

PostLoggingEvent(loggingEvent_);
}
catch (Exception ex_)
{
// Since we're already inside the event logging framework,
// let Log4Net handle this one.
// We don't want to get stuck in a loop!
ErrorHandler.Error("An error occured while posting the provided event; unable to proceed.", ex_);
}
}

#endregion
}
}

Line 44 of the above code uses the custom ToTweet extension method that chops the output of the RenderLoggingEvent to 140 characters (the maximum allowable length for a tweet) and adds an ellipsis if necessary. That extension method is provided below.

private const int MAXIMUM_TWEET_LENGTH = 140;

public static string ToTweet(this string fromString_)
{
if (fromString_ == null || fromString_.Length < MAXIMUM_TWEET_LENGTH)
return fromString_;

return (fromString_.Substring(0, MAXIMUM_TWEET_LENGTH - 3) + "...");
}

Configuring the appender is also pretty straight-forward. I won’t go into all of the details of Log4Net configuration, but, if you do need more information, take a look at the configuration documentation. My configuration is posted below.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>

<log4net>
<appender name="TwitterAppender" type="TwitterAppender.TwitterAppender, TwitterAppender">
<!-- This property will be automatically populated by the Log4Net framework. -->
<twitterUserName value="yourTwitterUserName" />
<!-- This property will be automatically populated by the Log4Net framework. -->
<twitterPassword value="yourTwitterPassword" />
<!--
The layout pattern specified here is used by the RenderLoggingEvent method of
the base AppenderSkeleton class.  This element is optional.
-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="#%level - %message" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="TwitterAppender" />
</root>
</log4net>

</configuration>

That’s pretty much all there is to it. This class has made my life so much simpler. Instead of checking the application log thirty times a day or having to set up special filtering rules in Outlook (I already don’t care for the Outlook client as it is), I can keep an eye on basically all of my applications in TweetDeck. The cool thing is that since Twitter is so ubiquitous, you can receive notifications on practically any desktop or mobile platform, including the iPhone. The solution itself is only limited in scale by the underlying Twitter service.

I, personally, set up an account for each of the applications that I’m monitoring. I would also highly recommend that you protect your tweets, so that the internal workings of your applications aren’t visible to everyone.

I realize that some may consider it a security risk to expose your application events to a public service like Twitter. It’s a good idea to keep these updates short and to the point in order to not expose too much information. I usually combine this method with a more detailed and secure database or file-based solution, using Twitter as a basic notification platform. Log4Net makes it simple to configure this type of model.

Something I just realized a few days ago is that by channeling your events through a Twitter account, you automatically get an RSS feed as well. Each Twitter account exposes status updates through an RSS feed. I don’t personally use this functionality, but there are certainly situations where it could be handy.

I mentioned before that I use TweetDeck. You can set up “search columns” within TweetDeck (which is now available on the iPhone), to organize your applications events by severity, source, type or pretty much anything else you want. I find this to be tremendously helpful. I’m not sure which other Twitter clients support this functionality, but I’m sure there are a handful out there.

For more information on using Log4Net, I recommend that you check out this excellent tutorial on beefycode.com. The official Log4Net documentation from Apache can get a little dry, so I recommend that you start here if you want to get up and running quickly.

I look forward to hearing your feedback.

UPDATE: TwitterAppender is now available on CodePlex at http://twitterappender.codeplex.com.  Enjoy!


About this entry