There are various little gotchas writing software for Vista. I've posted before about ridiculous things like logging not working. You must use the Admin token to write to the Program Files directory so in some cases your program will silently fail and create no logs. I've also been looking for a way to elevate a single action from .NET: an application that can run without elevation but has a button with the cute shield icon that invokes an action that requests elevation from the user. At any rate, I just switched to Enterprise Library from log4net and had to get it to produce log files in a User location rather than \program files\myprogdir and the configuration (though the tools are nice) do not support this. Luckily Enterprise Library 3.1 comes with the source so I dug around and found what to do. To save you the trouble.
I'm using RollingFlatFileTraceListener. This class only has one constructor:
public class UserScopeRollingFileListener : Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener
{
public UserScopeRollingFileListener(string fileName, string header, string footer, ILogFormatter formatter, int rollSizeKB, string timeStampPattern, RollFileExistsBehavior rollFileExistsBehavior, RollInterval rollInterval)
: base(GetUserScopedFilePath(fileName), header, footer, formatter, rollSizeKB, timeStampPattern, rollFileExistsBehavior, rollInterval)
{
}
protected static string GetUserScopedFilePath(string logFileName)
{
string path = logFileName;
if (Environment.OSVersion.Version.Major > 5)
{
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
path = System.IO.Path.Combine(homeDir, logFileName);
}
return path;
}
}
So, in the constructor call the GetUserScopedFilePath(). If the OS is Vista, it takes the log file name from the app.config file and maps it into My Documents. It could just as easily be Local Application Data or something else. The Logging configuration uses a "Data" class associated with each listener to build to the listener class. This seems odd since this Data class explicitly invokes the constructor of the Listener even though the configuration requires the Type of the listener to be present in the app.config.
/// <summary>
/// Represents the configuration data for a <see cref="RollingFlatFileTraceListenerData"/>.
/// </summary>
[Assembler(typeof(UserScopedRollingTraceListenerAssembler))]
public class UserScopedRollingTraceListenerData : Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.RollingFlatFileTraceListenerData
{
public UserScopedRollingTraceListenerData()
{
}
}
/// <summary>
/// This type supports the Enterprise Library infrastructure and is not intended to be used directly from your code.
/// Represents the process to build a <see cref="RollingFlatFileTraceListener"/> described by a <see cref="RollingFlatFileTraceListenerData"/> configuration object.
/// </summary>
/// <remarks>This type is linked to the <see cref="RollingFlatFileTraceListenerData"/> type and it is used by the Custom Factory
/// to build the specific <see cref="TraceListener"/> object represented by the configuration object.
/// </remarks>
public class UserScopedRollingTraceListenerAssembler : RollingTraceListenerAssembler
{
public UserScopedRollingTraceListenerAssembler()
{
}
/// <summary>
/// This method supports the Enterprise Library infrastructure and is not intended to be used directly from your code.
/// Builds a <see cref="FlatFileTraceListener"/> based on an instance of <see cref="FlatFileTraceListenerData"/>.
/// </summary>
/// <seealso cref="TraceListenerCustomFactory"/>
/// <param name="context">The <see cref="IBuilderContext"/> that represents the current building process.</param>
/// <param name="objectConfiguration">The configuration object that describes the object to build. Must be an instance of <see cref="FlatFileTraceListenerData"/>.</param>
/// <param name="configurationSource">The source for configuration objects.</param>
/// <param name="reflectionCache">The cache to use retrieving reflection information.</param>
/// <returns>A fully initialized instance of <see cref="FlatFileTraceListener"/>.</returns>
public override TraceListener Assemble(IBuilderContext context, TraceListenerData objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
{
UserScopedRollingTraceListenerData castObjectConfiguration
= (UserScopedRollingTraceListenerData)objectConfiguration;
ILogFormatter formatter = GetFormatter(context, castObjectConfiguration.Formatter, configurationSource, reflectionCache);
UserScopeRollingFileListener createdObject
= new UserScopeRollingFileListener(
castObjectConfiguration.FileName,
castObjectConfiguration.Header,
castObjectConfiguration.Footer,
formatter,
castObjectConfiguration.RollSizeKB,
castObjectConfiguration.TimeStampPattern,
castObjectConfiguration.RollFileExistsBehavior,
castObjectConfiguration.RollInterval
);
return createdObject;
}
}
Now, if you used the Enterprise Library configuration tool to create the RollingFlatFileListener configuration, you need to make two changes:
<
listeners>
<
add fileName="MyLogFileName.log" rollSizeKB="0" timeStampPattern="yyyy-MM-dd"
rollFileExistsBehavior="Overwrite" rollInterval="None" formatter="Text Formatter"
header="" footer=""
listenerDataType
="MyNS.MyNS.UserScopedRollingTraceListenerData, MyAssembly.Name"
traceOutputOptions="None"
type
="MyNS.MyNS.UserScopeRollingFileListener, MyAssembly.Name"
name="Rolling Flat File Trace Listener" />
</
listeners>
One other thing I wanted to change about the logging, is that I like the log4net syntax for different log levels. You would get an ILog implementation and call log.Debug(message) or log.Error(message) or log.Fatal(message). With the joy of Extension Methods you can create your own on-the-fly facade with the compiler doing most of the work:
public static class LogExtensions
{
static void Debug(this LogWriter l, string message)
{
LogEntry entry = new LogEntry();
entry.Message = message;
entry.Severity = System.Diagnostics.TraceEventType.Verbose;
l.Write(entry);
}
static void Error(this LogWriter l, string message)
{
LogEntry entry = new LogEntry();
entry.Message = message;
entry.Severity = System.Diagnostics.TraceEventType.Error;
l.Write(entry);
}
}
So extension methods now allow me to say Logger.Writer.Error("An error has occurred: value was null"). I am now happy with the Logging application block.