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 DD_TRACE_GLOBAL_TAGS Configuration Option #533

Merged
merged 13 commits into from
Oct 26, 2019
Merged

Conversation

bobuva
Copy link
Contributor

@bobuva bobuva commented Oct 18, 2019

Addresses: Addition of a new configuration variable, DD_TRACE_GLOBAL_TAGS, for setting global tags through environment. This is the first configuration setting whose value is a dictionary, with string key and string value.

Rationale

These changes make the .NET tracer consistent with the other language tracers which already have the ability to add global tags.

Changes proposed in this pull request:

Configuration Settings

The DD_TRACE_GLOBAL_TAGS configuration setting will now be recognized by the .NET Tracer, so that all new spans are tags with each of the entries in the configuration setting.

@DataDog/apm-dotnet

@bobuva bobuva added area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) type:new-feature status:work-in-progress Actively worked on. If this is a PR, no review needed yet. WIP. labels Oct 18, 2019
@bobuva bobuva added this to the 1.8.1 milestone Oct 18, 2019
@bobuva bobuva self-assigned this Oct 18, 2019
@bobuva bobuva marked this pull request as ready for review October 21, 2019 19:25
@bobuva bobuva requested a review from a team as a code owner October 21, 2019 19:25
@bobuva bobuva changed the title [WIP] Add DD_TRACE_GLOBAL_TAGS env variable and APIs Add DD_TRACE_GLOBAL_TAGS Configuration Option Oct 25, 2019

// Presence of a curly brace indicates that this is likely a JSON string. An exception will be thrown
// if it fails to parse or convert to a dictionary.
if (token.ToString().Contains("{"))
Copy link
Member

Choose a reason for hiding this comment

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

At this point the json library already deserialized the json string, so I don't think we should serialize it back to a json string by calling JToken.ToString(). Consider checking the JToken.Type instead.

Suggested change
if (token.ToString().Contains("{"))
if (token.Type == JTokenType.Object)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh that's interesting. I was looking for a JToken method or property to validate, but that looks good! Thanks I'll test it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lucaspimentel if (token.Type == JTokenType.Object) won't work. The token.Type is String even in the case where the config string contains valid JSON, so it is never converted. What might work best is to not convert it using a ToObject but instead always treat it as a string and use StringConfigurationSource.ParseCustomKeyValues all of the time. This has the side-benefit of being able to handle the case of an extra curly brace in the config.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After more thought, I'm inclined to continue to try to verify that the string is JSON by checking for the { character. Since this is considered to be a JSON-formatted config string, that appears to be the only way to verify it, at least that I can find. What I said above about always treating it like a string is not a good idea because bad JSON, e.g., including extra braces, will result in the keys and/or values including the braces, which would be a bad thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, this has finally been improved (thanks for the insight, @lucaspimentel ). Now the tests for JsonConfigurationSource do not start by putting the key/value data into a dictionary. Instead, the value is passed directly to the JsonConfigurationSource constructor, as it should.

I also added a second bad data test.

// if it fails to parse or convert to a dictionary.
if (token.ToString().Contains("{"))
{
var dictionary = JToken.Parse(token.ToString())
Copy link
Member

Choose a reason for hiding this comment

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

Similar to above, token.ToString() serializes the token back into a json string, only to deserialize it a second time with JToken.Parse(). We should be able drop these two calls and use token.ToObject(...) directly.

Suggested change
var dictionary = JToken.Parse(token.ToString())
var dictionary = token

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

if (token.ToString().Contains("{"))
{
var dictionary = JToken.Parse(token.ToString())
?.ToObject(typeof(ConcurrentDictionary<string, string>)) as ConcurrentDictionary<string, string>;
Copy link
Member

@lucaspimentel lucaspimentel Oct 25, 2019

Choose a reason for hiding this comment

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

This one's more of a nit, but there's a generic version of ToObject<T> which doesn't require the cast, making this more concise.

Suggested change
?.ToObject(typeof(ConcurrentDictionary<string, string>)) as ConcurrentDictionary<string, string>;
?.ToObject<ConcurrentDictionary<string, string>>();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

CopyToOutputDirectory="Always"
CopyToPublishDirectory="Always"
Link="profiler-lib\integrations.json" />
<None Include="..\..\src\Datadog.Trace.ClrProfiler.Native\bin\$(Configuration)\$(Platform)\**" CopyToOutputDirectory="Always" CopyToPublishDirectory="Always" Link="profiler-lib\%(RecursiveDir)\%(Filename)%(Extension)" />
Copy link
Member

Choose a reason for hiding this comment

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

Can we revert this entire files? Looks like VS added some json schema stuff at the end and also reformatted this line. I assume this happened while you were testing things.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@@ -90,7 +105,8 @@ public void DefaultSetting(Func<TracerSettings, object> settingGetter, object ex

[Theory]
[MemberData(nameof(GetTestData))]
public void NameValueConfigurationSource1(
[MemberData(nameof(GetJsonTestData))]
Copy link
Member

Choose a reason for hiding this comment

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

Nice. I didn't know we could use [MemberData] multiple times. TIL.


[Theory]
[MemberData(nameof(GetBadJsonTestData))]
public void JsonConfigurationSource_BadData(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice test! 👍

@bobuva bobuva removed the status:work-in-progress Actively worked on. If this is a PR, no review needed yet. WIP. label Oct 25, 2019
@@ -76,6 +78,9 @@ public TracerSettings(IConfigurationSource source)
false;

Integrations = new IntegrationSettingsCollection(source);

GlobalTags = source?.GetDictionary(ConfigurationKeys.GlobalTags) as IDictionary<string, string> ??
Copy link
Member

@lucaspimentel lucaspimentel Oct 25, 2019

Choose a reason for hiding this comment

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

nit: GetDictionary() already returns the correct type. No need to cast.

Suggested change
GlobalTags = source?.GetDictionary(ConfigurationKeys.GlobalTags) as IDictionary<string, string> ??
GlobalTags = source?.GetDictionary(ConfigurationKeys.GlobalTags) ??

if (token.Type == JTokenType.Object)
{
var dictionary = token
?.ToObject<ConcurrentDictionary<string, string>>() as ConcurrentDictionary<string, string>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I think the as ConcurrentDictionary<string,string> is redundant with the parameterized ToObject call

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, yes! I just removed another of these earlier. Thanks for catching it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zacharycmontoya I take that back, actually, the reason I put the as ConcurrentDictionary... in there is to avoid an exception being thrown. I think we need it there.

Copy link
Member

Choose a reason for hiding this comment

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

We don't need the cast. The generic ToObject<T>() returns T, that cast is a no-op.

What exception were you expecting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the cast fails at runtime, an InvalidCastException could be thrown. I don't know, maybe I'm wrong.

Copy link
Member

@lucaspimentel lucaspimentel Oct 28, 2019

Choose a reason for hiding this comment

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

Oh, I see. You're not wrong! We should probably wrap this in a try/catch in case the json is not valid (i.e. neither a string or an object).

But note that if the call to ToObject<T>() itself fails and throws an exception, that will happen whether we have that cast or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The as <type> construction is intended to avoid the exception. It will return null instead of throwing. Can't seem to find the docs on it at the moment, but check out https://www.geeksforgeeks.org/c-sharp-as-operator-keyword/

Copy link
Collaborator

@zacharycmontoya zacharycmontoya left a comment

Choose a reason for hiding this comment

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

I left one small nit, but otherwise looks good! Thanks for iterating patiently with all of the feedback 😄

@bobuva
Copy link
Contributor Author

bobuva commented Oct 25, 2019

I left one small nit, but otherwise looks good! Thanks for iterating patiently with all of the feedback 😄

You guys do a great job of reviewing code. Thanks for all of the good feedback!

@bobuva bobuva merged commit cbba883 into master Oct 26, 2019
@bobuva bobuva deleted the bob/add-global-tag-api branch October 26, 2019 22:18
@colin-higgins colin-higgins mentioned this pull request Oct 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) type:new-feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants