Log4net vs. Enterprise Library Logging Application Block

I was contemplating a logging strategy in the context of a customer project and I bumped into this slightly dated performance comparison between log4net and Microsoft Patterns & Practices – Enterprise Library 3.1 Logging Application Block (which I’ll refer to as LAB from now on). Considering the fact that our logging library of choice is LAB, I started wondering what the situation is today.

I wrote (heh) an empty trace listener for LAB and an empty appender for log4net and ran a couple of unit tests. While not nearly as dramatic as Loren Halvorson’s findings 4 years ago, the results weren’t exactly flattering from LAB’s perspective.

The unit tests wrote 100000 info level – not that it matters, after all we’re using empty appenders – log entries and timed the whole deal. Log4net’s effort took a respectable 153ms, while LAB’s time was a significantly longer 1396ms. I also tried actually writing the entries out, using FlatFileTraceListener and FileAppender on LAB and log4net respectively. The results: log4net 1450ms – LAB 4243ms. Not as dramatic, but relevant nevertheless.

Oh, and by the way, I’m not handing out a table of the dubiously conducted performance test results, because I’m mean and I want you guys to read my posts.

Going beyond performance, setting up LAB is – to put it gently – laborious. Microsoft’s documentation doesn’t offer configuration examples in their native xml form. Rather, they “encourage” you to use a configuration tool that is shipped with Enterprise Library. That sucks. I had to download and install Enterprise Library to use a tool to edit App.config, instead of just copying the DLL from a project I already had and modifying a copypasted configuration to suit my needs. I hate clicking around to produce stuff that’s essentially lines of code in my Visual Studio solution.

Then there’s the behemoth of an API that LAB sports. Constructing LogEntry objects, then asking whether it should be logged and then writing them out to the logger isn’t actually succinct. Surely you can encapsulate this behavior in your own helper methods, but I can hardly see the point in rewriting the API. Logging code should be really succinct, and by default log4net’s API provides ways to do just that.

log4net 1 – MS Logging Application Block 0

ps. Do note that Enterprise Library 3.1 is already outdated so I don’t know if these conclusions apply to Enterprise Library 4.1.

String comparisons in C#

This is my first in the line of note-to-self posts. I don’t know if there will be more. I sure hope there will, ’cause that’d make me remember all the trivia I pick up while scavenging the intar webs.

Anyhow.

I bumped into this semi-useful bit of information while browsing thru stackoverflow. How often do you normalize your strings for case-insensitive comparisons? Quite often, I presume. Well me too.

Turns out, that strings shouldn’t be normalized by calling ToLower() on both sides of the == operator. According to CLR via C#, the ToUpperInvariant() method should be used instead of the ToLowerInvariant(), as stated in chapter 11 of the book:

If you want to change the case of a string’s characters before performing an ordinal comparison, you should use String’s ToUpperInvariant or ToLowerInvariant method. When normalizing strings, it is highly recommended that you use ToUpperInvariant instead of ToLowerInvariant because Microsoft has optimized the code for performing uppercase comparisons. In fact, the FCL normalizes strings to uppercase prior to performing case-insensitive comparisons.

Whatever that means. I’d like to think that Microsoft has optimized the code for everything.

But there’s a catch. Had I been a C programmer, I would’ve probably thought of this myself, but luckily the comments on stackoverflow alerted me to a valuable realisation:

Don’t do case-insensitive string comparisons with the == operator.

The thing is, ToLower() and ToUpper() methods both have the same pitfall: due to the string type’s immutability, they create another copy of the string into memory.

Use String.Equals(String, String, StringComparison)

In addition to being resource-effective, using the static Equals method saves you the trouble of checking for null values. Like this, per favore:

string foo = "Foo";
string bar = "foo";
if (String.Equals(foo, bar, StringComparison.OrdinalIgnoreCase))
{
     // ...
}