December 14, 2009

Post-Rational

Due to my boss requesting an architecture review, I just find myself trying to put together all those pen&paper discussions I had with people on the team and rethinking the decisions I have made on how the project is set up and the application is structured.

While doing this, I find myself in a rather interesting situation: I realize that quite a lot of my decisions were based on intuition (which is of course likely driven by experience). Those decisions were not the result of a traditional, concious Analysis-Thesis-Synthesis approach. Much more, the process obviously happened somewhere in the back of my mind and at some point it comes back to the surface and presents me a solution that just "feels" right. Luckily most of those solutions proof useful over time and withstand all kinds of project challenges.

Nevertheless it leaves me thoughtfully. Should I break my habits and switch to e.g. famous +/- lists? How do I explain certain decisions? "I have a good feeling" is hardly an explanation my boss will eat...

I think Kevlin Hennley mentioned it in his talk "Five Considerations for Software Architects" that there is nothing wrong about making intuitive decisions. After all intuition comes a lot from experience. But "I had the idea under the shower" is likely an explanation that not all people will accept. People are different, Myers-Briggs spent a lot of time on this - and a lot of people don't take intuition as an argument. It always makes sense to rationalize your decisions afterwards, so that everyone can understand them. If your decisions proof right, it should be easy to rationalize them anyway.

December 10, 2009

Sniff HTTP traffic with ASP.NET Development Webserver

During developing webapplications or -services, every now and then you will face a strong desire to be able to see the HTTP traffic that is sent back and forth between a client and the webserver.

Tracing local HTTP traffic is not easy

You will quickly find out, that this task sounds easier than it actually is when your client and your server reside on the same machine. There are tools like Fiddler (and tons of others, but this is my favorite), but they all suffer the same problem: Requests to localhost/127.0.0.1 cannot be captured because they are optimized by the windows network stack and bypass the usual hooks used by capturing tools. Thus you either need to use your network card's IP address for submitting requests or - if you don't have a local ipaddress when e.g. you use DHCP and are disconnected from the network - you need to install the MS Loopback Adapter and use this adapter's IP address (see installing MS Loopback Adapter)

ASP.NET Development Webserver is locked down on "localhost"

If you are like me and like the ASP.NET Development Webserver (aka "Cassini" or "WebDev.Webserver") that comes with Visual Studio (and recently also with the Windows 7 SDK), you can't use it to capture traffic. Probably due to legal issues, the ASP.NET Development WebServer (aka Cassini) contains code that binds the TCP socket to the 127.0.0.1 address only and also contains a check, that the requesting client resides on localhost. In this case you have 2 choices:

a) setup a Webapplication in IIS this is possible but nasty when you want to do it in a build script

b) follow the instructions below to patch the WebDev.WebHost.dll on your system

Steps to patch WebDev.WebServer

Note: All steps below assume you have .NET 3.5 Service Pack 1 and the Windows 7 SDK installed. But with basic familarity of IL code you shouldn't have much problems applying those steps to e.g. the version of WebDev.WebServer that comes with VS 2005 (note, that this version resides under the installroot of VS!)

Step 1

Create a new directory "C:\patchcassini" that we will use to work and change to this directory on the commandline. You can create any directory you want, but I will refer to it as C:\patchcassini below.

Step 2

Copy the WebDev.WebHost.dll from the GAC to your working directory and disassemble into an IL script. The following batch script shows how this is done:

set SDKHOME=C:\Program Files\Microsoft SDKs\Windows\v7.0




cd C:\patchcassini

rem refresh copy from GAC
copy /Y C:\Windows\assembly\GAC_32\WebDev.WebHost\9.0.0.0__b03f5f7f11d50a3a\WebDev.WebHost.dll .

REM create backup
copy WebDev.WebHost.dll WebDev.WebHost.original.dll

REM disassemble
"%SDKHOME%\bin\ildasm.exe" WebDev.WebHost.original.dll /out=WebDev.WebHost.il

This will create 2 new files in your folder: WebDev.WebHost.il and WebDev.WebHost.res. Note that the script also creates a backup of the original assembly

Step 3

Patch the generated IL script. There are 2 things you have to do:

1) Open the script in any editor and find & replace all occurrences of [System]System.Net.IPAddress::Loopback with [System]System.Net.IPAddress::Any

2) Find the method body of Connection::get_IsLocal() (just search for this string), you will see something like

.method assembly hidebysig specialname
        instance bool get_IsLocal() cil managed
{
 ....
} // end of method Connection::get_IsLocal

Replace the whole method body with the code as shown below:

.method assembly hidebysig specialname
        instance bool  get_IsLocal() cil managed
{
.maxstack  2
IL_0014:  ldc.i4.1
IL_0015:  ret
} // end of method Connection::get_IsLocal
Step 4

You need to recompile the IL script and reinstall the patched dll into the GAC. Since we also modified a signed dll, we need to turn off signature validation for this dll. The batch script below shows how this is done:

set FRAMEWORKHOME=C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727
set SDKHOME=C:\Program Files\Microsoft SDKs\Windows\v7.0

REM recompile the patched IL script into WebDev.WebHost.dll
%FRAMEWORKHOME%\ilasm.exe /output=WebDev.WebHost.dll /quiet /resource=WebDev.WebHost.res /debug /dll WebDev.WebHost.il

rem Remove validation
"%SDKHOME%\bin\sn.exe" -Vr WebDev.WebHost.dll

rem Install into GAC, forcing overriding any existing assembly
"%SDKHOME%\bin\gacutil.exe" /i WebDev.WebHost.dll /f

Enjoy!

After patching and reinstalling the WebDev.WebHost.dll, hitting F5 in VS to launch your webapplication allows you to access the Webserver using any local IP address and thus allows tools like Fiddler to capture the traffic.

Hint: You can also make your life easier by registering the Webserver in the context menu of any folder. Just use the registry script below:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\webdev.webserver.9]
@="ASP.NET Webserver 2008"

[HKEY_CLASSES_ROOT\Directory\shell\webdev.webserver.9\command]
@="\"C:\\Program Files\\Common Files\\Microsoft Shared\\DevServer\\9.0\\WebDev.WebServer.EXE\"  /port:81 /vpath:\"/\" /path:\"%1\""

This allows you to launch the Webserver using a rightclick of your mouse on any arbitrary folder:

image

Hope this helps!

December 08, 2009

Have a cup of coffee with a friend

A friend recently told me a great story about priorities in your life. Not directly related to software development, I still think it might apply to one or another and I simply like this story very much. When things in your life seem almost too much to handle, when 24 hours in a day just are not enough, remember the mayonnaise jar and 2 cups of coffee.

A professor stood before his philosophy class and had some items in front of him. When the class began, wordlessly, he picked up the very large, empty mayonnaise jar and proceeded to fill it with golf balls.
When no more golf balls would fit in the jar he asked the students if the jar was full. They agreed it was full.

Then the professor picked up a box of pebbles and poured them into the jar. He shook the jar lightly. The pebbles rolled into open areas between the golf balls. He asked his students again if the jar was full. They agreed it was.

The professor next picked up a box of sand and poured it into the jar. The sand filled up everything else. He once more asked the students if the jar was full. The students responded with a unanimous "yes".

The professor then produced two cups of coffee from under the table. He poured the coffee into the jar, effectively filling the empty spaces between the sand. The students laughed.

"Now" said the professor, as the laughter subsided, "I want you to recognize that this jar represents your life. The Golf balls are the important things - God, Family, Children, Health, Friends, Passions - things that if everything else was lost and only they remained, your life would still be full. The pebbles are other things that matter - like your Job, House, Car, etc. The sand is everything else - the small stuff. If you put the sand in the jar first," he continued, "There is no room for the pebbles or the golf balls. The same is true for life. If you spend all your time and energy on the small stuff, you will never have room for the things that are important to you.

So pay attention to the things that are critical to your happiness. Play with your children. Take time to get that check-up. Take your partner out to dinner. Play another 18 holes. There will always be time to clean the house and fix the disposal. Take care of the golf balls first - the things that really matter. Set your priorities. The rest is just sand."

One of the students raised her hand and asked what the coffee represented. The professor smiled. "I am glad you asked. It just goes to show that no matter how full your life may seem, there's always room for a couple cups of coffee with friends."

October 29, 2009

Parallelism for the Masses?

I just returned from an interesting full-day seminar on parallelizing applications using Intel's tool suite with the quite ambitous title "How to write bug free and performance optimized parallel (threaded ) applications ( Turning a serial into a parallel application)". A demonstration of the capabilities of Intel Parallel Studio was quite impressive.

Clever Tools

Let Parallel Studio analyze your C/C++ code to detect performance hotspots and parallelizable code fragments. Then use OpenMP and add #pragma directives to actually "annotate" your code with parallelization hints for the compiler:

#pragma omp parallel for reduction(+: sum) 
for (i = 0; i < 1000000; i++) { 
 sum = CalcSum(i, sum); 
}

Pretty neat, isn't it? I found it quite surprising how far one can already get nowadays by just using clever tools for (semi-)automatic parallelization of applications. Still: Can you prove the above code does not cause semantic errors? Remove the "for reduction(+: sum)" from the above and you will get a random results.

Required: a radically new approach

Introducing technlogies like OpenMP (or the new .NET Parallel Programming libraries to mention a more recent one) doesn't help much to really improve the situation. It is like fixing  your bathroom with duct tape. It works - but it is not a sustainable solution. In my opinion, going parallel - and thus make your code scalable, be it on multi-core or a service in the cloud - requires more than tools. Everyone, who has already written one piece of multithreaded code, also has already had to debug a concurrency issue. It doesn't matter, how good you are, how much or little experience you have.

Actors enter the future stage

What does it take to write better scalable and parallelizable code? The problem with parallel code is "shared state". Message Passing avoids this problem. The Actor Programming Model takes this even further. Systems like Erlang have a long history in successfully applying this totally different programming paradigm (at least totally different from today's mainstream only of course). Multicore processors and cloud computing revived this paradigm by increasing the need for parallel programming support. Microsoft's Axum and attempts like the "XCoordination Application Space" try to bring this paradigm to the masses on the .NET platform, Scala will likely be the next success on the Java platform (imho ironically by replacing the Java language on the JMV). I dare to predict, that lot of our programming future will be Actor-based. And the platform that makes implementing actors most convenient has good chances to become the next mainstream.

What are your thoughts on the future of programming? How will we handle challenges like debugging or orchestration of actor communication in this new world? Keen to hear your opinions and/or experiences!

October 20, 2009

What Software Architects can learn from History - a conceptual look at SOA and EDA

This blog post was inspired by the article "SOA through the looking glass" by Udi Dahan, recently published in the MSDN Architecture Journal and a following discussion with collegues. One thing that quickly popped up was the different understandings of the terms SOA and EDA (we really should care about our own ubiquitous engineering language first ...). Here are my thoughts on Services, Events and why I think those concepts allow us to build better solutions, thus better meeting business needs, which is - after all - what we build all applications for.

How Lou Gerstner got IBM to dance

I do remember times where a big monolithic enterprise called IBM acted like a centralized mainframe in the market all over the world. Due to its organizational structure the company wasn't able to respond to new and changing market challenges within a reasonable timeframe. So why does IBM still exist? Because in the early 90s Lou Gerstner decided to restructure the whole company and split it into lots of small, autonomous profit centers ("How Lou Gerstner got IBM to dance"). Those profit centers act like small companies embedded within a large company, being responsible for their own revenue as well as finding the right partners to connect with both within as well as outside the enterprise, while being able to leverage synergy effects from being part of a large enterprise. As a matter of fact, companies like Amazon and Google are such effective market players because they are organized exactly this way - service oriented. Service Orientation is a quite natural concept describing a set of autonomous actors collaborating with each other, exchanging information by sending messages and reacting to external stimuli, also known as events. In his article, Udi Dahan correctly points out that service orientation and events are just 2 sides of the same coin.

The world doesn't block because you have a coffee

In contrast to an all-to-common misconception, I do not see SOA and EDA as technologies. In fact both describe organizational and structural concepts that apply to business organizations in the first place. Thus I see SOA and EDA as a way to translate those natural concepts onto the way we build software. Instead of relying on leaky abstractions like synchronous remote method calls (crossing process boundaries may always fail for hundreds of possible reasons) and distributed transactions (what if something goes wrong during the commit-phase??), software engineers need to take a good look at how the world really works that they are trying to project into bits and bytes. If you look at the real world, it doesn't use 2-phase-commit. Instead it uses compensation actions (see Greg Hohpes awarded article "Starbucks does not use 2-phase-commit". Also the real world seldom uses synchronous commands. Instead we send messages and react on events when they occur. You don't get blocked when you receive an email - instead it is up to you to decide when to come back and read it later. Also the sender is not blocked from her work, waiting for response from you. The real worlds acts sometimes sequentially (you receive the email before you can answer it), but seldom synchronous. You might have forgotten to pay a bill? You will receive a friendly reminder letter and if that overlaps with the fact that you already paid the bill in the meantime, it will state to "safely ignore this letter" in this case. How does the real world stay agile and responsive? Use small groups of people concentrating on a particular responsibility instead of large monolithic blocks responsible for everything, effectively achieving nothing.

Business Partners do not share database schemata

What is important about the word "autonomous"? I see the importance in the evolvability and flexibility of a system. Companies do not simply change their interfaces to partners they collaborate with. And 2 collaborating companies do seldom use a shared database, they will even use different fields for their "customer" datastructure. Here is where I see the "Bounded Contexts" first described by Eric Evans coming into play. Instead of falling into the trap of the anti-patterns "shared database" and "shared datastructure", where one single dataschema/-structure tries to please several different needs, split e.g. editing information like your personal profile on facebook from searching the whole user database for a particular name. Those are different taks, requiring datastructures and processes optimized for that particular task. Sharing a database does not work in this case not only because of the size of facebooks userdatabase, but also because changes to to either of the tasks would become impossible. Instead of achieving simplicity in the database schema, one introduces complexity in the evolvability of a system because of strong dependencies between components due to shared structures ("The 'One Truth Above All Else' Anti-Pattern").

Cloud Computing requires us to take new look

Traditional software development strategies hit the wall even faster when it comes to develop solutions to run in the cloud. As Gianpaolo Carraro describes in his blog post "Head in the Cloud, Feet on the Ground" (and in MSDN Architecture Journal #17), business demands will sooner or later likely force us to move at least parts of the systems we build into a cloud environment. How do you build a system, where service instances may come and go as needed? Where services simply move from one box to another without prior notice, controlled by the cloud operating system? Aside from other important implications, simple conventional synchronous method calls do not work anymore in such an environment. Different strategies are needed and service-orientation, asynchronously sent messages and reacting to events are part of the solution, leading to a system of collaborating services, talking to each other in conversations.

It's always about balance

Of course building a software following those principles also means, that you pull all those previously hidden complexities to the surface and into your application domain instead of leaving them up to the infrastructure layer, where they stay buried until something goes wrong and they bites you from the back. Thus one has to make a careful decision, where it is ok to use easy to write synchronous remote calls and accept the (hidden) risk of lack of scalability and blocking callers due to a broken infrastructure, eating up webserver resources, while blocking calls pile up instead of making this fact explicit in the organizational structure of an application system, partitioning functionality into several autonomous services, collaborating with each other by sending messages and reacting to events. As usual, it is a matter of compromise and finding the right balance. You just need to be aware of the risks you accept when synchronously calling a webservice method using SOAP over HTTP in favour of having to write a simple method call vs. the explicit and therefore more complex communication patterns happening between application components, but providing better flexibility and scalability due to higher decoupling.

We've already been there

Those ideas are not new. IBM's restructuring is just one of the examples taken from reality. Even within the Software Engineering industrie those ideas have been around for quite a while and most of us even already used them. Probably not everyone is writing programs in Erlang, which is quite successful in applying those ideas since the 60s. But almost everyone has written GUI applications, haven't we? Well, there is this thing called "Window Message Queue" and a technique called "reacting to user events". It is time to undust this knowledge. Although IT history is rather short compared to other industries, Architects should learn from it.

August 06, 2009

Spring for .NET 1.3.0 Release Candidate available

Finally we made it: The next release of Spring for .NET is available for preview and brings a couple of new things as well as over 100 fixed bugs to the table. Grab the new release as usual from our website http://www.springframework.net and give it a test run. The final release is currently scheduled for the first week in September. Below I will give you a short introduction to the new features.

New Features

NVelocity Support

Erez Mazor joined the team in June and brought a nice integration of the NVelocity template library (part of the Castle project) with him. Let's asume you need to send a confirmation email after processing T-Shirt order to a customer. Most of this email will come from a template, just a few variables will be individual for each customer. Here's how such a template looks like using the Velocity Template Language: 

Dear#if ($sex == "F") Ms#else Mr#end $recipient,
 
We are happy to withdraw $1Mio from your account

Here's an example on how to use Spring.NET's integration to send an Email from your application. The configuration causes the NVelocityFactory to load templates from the NVelocityDemo assembly's "NVelocityDemo" namespace:

<objects xmlns="http://www.springframework.net" xmlns:nv="http://www.springframework.net/nvelocity">
  <nv:engine id="velocityEngine">
    <nv:resource-loader>
      <nv:spring uri="assembly://NVelocityDemo/NVelocityDemo/"/>
    </nv:resource-loader>
  </nv:engine>
 
  <object id="emailSendService" type="NVelocityDemo.EmailSendService">
    <property name="VelocityEngine" ref="velocityEngine" />
  </object>
</objects>

Here's the C# Code of my EmailSendService class:

public class EmailSendService
{
  public VelocityEngine VelocityEngine { get; private set; }

  public void SendEmail(string recipientName, string sex, string emailAddress, string subject)
  {
    Hashtable model = new Hashtable();
    model["email"] = emailAddress;
    model["recipient"] = recipientName;
    model["sex"] = sex;
    IContext modelContext = new VelocityContext(model);
    StringWriter sw = new StringWriter();
    this.VelocityEngine.MergeTemplate("confirmationEmail.vm", Encoding.UTF8.HeaderName, modelContext, sw);

    string emailContent = sw.ToString();

    // send mail
    Console.WriteLine("Sending email to {0} with subject '{1}':\n{2}", emailAddress, subject, emailContent);
  }
}

Running this example will inform our customer about our pricing for T-Shirts:

Sending email to foo@world.com with subject 'Thank you!':
Dear Mr Smith,

we are happy to withdraw $1Mio from your account
TIBCO Enterprise Message Service

Similar to our support for MSMQ and ActiveMQ, consequently there is now also support for Tibco's EMS. If you are already familiar with Spring's ActiveMQ support, you will quickly feel comfortable. To get started, check out our reference documentation and the upcoming blog post at Mark Pollack's Blog.

MS Test

Some of us are forced to write their unit tests using Microsoft's own testing framework. To support them, Spring.NET now also integrates support for writing unit and integration tests similar to the one already available for NUnit. You can find the supporting stuff in the Spring.Testing.Microsoft assembly.

Noteworthy Improvements

Testing

In response to a lot of requests, we upgraded our NUnit testing environment to the latest NUnit 2.5.1. There is also new support for writing database integration tests. Use the SimpleAdoTestUtils class to execute arbitrary SQL scripts to setup/teardown your database. An example is Spring's NHibernate Integration tests. Here is the SQL script to recreate our integration database:

IF  EXISTS (SELECT name FROM sys.databases WHERE name = N'Spring')
BEGIN
    ALTER DATABASE Spring 
        SET SINGLE_USER 
        WITH ROLLBACK IMMEDIATE

    DROP DATABASE Spring
END
GO

IF  EXISTS (SELECT * FROM sys.server_principals WHERE name = N'springqa2')
    DROP LOGIN [springqa2]
GO

CREATE DATABASE Spring
GO

CREATE LOGIN [springqa2] WITH PASSWORD=N'springqa2', DEFAULT_DATABASE=[Spring], DEFAULT_LANGUAGE=[us_english]
GO

USE Spring
CREATE USER [springqa2] FOR LOGIN [springqa2] WITH DEFAULT_SCHEMA=[dbo]
EXEC sp_addrolemember 'db_owner', 'springqa2'
GO

Now, leveraging NUnit's [SetupFixture] feature, we can easily execute this and other scripts before the execution of each fixture:

[SetUpFixture]
public class Setup
{
  private const string DBConnection = "Data Source=SPRINGQA;Database=$DATABASE$;Trusted_Connection=False;User ID=springqa;Password=springqa";
  private readonly IResourceLoader resourceLoader = new ConfigurableResourceLoader();

  private IResource GetResource(object instance, string name)
  {
    string resourcePath = TestResourceLoader.GetAssemblyResourceUri(instance, name);
    return resourceLoader.GetResource(resourcePath);
  }

  [SetUp]
  public void InstallMSSQLDatabase()
  {
    IDbProvider dbProvider = DbProviderFactory.GetDbProvider("SqlServer-2.0");
    AdoTemplate ado = new AdoTemplate(dbProvider);

    // (re-)create database(s)
    dbProvider.ConnectionString = DBConnection.Replace("$DATABASE$", "master");
    SimpleAdoTestUtils.ExecuteSqlScript(ado, GetResource(this, "RecreateDatabases.sql"));

    // create tables
    dbProvider.ConnectionString = DBConnection.Replace("$DATABASE$", "Spring");
    SimpleAdoTestUtils.ExecuteSqlScript(ado, GetResource(this, "Data.NHibernate/creditdebit.sql"));
    SimpleAdoTestUtils.ExecuteSqlScript(ado, GetResource(this, "Data.NHibernate/testObject.sql"));
  }
}

Using the new NVelocity integration, you could even easily parameterize your SQL scripts for different environments.

Configuration Error Handling

Previously sometimes it was hard to read configuration errors. I put some effort into reducing this pain and make the error messages much more explicit. Now you get the exact filename + linenumber of the offending object definition and also a more detailled explanation, what is causing the problem.

AOP Performance

The performance of AOP proxies at runtime improved, but also *creating* such singleton proxies at startup time could cause some issues. You now get a bunch of different Auto-Proxy processor implementations to choose from to better suite your exact needs.

Sometimes using <tx:attribute-driven /> or <aop:config> could cause issues when an additional XXXAutoProxyCreator was defined in your configuration. This roots in the fact that those 2 configuration elements silently registered their own DefaultAutoProxyCreator under the hoods. This is now taken care of and it is guaranteed that infrastructure AOP elements will not interfere with user-defined ones anymore.

Feedback welcome

The release will be in ~ 3-4 weeks, so please grab a copy of the release candidate and give it a whirl - We'd be happy to get your feedback!

May 04, 2009

Common Logging 2.0 for .NET Released

Last week I released a new version of Common Logging 2.0 for the .NET platform. You can get the distribution as well as online API and User Reference documentation from the project website. For users of previous versions there is also a section about upgrading to 2.0.

What is Common.Logging?

Similar to Java's Apache commons-logging, Common.Logging is an ultra-thin brigde between different .NET logging implementations based on original work done by the iBatis.NET team. A framework or library using Common.Logging can be used with any logging implementation at runtime and thus doesn't lock a user to a specific framework or worse letting him struggle with different frameworks using different logging implementations.

image

Common.Logging comes with adapters for all popular logging implementations like log4net and Enterprise Library Logging.

Bridging between logging implementations

You might run into the problem, that libraries used by your application use different logging implementations. Common.Logging allows you to route from those implementations into any logging system of your choice:

image

Please see reference documentation for an example on how to configure such a bridging scenario.

What's new in 2.0?

Several extensions and improvements were made, namly

  • Dropped .NET 1.1 Support
  • Added support for Enterprise Library 4.1 Logging
  • Extended and improved ILog Interface
  • Convenience LogManager.GetCurrentClassLogger() Method
  • Bi-directional log event routing between logging implementations (see bridging above)
  • Improved performance

I already blogged about comparing Common.Logging performance to System.Diagnostics.Trace. According to a thread on stackoverflow, log4net seems even slower than System.Diagnostics.Trace.

Configuring Common.Logging

For examples on how to configure and use Common.Logging and bridge different logging implementations, please see the user refererence guide. The API documentation contains configuration and usage examples on each adapter implementation. Here is an example for configuring Common.Logging to write messages to the console:

<configuration>
<configSections>
 <sectionGroup name="common">
   <section name="logging"
            type="Common.Logging.ConfigurationSectionHandler, Common.Logging"
            requirePermission="false" />
 </sectionGroup>
</configSections>
<common>
 <logging>
   <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
     <arg key="level" value="ALL" />
   </factoryAdapter>
 </logging>
</common>
</configuration>

Using Common.Logging

Using Common.Logging is as simple as shown below:

ILog log = LogManager.GetCurrentClassLogger();
log.DebugFormat("Hi {0}", "dude");

Hint: When using NET 3.5, you can leverage lambda syntax for logging to avoid any performance penalties:

log.Debug( m=>m("value= {0}", obj.ExpensiveToCalculateInformation()) );

This is the equivalent to writing

if (log.IsDebugEnabled)
{
 log.Debug("value={0}", obj.ExpensiveToCalculateInformation());
}

and ensures that you don't have to pay for calculating the debug information message in case level "Debug" is disabled.

Further Roadmap

The following features and improvements are planned for the next release, some of them already in the works:

Of course I would like to invite you to submit any feature request, bug reports or other improvement suggestions to the project's issue tracker.

Happy logging!