log4net doesn't do the correct PatternString substitution for my login name. I want my log to be 
Logs\YYYYMMDD\MSMQcore_[username].log
When I use the %username property, I get the domain in the path, which adds another folder indirection in there. I only want the user name.
Logs\YYYYMMDD\MSMQcore_[domain]\[username].log
Anyone have an example of inserting the "username" in the appender's file name? I've tried a bunch of things, I'm still scratching my head.
<appender name="core_Appender" type="log4net.Appender.RollingFileAppender" >
<!-- <file type="log4net.Util.PatternString"  value="Logs/%date{yyyyMMdd}/MSMQcore_%identity.log" /> -->
<!-- <file type="log4net.Util.PatternString"  value="Logs/%date{yyyyMMdd}/MSMQcore_%property{user}.log" /> -->
<file type="log4net.Util.PatternString"  value="Logs/%date{yyyyMMdd}/MSMQcore_%username.log" />
</appender>
Using the environment variable pattern works for me:
<file type="log4net.Util.PatternString" value="Logs\\%env{USERNAME}.txt" />
Update: if the USERNAME environment variable is not an option, subclassing PatternString could be an alternative. Here is a simple implementation:
public class MyPatternString : PatternString
{
    public MyPatternString()
    {
        AddConverter("usernameonly", typeof(UserNameOnlyConverter));
    }    
}
public class UserNameOnlyConverter : PatternConverter 
{
    override protected void Convert(TextWriter writer, object state) 
    {
        var windowsIdentity = WindowsIdentity.GetCurrent();
        if (windowsIdentity != null && windowsIdentity.Name != null)
        {
            var name = windowsIdentity.Name.Split('\\')[1];
            writer.Write(name);
        }
    }
}
The new setting will look like this:
<file type="MyPatternString" value="Logs\\%usernameonly.txt" />
Update 2: to answer why %identity and %property{user} doesn't work:
The %identity pattern picks up the identity property on the current thread. This property is in my tests null, and is probably so until one assigns a specific Windows identity to the running thread. This will not work in the context of the appender because you will not know which thread will perform the actual appending.
The %property pattern picks up properties from the GlobalContext and ThreadContext classes. By default, only the log4net:HostName (LoggingEvent.HostNameProperty) is registered in the GlobalContext. So unless you actively register properties in those contexts you cannot use them with the %property pattern. Again, ThreadContext is useless in the context of the appender since you have no way of knowing which thread will be doing the appending.
That said, registering a property called username in the GlobalContext.Properties collection, somewhere in the application startup routine perhaps, will enable the %property{username} to work as expected.
Peter's answer almost worked for me; it definitely set me on the right path because I needed a similar solution. What I had to do was subclass PatternConverter:
public class ConfigurationSettingsConverter : PatternConverter
{
    protected override void Convert(TextWriter writer, object state)
    {
        // use Option as a key to get a configuration value...
        if (Option != null)
            writer.Write(ConfigUtils.Setting[Option]);
    }
}
and add this converter in the ActivateOptions override of a subclass of PatternString:
public class ConfigurationSettingsPatternString : PatternString
{
    public ConfigurationSettingsPatternString()
    {}
    public ConfigurationSettingsPatternString(string pattern): base(pattern)
    {}
    public override void ActivateOptions()
    {
        AddConverter("cs", typeof(ConfigurationSettingsConverter));
        base.ActivateOptions();
    }
}
I originally tried to do this in the constructor as Peter answered, but the converter was not returned from the pattern string's underlying call to parse the source string.  I also had to register a type converter (not to be confused with a PatternConverter) anywhere in the code path before log4net was configured:
ConverterRegistry.AddConverter(
    // type we want to convert to (from string)...
    typeof(ConfigurationSettingsPatternString),
    // the type of the type converter that will do the conversion...
    typeof(ConfigurationSettingsPatternStringConverter));
Not doing this prevents log4net from being able to convert the value attribute in a FileAppender's file node (i.e. a string) into a ConfigurationSettingsPatternString. For example, in this configuration fragment,
<file
  type="Some.Name.Space.ConfigurationSettingsPatternString, Some.Assembly"
  value="some\path\MyLog.%cs{SomeKey}.log" />
%cs.{SomeKey} would not get expanded, and log4net throws an exception. Here's the code for the type converter:
public class ConfigurationSettingsPatternStringConverter : IConvertTo, IConvertFrom
{
    public bool CanConvertFrom(Type sourceType)
    {
        return sourceType == typeof(string);
    }
    public bool CanConvertTo(Type targetType)
    {
        return typeof(string).IsAssignableFrom(targetType);
    }
    public object ConvertFrom(object source)
    {
        var pattern = source as string;
        if (pattern == null)
            throw ConversionNotSupportedException.Create(typeof(ConfigurationSettingsPatternString), source);
        return new ConfigurationSettingsPatternString(pattern);
    }
    public object ConvertTo(object source, Type targetType)
    {
        var pattern = source as PatternString;
        if (pattern == null || !CanConvertTo(targetType))
            throw ConversionNotSupportedException.Create(targetType, source);
        return pattern.Format();
    }
}
This turns out to work well for Windows multiple services hosted within the same executable (for example, you might add a %serviceName pattern as the file name to separate the services' logs.
Using "%username" works for me;
<parameter>
    <parameterName value="@identity" />
    <dbType value="String" />
    <size value="255" />
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%username" />
    </layout>
</parameter>
But then again I am in the context of a standard WinForms application, not an ASP.NET app. Not sure if this is what you're looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With