带有log4net.Ext.Json的自定义PatternLayoutConverter



我有以下log4net配置:

<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type='log4net.Layout.SerializedLayout, log4net.Ext.Json'>
<renderer type='log4net.ObjectRenderer.JsonDotNetRenderer, log4net.Ext.Json.Net'>
<DateFormatHandling value="IsoDateFormat" />
<NullValueHandling value="Ignore" />
</renderer>
<converter>
<name value="preparedMessage" />
<type value="JsonLogs.CustomLayoutConverter" />
</converter>
<default />
<remove value='message' />
<remove value='ndc' />
<member value='message:messageObject' />
<member value='details:preparedMessage' />
</layout> 
</appender>
<appender name="Console2" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="preparedMessage" />
<type value="JsonLogs.CustomLayoutConverter" />
</converter>
<conversionPattern value="%level %thread %logger - %preparedMessage%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="Console" />
<appender-ref ref="Console2" />
</root>
</log4net>

使用我的自定义PatternLayoutConverter:的以下实现

namespace JsonLogs
{
using System.IO;
using log4net.Core;
using log4net.Layout.Pattern;
public class CustomLayoutConverter : PatternLayoutConverter
{
#region Methods
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (loggingEvent.MessageObject is string stringMessage)
{
writer.Write(new { message = stringMessage });
}
else
{
writer.Write(loggingEvent.RenderedMessage);
}
}
#endregion
}
}

出于某种原因,转换器与Console2 appender(不是JSON驱动的(配合使用非常好,但与输出为JSON的Console appender不配合使用。输出示例:

Console -> {"date":"2018-12-09T12:25:28.0529041+03:00","level":"INFO","appname":"JsonLogs.exe","logger":"JsonLogs.Program","thread":"1","message":"Test","details":"preparedMessage"}
Console2 -> INFO 1 JsonLogs.Program - { message = Test }

我的目标是使details始终处于JSON中,这就是为什么我引入了自己的转换器来捕获基元值并将它们封装在自定义对象中。

我的配置有错吗?或者我错过了什么?你能帮我弄清楚吗?

谢谢

这个问题似乎是log4net.Ext.Json的一个bug。我将在他们的GitLab上报告它。到目前为止,我最终得到了我的自定义log4net布局,它看起来像这个

public class CustomLayout : PatternLayout
{
#region Public Methods and Operators
public override void Format(TextWriter writer, LoggingEvent loggingEvent)
{
var message = loggingEvent.MessageObject.GetType().IsPrimitive || loggingEvent.MessageObject is string || loggingEvent.MessageObject is decimal || loggingEvent.MessageObject is BigInteger
? new { message = loggingEvent.MessageObject }
: loggingEvent.MessageObject;
writer.WriteLine(JsonConvert.SerializeObject(new
{
timestamp = loggingEvent.TimeStampUtc,
threadId = loggingEvent.ThreadName,
details = message,
logger = loggingEvent.LoggerName,
level = loggingEvent.Level.DisplayName,
user = loggingEvent.UserName
}));
}
#endregion
}

它满足了我的需求,而且正是我想要的。

这个问题的确切位置是AddMember方法及其实现。以下是SerializedLayout的源代码:

public virtual void AddMember(string value)
{
var arrangement = log4net.Util.TypeConverters.ArrangementConverter.GetArrangement(value, new ConverterInfo[0]);
m_arrangement.AddArrangement(arrangement);
}

正如您所看到的,GetArrangment的第二个参数是ConverterInfo的空数组,尽管必须有我们自定义的附加参数(通过AddConverter方法或xml(。作为解决方案,您可以实现自己的子类,该子类将通过重写AddMember从SerializedLayout派生,如下所示:

public override void AddMember(string value)
{
var customConverter = new ConverterInfo("lookup", typeof(CustomPatternConverter));
var arrangement = log4net.Util.TypeConverters.ArrangementConverter.GetArrangement(value, new ConverterInfo[] { customConverter });
m_arrangement.AddArrangement(arrangement);
}

希望这对我的案子有帮助!

最新更新