Thursday, July 1, 2010

Making log4net run on .NET 4.0

I was playing around with .NET 4.0 and wanted to include logging. So I downloaded log4net (v1.2.10) and added the source project to my solution. The next thing to do was to configure log4net and I would be able to log to one or several 'appenders'. At least, that was what I thought! How to configure and use log4net is not the scope of this article, but you can find all what you need on the log4net site. A summary of the solution can be found at the bottom of this article.

When building I got some errors & warnings. One of these warnings was:

Could not resolve assembly "System.Web". The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client". Please remove references to assemblies not in the targeted framework or consider retargeting your project.

I went googling about the different target frameworks and I found a page explaining the this: http://msdn.microsoft.com/en-us/library/cc656912.aspx. Basicly there are some parts "missing" by default if the target is ".NET Framework 4 Client Profile".

OK, fair enough, I changed the target framework to ".NET Framework 4" instead of ".NET Framework 4 Client Profile" on the log4net project.


Finally, I can build my solution! I was dissapointed again when I tried to run it. Now I get the following error in log4net.Core.DefaultRepositorySelector.CreateRepository():

Exception has been thrown by the target of an invocation.

The inner exception was more detailed:

Inheritance security rules violated while overriding member: 'log4net.Util.ReadOnlyPropertiesDictionary.GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Security accessibility of the overriding method must match the security accessibility of the method being overriden.

It states that something is wrong with the overriden method GetObjectData() in the ReadOnlyPropertiesDictionary class. ReadOnlyPropertiesDictionary implements ISerializable which makes you have to implement GetObjectData(). When you look at the definition of ISerializable.GetObjectData() you'll see there is an SecurityCritical attribute specified on it. With the inner exception in mind, I added the SecurityCritical attribute to the method GetObjectData() in ReadOnlyPropertiesDictionary. To be honest, the people in this thread pointed me in the good direction: http://stackoverflow.com/questions/2903669/log4net-and-net-framework-4-0.

Having changed that I ran it again. Now other errors arised, however Visual Studio did not break on it, it was written to the VS output window and my console app. One of the errors was the same as above, but then for log4net.Core.LoggingEvent.GetObjectData(). So I added the attribute SecurityCritical there too.

Still no luck with that, because now this error was thrown:

System.MethodAccessException: Attempt by security transparent method 'log4net.Util.SystemInfo.get_CurrentThreadId()' to call native code through method 'System.AppDomain.GetCurrentThreadId()' failed. Methods must be security critical or security safe-critical to call native code.

The property CurrentThreadId of the class log4net.Util.SystemInfo calls System.AppDomain.GetCurrentThreadId(). However, it seems that this is obsolete and need to be changed to System.Threading.Thread.CurrentThread.ManagedThreadId. See http://msdn.microsoft.com/en-us/library/system.appdomain.getcurrentthreadid.aspx.

Changed the property, run it, get an error. Do you have the impression we are in a vicious circle? Don't worry, so do I, but we are getting out of it!

This time the error is:

System.MethodAccessException: Attempt by security transparent method 'log4net.Appender.ColoredConsoleAppender.ActivateOptions()' to call native code through method 'log4net.Appender.ColoredConsoleAppender.GetConsoleOutputCP()' failed. Methods must be security critical or security safe-critical to call native code.

Some security changes were made in the .NET 4.0 framework. In the AssemblyInfo.cs file of the log4net project, you'll find the following line:

[assembly: System.Security.AllowPartiallyTrustedCallers]


This is affected by the security changes. Take a look at this page for more details: http://msdn.microsoft.com/en-us/library/system.security.allowpartiallytrustedcallersattribute.aspx.

I commented that line and now I can succesfully run my application with the logging capabilities of log4net! Isn't that great?

Because the AllowPartiallyTrustedCallers is in comment, the SecurityCritical attributes we added can be removed again.

As a summary, this is what you'll need to do to make log4net run on .NET framework 4:
  • Include the source as a project
  • Change log4net.Util.SystemInfo.CurrentThreadId to return System.Threading.Thread.CurrentThread.ManagedThreadId instead of System.AppDomain.GetCurrentThreadId().
  • Put [assembly: System.Security.AllowPartiallyTrustedCallers] in comment in the AssemblyInfo.cs file.
That's it! Please do leave some comment or feel free to ask a question. I'll be happy to respond to it.

22 comments:

  1. I made those changes, howether I still had log4net crashing, so I had to remove %thread and %ndc properties from config , and it started to work..

    ReplyDelete
  2. I have both of the paremeters in my config, but I cannot reproduce the crash. Which exception do you get exactly?

    ReplyDelete
  3. Version 1.2.10 already uses System.Threading.Thread.CurrentThread.ManagedThreadId, so no need to do first action now.

    ReplyDelete
  4. I Added NET_4_0 along with NET_2_0 and did the following:
    -Used #if (!NETCF && !NET_4_0) for [assembly: System.Security.AllowPartiallyTrustedCallers] section.
    - In XmlConfigurator: replaced settings.ProhibitDtd = false; with
    #if NET_4_0
    settings.DtdProcessing = DtdProcessing.Parse;
    #else
    settings.ProhibitDtd = false;
    #endif

    ReplyDelete
  5. hi. can you please post the compiled dll?

    ReplyDelete
  6. @a soldier: I don't think I'm allowed to distribute modified code... sorry!

    ReplyDelete
  7. a simpliest answer here
    http://mocella.blogspot.com/2010/01/using-log4net-with-net-40-wpf.html

    ReplyDelete
  8. Putting
    [assembly:SecurityRules(SecurityRuleSet.Level1)]

    solved issue for me

    ReplyDelete
  9. I tried log4net in framework4.0 , i decided to do log files manually creating StreamWriter , finailly i goggled i got this blog , really i feel happy, thanks to Tseo

    ReplyDelete
  10. I also thank you for the solution you have provided us with. As the solution is not obvious at all, I have saved a lot of time because of this blog post.

    ReplyDelete
  11. Great! Thanks a lot, you saved my day.

    ReplyDelete
  12. You're all welcome!

    It's been a long time since the last post, time to post a new one...

    ReplyDelete
  13. Adding [SecurityCritical] is a better solution.
    see http://stackoverflow.com/questions/3055792/inheritance-security-rules-violated-while-overriding-member-securityruleset-lev

    ReplyDelete
  14. Brilliant, thank you so much. I used this in conjunction with http://www.codeproject.com/KB/dotnet/Log4NetWithClient.aspx to make a Client Profile friendly log4net assembly which seems to work a treat!

    ReplyDelete
  15. Hi Tseo,

    Apparently you did a great job for all the guys above. Unfortunately it doesn't work for me... I'm trying to get it to work in .net framework 4.0. I did follow all your steps, even in combination with the instructions from the link in previous comment.

    What I notice is that it works well when the layout is set to SimpleLayout, but it fails when I put it to PatternLayout with the following pattern "%date [%thread] %-5level - %message%newline". Also tried it with when I removed "%thread"... I only get blank spaces. My assumption is that Log4Net wants to log but can't do the translation/mapping of the pattern with the exact values.

    Any help is more than welcome.

    Kind regards,

    JDB

    ReplyDelete
  16. Hi JDB749,

    In my config file I have the following sections in the log4net section:

    <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
    <mapping>
    <level value="ERROR" />
    <foreColor value="Red" />
    </mapping>
    <mapping>
    <level value="DEBUG" />
    <foreColor value="Yellow" />
    </mapping>
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level - %message%newline"/>
    </layout>
    </appender>

    <logger name="MyLogger">
    <level value="ALL"></level>
    <appender-ref ref="ColoredConsoleAppender"/>
    </logger>

    As you can see this is a patternlayout with the use of the %thread parameter.

    Do you get any kind of exception?

    ReplyDelete
  17. Put the config part here & click the button: http://accessify.com/tools-and-wizards/developer-tools/quick-escape/

    Copy/paste the result in your comment.

    ReplyDelete
  18. I received a mail saying you responded, but I don't see your comment here. Can you please post it again?

    ReplyDelete
  19. Hi Tseo,

    It is even hard to post something on a blog :)

    Anyhow... What I was saying...

    It works great when I just use the ColoredConsoleAppender and when I change your tag <logger> with <root>. But no luck with the RollingFileAppender.

    My app.config section looks something like below:

    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file type="log4net.Util.PatternString" >
    <converter>
    <name value="folder" />
    <type value="Log4Net.Test.SpecialFolderPatternConverter,Log4Net.Test" />
    </converter>
    <conversionPattern value="%folder{CommonApplicationData}\\Log\\test.log" />
    </file>
    <param name="AppendToFile" value="true" />
    <rollingStyle value="Size" />
    <maxSizeRollBackups value="10" />
    <maximumFileSize value="1MB" />
    <staticLogFileName value="true" />
    <!--<layout type="log4net.Layout.SimpleLayout" />-->
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level -> %message%newline" />
    </layout>
    </appender>

    The creation of the file succeeds and when I want to log then I see only blank spaces (noticable when I open the file and do a CTRL+A).
    I even stepped through the Log4Net sources but no exception popped up.

    Hopefully you could point me in the right direction.

    ReplyDelete
  20. @JDB749: Could you try with a "simple" RollingLogFileAppender?

    Here is my complete log4net section with a console and a file appender:

    <log4net>
    <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
    <mapping>
    <level value="ERROR" />
    <foreColor value="Red" />
    </mapping>
    <mapping>
    <level value="DEBUG" />
    <foreColor value="Yellow" />
    </mapping>
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level - %message%newline"/>
    </layout>
    </appender>

    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="./Logs/logfile.log"/>
    <appendToFile value="false"/>
    <rollingStyle value="Date"/>
    <datePattern value="yyyyMMdd"/>
    <maxSizeRollBackups value="30"/>
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level - %message%newline"/>
    </layout>
    </appender>

    <logger name="MyLogger">
    <level value="ALL"></level>
    <appender-ref ref="ColoredConsoleAppender"/>
    <appender-ref ref="RollingLogFileAppender"/>
    </logger>
    </log4net>

    I hope this will help!

    ReplyDelete
  21. Thanks! It is working now strangely enough... must be a typo somewhere.

    I can stop banging my head against my desk now.

    You got my appreciation!

    Kind regards,

    JDB

    ReplyDelete