3
votes

I have a Windows Service process that receives WCF requests. For each request, it creates new thread that does some work. I want to create one log file for the host process and separate unique log file per thread instance. For example, I want the log file to be something like '%date{yyyyMMdd}_%property{UniqueId}.log'. Here is what I have so far.

<appender name="ThreadAppender" type="log4net.Appender.RollingFileAppender">
      <file type="log4net.Util.PatternString" value="c:\Logs\%date{yyyyMMdd}\%date{HHmmss}_%property{FileID}.log"/>
      <appendToFile value="true"/>
      <maxSizeRollBackups value="10"/>
      <maximumFileSize value="10MB"/>
      <rollingStyle value="Size"/>
      <staticLogFileName value="true"/>
      <layout type="log4net.Layout.PatternLayout">
        <header value="[Header]
"/>
        <footer value="[Footer]
"/>
        <conversionPattern value="[%date] [%thread] [%-5level] %logger  - %message%newline"/>
      </layout>
    </appender>
    <appender name="ServiceAppender" type="log4net.Appender.RollingFileAppender">
      <file value="C:\Logs\Service.log"/>
      <appendToFile value="true"/>
      <maxSizeRollBackups value="10"/>
      <maximumFileSize value="10MB"/>
      <rollingStyle value="Size"/>
      <staticLogFileName value="true"/>
      <layout type="log4net.Layout.PatternLayout">
        <header value="[Header]
"/>
        <footer value="[Footer]
"/>
        <conversionPattern value="[%date] [%thread] [%-5level] %logger  - %message%newline"/>
      </layout>
    </appender>
    <root>
      <level value="ALL"/>
      <appender-ref ref="ThreadAppender"/>
    </root>
    <logger name="ServiceLogger" additivity="false">
      <level value="INFO"/>
      <appender-ref ref="ServiceAppender"/>
    </logger>

Then, in my thread, I use LogicalContext.Properties["FileID"] = <some id>. I also have [assembly: log4net.Config.XmlConfigurator(Watch = true)] line in the assemblyinfo.cs file.

Everything almost works :). The one issue is that when I start the Windows Service, it automatically creates new %date{HHmmss}_(null).log file. What do I need to configure so that log4net does not create this dummy file? Also, do I need to specify file locking for ServiceAppender and ThreadAppender?

2
Do you have any static fields that initialise loggers? That would happen before you set FileId - stuartd

2 Answers

1
votes

That code does not work as expected, because of concurrency issues. log4net.Config.XmlConfigurator.Configure forces to apply new property value, but it will impact on all threads, so messages for other concurrent request will be redirected to the new file.

The only working solution I would is with using ILoggerRepository.

0
votes

Creating unique log file per thread in your WCF service

If you don't wan't log4net to create a file with an empty FileID (e.g. c:\Logs\19850101\131510_null.log)
you will have to make sure that you set the properties first and then configure log4net.

In a WCF service you can take the following steps:
1. Remove the call to the XmlConfigurator in file 'AssemblyInfo.cs'.
2. Add a Global Application Class (Global.asax) to your service.
3. Add the following bit of code to your newly created class.

    /// <summary>
    /// Begins the application request.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A <see cref="EventArgs"/> that contains the event data.</param>
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        log4net.LogicalContext.Properties["FileID"] = <some id>;

        // Configure log4net. Log4net will load settings and create a new 
        //   file if it does not exist yet.
        log4net.Config.XmlConfigurator.Configure();
        log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        logger.Debug(<some id>);
    }

Start logging

Now create a logger in every class by using this snippet:

    /// <summary>
    /// Logger that can be used to report messages, errors, etc.
    /// </summary>
    private static log4net.ILog Logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

About locking

Not sure whether you need to use locking for your ServiceAppender and ThreadAppender.
Every WCF service will by default set the 'concurrency mode' to 'Single' and the
'instance context mode' to 'PerSession'.

That means only one request will be processed at any given moment of time. I tested
this on a virtual machine with 1 client using the service and logging seems to work
fine! So by default, no need for locking.