Wednesday, March 7, 2012

Pathetic breaking change between log4net 1.2.10 and 1.2.11

Somewhere around October 2011 a bugfixed version 1.2.11 of log4net has been released. As log4net is around for years, I guess most people like myself did not update it on day zero.

Today I’ve decided to update one of our applications to use the newest version of log4net and immediately bumped into issues.

First, it seems that for some reason they have signed the newest version of log4net 1.2.11 with a different key. If your application uses any external component (a *.dll) relying on 1.2.10 (and I bet you have such components), the new key means that assembly rebinding will not be possible! In other words, the runtime will not be able to pretend that the new log4net 1.2.11 loaded with your module is the same log4net 1.2.10 which is required by one of your external components.

But hey, they have a workaround for this! It seems that they have two different releases. One is signed with the new key and the other with the old key.

Supposedly then, if you download the 1.2.11 signed with the old key and you rebind the 1.2.10 to 1.2.11 it should work.

So how do you rebind? There are two possible approaches.

A static rebind is just a matter of your configuration file.

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
          <assemblyIdentity name="log4net" publicKeyToken="1b44e1d426115821" culture="neutral" />
          <bindingRedirect oldVersion="0.0.0.0-1.2.10.0"
                           newVersion="1.2.11.0"/>
      </dependentAssembly>
  </assemblyBinding>
</runtime>

A dynamic rebind is a matter of the AssemblyResolve event handler:

AppDomain.CurrentDomain.AssemblyResolve += 
  new ResolveEventHandler( CurrentDomain_AssemblyResolve );
 
static Assembly CurrentDomain_AssemblyResolve( 
    object sender, 
    ResolveEventArgs args )
{
    // rebind any log4net to the current log4net 
    // (assuming you have 1.2.11 added to references
    if ( args.Name.IndexOf( "log4net" ) >= 0 )
        return Assembly.Load( "log4net" );
 
    // a fix to handle mscorlib.resources
    if ( args.Name.IndexOf( ".resources" ) >= 0 )
        return null;
 
    return Assembly.Load( args.Name );
}

So now, no matter which approach you take, it should work. And guess what - unfortunately, it doesn’t.

The reason is really unfortunate. It seems that they have changed the signature of the XmlConfigurator.Configure() method between releases! To me it’s unacceptable, it’s against OOP to break backward compatibility at the object contract level between two minor releases.

In 1.2.10 the Configure method returns void.

In 1.2.11 the Configure method returns ICollection.

The exact problem of the rebinding is then

TypeInitializationException: Method not found: 'Void log4net.Config.XmlConfigurator.Configure()'.

and of course the method is not found as someone has decided to change its signature in 1.2.11! In other words then, the external component referencing 1.2.10 calls XmlConfigurator.Configure() and finds the method in 1.2.11 you provide. But since the method’s signature has changed, the type loader raises the exception and definitely should raise it as calling the method with wrong signature could blow up your runtime because of stack issues.

To me it’s a super fail from the log4net team. The release notes mentions this as one of "new features":

http://logging.apache.org/log4net/release/release-notes.html

I guess my migration to 1.2.11 has ended prematurely.

8 comments:

Anonymous said...

Same problem here.
This missing "Configure" is a show stopper for me.
Downgrading to 1.2.10

Anonymous said...

Keep in mind, it takes about 10 years for MS to rip-off Java technology like logging, ORM, unit-test, dependency injection u-name it. Microsoft is generally populated by ignorant Microsoft ass kissers, so to even recognize how pathetic they are (see current, previous versions of Windows phone) is blaspheme.

Wiktor Zychla said...

Something is in the air. Note however, that log4net doesn't come from Microsoft. And unfortunately, the mentioned problem (migration from 1.2.10 to 1.2.11) doesn't have any safisfiable solutions.

Nachbars Lumpi said...

There are two log4net 1.2.11 available for download. One is signed with the old key and the other one with the new key. It is also described here: http://logging.apache.org/log4net/release/faq.html#two-snks

Unknown said...

I love how the hater comes off with a tirade about a product made by someone else - then goes on to call other people "ignorant".

The whole industry does the stuff you talk of. Last I checked Google/Samsung were the ones being sued for copying Apple's phone. As if you can give Java credit for ORM, Logging, Unit-Testing and Dependency Injection... lol.

Why don't you go use a native Java bug logger if you hate MSFT related technologies?

Wiktor Zychla said...

@Dylan: I think you misinterpreted my article - I blame log4net team for the mess they introduced with their own, quite basic mistake, a broken method contract between releases. Because of this, people got into real problems, I can't believe you miss that. And I am not going to switch to other technologies by myself, if I use A, B, C as third-party frameworks then their developers make the choice for me, THEY use log4net so do I. Problems start when A uses log4net 1.2.10 and B uses 1.2.11, there is really NO easy way to have both A and B in your code. And I don't care about Google, Samsung or Apple, I care about my code and my problems I got into because of the decision of someone from the log4net team.

Neo said...

@Wiktor: I think Dylan was having a go at the second 'Anonymous' comment, not you!

Anyway, I completely agree with this article. I've just wasted several hours trying to get to the bottom of why I can't do a bindingRedirect between 1.2.10.0 and 1.2.11.0 till I found your article. Now, I'm going to have to back out log4net via NuGet and use a the previous version (1.2.10.0) as non-NuGet-controlled library. Not ideal.

Rast said...

This is a kind of old post, but just want to give some people a clue how to resolve this kind of problems.

Basically you need to disassembly the old dll which references log4net 1.2.10, convert the code to MSIL, replace the publickey token in MANIFEST referencing log4net AND replace the signature of log4net Configure* method invocations to return ICollection. After those modifications compile the assembly back from MSIL code and voila! I did that using ILDASM + ILASM, which are both tools coming with Visual Studio.

As far as I know there are no other compatibility issues except those mentioned.

I have a success doing this to replace dependency on old log4net and moving to recent 1.2.13.0 version.