Angelos Arampatzis
Angelos Arampatzis

Reputation: 113

How can I have log4net create a separate log file for each Action in a Parallel.Invoke?

I have a series of Action that I want to execute in parallel using Parallel.Invoke. But for each Action I want to have a separate log file created by log4net. In my log4net configuration file I have added this:

<file type="log4net.Util.PatternString" value="3S2M3_%property{UniqueIdentifier}.log" />

If there is only one Action to execute this works fine: the log file has the correct file name.

But, if there is more than one Action to be executed, all the log entries end up in the same log file.

The code for creating the Action is this:

    Dim lstActions(4) As Action
    Dim iCount As Integer = 0

    For iCount = 0 To 4
        Dim sUniqueIdentifier As String = iCount.ToString("D4")

        Dim aOrderTask As Action = Sub()
           log4net.LogicalThreadContext.Properties("UniqueIdentifier") = sUniqueIdentifier

       ' Some process that takes some time to complete
                                   End Sub
        lstActions(iCount) = aOrderTask
    Next

    Parallel.Invoke(lstActions)

The log file that gets created is using one of the values assigned to the UniqueIdentifier property. I cannot figure out how this value is picked: it is not always the first or the last. It he example above, only the file 3S2M3_0004.log got created and it contained all the log entries.

I have tried both LogicalThreadContext and ThreadContext and it makes not difference.

Upvotes: 3

Views: 634

Answers (1)

stuartd
stuartd

Reputation: 73253

log4net assigns log file names when loading the configuration, so you're going to need to set your custom names at runtime.

The other thing to note is that log4net shares appenders between loggers, so to log to different files simultaneously you're going to need to assign appenders programatically, and you're going to have to do it within the process, and not outside it.

(If you really don't want to set the appender properties in code, you can define one in the config, get it from the hierarchy, and clone it for each task. Whichever way, each instance of the task needs it's own logger and a unique appender)

So, given config like this:

<?xml version="1.0" encoding="utf-8" ?>
<log4net debug="false">
   <root>
     <level value="DEBUG" />
   </root>

   <!-- more config -->

   <logger name="EmptyLogger" additivity="false">
   </logger>
   <!-- additivity="false" specifies not to load any appenders defined on the root logger -->
</log4net>

You can then have code like this:

Private Shared Sub ProcessThatTakesSomeTimeToComplete()
    Dim sUniqueIdentifier As New String(Guid.NewGuid().ToString("N").Take(4).ToArray())

    ' Get a uniquely-named empty logger containing no appenders
    Dim log = LogManager.GetLogger("EmptyLogger." & sUniqueIdentifier)

    ' Create an appender manually, setting the path as required
    Dim sPath As New String(Path.Combine("c:\temp", sUniqueIdentifier & ".log"))

    Dim appender = New FileAppender() With { _
         .Layout = New PatternLayout("%date [%thread] %message"), _
         .File = sPath, _
         .AppendToFile = True _
    }

    appender.ActivateOptions()

    ' Add the appender to the logger
    DirectCast(log.Logger, Logger).AddAppender(appender)

    ' And off we go.
    log.Debug("Hello from thread " + Thread.CurrentThread.ManagedThreadId)

    Thread.Sleep(100)
End Sub

Imports:

Imports System.IO
Imports System.Threading
Imports log4net
Imports log4net.Appender
Imports log4net.Layout
Imports log4net.Repository.Hierarchy

Upvotes: 2

Related Questions