3
votes

I have a small set of ServiceStack REST services that is using NLog 2.1 (from NuGet) for logging.

My test server is running:

  • Windows 7
  • IIS 7.5
  • .NET 4.5
  • NLog config:
<nlog throwExceptions="true" internalLogToConsole="true" internalLogLevel="Debug" xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <targets>
        <target name="c" xsi:type="Console" />
        <target name="f1" xsi:type="File" fileName="C:\logs\test.log" />
    </targets>
    <rules>
        <logger name="*" writeTo="c,f1" />
    </rules>
</nlog>

My NLog config is exceedingly simple... just trying to get it working... and in this configuration, everything works fine. NLog creates the log files correctly.

On my DEVELOPMENT machine, I am using:

  • Windows 7
  • Xamarin Studio / XSP4
  • Mono 3.2.3

Here is my Application_Start...

protected void Application_Start() {
   LogManager.LogFactory = new NLogFactory();
   ILog log = LogManager.GetLogger(typeof(Global));
   log.Info("Application_Start called");

   try {
      new AppHost().Init();
   } catch (Exception e) {
      log.Error("Exception caught initializing AppHost");
   }
}

In this configuration, my service's AppHost().Init() throws an exception as ServiceStack is registering my services in ServiceController.cs. I believe that part is irrelevant except that it is the first time something is logged outside of Application_Start (because both of the calls in Application_Start work... the log.info before the exception and the log.error after the exception).

Here is the exception that is shown:

enter image description here

The most relevant bit is that there was a System.NotImplementedException thrown at NLog.Internal.FileAppenders.BaseFileAppender.WindowsCreateFile (System.String fileName, Boolean allowConcurrentWrite).

I have found a workaround (in the accepted answer below). Hopefully anyone else who runs into this will quickly come upon this solution.

1

1 Answers

2
votes

Some digging around on Google led me to this NLog pull request:

Avoid Win32-specific file functions in Mono where parts not implemented.

It appears that this change tries to use the preprocessor to NOT call WindowsCreateFile at all. However, for some reason, this still executes.

So I then went to check the newest version of BaseFileAppender.cs in the NLog GitHub repository to make sure someone didn't at some later point break this again.

#if !NET_CF && !SILVERLIGHT && !MONO
    try
        {
            if (!this.CreateFileParameters.ForceManaged && PlatformDetector.IsDesktopWin32)
            {
                return this.WindowsCreateFile(this.FileName, allowConcurrentWrite);
            }
        }
        catch (SecurityException)
        {
            InternalLogger.Debug("Could not use native Windows create file, falling back to managed filestream");
        }
#endif 

Hmmm... it's still there. What gives? Why doesn't MONO seem to be defined by the preprocessor, thus allowing this block to execute? I'm not sure. Before I started down that path of investigation, I noticed another change...

if (!this.CreateFileParameters.ForceManaged && ...

So after following that tracing that ForceManaged boolean back to it's origin, it appeared that I could set forceManaged="true" on my FileTarget declaration in my NLog config. Could it really be that simple?!

<nlog throwExceptions="true" internalLogToConsole="true" internalLogLevel="Debug" xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <targets>
        <target name="c" xsi:type="Console" />
        <target name="f1" xsi:type="File" forceManaged="true" fileName="C:\logs\test.log" />
    </targets>
    <rules>
        <logger name="*" writeTo="c,f1" />
    </rules>
</nlog>

Once that change was made, everything worked... the call to WindowsCreateFile that was throwing the exception was skipped & a managed filestream was used instead, which works great. Hallelujah!

Lastly, I could not find that forceManaged flag in the NLog documentation anywhere. Would likely have found this solution sooner if it was.