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

feat: Add OpenTelemetry Instrumentation Provider #68

Merged
merged 5 commits into from
Nov 16, 2023
Merged

Conversation

ashovlin
Copy link
Contributor

@ashovlin ashovlin commented Nov 1, 2023

Issue #, if available: DOTNET-7031

Description of changes:

  • Adds AWS.Messaging.Telemetry.OpenTelemetry, which instruments the AWS Message Processing Framework for .NET for OpenTelemetry
  • Outside of OTel, the framework will now serialize MessageEnvelope.Metadata as top-level properties in the JSON envelope when sending messages. We're using this to telemetry, but us and customers can use for other CloudEvents extensions and/or custom attributes.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@@ -7,10 +7,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.6.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.6.0" />
Copy link
Contributor Author

@ashovlin ashovlin Nov 1, 2023

Choose a reason for hiding this comment

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

The OpenTelemetry.Extensions.Hosting is necessary to use our new AddAWSMessagingInstrumentation extension method. Should it just be a dependency of our OTel package?

I don't think it's strictly required, see how we're setting up OTel during the unit tests via CreateTracerProviderBuilder() instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is OpenTelemetry.Extensions.Hosting just something a user is going to have on their application that is doing the OTel configuration? Doesn't look like we have a compile dependency on the package.

Copy link
Contributor Author

@ashovlin ashovlin Nov 9, 2023

Choose a reason for hiding this comment

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

OpenTelemetry.Extensions.Hosting enables IServiceCollection.AddOpenTelemetry, which is necessary to use our AddAWSMessagingInstrumentation extension that registers our OpenTelemetryProivder singleton and adds our source name.

But it is not strictly required, since you can also enable OpenTelemetry via CreateTracerProviderBuilder like we do in the tests. This is brought in via OpenTelemetry, which is a transitive dependency for the user via our new AWS.Messaging.Telemetry.OpenTelemetry.

// Alternative to DI, see https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/docs/trace/customizing-the-sdk/README.md#using-sdkcreatetracerproviderbuilder
using (var tracerProvider = Sdk.CreateTracerProviderBuilder()
           .AddSource(TelemetryKeys.Source)
           .ConfigureResource(resource => resource.AddService("unittest"))
           .AddInMemoryExporter(activities).Build())
{
    await _publisher.PublishAsync(new ChatMessage { MessageDescription = "Test Description" });
}

So my thinking was that because the user isn't required to configure OTel via DI, we didn't want to require the dependency ourselves. Though on the other hand it is only ~44KB so if we think it'd be more convenient we could require it ourselves.

Copy link
Contributor

Choose a reason for hiding this comment

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

Lets start without it. Easier to add it then remove it.

{
if (envelope.Metadata.TryGetValue(key, out var value))
{
if (value is JsonElement jsonValue)
Copy link
Contributor Author

@ashovlin ashovlin Nov 1, 2023

Choose a reason for hiding this comment

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

Norm's prototype introduced DictionaryStringObjectConverter, which was deserializing these back to strings.

I didn't implement that yet since we'd have to cover a lot of primitive types, so for now both customers and us will have to use JsonElement and know what type corresponds to which key. Let me know if you'd rather use Norm's approach though.

This also means the else if is dead code, had left it in from iterating on tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer we do some conversion. It seems confusing to users that in the publish they put in a string or number and then on the subscriber they type has changed. And it feels like we are leaking our serialization logic potentially into user's business code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I attempted to apply the converter like the prototype, but realized via dotnet/runtime#32903 that a custom JsonConverter cannot deserialize the dictionary attributed with JsonExtensionData by design.

The prototype was working because it was doing something like this, so that MessageEnvelope.Metadata was not being treated as overflow via JsonExtensionData

{ 
    "metadata": {
         "traceparent": "..."
    } 
}

But we want the OTel info (and possibly other extensions, set by us internally or users) in the top of the CloudEvents envelope.

We had an internal discussion on 11/9/23 debating:

  1. Leave deserialization as-is (both us and users will have to work with JsonElement values) when handling Metadata. We’ll route MPF’s OTel info through this path.
  2. Move the OTel properties to real properties in our MessageEnvelope<T>
    • Simplifies this PR since we're not working with overflow, but not user-provided extensions.
    • We also want to avoid any OTel code in the core AWS.Messaging package if possible.
  3. We could use JsonExtensionData to initially de-serialize overflow into something like internal RawMetadata<string, JsonElement> and then do our own copy+casting into a separate public Metadata<string, object>.

We agreed to start with approach number 1 for now.

src/AWS.Messaging/MessageEnvelope.cs Show resolved Hide resolved
src/AWS.Messaging/Telemetry/TelemetryKeys.cs Outdated Show resolved Hide resolved
@@ -7,10 +7,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.6.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.6.0" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Is OpenTelemetry.Extensions.Hosting just something a user is going to have on their application that is doing the OTel configuration? Doesn't look like we have a compile dependency on the package.

{
if (envelope.Metadata.TryGetValue(key, out var value))
{
if (value is JsonElement jsonValue)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer we do some conversion. It seems confusing to users that in the publish they put in a string or number and then on the subscriber they type has changed. And it feels like we are leaking our serialization logic potentially into user's business code.

src/AWS.Messaging/MessageEnvelope.cs Show resolved Hide resolved
@ashovlin ashovlin requested a review from normj November 10, 2023 17:12
}

/// <summary>
/// Extracts propogation context from a <see cref="MessageEnvelope"/>, meant to be used with <see cref="TextMapPropagator"/>
Copy link
Contributor

Choose a reason for hiding this comment

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

typo in "propogation" should be "propagation"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7592368

namespace AWS.Messaging.Telemetry.OpenTelemetry;

/// <summary>
/// An OpenTelemetry trace (wrapper around a <see cref="Activity"/>
Copy link
Contributor

Choose a reason for hiding this comment

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

you opened a paranthesis but didn't close 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.

Fixed in 7592368

contextToInject = _activity.Context;
}
// Even if an "AWS.Messaging" activity was not created, we still
// propogate the the current activity (if it exists) through the message envelope
Copy link
Contributor

Choose a reason for hiding this comment

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

you have 2 "the" in a row.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7592368

envelope.Metadata[key] = JsonSerializer.SerializeToElement(value);
}

private bool _disposed;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add a new line between the property and the summary below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7592368

@@ -362,7 +362,7 @@ public async Task SerializationCallbacks_AreCorrectlyInvoked()
Assert.Equal("1.0", envelope.Version);
Assert.Equal("/aws/messaging", envelope.Source?.ToString());
Assert.Equal("addressInfo", envelope.MessageTypeIdentifier);
Assert.Equal(true, envelope.Metadata["Is-Delivered"]);
Assert.Equal(true, envelope.Metadata["Is-Delivered"].GetBoolean());
Copy link
Contributor

Choose a reason for hiding this comment

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

could you please resolve the warnings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was taking a small moral stance here, since we weren't really asserting that "some condition is true", rather more like "this piece of data is equal to the value true".

But updated, not a big deal.

@ashovlin ashovlin merged commit 7a38d82 into dev Nov 16, 2023
5 checks passed
@ashovlin ashovlin deleted the shovlia/otel branch November 16, 2023 17:55
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.

3 participants