-
Notifications
You must be signed in to change notification settings - Fork 772
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
[sdk.logging] More consistent handling of StateValues/Attributes #4334
[sdk.logging] More consistent handling of StateValues/Attributes #4334
Conversation
src/OpenTelemetry.Exporter.OpenTelemetryProtocol.Logs/OtlpLogExporterHelperExtensions.cs
Show resolved
Hide resolved
src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/LogRecordExtensions.cs
Outdated
Show resolved
Hide resolved
/// cref="Activity"/> should be included on generated <see | ||
/// cref="LogRecord"/>s. Default value: <see langword="false"/>. | ||
/// </summary> | ||
public bool IncludeTraceState { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not excited about this new option. Is the cost of keeping things as is pretty high? In a lot of cases I think TraceState is null or empty anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not passionate that we absolutely need this. What the Activity.TraceStateString getter does is walk the chain and look for a value. So if you happen to be logging inside of a deeply nested span those cycles get spent even if trace state isn't being used. The idea here was to not spend any time on it unless it was needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should not add this option unless we get perf complaints for this as the use-case it's trying to optimize seems like a rare occurrence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it rare though? Using TraceState is probably rare. But whether or not it is used doesn't change the perf hit we take to access it! We have seen some epic deep traces. Let's say you are in an AspNetCore app. Incoming request creates a root span. Then maybe some span for controller. Then maybe some span for service. Then outgoing Http span. If you log at that 4th level trying to read TraceStateString has to look at all 4 of those spans to see if any have TraceState. Just to drop it or not find it at all. If you are writing a lot of logs, it adds up.
I think of this change as more of us moving into spec compliance with logs. TraceState is not on log data model. Adding it on LogRecord
was probably a mistake?
Again I'm not passionate that we need the setting. But I think dropping it by default is good. The setting is to make sure anyone that is actually reliant on the existing behavior can avoid a break.
Alternatively we could obsolete it and tackle this later, but I really don't like the wasted perf 👊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appreciate the perf concerns and trying to remedy the fact that we shipped stable logs before the spec was ready, but I vote this moves to a separate PR for discussion. I'd actually prefer we discuss removing tracestate altogether and just deal with any fallout from this breaking change.
/// </summary> | ||
public bool IncludeFormattedMessage { get; set; } | ||
public bool IncludeState { get; set; } = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to let feedback drive the need for this option? I like the new behavior or ParseStateValues - it's name is still unfortunate being ILoggery, but I'd prefer to avoid adding another.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not passionate that we absolutely need this. The idea here is that on this PR I'm redefining (essentially) what ParseStateValues
means. If it was false
previously we wouldn't look at the state at all and we wouldn't buffer anything for batch cases. With the redefinition, we automatically process state and also automatically buffer things. So I introduced IncludeState
to enable what is possible today which is disable the processing & buffering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we would only set the state
in the newly added ProcessState
method with the requirement that ParseStateValue
is also set to false
otherwise we could run into situations where state
is null
even with IncludeState
set to true
which feels a bit weird.
If we want the new default behavior to always generate attributes, then we could achieve that while continuing to respect the older options. Essentially, we would still only assign one of these: State
or StateValues
depending on ParseStateValues
option but we always generate attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could run into situations where state is null even with IncludeState set to true which feels a bit weird.
This is totally by design. State
is obsolete. No one should rely on it or use it going forward. It is only set at all to preserve the existing behavior(s) we have today. Maybe it would help to rename IncludeState
-> IncludeAttributes
? I went ahead and made this change so we could see how it might look. Also updated XML comments to try and document all the behaviors.
Essentially, we would still only assign one of these: State or StateValues depending on ParseStateValues option but we always generate attributes.
I suppose we could do that. But consider an exporter today like GenevaExporter
. It checks State
and then StateValues
. Let's say ParseStateValues = false
. So we set State = state, StateValues = null
as we do today and then we automatically set Attributes
to something useful (new behavior). GenevaExporter
now needs an update to take advantage of this. The current behavior on the PR is State = null, StateValues = Attributes = usefulStuff
so it will just fall into its existing StateValues
branch and doesn't really need to be touched to get the new useful things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to make the exporters update their implementation to start using new features. I feel by marking State
and StateValues
oboslete, we essentially want to discourage exporters from using the obsolete properties and use the newer spec complaint properties instead such as Body
and Attributes
.
Why make it easy for them to continue consuming obsolete properties by providing them newer features through the obsolete properties?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting perspective. Thinking I would prefer to just drop Attributes
and continue along with StateValues
. Attributes
really isn't a new feature, it is just renaming StateValues
to be more compliant with the spec. I like your idea to encourage exporters to update but I don't think this is the best way to go about it. Introducing differences between StateValues
and Attributes
just seems like unnecessary confusion and documentation burden 😄
Body
is a new feature for sure. And it more fits into what you are saying. Exporters have to update if they want to take advantage of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's hold off on adding a new configuration option. There's a handful of things going on here.
- New behavior to StateValues
- Obsoleting StateValues and introducing Attributes to replace it
- Introducing IncludeAttributes for configuring old behavior of not processing them by default
I see each of these as a separate PR. I support the first item being in this PR. I also favor doing the second item, but a separate PR would nicely document our conversation and ultimate decision. I'm not in favor of the third item, but happy to continue a focused conversation on a separate PR that introduces it.
src/OpenTelemetry/Logs/LogRecord.cs
Outdated
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled. | ||
/// </remarks> | ||
[Obsolete("State cannot be accessed safely outside of an ILogger.Log call stack. It will be removed in a future version.")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add any more detail to this obsolete message? Basically, what should one do if they see this obsolete message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm the user, I most likely will suppress the warnings in my build until there is a non-backward compatible version got launched and I have to update to that version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add any more detail to this obsolete message? Basically, what should one do if they see this obsolete message.
I updated the message so it has more guidance.
If I'm the user, I most likely will suppress the warnings in my build until there is a non-backward compatible version got launched and I have to update to that version.
Fine if that's what people want to do. We may never actually remove the obsolete things 😄 The audience for LogRecord
is primarily exporter authors. The number of exporters out there really shouldn't be huge so hopefully we can assist with these updates when they are available.
Just for my learning, from the description: |
All logs messages one way or another flow through ILogger.Log. Probably the most common way to log is via extensions. You do something like In order to pass a state which does not use public class MyCustomState
{
public string Name {get;set;}
}
myLogger.Log(
LogLevel.Information,
eventId: default,
new MyCustomState { Name = "Mike" },
exception: null,
formatter: (state, exception) => state.Name); For that "MyCustomState" log message example prior to this PR users probably wouldn't see any attributes at all. After this PR, we will build attributes as |
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #4334 +/- ##
==========================================
- Coverage 84.77% 84.69% -0.09%
==========================================
Files 300 300
Lines 12010 12064 +54
==========================================
+ Hits 10182 10218 +36
- Misses 1828 1846 +18
|
/// Gets or sets a value indicating whether or not the <see | ||
/// cref="Activity.TraceStateString"/> for the current <see | ||
/// cref="Activity"/> should be included on generated <see | ||
/// cref="LogRecord"/>s. Default value: <see langword="false"/>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree to make this public + defaulting to false. (in another PR to reduce scope here)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CodeBlanch LGTM to be merged. Left a comment about updating the CHANGELOG. This could be done in a follow-up PR.
@@ -23,15 +23,9 @@ internal class MyRedactionProcessor : BaseProcessor<LogRecord> | |||
{ | |||
public override void OnEnd(LogRecord logRecord) | |||
{ | |||
if (logRecord.State == null) | |||
if (logRecord.Attributes != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding a comment to the PR for anyone wondering: This change is not required really (except to silence warning about using obsolete. The change is backcompatible, so if a Redactor is looking at State or Statealues as before, it'll continue to work, and there is no risk of "Oh damn, I missed to redact secret attributes".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @CodeBlanch for actively driving this forward. Left a nit about changelogs being more clear about what is the impact (or state that there is no impact) for end users.
[This work was originally done as part of 1.4 dev but got moved to main-logs branch with the other things waiting for the spec to go stable. I think this is safe to go in for 1.5 so opening this to get feedback.]
Background
Originally we just had
LogRecord.State
but that is just anobject
. Pushes all the complexity onto exporters to figure out what to do. Later on we realized the state isn't really safe to use in batching. The state is only good during the "Log" call lifecycle.Later we added
StateValues
which isIReadOnlyList<KeyValuePair<string, object?>>
a more friendly thing for exporters to deal with. But a mistake was likely made here. We only set that ifOpenTelemetryLoggerOptions.ParseStateValues
is set totrue
(it isfalse
by default). That was done because the thinking was parsing the state would be expensive. As it turns out, most state is just castable. It is a cheap thing for the most part. Some exporters have done weird things like turn this on automatically.Goals
Primarily this PR is seeking to smooth out the experience for users. When it is cheap, we should just expose the
IReadOnlyList<KeyValuePair<string, object?>>
automatically.State
isn't safe so we should obsolete it.Some of the names are very different from what the spec (LogModel) has. Trying to make it a bit closer. This will make things more cohesive with the upcoming bridge api as well.
We only have
FormattedMessage
which may be null. There is no concept ofBody
(could be template or formatted message). It should be more consistent. You should always get something to log. Either template, template & formatted message (depending on settings), or a formatted message. This also sets up the upcoming bridge api as well whereBody
can be passed directly.Changes
Marks
LogRecord.State
as obsolete. State really isn't safe to use outside of the frame where the log is written.Adds
LogRecord.Body
which is now set automatically to{OriginalFormat}
if it is found as the last tag on state. If it isn't found we automatically call the formatter (regardless ofIncludeFormattedMessage
) and use theFormattedMessage
value asBody
. If there is no formatter we callToString()
on the state. The idea here is you should always get something to log. A log with no message is kind of silly?Adds
LogRecord.Attributes
and obsoletesLogRecord.StateValues
. Really no reason for this other than it makesLogRecord
a little closer to the spec.Redefines
ParseStateValues
. Ifstate
isIReadOnlyList<KeyValuePair<string, object?>>
orIEnumerable<KeyValuePair<string, object?>>
thanAttributes
\StateValues
will exist automatically. Ifstate
is some random unknownobject
(which should be rare)...ParseStateValues
isfalse
:Attributes
\StateValues
will benull
.ParseStateValues
istrue
: We actually generateAttributes
\StateValues
for the object's top-level public properties using reflection. This is expensive, so it is stillfalse
by default.If users really don't want state/attributes at all (perhaps they just want formatted message) a new option
IncludeAttributes
(defaulttrue
) is available. Note: The option was made internal during review of this PR.A new option
IncludeTraceState
has been added, defaulted tofalse
.TraceState
isn't on LogModel. It is dropped by Otlp for example. Made it optional so we don't waste cycles on it. Note: The option was made internal during review of this PR.Public API changes
Merge requirement checklist
CHANGELOG.md
files updated for non-trivial changes