5

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>
Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
Craig
  • 173
  • 2
  • 3
  • 9
  • You can get this via an appender pattern in Log4Net version 1.2.11. See stackoverflow.com/a/26277219/203371 – Ben Smith Oct 09 '14 at 11:31

5 Answers5

12

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
  • 33,668
  • 7
  • 97
  • 131
  • 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 Oct 14 '09 at 09:29
  • 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 Jul 15 '10 at 18:58
  • @Peter, the `ThreadContext` is set when the logging statement is made, not when the message is appended. So, it's the thread where you call `Log.Info("blah blah blah")`. – Anthony Mastrean Jun 16 '11 at 04:00
  • @Anthony, yes that is correct. My point here is that at the time the appender generates the filename, there is no ThreadContext since it is not necessarily done as part of a logging statement. – Peter Lillevold Jun 16 '11 at 08:51
  • Oh, for use in generating the appender filename, right? I see a lot of people trying to generate custom filenames, I think they're better off coming up with a different solution. log4net questions really need to be phrased as "how do I achieve this goal" not "how do I perform this task". There's too many ways to configure a system and too little advice on what's prudent. – Anthony Mastrean Jun 16 '11 at 11:38
  • I tend to agree. It is cool though to see how log4net still is flexible enough to handle a lot of scenarios. – Peter Lillevold Jun 16 '11 at 13:12
4

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.

Kit
  • 20,354
  • 4
  • 60
  • 103
1

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.

Mel Padden
  • 983
  • 1
  • 9
  • 21
1

Just one improvement to the Kit's solution: Use attribute

[TypeConverter("namespace.ConfigurationSettingsPatternStringConverter")]
public class ConfigurationSettingsPatternString : PatternString
{

And the call to

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));

is no longer required.

user1073029
  • 116
  • 1
  • 3
0
<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="Running on ${COMPUTERNAME} / ${USERNAME} %newline %logger %date%newline Thread ID=[%thread]%newline %-5level - %message%newline" />
</layout>

This worked well for me.

Corey
  • 51
  • 1
  • 1