-
Notifications
You must be signed in to change notification settings - Fork 90
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
Add OTel semantic conventions for Outbound HTTP (span name and attributes) #63
Conversation
src/Datadog.Trace.ClrProfiler.Managed/Conventions/Convention.cs
Outdated
Show resolved
Hide resolved
src/Datadog.Trace.ClrProfiler.Managed/Conventions/OutboundHttpArgs.cs
Outdated
Show resolved
Hide resolved
@macrogreg @nrcventura @zacharycmontoya PTAL: it is in the initial stages and still small so easy to take a look and help guide @pellared here. |
Refactored and improved a little. Description of the PR is updated. |
switch (Settings.Convention) | ||
{ | ||
default: | ||
OutboundHttpConvention = new DatadogOutboundHttpConvention(this); |
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 bi-directional reference is ugly. I think it should be refactored.
The tracer is used this way:
string serviceName = _tracer.Settings.GetServiceName(_tracer, "http-client");
var scope = _tracer.StartActiveWithTags("http.request", tags: tags, serviceName: serviceName, spanId: args.SpanId);
However, it is still possible to test this code in ScopeFactoryTests
.
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.
hmmm now I see the reason of the .ctor with the tracer object.
It seems weird to me storing data from integrations (IOutboundHttpConvention
in this case) inside the tracer, this means that if we add new integrations that support conventions we need to modify the tracer as well?
Consider to expose only the ConventionType
from the settings and each integration (that supports conventions) reads this value from the current Tracer.Instance
and configure itself.
I prefer decouple the integrations from the tracer.
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.
Relying on Tracer.Instace
would make the code less maintainable/readable/testable.
I think that this proposal: https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/pull/63/files#r585368841 together with https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/pull/63/files#r585374238 will address your main concerns.
The Tracer
is the root object (aggregate) so it is normal that something "inside" would need to be changed when adding new stuff. Creating a facade would at least do it not "directly".
Maybe extracting some of the Tracer's functionalities to create spans would make it also a little cleaner, but TBH I am not sure if it is worth the effort. For sure I think it should not be done within this PR as it could cause too many merge conflicts with Datadog repo.
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.
Good thanks for clarifying.
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
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.
These changes look good to me. I only noticed a couple of minor spelling errors.
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 like the direction that it is going @pellared, one key question about how this grows to cover the other instrumentations.
@@ -253,6 +261,8 @@ public static Tracer Instance | |||
|
|||
internal IDogStatsd Statsd { get; private set; } | |||
|
|||
internal IOutboundHttpConvention OutboundHttpConvention { get; } |
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 assume that in the end tracer has a single property for the semantic conventions instead of one for outbound HTTP a separate one for DB, etc. Right now it is only the first "target", correct?
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.
In other words, what are your plans for it to grow?
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.
Option 1. Have multiple fields
Option 2. Have a facade with multiple sematic conventions implementations
I would go with Option 1 and when we start having 3 interfaces then we should probably refactor it to Option 2.
I do not want to create a single gigantic interface. Even the OTEL specs have semantics conventions separated by protocol and I would prefer to design the code in a similar manner: https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions E.g. maybe we will combine HTTP Outbound and Inbound into a single interface.
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.
SGTM
|
||
public OtelOutboundHttpConvention(Tracer tracer) | ||
{ | ||
_tracer = tracer; |
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.
As you commented earlier, this reference is kind of ugly. The conventions are meant to differentiate spans right? Perhaps, we can have a Tracer create the scope first (not inside this class) and this class is only responsible for setting the tags or modifying the span, but that can all be done without the Tracer. That might require also identifying the operation name, but that could be a readonly property on this class
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 did the way you suggest initially, but modifying the span's OperationName
and setting the HttpTag
's tag mapping after the object is created looked even worse to my eyes.
This is the problematic code:
tags = new HttpTags(tagKeys); // sets the tags mapping conventions
string serviceName = _tracer.Settings.GetServiceName(_tracer, "http-client"); // I do not know what it is yet :)
var scope = _tracer.StartActiveWithTags("http.request", tags: tags, serviceName: serviceName, spanId: args.SpanId); // this sets some stuff
I am not sure yet if we want to have a different ServiceName, OperationName for OTel and Datadog.
Maybe I will be able to refactor it if I simply know more about OTel and be more familiar with this repo 😉
Let me know if you think that this issue is a blocker.
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.
No I don't think this is a blocker. Maybe later we can revisit this to make it cleaner, but anyways we will need to make a runtime decision based on how the Tracer is configured, and this accomplishes that 👍🏼
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 missing something for sure, but, why not use Tracer.Instance
instead? If the configuration is changed at runtime we cannot pick the new configuration. Also removes this .ctor.
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 will be picked up. Here is why
HttpClientHandlerCommon
invokesScopeFactory.CreateOutboundHttpScope(Tracer.Instance, ...
ScopeFactory.CreateOutboundHttpScope
invokestracer.OutboundHttpConvention.CreateScope(args, out tags);
- the
OutboundHttpConvention
ussesTracer
passed viathis
Not using Tracer.Instance
makes it unit-testable, less tightly coupled and you see the dependency in the constructor. And it is easier to see the bi-directional dependency code smell.
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.
Good thanks for clarifying.
@zacharycmontoya I noticed that the Reference:
|
public string HttpUrlTag; | ||
public string HttpTargetTag; | ||
|
||
public override string ToString() |
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 would prefer to use https://github.com/fluentassertions/fluentassertions
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 can do it in the next PR
string resourceUrl = UriHelpers.CleanUri(requestUri, removeScheme: true, tryRemoveIds: true); | ||
scope.Span.ResourceName = $"{httpMethod} {resourceUrl}"; | ||
|
||
var integrationId = args.IntegrationInfo; | ||
tags.InstrumentationName = IntegrationRegistry.GetName(integrationId); | ||
tags.SetAnalyticsSampleRate(integrationId, _tracer.Settings, enabledWithGlobalSetting: 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.
Double-check:
Do you want to keep these? They are not required by OTel but I do not think it hurts to have this stuff.
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 is only used for Datadog so it should probably be removed
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.
@pjanotti @nrcventura can you confirm, please?
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.
Keep tag.InstrumentationName
(it follow an OT convention)
My vote is to remove tags.SetAnalyticsSampleRate
: it is a Datadog convention.
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.
@pjanotti What about ResourceName
? I think it is consumed only by SpanMessagePackFormatter
and DatadogLogger
. Currently, I remove it, but I can bring it back if needed.
scope.Span.Type = SpanTypes.Http; | ||
|
||
tags.HttpMethod = httpMethod; | ||
tags.HttpUrl = UriHelpers.CleanUri(requestUri, removeScheme: false, tryRemoveIds: 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.
I removed the null-checks for requestUri
and UpercaseInvariant
for httpMethod
.
Both looked unnecessary to me.
For application development, I tend to be a defensive programmer, but here (for auto instrumentation) I think that performance matters and we should not do any unnecessary stuff.
@zacharycmontoya : AFAIK @RassK is going to take a look at it |
I will address it in the next PR, just to have this one focusing on how an OTel semantic convention can be applied to other places. |
otelTags.HttpScheme = uri.Scheme; | ||
otelTags.HttpHost = uri.Authority; | ||
otelTags.HttpTarget = uri.PathAndQuery + uri.Fragment; |
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.
TBH I am not sure if we need (and want) these.
Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md
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.
Well these are not Required
by the specs, maybe we can think later if we should have an optional flag setting to expose those keys, but by default only use the required one for performance reasons???
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.
@tonyredondo I am OK with removing these and making an issue to add an optional flag setting to expose those additional attributes/tags.
@pjanotti @zacharycmontoya do you agree?
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.
Sounds good to me
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.
@pellared please, create an issue to add an option for the extra tags.
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 have removed these attributes and created an issue #73
|
||
var uri = args.RequestUri; | ||
otelTags.HttpMethod = args.HttpMethod; | ||
otelTags.HttpUrl = string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment); |
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.
The OTel. .NET SDK uses OrginalString
. However, this can cause leakage of auth data.
See: https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs#L115
I feel like this is a little vuln in .NET SDK. Do you agree?
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 we shouldn't use the OriginalString
approach if this can contain auth data.
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 asked a question on #otel-specification Slack channel: https://cloud-native.slack.com/archives/C01N7PP1THC/p1614854143044600
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.
Good find @pellared definitely do not use OriginalString
.
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.
Overall LGTM, I left some comments.
|
||
public OtelOutboundHttpConvention(Tracer tracer) | ||
{ | ||
_tracer = tracer; |
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 missing something for sure, but, why not use Tracer.Instance
instead? If the configuration is changed at runtime we cannot pick the new configuration. Also removes this .ctor.
|
||
var uri = args.RequestUri; | ||
otelTags.HttpMethod = args.HttpMethod; | ||
otelTags.HttpUrl = string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment); |
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 we shouldn't use the OriginalString
approach if this can contain auth data.
switch (Settings.Convention) | ||
{ | ||
default: | ||
OutboundHttpConvention = new DatadogOutboundHttpConvention(this); |
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.
hmmm now I see the reason of the .ctor with the tracer object.
It seems weird to me storing data from integrations (IOutboundHttpConvention
in this case) inside the tracer, this means that if we add new integrations that support conventions we need to modify the tracer as well?
Consider to expose only the ConventionType
from the settings and each integration (that supports conventions) reads this value from the current Tracer.Instance
and configure itself.
I prefer decouple the integrations from the tracer.
otelTags.HttpScheme = uri.Scheme; | ||
otelTags.HttpHost = uri.Authority; | ||
otelTags.HttpTarget = uri.PathAndQuery + uri.Fragment; |
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.
Well these are not Required
by the specs, maybe we can think later if we should have an optional flag setting to expose those keys, but by default only use the required one for performance reasons???
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
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, small things for follow-ups.
|
||
var uri = args.RequestUri; | ||
otelTags.HttpMethod = args.HttpMethod; | ||
otelTags.HttpUrl = string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment); |
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.
Good find @pellared definitely do not use OriginalString
.
otelTags.HttpScheme = uri.Scheme; | ||
otelTags.HttpHost = uri.Authority; | ||
otelTags.HttpTarget = uri.PathAndQuery + uri.Fragment; |
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.
@pellared please, create an issue to add an option for the extra tags.
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.
@pjanotti
I think this PR is ready for being merged.
Why
Attempt to implement @macrogreg idea described under #42
Extension points for trace/span conventions are needed so that we can support more than one model (e.g. OTel, DataDog, NewRelic, Splunk). We want to have it in a way that would maximize the possibility of having a common codebase. At the same time, we want to minimize potential conflicts if something "vendor" specific would need to be done on a fork.
What
OTEL_CONVENTION
environmental variable. Currently defaults toDatadog
.I tried to make make the least changes in the existing code so that it is easy to cherry-pick this change by Datadog and other forks. All the new part is in dedicated classes to minimize potential conflicts.
TODO for this PR
Next PRs
Questions