-
Notifications
You must be signed in to change notification settings - Fork 197
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
feat: add fallback service target type for exit spans #1294
Conversation
This is the final step for complete service target fields support. Exit spans will fallback to type and subtype similar to what the deprecated destination resource was doing. Assertions have been added to tests to verify the service target type is not overwritten if previously set and that the fallback mechanism is working properly.
🌐 Coverage report
|
span.go
Outdated
if s.exit && !s.Context.setServiceTargetCalled { | ||
// The span was created as an exit span, but the user did not | ||
// manually set the service.target fields. | ||
s.setExitSpanServiceTarget() | ||
} |
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.
This doesn't quite follow the logic described at https://github.com/elastic/apm/blob/main/specs/agents/tracing-spans-service-target.md#field-values. I think users should be able to set just the Name, and have the Type be inferred from the span type/subtype. (I realise this is different to what we had with service destination, but let's just ignore that since it's being replaced.)
Rather than checking if the user has called the method, how about something like this:
if s.exit {
s.Context.setDefaultServiceTarget(s.Type, s.Subtype)
}
...
func (c *SpanContext) setServiceTargetDefaults(spanType, spanSubtype string) {
if c.serviceTarget.Type == "" {
c.serviceTarget = spanSubtype
if c.serviceTarget.Type == "" {
c.serviceTarget = spanType
}
}
if c.serviceTarget.Name == "" {
// ...
}
c.SetServiceTarget(ServiceTargetSpanContext{Type: c.serviceTarget.Type, Name: c.serviceTarget.Name})
}
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.
Ok, just a question to clarify: it seems that would infer the value even if an explicit empty name/type was set. Is that intended ?
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 thought so, since the type can always be inferred and the name is optional, but the spec also says this:
Agents SHOULD provide an API entrypoint to set the value of span.context.service.target.type and span.context.service.target.name, setting an empty or null value on both of those fields allows the user to discard the inferred values.
@SylvainJuge why would a user need to disable the inference of service.target.type
or service.target.name
?
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.
Hi !
I remember it was requested at some point for the Java agent in order to provide a work-around when our heuristics fail and or the user wants more control on how things are named (and maybe ignore a few dependencies):
- allow to split a dependency when the heuristic groups everything (for example when calls are made to a gateway, we currently only show the gateway, the user might want to manually split if the app has some knowledge about this).
- allow to limit cardinality : for example a single database server with dozens of databases that might be better grouped together, or a web-crawling app that has one dependency per DNS entry by default.
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.
@SylvainJuge let me know if I understand correctly: what you're saying is that in some cases the inferred type & name may not be ideal, so users may wish to override the values and provide either more specific values (e.g. in the case of a gateway) or less specific values (e.g. in the case of many databases instances).
That's a good argument for allowing users to manually set non-empty values, but I don't see why we should completely omit the service.target.*
fields if a user sets them both to null/empty values.
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.
@SylvainJuge let me know if I understand correctly: what you're saying is that in some cases the inferred type & name may not be ideal, so users may wish to override the values and provide either more specific values (e.g. in the case of a gateway) or less specific values (e.g. in the case of many databases instances).
Yes, that's exactly what I meant.
That's a good argument for allowing users to manually set non-empty values, but I don't see why we should completely omit the
service.target.*
fields if a user sets them both to null/empty values.
Omitting the fields when empty would be consistent with what we do for resource
field (spec) for context.destination.service.resource
:
Agents SHOULD offer a public API to set this field so that users can customize the value if the generic mapping is not
sufficient. If set tonull
or an empty value, agents MUST omit thespan.destination.service
field altogether, thus
providing a way to manually disable the automatic setting/inference of this field (e.g. in order to remove a node
from a service map or an external service from the dependencies table).
Also, when set to empty/null values, omitting both destination.service.resource
and service.target.*
would avoid making the apm-server try to infer any of those fields which could lead to inconsitencies.
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 @SylvainJuge.
How about this then:
- if the user does not call SetServiceTarget at all, we'll use inferred values for both (where possible)
- if the user calls SetServiceTarget with an empty name, this will disable inference
- if the user calls SetServiceTarget with a non-empty name, but empty type, we'll use the specified name and infer the type
I think that covers the intended use case of controlling the service.target.name
cardinality.
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.
How about this then:
* if the user does not call SetServiceTarget at all, we'll use inferred values for both (where possible) * if the user calls SetServiceTarget with an empty name, this will disable inference * if the user calls SetServiceTarget with a non-empty name, but empty type, we'll use the specified name and infer the type
I think that covers the intended use case of controlling the
service.target.name
cardinality.
I've updated the code to reflect this!
@@ -273,6 +277,7 @@ func (c *SpanContext) SetDestinationService(service DestinationServiceSpanContex | |||
|
|||
// SetServiceTarget sets the service target info in the context. | |||
func (c *SpanContext) SetServiceTarget(service ServiceTargetSpanContext) { | |||
c.setServiceTargetCalled = 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.
To implement this part of the spec:
Agents SHOULD provide an API entrypoint to set the value of span.context.service.target.type and span.context.service.target.name, setting an empty or null value on both of those fields allows the user to discard the inferred values.
Should we check here if the specified name is empty, and then just set c.setServiceTargetCalled = true
, set c.service.Target = nil
, and return?
i.e.
// SetServiceTarget sets the service target info in the context.
//
// If service.Name is empty, then any previously set service target values
// will be cleared, and inference of service target values will be disabled.
func (c *SpanContext) SetServiceTarget(service ServiceTargetSpanContext) {
c.setServiceTargetCalled = true
if service.Name == "" {
c.service.Target = nil
} else {
c.serviceTarget.Type = truncateString(service.Type)
c.serviceTarget.Name = truncateString(service.Name)
c.service.Target = nil
c.model.Service = &c.service
}
}
span.go
Outdated
if s.exit && !s.Context.setServiceTargetCalled { | ||
// The span was created as an exit span, but the user did not | ||
// manually set the service.target fields. | ||
s.setExitSpanServiceTarget() | ||
} |
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 @SylvainJuge.
How about this then:
- if the user does not call SetServiceTarget at all, we'll use inferred values for both (where possible)
- if the user calls SetServiceTarget with an empty name, this will disable inference
- if the user calls SetServiceTarget with a non-empty name, but empty type, we'll use the specified name and infer the type
I think that covers the intended use case of controlling the service.target.name
cardinality.
if the user does not call SetServiceTarget at all, we'll use inferred values for both (where possible) if the user calls SetServiceTarget with an empty name, this will disable inference if the user calls SetServiceTarget with a non-empty name, but empty type, we'll use the specified name and infer the type
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.
LGTM :) Just a couple of minor things
Co-authored-by: Andrew Wilkins <[email protected]>
This is the final step for complete service target fields
support. Exit spans will fallback to type and subtype similar
to what the deprecated destination resource was doing.
Assertions have been added to tests to verify the service target
type is not overwritten if previously set and that the fallback
mechanism is working properly.
related to #1276
Closes #1294