Writing only the topmost element of the ThreadContext stack
We are using Log4net ThreadContext.Stacks and mostly work well. My problem occurs if there were multiple ThreadContext.Stacks ["key"]. Push (...).
With a simple ConversionPattern:
<param name="ConversionPattern value="... topProp=%properties{key} ..."/>
I see log entries like:
... topProp=first second third ...
I would really like to see only the last pressed value, not all values. I was hoping that something like the following could be added in my appender / layout / ConversionPattern:
<param name="ConversionPattern value="... topProp=%properties{key}{1} ..."/>
but that won't work. I can kill it by assuming / requiring all values ββto be the same length (e.g. 5) and doing:
<param name="ConversionPattern value="... topProp=%5.5properties{key} ..."/>
But it's not really attractive. Any ideas?
Thank!
[Edit to add a very simple example]
using System;
using System.IO;
using log4net;
using log4net.Config;
namespace ThreadLocalExample {
class Program {
private const string PropJobId = "Example:JobId";
static void Main() {
XmlConfigurator.Configure(new FileInfo("log4net.cfg"));
var log = LogManager.GetLogger(typeof(Program));
ThreadContext.Stacks[PropJobId].Push("Old");
log.Debug("Enter using");
using (ThreadContext.Stacks[PropJobId].Push("New")) {
log.Debug("stuff");
}
log.Debug("Out of using");
log.Debug("done.");
Console.ReadKey();
}
}
}
With log4net config:
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<threshold value="ALL" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="[jobId=%P{Example:JobId}]: %m%n" />
</layout>
</appender>
Outputs:
[jobId=Old]: Enter using
[jobId=Old New]: stuff
[jobId=Old]: Out of using
[jobId=Old]: done.
But I would like:
[jobId=Old]: Enter using
[jobId=New]: stuff
[jobId=Old]: Out of using
[jobId=Old]: done.
source to share
I had the same problem and it was not just about "bad formatting" because I was using a database application (AdoNetAppender) that expects integers. Thus, after combining all the summed values, the result is no longer an integer. Consider an application like this:
<appender name="DbAppender" type="log4net.Appender.AdoNetAppender">
...
<commandText value="INSERT INTO Log ([Id]) VALUES (@Id)" />
<parameter>
<parameterName value="@Id" />
<dbType value="Int32" />
<layout type="log4net.Layout.PatternLayout" value="%P{someId}" />
</parameter>
This appender accepts no log message where "someId" gets stacked twice or more - no logs in the database ...
So, to solve this problem, I ditched stacks and went back to flat properties. I have coded a little extension:
public static class Log4NetExt {
public static IDisposable ThreadContextPush(string key, object value) {
object oldVal = ThreadContext.Properties[key];
ThreadContext.Properties[key] = value;
var topMostCleaner = new DispCleaner();
topMostCleaner.EvDispose += () => {
// Pop = restore old value
ThreadContext.Properties[key] = oldVal;
};
return topMostCleaner;
}
private class DispCleaner : IDisposable {
public event Action EvDispose;
public void Dispose() {
if (EvDispose != null) EvDispose();
}
}
}
And now instead of:
using (ThreadContext.Stacks[PropJobId].Push("New")) {
records:
using (Log4NetExt.ThreadContextPush(PropJobId, "New")) {
and it works fine;)
(This shortcode doesn't follow all the best practices for creating disposable objects, removing event handlers and all that stuff, but it's short and I think it's safe in this simple case.)
source to share
I did this with Filip's solution modified as
public static class Log4NetExt
{
public static IDisposable ThreadContextSet(string key, object value)
{
//object oldVal = ThreadContext.Properties[key];
ThreadContext.Properties[key] = value;
var topMostCleaner = new DispCleaner();
topMostCleaner.EvDispose += () => {
// Pop = restore old value
//ThreadContext.Properties[key] = oldVal;
ThreadContext.Properties[key] = null;
};
return topMostCleaner;
}
private class DispCleaner : IDisposable
{
public event Action EvDispose;
public void Dispose()
{
if (EvDispose != null)
{
EvDispose();
}
}
}
}
And used it like
using (Log4NetExt.ThreadContextSet ("ContextId", "Feed")) using (Log4NetExt.ThreadContextPush ("ContextValue", objFeed.FeedId)) {
}
source to share