Custom PatternLayoutConverter with log4net.Ext.Json?

1.8k views Asked by At

I have the following log4net configuration:

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

with the following implementation of my custom 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
    }
}

For some reason, the converter works perfectly fine with the Console2 appender(which is not JSON driven) but it doesn't work with the Console appender whose output is JSON. Example of the output:

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 }

My goal is to have details always in JSON that's why I introduced my own converter to catch primitive values and wrap them in a custom object.

Is my configuration wrong? Or I'm missing something? Could you help me, please, to figure this out?

Thank you

2

There are 2 answers

0
Dmitry Senin On BEST ANSWER

The issue seems to be a bug of log4net.Ext.Json. I'm going to report it on their GitLab. So far, I ended up with my custom log4net layout which looks like this

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
}

it meets my needs and does exactly what I want.

0
ISkandar On

The exact place of this problem is AddMember Method and its implementation. Here is SerializedLayout source code for that:

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

As you can see the second parameter of GetArrangment is empty array of ConverterInfo, Though there must be our custom attached ones (by AddConverter method or by xml). As the solution you can implement your own subclass that will derive from SerializedLayout with overridden AddMember like this:

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

Hope it helps as it did with my case!