I was recently working on a bulk processor application which was using log4net
for its logging needs (specifically the RollingFileAppender
). There were multiple jobs executing in independent threads each having its own Logger
instance. We wanted the RollingFileAppender
to use an independent file for each logger instance. In my attempts to achieve the same, here’s what I learned about log4net
:
- The
file
property ofRollingFileAppender
can be specified as of typelog4net.Util.PatternString
in the configuration, thus enabling using patterns that are replaced dynamically byRollingFileAppender
. Here’s how you specify a dynamic pattern forfile
property ofRollingFileAppender
in the configuration:123<file type="log4net.Util.PatternString" value="Logs\%appdomain.log" />This link lists the pattern names and formats supported out of the box in
PatternLayout
strings. PatternLayout
supports a wider variety of patterns, includinglogger
pattern name which essentially is the name of theLogger
instance.
Now because our jobs were using different names for theirLogger
instances, ability to uselogger
pattern infile
configuration would have enabled us to achieve our objective of different files perLogger
instance. However we found out the additional patterns inPatternLayout
can be used asconversionPattern
but not inside thefile
property which is limited toPatternString
.- You can pass custom pattern names to
file
property ofRollingFileAppender
by usinglog4net.GlobalContext
. For example, you can define a custom property value like:123log4net.GlobalContext.Properties["someproperty"] = "some value";which would enable you to use
%someproperty
as a pattern inside thefile
property:123<file type="log4net.Util.PatternString" value="Logs\File%someproperty.log" />The same would be replaced with its value when you initialise
log4net
‘s configuration. Please note you can only uselog4net.GlobalContext
for this purpose; specifying properties throughlog4net.ThreadContext
orlog4net.ThreadLogicalContext
does not make them available as patterns to appenders. Appender
instances are created when you initialise thelog4net
configuration using:123log4net.Config.XmlConfigurator.Configure();It is important to note this point, as this underlines why
Appenders
do not support per-Logger configuration, they are created at theRepository
level inlog4net
and not atLogger
instance level.
This also means if you are specifying properties throughlog4net.GlobalContext
, the same needs to happen before you invokelog4net.Config.XmlConfigurator.Configure()
. Otherwise those custom patterns won’t be replaced by their values you defined in your code.- All
Logger
instances using the sameappender-ref
via your configuration file are essentially using the sameAppender
instance. If you dig into members of aLogger
instance, you would observe it does not hold any direct reference to aAppender
instance. Instead it makes an object ofILoggerRepository
available, which is shared by allLogger
instances in your application (unless you are manually managing customRepository
instances).ILoggerRepository
further makes Appenders available by a call to itsGetAppenders
instance method.
The gist of all these observations and findings is that Appender instances are managed by an ILoggerRepository
instance which in itself is shared by all Logger
instances, effectively meaning Appenders themselves are also shared between Logger
instances.
So the only way to have 2 Logger
instances use different output files for a RollingFileAppender
would be to define 2 different <appender>
configurations having different names in your log4net
configuration file and then configuring your loggers in the same configuration file to use their respective appender instances by suitably referencing an appender via appender-ref
.
The following sample configuration demonstrates how to achieve the same:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<log4net> <appender name="RollingFile1" type="log4net.Appender.RollingFileAppender"> <!-- If multiple instances of Jobs need to run pointing at the same executable, use can use PatternString to have them log to separate files. Logging to same file from multiple instances of this executable can lead to unpredicatable results in logging as should be obvious. --> <!--<file type="log4net.Util.PatternString" value="Logs\%appdomain.log" />--> <file value="Logs\Job1.log" /> <appendToFile value="true" /> <maximumFileSize value="10240KB" /> <maxSizeRollBackups value="10" /> <countDirection value="1" /> <preserveLogFileNameExtension value="true" /> <!-- Can customise default pattern below --> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message%newline" /> </layout> </appender> <appender name="RollingFile2" type="log4net.Appender.RollingFileAppender"> <!-- If multiple instances of Jobs need to run pointing at the same executable, use can use PatternString to have them log to separate files. Logging to same file from multiple instances of this executable can lead to unpredicatable results in logging as should be obvious. --> <!--<file type="log4net.Util.PatternString" value="Logs\%appdomain.log" />--> <file value="Logs\Job2.log" /> <appendToFile value="true" /> <maximumFileSize value="10240KB" /> <maxSizeRollBackups value="10" /> <countDirection value="1" /> <preserveLogFileNameExtension value="true" /> <!-- Can customise default pattern below --> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%-4timestamp [%thread] %-5level %logger %ndc - %message%newline" /> </layout> </appender> <!-- Set root logger level to DEBUG and its only appender to RollingFile --> <root> <level value="DEBUG" /> <appender-ref ref="RollingFile1" /> </root> <!-- Configure options for all jobs. Alternatively use Job.JobName to configure options for a single job --> <logger name="Job2"> <appender-ref ref="RollingFile2" /> </logger> </log4net> |