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

Add OTel semantic conventions for Outbound HTTP (span name and attributes) #63

Merged
merged 27 commits into from
Mar 5, 2021
Merged

Add OTel semantic conventions for Outbound HTTP (span name and attributes) #63

merged 27 commits into from
Mar 5, 2021

Conversation

pellared
Copy link
Member

@pellared pellared commented Feb 26, 2021

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

  1. Extension points for a semantic convention that is chosen at runtime via the OTEL_CONVENTION environmental variable. Currently defaults to Datadog.
  2. Add OpenTelemetry Outbound HTTP semantic convention for span's operation name and attributes. Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md

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

  1. Manual E2E testing
  2. Double-checking with OTel specs and .NET SDK

Next PRs

  1. Refactor test and use https://github.com/fluentassertions/fluentassertions
  2. Address Add OTel semantic conventions for Outbound HTTP (span name and attributes) #63 (comment)
  3. Add support for setting OTel status
  4. Add support for adding OTel events (exceptions)
  5. Try to address Add OTel semantic conventions for Outbound HTTP (span name and attributes) #63 (comment)

Questions

  1. Do you want to use GitHub issues for making this ⬆️ stuff more transparent?
  2. Any missing parts? E.g. do we have some docs where the environmental variables are described?
  3. Do you want more descriptive comments in the code that describes the design decisions? Feel free to point me where such comments would be placed.

@pellared pellared requested a review from a team as a code owner February 26, 2021 12:34
@pellared pellared marked this pull request as draft February 26, 2021 12:36
@pellared pellared changed the title Add example extension point for span conventions Example extension point for span conventions Feb 26, 2021
@pjanotti
Copy link
Contributor

pjanotti commented Mar 1, 2021

@macrogreg @nrcventura @zacharycmontoya PTAL: it is in the initial stages and still small so easy to take a look and help guide @pellared here.

@pellared pellared requested a review from pjanotti March 1, 2021 10:28
@pellared
Copy link
Member Author

pellared commented Mar 1, 2021

Refactored and improved a little. Description of the PR is updated.

switch (Settings.Convention)
{
default:
OutboundHttpConvention = new DatadogOutboundHttpConvention(this);
Copy link
Member Author

@pellared pellared Mar 1, 2021

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.

Copy link
Member

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.

Copy link
Member Author

@pellared pellared Mar 4, 2021

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.

Copy link
Member

Choose a reason for hiding this comment

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

Good thanks for clarifying.

Copy link
Contributor

@dszmigielski dszmigielski left a comment

Choose a reason for hiding this comment

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

LGTM

@pellared pellared changed the title Example extension point for span conventions Prepare extension point for span conventions Mar 1, 2021
@pellared pellared marked this pull request as ready for review March 1, 2021 13:41
@pellared pellared changed the title Prepare extension point for span conventions Prepare extension points for span conventions Mar 1, 2021
Copy link
Member

@nrcventura nrcventura left a 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.

src/Datadog.Trace/Tagging/HttpTags.cs Outdated Show resolved Hide resolved
src/Datadog.Trace/Tagging/HttpTags.cs Outdated Show resolved Hide resolved
Copy link
Contributor

@pjanotti pjanotti left a 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.

src/Datadog.Trace/Configuration/TracerSettings.cs Outdated Show resolved Hide resolved
src/Datadog.Trace/Configuration/ConfigurationKeys.cs Outdated Show resolved Hide resolved
@@ -253,6 +261,8 @@ public static Tracer Instance

internal IDogStatsd Statsd { get; private set; }

internal IOutboundHttpConvention OutboundHttpConvention { get; }
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Member Author

@pellared pellared Mar 2, 2021

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM

test/Datadog.Trace.Tests/Tagging/TagsListTests.cs Outdated Show resolved Hide resolved

public OtelOutboundHttpConvention(Tracer tracer)
{
_tracer = tracer;
Copy link
Contributor

@zacharycmontoya zacharycmontoya Mar 2, 2021

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

Copy link
Member Author

@pellared pellared Mar 2, 2021

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.

Copy link
Contributor

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 👍🏼

Copy link
Member

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.

Copy link
Member Author

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

  1. HttpClientHandlerCommon invokes ScopeFactory.CreateOutboundHttpScope(Tracer.Instance, ...
  2. ScopeFactory.CreateOutboundHttpScope invokes tracer.OutboundHttpConvention.CreateScope(args, out tags);
  3. the OutboundHttpConvention usses Tracer passed via this

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.

Copy link
Member

Choose a reason for hiding this comment

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

Good thanks for clarifying.

@pellared pellared closed this Mar 3, 2021
@pellared pellared reopened this Mar 3, 2021
@pellared
Copy link
Member Author

pellared commented Mar 3, 2021

@zacharycmontoya I noticed that the ScopeFactory does not assign the http.request.headers.host tag. Is it OK for you? If you want it I can add it to this PR.

Reference:

public const string HttpRequestHeadersHost = "http.request.headers.host";

public string HttpUrlTag;
public string HttpTargetTag;

public override string ToString()
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

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

@pellared pellared changed the title Prepare extension points for span conventions Add extension points for semantic conventions and implement OutboundHttp span sematics Mar 3, 2021
@pellared pellared changed the title Add extension points for semantic conventions and implement OutboundHttp span sematics Add extension points for semantic conventions and implement OutboundHttp span conventions Mar 3, 2021
@pellared pellared changed the title Add extension points for semantic conventions and implement OutboundHttp span conventions Add extension points for semantic conventions and implement it for OutboundHttp span name and attributes Mar 3, 2021
@pellared pellared changed the title Add extension points for semantic conventions and implement it for OutboundHttp span name and attributes Add OTel semantic conventions for Outbound HTTP (span name and attributes) Mar 3, 2021
Comment on lines 34 to 39
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);
Copy link
Member Author

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.

Copy link
Contributor

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

Copy link
Member Author

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?

Copy link
Contributor

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.

Copy link
Member Author

@pellared pellared Mar 4, 2021

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);
Copy link
Member Author

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
Copy link
Contributor

zacharycmontoya commented Mar 3, 2021

image

Zipkin exporter misses the AdditionalTags

Yes, the ZipkinSerializer is failing. Currently we're not surfacing the TagsList.GetAdditionalTags except inside TagsList.WriteTags...Let me think on this one...

@pellared
Copy link
Member Author

pellared commented Mar 3, 2021

image
Zipkin exporter misses the AdditionalTags

Yes, the ZipkinSerializer is failing. Currently we're not surfacing the TagsList.GetAdditionalTags except inside TagsList.WriteTags...Let me think on this one...

@zacharycmontoya : AFAIK @RassK is going to take a look at it

@pellared
Copy link
Member Author

pellared commented Mar 3, 2021

@zacharycmontoya I noticed that the ScopeFactory does not assign the http.request.headers.host tag. Is it OK for you? If you want it I can add it to this PR.
Reference:

public const string HttpRequestHeadersHost = "http.request.headers.host";

Yeah that sounds good to me. I'm guessing we didn't have that because the project started with a OpenTracing conventions and that wasn't part of the spec

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.

Comment on lines 29 to 31
otelTags.HttpScheme = uri.Scheme;
otelTags.HttpHost = uri.Authority;
otelTags.HttpTarget = uri.PathAndQuery + uri.Fragment;
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

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???

Copy link
Member Author

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good to me

Copy link
Contributor

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.

Copy link
Member Author

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);
Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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

Copy link
Contributor

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.

Copy link
Member

@tonyredondo tonyredondo left a 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.

src/Datadog.Trace.ClrProfiler.Managed/ScopeFactory.cs Outdated Show resolved Hide resolved

public OtelOutboundHttpConvention(Tracer tracer)
{
_tracer = tracer;
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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.

Comment on lines 29 to 31
otelTags.HttpScheme = uri.Scheme;
otelTags.HttpHost = uri.Authority;
otelTags.HttpTarget = uri.PathAndQuery + uri.Fragment;
Copy link
Member

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???

Copy link
Member

@tonyredondo tonyredondo left a comment

Choose a reason for hiding this comment

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

LGTM

@pellared pellared closed this Mar 4, 2021
@pellared pellared reopened this Mar 4, 2021
Copy link
Contributor

@pjanotti pjanotti left a 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.

src/Datadog.Trace/Conventions/OtelHttpTags.cs Outdated Show resolved Hide resolved

var uri = args.RequestUri;
otelTags.HttpMethod = args.HttpMethod;
otelTags.HttpUrl = string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment);
Copy link
Contributor

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.

Comment on lines 29 to 31
otelTags.HttpScheme = uri.Scheme;
otelTags.HttpHost = uri.Authority;
otelTags.HttpTarget = uri.PathAndQuery + uri.Fragment;
Copy link
Contributor

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.

Copy link
Member Author

@pellared pellared left a 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.

@pjanotti pjanotti merged commit 1861c27 into open-telemetry:main Mar 5, 2021
@pellared pellared mentioned this pull request Mar 8, 2021
@pellared pellared deleted the otel-span-model branch March 8, 2021 11:08
zacharycmontoya added a commit to DataDog/dd-trace-dotnet that referenced this pull request Mar 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants