Log to Twitter
Based on this post about a log4net Twitter Appender, I thought I would try my hand at writing a NLog Twitter Target (using NLog 1.0 refresh, not 2.0). Alas, so far I have not been able to get a Tweet to actually post successfully. I don't know if it is something wrong in my code, Twitter, our company's internet connection/firewall, or what. I am posting the code here in case someone is interested in trying it out. Note that there are three different "Post" methods. The first one that I tried is PostMessageToTwitter. PostMessageToTwitter is essentially the same as PostLoggingEvent in the orignal post. If I use that I get a 401 exception. PostMessageBasic gets the same exception. PostMessage runs with no errors, but the message still does not make it up to Twitter. PostMessage and PostMessageBasic are based on examples that I found here on SO.
FYI - I just now found a comment by @Jason Diller to an answer in this post that says that twitter is going to turn off basic authentication "next month". This was back in May 2010 and it is now December 2010, so I guess that could be why this is not working.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Web;
using System.IO;
using NLog;
using NLog.Targets;
using NLog.Config;
namespace NLogExtensions
{
[Target("TwitterTarget")]
public class TwitterTarget : TargetWithLayout
{
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}";
[RequiredParameter]
public string TwitterUserName { get; set; }
[RequiredParameter]
public string TwitterPassword { get; set; }
protected override void Write(LogEventInfo logEvent)
{
if (string.IsNullOrWhiteSpace(TwitterUserName) || string.IsNullOrWhiteSpace(TwitterPassword)) return;
string msg = this.CompiledLayout.GetFormattedMessage(logEvent);
if (string.IsNullOrWhiteSpace(msg)) return;
try
{
//PostMessageToTwitter(msg);
PostMessageBasic(msg);
}
catch (Exception ex)
{
//Should probably do something here ...
}
}
private void PostMessageBasic(string msg)
{
// Create a webclient with the twitter account credentials, which will be used to set the HTTP header for basic authentication
WebClient client = new WebClient { Credentials = new NetworkCredential { UserName = TwitterUserName, Password = TwitterPassword } };
// Don't wait to receive a 100 Continue HTTP response from the server before sending out the message body
ServicePointManager.Expect100Continue = false;
// Construct the message body
byte[] messageBody = Encoding.ASCII.GetBytes("status=" + msg);
// Send the HTTP headers and message body (a.k.a. Post the data)
client.UploadData(@"http://twitter.com/statuses/update.xml", messageBody);
}
private void PostMessage(string msg)
{
string user = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(TwitterUserName + ":" + TwitterPassword));
byte [] bytes = System.Text.Encoding.UTF8.GetBytes("status=" + msg.ToTweet());
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://twitter.com/statuses/update.xml");
request.Method = "POST";
request.ServicePoint.Expect100Continue = false;
request.Headers.Add("Authorization", "Basic " + user);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
Stream reqStream = request.GetRequestStream();
reqStream.Write(bytes, 0, bytes.Length);
reqStream.Close();
}
private void PostMessageToTwitter(string msg)
{
var updateRequest = HttpWebRequest.Create(string.Format(TWITTER_UPDATE_URL_FORMAT,
HttpUtility.UrlEncode(msg.ToTweet()))) as HttpWebRequest;
updateRequest.ContentLength = 0;
updateRequest.ContentType = REQUEST_CONTENT_TYPE;
updateRequest.Credentials = new NetworkCredential(TwitterUserName, TwitterPassword);
updateRequest.Method = REQUEST_METHOD;
updateRequest.ServicePoint.Expect100Continue = false;
var updateResponse = updateRequest.GetResponse() as HttpWebResponse;
if (updateResponse.StatusCode != HttpStatusCode.OK && updateResponse.StatusCode != HttpStatusCode.Continue)
{
throw new Exception(string.Format("An error occurred while invoking the Twitter REST API [Response Code: {0}]", updateResponse.StatusCode));
}
}
}
public static class Extensions
{
public static string ToTweet(this string s)
{
if (string.IsNullOrEmpty(s) || s.Length < 140)
{
return s;
}
return s.Substring(0, 137) + "...";
}
}
}
Configure it like this:
Tell NLog the assembly containing the target:
<extensions>
<add assembly="NLogExtensions"/>
</extensions>
Configure the target:
<targets>
<target name="twitter" type="TwitterTarget" TwitterUserName="yourtwittername" TwitterPassword="yourtwitterpassword" layout="${longdate} ${logger} ${level} ${message}" />
</targets>
If someone tries this out and has success, post back with your findings.