Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsonConsoleFormatter to use array of scope to fix unique key issue #39860

Merged
1 commit merged into from
Jul 27, 2020

Conversation

maryamariyan
Copy link
Member

Fixes duplicate keys in Json
Fixes: #39664

@ghost
Copy link

ghost commented Jul 23, 2020

Tagging subscribers to this area: @maryamariyan
See info in area-owners.md if you want to be subscribed.

var timestampFormat = FormatterOptions.TimestampFormat;
if (timestampFormat != null)
{
var dateTime = FormatterOptions.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
timestamp = dateTime.ToString(timestampFormat);
writer.WriteString("Timestamp", dateTime.ToString(timestampFormat));
Copy link
Member Author

@maryamariyan maryamariyan Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also would skip showing Timestamp when value is null

}
else if (logEntry.State != null)
{
writer.WriteString(nameof(logEntry.State), logEntry.State.ToString());
Copy link
Member Author

@maryamariyan maryamariyan Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note the current implementation is missing the rendered message for some of the scopes, not sure if that should be tracked as separate issue?

(taken from #39664 (comment))
@alefranz I think this is what you were asking for.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, I was referring to the scope. Sorry I should have raised the issue before. Here it is: #39867

Regarding this change, in which scenarios would the state not be a k/v collection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding this change, in which scenarios would the state not be a k/v collection?

Refer to test: Log_IncludeScopes_ContainsDuplicateNamedPropertiesInScope_AcceptableJson

            using (logger.BeginScope("scope1 {name1}", 123))
            using (logger.BeginScope("scope2 {name1} {name2}", 456, 789))
                logger.Log(LogLevel.Information, 0, state: "exception message", exception: null, formatter: (a, b) => a);

@maryamariyan maryamariyan requested review from layomia and tarekgh July 23, 2020 20:07
@@ -79,10 +77,16 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP

if (logEntry.State is IReadOnlyCollection<KeyValuePair<string, object>> stateDictionary)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IReadOnlyCollection [](start = 42, length = 19)

does it make sense to use IEnumerable rather than IReadOnlyCollection? this will give more coverage to other types.

Copy link
Member Author

@maryamariyan maryamariyan Jul 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is supposed to work if it is not IReadOnlyCollection

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why we are testing the type? doesn't mean you can get other types?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my understanding the contract expects IReadOnlyCollection<KeyValuePair<string, object>> and that is why I check for it. (Referring to @davidfowl 's comment in #34742 (comment))

Copy link
Member

@tarekgh tarekgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a question. other than that LGTM.

@alefranz
Copy link
Contributor

I'm not super convinced as while it solve the duplicate keys issue, I had originally in mind mainly the {OriginalFormat} field. I wonder if once this logs gets ingested into a queryable datastore, it would make harder to write queries against properties.

At least for me, the main scenario to use scopes is to attach properties to have in all log entries (e.g. a transaction id) so it needs to be possible to query the datastore with this property no matter how nested is the specific property.

For example:

using (logger.BeginScope("{Number}", 2))
using (logger.BeginScope("{AnotherNumber}", 3))
{
    logger.LogInformation("{LogEntryNumber}", 1);
}

will become:

{
  "Timestamp": "2020-07-23T22:09:59.4750365\u002B01:00",
  "EventId": 0,
  "LogLevel": "Information",
  "Category": "JsonLogging.Program",
  "Message": "1",
  "State": {
    "LogEntryNumber": "1",
    "{OriginalFormat}": "{LogEntryNumber}"
  },
  "Scopes": [
    {
      "Number": "2",
      "{OriginalFormat}": "{Number}"
    },
    {
      "AnotherNumber": "3",
      "{OriginalFormat}": "{AnotherNumber}"
    }
  ]
}

I need to play with ingest this entries into a system like Seq or Kibana.
My worry is that out of the box you will have to know the order of the scopes if you want for example to get all the logs entries with Number = 2, unless you define some custom indexing that would be needed for each of this properties.
I wonder if the properties should be stored in a different field and merged together.
But then there would be a risk of collision, not sure how high it would be?
Unless we remove duplicates keeping latest like Serilog does, but then you will lose date.

@tarekgh
Copy link
Member

tarekgh commented Jul 23, 2020

CC @davidfowl

@alefranz
Copy link
Contributor

My worry is that out of the box you will have to know the order of the scopes if you want for example to get all the logs entries with Number = 2, unless you define some custom indexing that would be needed for each of this properties.

I've tried in Seq and you can query with Scopes[?].Number = '2', so it would work, although you will not be able to create such filter just clicking on a log entry in the UI.

@maryamariyan
Copy link
Member Author

maryamariyan commented Jul 24, 2020

I wonder if the properties should be stored in a different field and merged together.
But then there would be a risk of collision, not sure how high it would be?
Unless we remove duplicates keeping latest like Serilog does, but then you will lose date.

I added your use case as well to JsonConsoleFormatterTests
Interesting observation about Serilog. At least for the scope of this PR the aim is to make sure we don't end up with invalid Json format (duplicate keys).

UPDATE:
Now for your example

using (logger.BeginScope("{Number}", 2))
using (logger.BeginScope("{AnotherNumber}", 3))
{
    logger.LogInformation("{LogEntryNumber}", 1);
}

we'd get:

{
  "Timestamp": "15:57:40 ",
  "EventId": 0,
  "LogLevel": "Information",
  "Category": "ConsoleApp46.Program",
  "Message": "1",
  "State": {
    "Message": "1",
    "LogEntryNumber": "1",
    "{OriginalFormat}": "{LogEntryNumber}"
  },
  "Scopes": [
    {
      "Message": "2",
      "Number": "2",
      "{OriginalFormat}": "{Number}"
    },
    {
      "Message": "3",
      "AnotherNumber": "3",
      "{OriginalFormat}": "{AnotherNumber}"
    }
  ]
}

@maryamariyan
Copy link
Member Author

maryamariyan commented Jul 25, 2020

There is an issue to solve that I'll leave out of scope for this PR, and that is, we are now introducing more "Message" JSON elements per logged output. and it would be better if the name "Message" was reduced to something like @m or something to make more compact and efficient. Other JSON elements could also be shortened, but as this needs more thought I will tackle it separately.

There is a conversation about it here: #39664 (comment).

@maryamariyan
Copy link
Member Author

maryamariyan commented Jul 27, 2020

fixed PR conflict through rebase

@ghost
Copy link

ghost commented Jul 27, 2020

Hello @maryamariyan!

Because this pull request has the auto-merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit 168409e into dotnet:master Jul 27, 2020
Jacksondr5 pushed a commit to Jacksondr5/runtime that referenced this pull request Aug 10, 2020
@karelz karelz added this to the 5.0.0 milestone Aug 18, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JsonConsoleFormatter: Should not put the same key in the scopes multiple times (not allowed in Json)
4 participants