Serilog only logs first element of array

8.3k Views Asked by At

I'm using Serilog 2.10.0 for logging in an ASP.NET Core Application on .NET 5. I'm running into a problem when trying to log an event where there is only one parameter and this parameter is an array. The following is some example code and the log output with a JSON file sink:

var myArray = new string[] { "foo", "bar" };
logger.LogInformation("Log stuff: {@MyArray}", myArray);
{"@t":"2021-02-22T14:09:46.8673482Z","@mt":"Log stuff: {@MyArray}","MyArray":"foo","SourceContext":"MyNamespace.MyClass"}

The logger is an ILogger injected via dependency injection. The logged event only contains the first element of my string array. If I add another parameter the string array is correctly logged. I tried this with and without the @ and also with a different sink.

This is a modified example with an additional parameter, and this works as I would expect:

var myArray = new string[] { "foo", "bar" };
logger.LogInformation("Log stuff: {baz} {@MyArray}", "baz", myArray);
{"@t":"2021-02-22T14:19:21.3580354Z","@mt":"Log stuff: {baz} {@MyArray}","baz":"baz","MyArray":["foo","bar"],"SourceContext":"MyNamespace.MyClass"}

I suspect I'm misunderstanding something here about how the variadic function determines the mapping between parameters and variables in the template string. But I couldn't figure out a way to get this to work properly without adding irrelevant additional parameters.

How do I get Serilog to properly handle a log message with a single array as a parameter?

3

There are 3 best solutions below

3
On BEST ANSWER

I was able to reproduce your issue. Issue was flagged by Resharper as shown in the screenshot below.

enter image description here

If we enter the source for LoggerExtensions#LogInformation, it has the following signature:

public static void LogInformation(this ILogger logger, string message, params object[] args)

The last parameter is params object[] args, and when passing in a single array, this method believes the array is a list of parameters and casts the array to object[], while your intention was to pass in the array as a parameter. This is confirmed by changing the template like this:

_logger.LogInformation("Log stuff: {FirstElement} {SecondElement}", myArray); 

which will output Log stuff: foo bar. The above code has the same logic and behaviour as this:

_logger.LogInformation("Log stuff: {FirstElement} {SecondElement}", "foo", "bar");

Changing the array to e.g. a list or, as @IharYakimush suggests, simply cast array to object, does the trick because the collection will now be treated as a parameter, not a list of parameters.

_logger.LogInformation("Log stuff: {MyArray}", (object)myArray);
0
On

Perhaps you could change you log statement to something like

logger.LogInformation("Log stuff: {baz} {myArrayString}", "baz",  String.Join(", ", myArray));
2
On

The destructuring operator @ that you are using is probably the cause of the issue. According to Serilog docs, https://github.com/serilog/serilog/wiki/Structured-Data#collections, this should work out of the box:

var fruit = new[] { "Apple", "Pear", "Orange" };
Log.Information("In my bowl I have {Fruit}", fruit);

This would produce following JSON:

{ "Fruit": ["Apple", "Pear", "Orange"] }

So, no casting to object is necessary!

EDIT:

After seeing the comment from @camelcase and investigating further, it seems that the above code (provided from Serilog docs) does not workas intented, this does the trick though (again, no casting and no destructuring operator):

List<string> fruit = new List<string> { "Apple", "Pear", "Orange" };
Log.Information("In my bowl I have {Fruit}", fruit);