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

EnrichmentScope & EnrichingActivityProcessor #969

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
73cee9b
Added EnrichmentScope.
CodeBlanch Aug 1, 2020
66e1724
Tweak EnrichmentScope so it doesn't flow to children where it shouldn't.
CodeBlanch Aug 3, 2020
d52bec5
Merging from master.
CodeBlanch Aug 15, 2020
35a1a9c
Incorporating feedback.
CodeBlanch Aug 15, 2020
b4d6532
Fixed potential null ref exception.
CodeBlanch Aug 15, 2020
1b16067
OnEnd isn't called unless activity.IsAllDataRequested is already true.
CodeBlanch Aug 15, 2020
320c946
Code review.
CodeBlanch Aug 15, 2020
f33057c
Parent null check.
CodeBlanch Aug 15, 2020
651b4c7
Bug fixes and the first round of tests.
CodeBlanch Aug 15, 2020
04a86dd
Updated changelog.
CodeBlanch Aug 15, 2020
e682a6a
Merge branch 'master' into enrichmentscope
CodeBlanch Aug 15, 2020
6b88c9f
Test artifact cleanup.
CodeBlanch Aug 15, 2020
7eeaf58
Out of order disposal tests.
CodeBlanch Aug 15, 2020
7f76856
Added log message for exception thrown enriching activity.
CodeBlanch Aug 15, 2020
793a98c
Fixing spots broken by namespace change.
CodeBlanch Aug 15, 2020
96f9d30
Removed example code.
CodeBlanch Aug 15, 2020
60448bd
Renamed "NextActivity" -> "FirstChild". Added an example + README.
CodeBlanch Aug 15, 2020
102c6b1
Markdown fixup.
CodeBlanch Aug 15, 2020
d319e56
Update docs/trace/enriching-activity-processor/README.md
CodeBlanch Aug 15, 2020
24ef002
Update docs/trace/enriching-activity-processor/README.md
CodeBlanch Aug 15, 2020
239e212
Doc review.
CodeBlanch Aug 15, 2020
caf1dbb
Switch span to activity in doc.
CodeBlanch Aug 15, 2020
6dbe93e
Doc review.
CodeBlanch Aug 15, 2020
3463097
Code/doc review.
CodeBlanch Aug 15, 2020
7c116de
Removed table.
CodeBlanch Aug 15, 2020
505b87d
Merge branch 'master' into enrichmentscope
cijothomas Aug 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "building-your-own-exporter"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "building-your-own-sampler", "docs\trace\building-your-own-sampler\building-your-own-sampler.csproj", "{D6318071-BE9F-43AF-9F28-A38894238627}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "enriching-activity-processor", "docs\trace\enriching-activity-processor\enriching-activity-processor.csproj", "{89EA1013-6E8D-4A69-9D8B-D3FBA0355726}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -380,6 +382,10 @@ Global
{D6318071-BE9F-43AF-9F28-A38894238627}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6318071-BE9F-43AF-9F28-A38894238627}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6318071-BE9F-43AF-9F28-A38894238627}.Release|Any CPU.Build.0 = Release|Any CPU
{89EA1013-6E8D-4A69-9D8B-D3FBA0355726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89EA1013-6E8D-4A69-9D8B-D3FBA0355726}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89EA1013-6E8D-4A69-9D8B-D3FBA0355726}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89EA1013-6E8D-4A69-9D8B-D3FBA0355726}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -408,6 +414,7 @@ Global
{CB401DF1-FF5C-4055-886E-1183E832B2D6} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
{9574368D-1230-4334-AA5D-EC2C2F47AA43} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
{D6318071-BE9F-43AF-9F28-A38894238627} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
{89EA1013-6E8D-4A69-9D8B-D3FBA0355726} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
Expand Down
67 changes: 67 additions & 0 deletions docs/trace/enriching-activity-processor/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// <copyright file="Program.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using System.Net.Http;
using System.Threading.Tasks;
using OpenTelemetry;
using OpenTelemetry.Trace;

public class Program
{
private static readonly HttpClient HttpClient = new HttpClient();

public static async Task Main()
{
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation()
.AddProcessor(new EnrichingActivityProcessor())
.AddConsoleExporter()
.Build();

using (EnrichmentScope.Begin(a =>
{
a.AddTag("mycompany.user_id", 1234);
a.AddTag("mycompany.customer_id", 5678);

HttpRequestMessage request = (HttpRequestMessage)a.GetCustomProperty("HttpHandler.Request");
if (request != null)
{
a.AddTag("http.user_agent", request.Headers.UserAgent.ToString());
}

HttpResponseMessage response = (HttpResponseMessage)a.GetCustomProperty("HttpHandler.Response");
if (response != null)
{
a.AddTag("http.content_type", response.Content.Headers.ContentType.ToString());
}
}))
{
using var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://www.google.com/"),
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
};

request.Headers.UserAgent.TryParseAdd("mycompany/mylibrary");

using var response = await HttpClient.SendAsync(request).ConfigureAwait(false);
}

Console.WriteLine("Press any key to exit.");
Console.ReadKey();
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
}
}
113 changes: 113 additions & 0 deletions docs/trace/enriching-activity-processor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# EnrichingActivityProcessor

Sometimes the built-in instrumentation doesn't add enough data or you want to
augment the data it provides with contextual information your application or
library has available. For these cases OpenTelemetry-dotnet provides an
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
`EnrichingActivityProcessor` and `EnrichmentScope` API.

1. To enrich your spans first add the `EnrichingActivityProcessor` to your
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
`TracerProvider`:

```csharp
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation()
.AddProcessor(new EnrichingActivityProcessor())
.AddConsoleExporter()
.Build();
```
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved

**Note:** Order is important. Make sure your `EnrichingActivityProcessor` is
registered before your `Exporter` and any other `ActivityProcessor`s you are
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
using.

2. Use `EnrichmentScope.Begin` to wrap the call you want to instrument:
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved

```csharp
using (EnrichmentScope.Begin(a =>
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
{
a.AddTag("mycompany.user_id", 1234);
a.AddTag("mycompany.customer_id", 5678);

HttpRequestMessage request = (HttpRequestMessage)a.GetCustomProperty("HttpHandler.Request");
if (request != null)
{
a.AddTag("http.user_agent", request.Headers.UserAgent.ToString());
}

HttpResponseMessage response = (HttpResponseMessage)a.GetCustomProperty("HttpHandler.Response");
if (response != null)
{
a.AddTag("http.content_type", response.Content.Headers.ContentType.ToString());
}
}))
{
using var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("https://www.google.com/"),
};

request.Headers.UserAgent.TryParseAdd("mycompany/mylibrary");

using var response = await HttpClient.SendAsync(request).ConfigureAwait(false);
}
```

In that example the span created for the call to Google will be decorated
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
with `mycompany.user_id` & `mycompany.customer_id` tags (simulating
contextual data) and `http.user_agent` & `http.content_type` tags which are
taken directly from the raw HTTP objects.

## Advanced Usage

### EnrichmentScopeTarget

`EnrichmentScope.Begin` supports a "target" argument which can be used to alter
the enrichment behavior via the `EnrichmentScopeTarget` enumeration:

```csharp
using EnrichmentScope.Begin(
target: EnrichmentScopeTarget.FirstChild,
enrichmentAction: a => a.AddTag("mycompany.user_id", 1234));

using EnrichmentScope.Begin(
target: EnrichmentScopeTarget.AllChildren,
enrichmentAction: a => a.AddTag("mycompany.user_id", 1234));
```

The default behavior is `EnrichmentScopeTarget.FirstChild`.

| Name | Description |
| ---- | ----------- |
| FirstChild | The first child `Activity` created under the scope will be enriched and then the scope will automatically be closed. |
| AllChildren | All child `Activity` objects created under the scope will be enriched until the scope is closed. |

### Nesting

Enrichment scopes may be nested.

```csharp
using EnrichmentScope.Begin(
target: EnrichmentScopeTarget.AllChildren,
enrichmentAction: a => a.AddTag("mycompany.customer_id", 5678))
{
Comment on lines +95 to +98
Copy link
Member

Choose a reason for hiding this comment

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

Cool, I like what is demonstrated here!

using EnrichmentScope.Begin(
target: EnrichmentScopeTarget.FirstChild,
enrichmentAction: a => a.AddTag("mycompany.user_id", 1234))
{
using var response1 = await HttpClient.GetAsync("https://www.google.com/").ConfigureAwait(false);
}

using EnrichmentScope.Begin(
target: EnrichmentScopeTarget.FirstChild,
enrichmentAction: a => a.AddTag("mycompany.user_id", 1818))
{
using var response2 = await HttpClient.GetAsync("https://www.bing.com/").ConfigureAwait(false);
}
}
```

In that example the request to `www.google.com` will be enriched with the
`mycompany.user_id=1234` and `mycompany.customer_id=5678` tags (in that order)
and the request to `www.bing.com` will be enriched with the
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
`mycompany.user_id=1818` and `mycompany.customer_id=5678` tags (in that order).
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<!---
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="$(OpenTelemetryExporterConsolePkgVer)" />
-->
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.Http\OpenTelemetry.Instrumentation.Http.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public override void OnStartActivity(Activity activity, object payload)

activity.SetKind(ActivityKind.Client);
activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method);
activity.SetCustomProperty("HttpHandler.Request", request);
Copy link
Member

Choose a reason for hiding this comment

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

How does this relate to the enrichment stuff?

Copy link
Member Author

Choose a reason for hiding this comment

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

In the demo I wanted to enrich by some context stuff and by adding userAgent from request & contentType from response. Surprised to notice that the raw objects were not available (they are for .NET Framework). So this is making the raw objects available to anyone downstream that might be interested.


this.activitySource.Start(activity);

Expand Down Expand Up @@ -135,7 +136,8 @@ public override void OnStopActivity(Activity activity, object payload)

if (this.stopResponseFetcher.Fetch(payload) is HttpResponseMessage response)
{
// response could be null for DNS issues, timeouts, etc...
activity.SetCustomProperty("HttpHandler.Response", response);

activity.SetTag(SemanticConventions.AttributeHttpStatusCode, (int)response.StatusCode);

activity.SetStatus(
Expand All @@ -158,6 +160,8 @@ public override void OnException(Activity activity, object payload)
return;
}

activity.SetCustomProperty("HttpHandler.Exception", exc);

if (exc is HttpRequestException)
{
if (exc.InnerException is SocketException exception)
Expand Down
2 changes: 2 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* Added EnrichmentScope & EnrichingActivityProcessor
([#969](https://github.com/open-telemetry/opentelemetry-dotnet/pull/969))
* Modified Sampler implementation to match the spec
([#1037](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1037))
* Changed `ActivityProcessor` to implement `IDisposable`
Expand Down
54 changes: 54 additions & 0 deletions src/OpenTelemetry/Trace/EnrichingActivityProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// <copyright file="EnrichingActivityProcessor.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using System.Diagnostics;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Trace
{
/// <summary>
/// An <see cref="ActivityProcessor"/> implementation that enriches <see cref="Activity"/> objects using <see cref="EnrichmentScope"/>s.
/// </summary>
public class EnrichingActivityProcessor : ActivityProcessor
{
/// <inheritdoc/>
public override void OnEnd(Activity activity)
{
EnrichmentScope scope = EnrichmentScope.Current;
while (scope != null)
{
try
{
scope.EnrichmentAction?.Invoke(activity);
}
catch (Exception ex)
{
OpenTelemetrySdkEventSource.Log.SpanProcessorException(nameof(EnrichingActivityProcessor), ex);
}

var nextParent = scope.Parent;

if (scope.EnrichmentTarget == EnrichmentScopeTarget.FirstChild)
{
scope.Dispose();
}

scope = nextParent;
}
}
}
}
Loading