Add custom fields in Log4Net PatternString

265 Views Asked by At

I'm working on getting my logging pattern into a standard set by my company and could use some help. I need to log from my application into JSON, so I put together a Json layout that extends the LayoutSkeleton

public class JsonLayout : LayoutSkeleton
{

    public override void ActivateOptions()
    {
    }

    public override void Format(TextWriter writer, LoggingEvent e)
    {
        var dic = new Dictionary<string, object>
        {
            ["level"] = e.Level.DisplayName,
            ["message"] = e.MessageObject,
            ["timestampUtc"] = e.TimeStamp.ToUniversalTime().ToString("O"),
            ["userName"] = e.UserName
        };
        writer.WriteLine(JsonConvert.SerializeObject(dic));
    }
}

and reference it in my app.config as follows

    <appender name="file" type="log4net.Appender.RollingFileAppender,log4net">
        <rollingStyle value="Date" />
        <datePattern value="yyyy-MM-dd" />
        <preserveLogFileNameExtension value="true" />
        <staticLogFileName value="false" />
        <file type="log4net.Util.PatternString" value="c:\\Logs\\.log" />
        <layout type="MyPackage.JsonLayout">
        </layout>
    </appender>

And then when we log, it's simple:

Dictionary<string, string> logObject = new Dictionary<string, string>
{
   { "systemId", systemId },
   { "workflowId", workflowId },
   { "transitionName", transitionName },
   { "instrumentId", instrumentId },
   { "instrumentName", instrumentName },
   { "commandId", commandId.ToString() },
   { "message", statusMessage }                    
};
Logger.Info(logObject);

This works. However, what ends up getting logged is

{
"level": "INFO",
"message": {
    "systemId": "LM9125",
    "workflowId": "WF7656789876",
    "transitionName": "My transition",
    "instrumentId": "LM123",
    "instrumentName": "Centrifuge 1",
    "commandId": "d21fb5fa-5b39-4c43-94cc-72dc6a7a174d",
    "message": "Centrifuge 1 started"
},
"timestampUtc": "2022-05-06T20:23:06.5136166Z",
"userName": "labuser"

}

What has been requested is that in the logged message I remove the nesting from within the message object so that the log statement becomes

{
  "level": "INFO",
  "systemId": "LM9125",
  "workflowId": "WF7656789876",
  "transitionName": "My transition",
  "instrumentId": "LM123",
  "instrumentName": "Centrifuge 1",
  "commandId": "d21fb5fa-5b39-4c43-94cc-72dc6a7a174d",
  "message": "Centrifuge 1 started",
  "timestampUtc": "2022-05-06T20:23:06.5136166Z",
  "userName": "labuser"
}

I've updated the Format method to be

...
var dic = new Dictionary<string, object>
{
   ["level"] = e.Level.DisplayName,
   ["systemId"] = "",
   ["workflowId"] = "",
   ["transitionName"] = "",
   ["instrumentId"] = "",
   ["instrumentName"] = "",
   ["message"] = e.MessageObject,
   ["timestampUtc"] = e.TimeStamp.ToUniversalTime().ToString("O"),
   ["userName"] = e.UserName
 };
 writer.WriteLine(JsonConvert.SerializeObject(dic));
 ...

What I can't seem to figure out is how to pull the values out of the e.MessageObject so I can put the values into the necessary fields. From within the debugger I can see that the e.MessageObject is of type object {System.Collections.Generic.Dictionary<string, string>}.

When I try to access it like a normal dictionary by using e.MessageObject["systemId"] I get the following error message error CS0021: Cannot apply indexing with [] to an expression of type 'object'. I'm not a .NET programmer and cannot find a solution to this silly error. Please help!

I've searched on stack overflow and the closest answer I've found is this. But it wasn't helpful in getting around this problem.

1

There are 1 best solutions below

0
Sam Axe On

object is a kind of generic type that means "I can store nearly anything of nearly any type here". So the compiler doesn't know what type will be there, so it's refusing to apply the indexing semantics to it.

You need to let the compiler know what kind of object you are dealing with...

Something along the lines of:

Dictionary<string,string> messages = e.MessageObject as Dictionary<string,string>;
if(null == messages) {
    // The thing stored here is NOT a Dictionary<string,string>.
    throw new Exception("not the right type");
}
// code to work with the Dictionary...  messages["systemId"]