Why I Switched from log4net to NLog

As the people who know me know, I have been a big proponent of log4net over the years. I tried very hard to stick with log4net despite years of inactivity of the project. Over the years, I found, reported and fixed issues, but the patches have never been applied. I have reached out to the remaining members of the team to get involved and help revive the project, but received few responses. I even applied patches and released an updated version myself.

As I started to use that updated version, I soon discovered that the current source has many bugs and actually fails many of its own test suites. I tried to get the test suites running and submitted more patches for those, but still the project remains dormant. Without the support of at least one member of the team, it is difficult to revive a project without forking it, which I don’t want to do.

I ended up talking with a member of the NHibernate team, I learned that they were also moving away from log4net and were looking at NLog, the new player in .NET logging. I read through the website, followed the forums and liked what I saw, so I decided to move over to NLog.

Switching over actually turned out to be fairly straight-forward once I managed a few regular expressions for search and replace. Roughly, I performed the following steps,

  1. I did a search and replace using Notepad++ in all of my project files to switch the references over.
  2. I then replaced all of my using statements.
  3. Next came the log instantiation. log4net uses ILog where NLog uses Logger. In log4net you also tend to use GetLogger( typeof( MyClass ) ) where in NLog you just use GetCurrentClassLogger(). This required a bit of RegEx magic.
  4. The hardest step was finding every instance of log.Warn( msg, exception) and converting them to log.WarnException( msg, Exception ). I did this nearly manually for each of the log levels, inspecting to see where I was passing in exceptions.
  5. Then for each of the log levels, I converted instances of log.WarnFormat( “Substitute this {0}”, val ) to log.Warn( “Substitute this {0}”, val ). This was another easy file replacement.
  6. Lastly, I removed my XmlConfigurator attributes and changed my log configurations. A quick recompile, a few minor fixes and I was up and running. Painless!

So far, I don’t regret my choice at all. I find the configuration of NLog to be much more intuitive and the Intellisense for the config file in Visual Studio sure helps. The documentation is still a bit sparse in places, but it is getting better. I do worry because it is currently a one man show, but it Jaroslaw Kowalski does seem fairly active and committed. Time will tell…

Read more

log4net UdpAppender with IPv6 on Windows Vista and 7

log4net is a great logging framework for .NET, but it hasn’t been updated in years. When we moved to Windows Vista, we noticed that the UdpAppender stopped working with Chainsaw and with our internal logging tools when logging to localhost. After some Googling, we discovered that replacing localhost with 127.0.0.2 got everything working and we promptly forgot about it.

Earlier this week, we moved one of our projects over to .NET 4.0 and once again logging failed. In the console window while debugging the app, I noticed the following,

log4net.Util.TypeConverters.ConversionNotSupportedException: Cannot convert from type [System.String]
  value [127.0.0.2] to type [System.Net.IPAddress] --->
  System.Net.Sockets.SocketException: No such host is known
       at System.Net.Dns.InternalGetHostByAddress(IPAddress address, Boolean includeIPv6)
       at System.Net.Dns.GetHostEntry(String hostNameOrAddress)
       at log4net.Util.TypeConverters.IPAddressConverter.ConvertFrom(Object source)

So, I tried changing back to localhost and the error changed to,

log4net:ERROR [UdpAppender] Unable to send logging event to remote host ::1 on port 7071.
      System.Net.Sockets.SocketException (0x80004005): An address incompatible with
      the requested protocol was used
       at System.Net.Sockets.Socket.SendTo(Byte[] buffer, Int32 offset, Int32 size, SocketFlags
         socketFlags, EndPoint remoteEP)
       at System.Net.Sockets.UdpClient.Send(Byte[] dgram, Int32 bytes, IPEndPoint endPoint)
       at log4net.Appender.UdpAppender.Append(LoggingEvent loggingEvent)

At this point, I have some clues, specifically that localhost was being resolved as the IPv6 address ::1, not the IPv4 address 127.0.0.1. Looking through the reported issues I found that this problem had been reported and fixed in 2007. Unfortunately, that code isn’t in the release, so I downloaded the latest source and recompiled log4net. Of course, they don’t release the signing key, so I generated my own and signed the assembly myself.

This fixed my problems with the UdpAppender, although, if you use localhost on a Windows Vista or Windows 7 machine, it will resolve to the IPv6 address, so make sure that your receiver is listening on the IPv6 address. For example, in log2console, under Receivers, set Use IPv6 Addresses to true for the UDP receiver. If your receiver does not support IPv6, use 127.0.0.2 for the address.

To save other people the hassle of recompiling log4net themselves to get the latest fixes, I have uploaded a release version of the assembly. Download it here.

We are using this version of the assembly in our own projects, but I make no guarantees as to its stability.

Update: Just to  be clear, this is not an official log4net release and it is only compiled against .NET 2.0. I have made no code changes, it is just the code that is currently in the repository. This is only intended to save you having to download and compile if you run into the same problems I did. It is also not extensively tested. We are using it and File, Rolling File, Event Log and UDP Appenders seem to be working fine.

Update 2: Since I have released this, I have found several bugs in the current source. For example, setting Append=true to a File or Rolling File causes every log message to truncate the file! Because of that I would recommend that you don’t download this unless you really need it. I have finally given up on log4net and converted all of my projects over to NLog.

Read more

log4net Slides and Example Code

I have been contacted people who cannot attend my Toronto Code Camp session on log4net tomorrow requesting a copy of my presentation and example code. I cannot find it posted on the Code Camp site, so here is a copy for anyone who is interested.

The presentation is in PowerPoint 2007 and the example code is for Visual Studio 2008. update: I have uploaded a PDF version of the presentation for those people without Office 2007.

If you are attending, I am looking forward to meeting you tomorrow at 9 AM.

Read more

Speaking at Toronto Code Camp

tcc-logosmall I found out last week that I will be speaking at this year’s Toronto Code Camp on March 1st.  I will be giving an Introduction to log4net from 9:00 AM to 10:15 AM.

I will begin the session with an overview of the license, features and capabilities of log4net, including log levels, log hierarchies, logging contexts, configuration and filters.

I will then dive into code by adding logging to a simple application. Next, I will configure the logging to output the logs to multiple destinations. I will end by discussing best practices for logging with log4net and answer questions.

Read more

Writing An Appender For log4net

In log4net speak, an appender is an output destination for a log such as a file, the console, a database or even email.  log4net ships with so many appenders that most of us will never need to write our own.  There are cases where you may find a need for your own appender, for example, you may want to log errors to your company’s bug tracking software. 

In our case, we simply wanted error logs to pop up a message box with the error and location.  We run this internally so that developers run into errors immediately during development and can break into the debugger to fix them.  We found that logging to a file was too easy to ignore.

MessageBoxAppender

I was pleasantly surprised how easy it is to write a new appender, but there is very little information on the web, so I thought it would be best to give an example.

  1. Create a new Class Library project in Visual Studio.
  2. Add a reference to log4net. My appender also uses MessageBox, so I also added references to System.Drawing and System.Windows.Forms.
  3. Remove the default Class1.cs added to the project.
  4. Add your appender class. In my case, MessageBoxAppender.cs.
  5. You could implement the log4net.Appender.IAppender interface, but it is easiest to derive from log4net.Appender.AppenderSkeleton, then most of the work is done for you.
  6. At a minimum, override the Append method. This is where you do your work.
  7. If you are going to use the RenderLoggingEvent method to create your logging message based on the configured layout (such as PatternLayout), override the RequiresLayout property and return true.
  8. When you configure your appender, you must give the assembly qualified name for your appender.  For example,

<appender name=”…” type=”MyNamespace.MyAppender, MyAssembly”>

Here is the simplified code for the MessageBoxAppender that I wrote.

using System;
using System.Windows.Forms;
using System.Diagnostics;

using log4net.Core;
using log4net.Appender;

namespace Alteridem.log4net
{
    /// <summary>
    /// Displays a MessageBox for all log messages.
    /// </summary>
    public class MessageBoxAppender : AppenderSkeleton
    {
        /// <summary>
        /// Writes the logging event to a MessageBox
        /// </summary>
        override protected void Append( LoggingEvent loggingEvent )
        {
            string title = string.Format( "{0} {1}",
                loggingEvent.Level.DisplayName,
                loggingEvent.LoggerName );

            string message = string.Format(
                "{0}{1}{1}{2}{1}{1}(Yes to continue, No to debug)",
                RenderLoggingEvent( loggingEvent ),
                Environment.NewLine,
                loggingEvent.LocationInformation.FullInfo );

            DialogResult result = MessageBox.Show( message, title, MessageBoxButtons.YesNo );

            if ( result == DialogResult.No )
            {
                Debugger.Break();
            }
        }
        
        /// <summary>
        /// This appender requires a <see cref="Layout"/> to be set.
        /// </summary>
        override protected bool RequiresLayout
        {
            get { return true; }
        }
    }
}

There isn’t much to see here. I use the log level and the log name in the titlebar. I display the rendered message string and the calling location in the MessageBox and I break into the debugger if you press No.

Now, to configure this for your developers to see ERROR messages, the following configuration would work.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="log4net"
        type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
    </configSections>
    <log4net>
        <appender name="MessageBoxAppender"
            type="Alertidem.log4net.MessageBoxAppender, log4netExtensions">
            <layout type="log4net.Layout.PatternLayout">
                <ConversionPattern value="%m" />
            </layout>
        </appender>
        <root>
            <level value="ERROR"/>
            <appender-ref ref="MessageBoxAppender" />
        </root>
    </log4net>
</configuration>

The only thing to not here is that the log4netExtensions in the appender line is the assembly.

This should be enough to give you a basic framework to build whatever type of appender you want.  Every time I delve into a new area of log4net, I am once again surprised how easy it is to work with and how well designed it is.  If you aren’t using it, I would highly recommend it.

Read more