tags:

views:

772

answers:

2

Hi, Log4Net doesn't do the correct patternString substitution for my login name (minus domian) that I'm expecting. Anyone have an example of inserting the "username" in the apppender'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>

I want my log to be "Logs/YYYYMMDD/MSMQcore_%USERNAME.log" .... when I use the %username property, I get the "domain\nusername" ... which adds another folder indirection in there. I only want the user name.

anyone ?

Thanks,

Craig

+3  A: 

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 Lillevold
Peter, Thanks ... this also worked for me. Lucky the environment variable was set. I'm still not sure why the other versions didn't work. perhaps the PatternString only parses the string in one pass ??tvm,Craig
Craig
Thanks a bunch. This is awesome.
Skinniest Man
Craig, I think calling AddConverter too soon in the code may have been part of the problem. See my answer below for what worked for me.
Kit
A: 

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 services hosted in the same executable (for example, you might a %serviceName pattern as the file name to separate the services' logs.

Kit