
Tuesday, November 30, 2004
We use log4net 1.2 beta 8 in our web applications for tracing and debugging purposes. Log4Net is great, but as with any community project, you always wish it had built in functionality to do a few extra things. In our case, I wanted to be able to log all statements to a file in a folder that exists off the web root. By default, log4net will log to a file relative to the virtual directory where the application that is logging is defined. For example, the following log4net configuration will log to a file named TmsLog.txt in the same folder as the web.config that this entry exists in:
156 <log4net>
157 <appender name="TMSAppender" type="log4net.Appender.FileAppender,log4net">
158 <param name="File" value="TmsLog.txt" />
159 <param name="AppendToFile" value="true" />
160 <layout type="log4net.Layout.PatternLayout,log4net">
161 <param name="ConversionPattern" value="%d %-5p - %m%n" />
162 </layout>
163 </appender>
164 <root>
165 <priority value="DEBUG" />
166 <appender-ref ref="TMSAppender" />
167 </root>
168 </log4net>
I wanted to be able to specify the file location at run time, which lead me into a few traps.
A coworker pointed out that I could simply use relative paths in the configuration file:
158 <param name="File" value="..\\LogLocation\\TmsLog.txt" />
This is ok, but our Dev, QA, and Production environments are all a little different. We'd written a utility object that uses some logic to determine the current execution environment and return the correct folder to use for logging purposes. What I really wanted was to be able to use the information provided by this library to change the logging folder at runtime. One option was to create the configuration programmatically, but I wanted to stay with the basic DomConfigurator.Configure() solution.
The first programmatic solution that I tried was to change the file path at runtime. The following code does allow me to change the path of the configured path, but for some reason it didn't 'stick', and the path used in the configuration file was used instead:
22 ILog myLog=LogManager.GetLogger(typeof(Logger));
23 ILoggerRepository aRepos = myLog.Logger.Repository;
24 foreach(IAppender anAppender in ((log4net.Repository.Hierarchy.Hierarchy)aRepos).Root.Appenders)
25 if (anAppender is FileAppender)
26 ((FileAppender)anAppender).File = @"c:\temp\test.txt";
27 return myLog;
My shorthand approach to this was the following:
28 public static string LogFile
29 { 30 get{ return ((FileAppender)((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetLoggerRepository()).Root.Appenders[0]).File;} 31 set{ ((FileAppender)((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetLoggerRepository()).Root.Appenders[0]).File = value;} 32 }
Again, no dice. Finally, after a little digging, I came across an email on the log4Net discussion list that pointed me in the right direction. I created my own FileAppender class that derived from the log4net FileAppender, and overwrote the File property:
76 public class InsarioWebLogFileAppender : log4net.Appender.FileAppender
77 { 78 public override string File
79 { 80 get{return base.File;} 81 set{base.File = Web.WebEnvironment.LogDirectory + value;} 82 }
83 }
Originally I tried to store the file setting in a local variable and return it in the getter but that didn't work, probably due to caching and optimization techniques used in the internals of log4net. Instead, I had to set the property on the base entity, as you see above.