Friday, November 5, 2010

Logverse Available for Purchase!

Because some people have expressed interest in purchasing the source code for Logverse rather than paying a monthly subscription fee, I've decided to add that option.
If you're interested, contact me through the Logverse site for more details.

Friday, September 3, 2010

Tweeting Logger (Twitter Logger Updated -- Using OAuth)

Twitter now requires OAuth in order to use their API. I thought it might be nice to provide a new Logger that tweets using Twitter's OAuth.

Unfortunately, compared to the original TwitterLogger, the code has ballooned quite a bit. However, if you want to use one of the available Twitter API libraries, then most of the code here would go away and you'd be back to a tiny Logger subclass that Tweets.

But if you want to avoid adding yet another DLL to your application and you have limited needs to integrate with Twitter--such as just tweeting, then this should get you started.

Check out the Twitter help for obtaining a Consumer Key, a Consumer Secret, an Access Token, and an Access Token Secret.

Notice that there is only one method that is overridden. The rest of the code is there to support OAuth. Remember, this is intended to be neither a complete OAuth implementation, nor a complete Twitter API integration. It's just a simple TweetingLogger. And frankly, it could be improved quite a bit. (e.g. Separating out the tweeting specifics into another class, parameterizing the constants, etc.) But it's good enough to get you started.


public class TweetingLogger : Logger
    {
        const string TweetUrl = "http://api.twitter.com/1/statuses/update.json";

        const string ConsumerKey = "[Your Consumer Key]";
        const string ConsumerSecret = "[Your Consumer Secret]";
        const string AccessToken = "[Your Access Token]";
        const string AccessTokenSecret = "[Your Access Token Secret]";

        protected string GetOAuthUrlEncode(string aValue)
        {
            // Thanks to Stephen Denton for this url encode

            var newValue = System.Web.HttpUtility.UrlEncode(aValue).Replace("+", "%20");

            // UrlEncode escapes with lowercase characters (e.g. %2f) but oAuth needs %2F
            newValue = System.Text.RegularExpressions.Regex.Replace(newValue, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper());

            // these characters are not escaped by UrlEncode() but needed to be escaped
            newValue = newValue.Replace("(", "%28").Replace(")", "%29").Replace("$", "%24").Replace("!", "%21").Replace("*", "%2A").Replace("'", "%27");

            // these characters are escaped by UrlEncode() but will fail if unescaped!
            newValue = newValue.Replace("%7E", "~");

            return newValue;
        }

        protected string GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        protected string GetNonce()
        {
            return Guid.NewGuid().ToString();
        }

        protected string GetOAuthBaseString(string anHttpMethod, string aBaseUri, SortedDictionary<string, string> someParameters)
        {
            var paramStrings = new List<String>();
            foreach (string key in someParameters.Keys)
                paramStrings.Add(key + "=" + someParameters[key]);
            return anHttpMethod.ToUpper() + "&" + GetOAuthUrlEncode(aBaseUri) + "&" + GetOAuthUrlEncode(String.Join("&", paramStrings.ToArray()));
        }

        protected string GetAuthSignature(SortedDictionary<string,string> someParameters)
        {
            var hmacsha1 = new System.Security.Cryptography.HMACSHA1(Encoding.ASCII.GetBytes(ConsumerSecret + "&" + AccessTokenSecret));
            var bytes = hmacsha1.ComputeHash(Encoding.ASCII.GetBytes(GetOAuthBaseString("POST", TweetUrl, someParameters)));
            return Convert.ToBase64String(bytes);
        }

        protected SortedDictionary<string, string> GetOAuthParameters()
        {
            return new SortedDictionary<string,string>()
            {
                { "oauth_nonce", GetNonce() },
                { "oauth_consumer_key", ConsumerKey },
                { "oauth_signature_method", "HMAC-SHA1" },
                { "oauth_timestamp", GetTimeStamp() },
                { "oauth_token", AccessToken },
                { "oauth_version", "1.0" },
                //{ "oauth_signature", GetAuthSignature() },    // add later in process
            };
        }

        protected string GetOAuthString(SortedDictionary<string, string> baseParameters, SortedDictionary<string, string> additionalParameters)
        {
            var sb = new StringBuilder();
            sb.Append("OAuth ");

            var paramList = new List<string>();
            foreach (string key in baseParameters.Keys)
                paramList.Add(key + "=" + "\"" + baseParameters[key] + "\"");

            sb.Append(string.Join(",", paramList.ToArray()));

            var allParameters = new SortedDictionary<string, string>(baseParameters);
            foreach (KeyValuePair<string, string> kv in additionalParameters)
                allParameters.Add(kv.Key, kv.Value);

            sb.Append(",oauth_signature=\"" + GetOAuthUrlEncode(GetAuthSignature(allParameters)) + "\"");

            return sb.ToString();
        }

        protected override bool DoLog(LogEntry aLogEntry)
        {
            // without this, you may receive a '417 Expectation Failed' error
            ServicePointManager.Expect100Continue = false;

            using (WebClient wClient = new WebClient())
            {
                var parameters = GetOAuthParameters();
                var additionalParameters = new SortedDictionary<string, string>() { { "status", GetOAuthUrlEncode(aLogEntry.Message) } };
                var theString = GetOAuthString(parameters, additionalParameters);
                wClient.Headers.Add("Authorization", theString);

                var nvc = new NameValueCollection();
                nvc["status"] = aLogEntry.Message;
                wClient.UploadValues(TweetUrl, nvc);
            }

            return true;
        }

    }

Wednesday, March 24, 2010

Logging to Logverse

If you haven't heard of Logverse, it's a service that allows applications to log to it. And just about anything can be logged to it--errors, user actions, debug information... whatever you want.

It's perfect for applications out in the field, like desktop and mobile applications. But even web or other server applications can take advantage of it.

You don't need to use a logging framework to use Logverse. But if you use The Object Guy's Logging Framework, it is extremely easy to create a logger that logs to Logverse.

I have two very small classes here. The first is just a utility class that will write to Logverse.

public class LogverseWriter
{
   private readonly Guid LogId;
   private readonly string SubscriptionKey;

   public LogverseWriter(string aLogId, string aSubscriptionKey)
   {
      LogId = new Guid(aLogId);
      SubscriptionKey = aSubscriptionKey;
   }

   public bool Write(Dictionary<string, string> values)
   {
      var logEntryInfo = new Logverse.LogEntryInfo()
         {
           LogId = LogId,
           SubscriptionKey = SubscriptionKey,
           ClientTimeStampUtc = DateTime.UtcNow
         };

      logEntryInfo.Fields = values;

      var service = new Logverse.LogServiceClient();
      var result = service.Log(logEntryInfo);
      service.Close();

      return result.Result == Logverse.LogResult.EResult.Logged;
   }
}

The second class I have is the LogverseLogger.

public class LogverseLogger : Logger
{
   private readonly LogverseWriter LogverseWriter;

   public LogverseLogger(string aLogId, string aSubscriptionKey)
   {
      LogverseWriter = new LogverseWriter(aLogId, aSubscriptionKey);
   }

   protected override bool DoLog(LogEntry aLogEntry)
   {
      return LogverseWriter.Write(
              new Dictionary<string, string>()
              {
                { "Category", aLogEntry.Category != null ? aLogEntry.Category.ToString() : "" },
                { "Severity", aLogEntry.SeverityString },
                { "Message",  aLogEntry.Message }
              });
   }

   public static void Create(string aLogId, string aSubscriptionKey)
   {
      ConfigLogger.Instance.AddLogger("logverse_" + aLogId, new LogverseLogger(aLogId, aSubscriptionKey));
   }
}

The Logverse logger can't currently be configured using the web.config (or app.config), so at the beginning of the application I call the Create method. Here's an example for a web application. This code would go into global.asax.cs.

protected void Application_Start(object sender, EventArgs e)
{
   LogverseLogger.Create("<mylogid>", "<mysubscriptionkey>");
}

WOW!

Now that was easy. And now I can log to Logverse. Pretty cool, huh?

One might ask, "What about exception handling?" Good question. Actually, it isn't required in the Logger, because that's handled automatically by the framework. (Cool!) However, if you were to use the LogverseWriter class independently, then you'd probably would want to put some exception handling in it. The only reason I didn't was so that I'd have an excuse to talk about it in this paragraph. :)

Friday, March 19, 2010

Logverse Videos



Sunday, March 14, 2010

Logverse

Logverse is coming...

Saturday, January 2, 2010

Configuration Insanity

Obviously something is wrong when a logging framework is so complicated that people feel they need a special GUI to configure it. This is what I'm talking about.

My framework doesn't require any configuration. But it supports it, if that's your style. It's intuitive enough--just like the rest of the framework--that you won't feel the need to call The Geek Squad to help you figure it out.

Watch this re-post of a 7-minute video showing how easy it is to use and configure.

Friday, January 1, 2010

Multi-threaded Logging

In light of my previous post, I thought it would be a good idea for me to demonstrate logging in a multi-threaded application using my framework.

Watch this very short video. It's just a little over 3 minutes long.