I have database logging in place using the AdoNetAppender. What I’d like to do is log the user identity on each log statement. However, I don’t want to use the standard log4net %identity parameter for two reasons:
- log4net warn that its extremely slow as it has to look up the context identity.
- In some service components the standard identity is a service account but we have already captured the user identity in a variable and I’d like to use that.
I have seen code where some people use the log4net.ThreadContext to add additional properties but I understand that this is ‘unsafe’ due to thread interleaving (and it is also a performance drain).
My approach has been to extend the AdoNetAppenderParameter class thus:
public class UserAdoNetAppenderParameter : AdoNetAppenderParameter
{
public UserAdoNetAppenderParameter()
{
DbType = DbType.String;
PatternLayout layout = new PatternLayout();
Layout2RawLayoutAdapter converter = new Layout2RawLayoutAdapter(layout);
Layout = converter;
ParameterName = "@username";
Size = 255;
}
public override void Prepare(IDbCommand command)
{
command.Parameters.Add(this);
}
public override void FormatValue(IDbCommand command, LoggingEvent loggingEvent)
{
string[] data = loggingEvent.RenderedMessage.Split('~');
string username = data[0];
command.Parameters["@username"] = username;
}
}
and then programmatically add this to the current appender like so:
ILog myLog = LogManager.GetLogger("ConnectionService");
IAppender[] appenders = myLog.Logger.Repository.GetAppenders();
AdoNetAppender appender = (AdoNetAppender)appenders[0];
appender.AddParameter(new UserAdoNetAppenderParameter());
myLog.InfoFormat("{0}~{1}~{2}~{3}", userName, "ClassName", "Class Method", "Message");
The intention here is to use a standard format for messages and parse the first part of the string which should always be the username. The FormatValue() method of the custom appender parameter should then use only that part of the string so that it can be written to a separate field in the log database.
My problem is that no log statements are written to the database. Oddly, when debugging, a breakpoint in the FormatValue() method is only hit when I stop the service.
I’ve trawled through loads of stuff relating to this but haven’t yet found any answers.
Has anyone managed to do this, or am I on the wrong trail.
P.S. I’ve also tried extending the AdoNetAppender but it doesnt give you access to set the parameter values.
After some experimentation, I finally got this to work. Ensuring that log4net’s internal logging helped identify the errors and downloading the log4net source code and reviewing the AdoNetAppenderParameter class showed how the FormatValue() method should be used. So, here’s the amended custom appender parameter:
And to use this, I add it in the log4net config file like this:
And by convention, my log statements will be something like this:
If you look at the class, it uses data[0] as the username, so it is dependant on following the convention. It does, however, get the username into its own parameter and into a separate field in the log database table, without resorting to stuffing it temporarily into the unsafe ThreadContext.