Remove ActivitySourceAdapter (#1836)

Removed ActivitySourceAdapater and made TracerProviderSdk natively support legacy activites
This commit is contained in:
Utkarsh Umesan Pillai 2021-02-25 11:32:17 -08:00 committed by GitHub
parent 39841e5e1f
commit 0581ce2957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 979 additions and 742 deletions

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<!---
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="$(OpenTelemetryExporterConsolePkgVer)" />

View File

@ -31,12 +31,11 @@ namespace OpenTelemetry.Instrumentation.AspNet
/// <summary>
/// Initializes a new instance of the <see cref="AspNetInstrumentation"/> class.
/// </summary>
/// <param name="activitySource">ActivitySource adapter instance.</param>
/// <param name="options">Configuration options for ASP.NET instrumentation.</param>
public AspNetInstrumentation(ActivitySourceAdapter activitySource, AspNetInstrumentationOptions options)
public AspNetInstrumentation(AspNetInstrumentationOptions options)
{
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(
name => new HttpInListener(name, options, activitySource),
name => new HttpInListener(name, options),
listener => listener.Name == AspNetDiagnosticListenerName,
null);
this.diagnosticSourceSubscriber.Subscribe();

View File

@ -37,13 +37,11 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
private readonly PropertyFetcher<object> routeFetcher = new PropertyFetcher<object>("Route");
private readonly PropertyFetcher<string> routeTemplateFetcher = new PropertyFetcher<string>("RouteTemplate");
private readonly AspNetInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
public HttpInListener(string name, AspNetInstrumentationOptions options, ActivitySourceAdapter activitySource)
public HttpInListener(string name, AspNetInstrumentationOptions options)
: base(name)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.activitySource = activitySource;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Activity is retrieved from Activity.Current later and disposed.")]
@ -98,6 +96,9 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
// correctly stop and restore Activity.Current.
newOne.SetCustomProperty("OTel.ActivityByAspNet", activity);
activity.SetCustomProperty("OTel.ActivityByHttpInListener", newOne);
// Set IsAllDataRequested to false for the activity created by the framework to only export the sibling activity and not the framework activity
activity.IsAllDataRequested = false;
activity = newOne;
}
@ -111,7 +112,8 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
var path = requestValues.Path;
activity.DisplayName = path;
this.activitySource.Start(activity, ActivityKind.Server, ActivitySource);
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource);
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Server);
if (activity.IsAllDataRequested)
{
@ -244,8 +246,6 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
Activity.Current = activity;
}
}
this.activitySource.Stop(activityToEnrich);
}
}
}

View File

@ -16,6 +16,7 @@
using System;
using OpenTelemetry.Instrumentation.AspNet;
using OpenTelemetry.Instrumentation.AspNet.Implementation;
namespace OpenTelemetry.Trace
{
@ -42,7 +43,10 @@ namespace OpenTelemetry.Trace
var aspnetOptions = new AspNetInstrumentationOptions();
configureAspNetInstrumentationOptions?.Invoke(aspnetOptions);
builder.AddDiagnosticSourceInstrumentation((activitySource) => new AspNetInstrumentation(activitySource, aspnetOptions));
builder.AddInstrumentation(() => new AspNetInstrumentation(aspnetOptions));
builder.AddSource(HttpInListener.ActivitySourceName);
builder.AddLegacyActivity("Microsoft.AspNet.HttpReqIn"); // for the activities created by AspNetCore
builder.AddLegacyActivity("ActivityCreatedByHttpInListener"); // for the sibling activities created by the instrumentation library
return builder;
}

View File

@ -29,11 +29,10 @@ namespace OpenTelemetry.Instrumentation.AspNetCore
/// <summary>
/// Initializes a new instance of the <see cref="AspNetCoreInstrumentation"/> class.
/// </summary>
/// <param name="activitySource">ActivitySource adapter instance.</param>
/// <param name="options">Configuration options for ASP.NET Core instrumentation.</param>
public AspNetCoreInstrumentation(ActivitySourceAdapter activitySource, AspNetCoreInstrumentationOptions options)
public AspNetCoreInstrumentation(AspNetCoreInstrumentationOptions options)
{
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpInListener("Microsoft.AspNetCore", options, activitySource), null);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpInListener("Microsoft.AspNetCore", options), null);
this.diagnosticSourceSubscriber.Subscribe();
}

View File

@ -30,12 +30,13 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
{
internal class HttpInListener : ListenerHandler
{
internal const string ActivityOperationName = "Microsoft.AspNetCore.Hosting.HttpRequestIn";
internal const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener";
internal static readonly AssemblyName AssemblyName = typeof(HttpInListener).Assembly.GetName();
internal static readonly string ActivitySourceName = AssemblyName.Name;
internal static readonly Version Version = AssemblyName.Version;
internal static readonly ActivitySource ActivitySource = new ActivitySource(ActivitySourceName, Version.ToString());
private const string UnknownHostName = "UNKNOWN-HOST";
private const string ActivityNameByHttpInListener = "ActivityCreatedByHttpInListener";
private static readonly Func<HttpRequest, string, IEnumerable<string>> HttpRequestHeaderValuesGetter = (request, name) => request.Headers[name];
private readonly PropertyFetcher<HttpContext> startContextFetcher = new PropertyFetcher<HttpContext>("HttpContext");
private readonly PropertyFetcher<HttpContext> stopContextFetcher = new PropertyFetcher<HttpContext>("HttpContext");
@ -45,14 +46,12 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
private readonly PropertyFetcher<string> beforeActionTemplateFetcher = new PropertyFetcher<string>("Template");
private readonly bool hostingSupportsW3C;
private readonly AspNetCoreInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
public HttpInListener(string name, AspNetCoreInstrumentationOptions options, ActivitySourceAdapter activitySource)
public HttpInListener(string name, AspNetCoreInstrumentationOptions options)
: base(name)
{
this.hostingSupportsW3C = typeof(HttpRequest).Assembly.GetName().Version.Major >= 3;
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.activitySource = activitySource;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
@ -99,6 +98,9 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
// Starting the new activity make it the Activity.Current one.
newOne.Start();
// Set IsAllDataRequested to false for the activity created by the framework to only export the sibling activity and not the framework activity
activity.IsAllDataRequested = false;
activity = newOne;
}
@ -108,7 +110,8 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
}
}
this.activitySource.Start(activity, ActivityKind.Server, ActivitySource);
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource);
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Server);
if (activity.IsAllDataRequested)
{
@ -208,8 +211,6 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
// the one created by the instrumentation.
// And retrieve it here, and set it to Current.
}
this.activitySource.Stop(activity);
}
public override void OnCustom(string name, Activity activity, object payload)

View File

@ -16,6 +16,7 @@
using System;
using OpenTelemetry.Instrumentation.AspNetCore;
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
namespace OpenTelemetry.Trace
{
@ -41,7 +42,10 @@ namespace OpenTelemetry.Trace
var aspnetCoreOptions = new AspNetCoreInstrumentationOptions();
configureAspNetCoreInstrumentationOptions?.Invoke(aspnetCoreOptions);
builder.AddDiagnosticSourceInstrumentation((activitySource) => new AspNetCoreInstrumentation(activitySource, aspnetCoreOptions));
builder.AddInstrumentation(() => new AspNetCoreInstrumentation(aspnetCoreOptions));
builder.AddSource(HttpInListener.ActivitySourceName);
builder.AddLegacyActivity(HttpInListener.ActivityOperationName); // for the activities created by AspNetCore
builder.AddLegacyActivity(HttpInListener.ActivityNameByHttpInListener); // for the sibling activities created by the instrumentation library
return builder;
}

View File

@ -29,16 +29,10 @@ namespace OpenTelemetry.Instrumentation.GrpcNetClient
/// <summary>
/// Initializes a new instance of the <see cref="GrpcClientInstrumentation"/> class.
/// </summary>
/// <param name="activitySource">ActivitySource adapter instance.</param>
/// <param name="options">Configuration options for Grpc client instrumentation.</param>
public GrpcClientInstrumentation(ActivitySourceAdapter activitySource, GrpcClientInstrumentationOptions options = null)
public GrpcClientInstrumentation(GrpcClientInstrumentationOptions options = null)
{
if (activitySource == null)
{
throw new ArgumentNullException(nameof(activitySource));
}
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new GrpcClientDiagnosticListener(activitySource, options), null);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new GrpcClientDiagnosticListener(options), null);
this.diagnosticSourceSubscriber.Subscribe();
}

View File

@ -32,20 +32,13 @@ namespace OpenTelemetry.Instrumentation.GrpcNetClient.Implementation
internal static readonly ActivitySource ActivitySource = new ActivitySource(ActivitySourceName, Version.ToString());
private readonly GrpcClientInstrumentationOptions options;
private readonly ActivitySourceAdapter activitySource;
private readonly PropertyFetcher<HttpRequestMessage> startRequestFetcher = new PropertyFetcher<HttpRequestMessage>("Request");
private readonly PropertyFetcher<HttpResponseMessage> stopRequestFetcher = new PropertyFetcher<HttpResponseMessage>("Response");
public GrpcClientDiagnosticListener(ActivitySourceAdapter activitySource, GrpcClientInstrumentationOptions options)
public GrpcClientDiagnosticListener(GrpcClientInstrumentationOptions options)
: base("Grpc.Net.Client")
{
if (activitySource == null)
{
throw new ArgumentNullException(nameof(activitySource));
}
this.options = options;
this.activitySource = activitySource;
}
public override void OnStartActivity(Activity activity, object payload)
@ -89,7 +82,8 @@ namespace OpenTelemetry.Instrumentation.GrpcNetClient.Implementation
activity.DisplayName = grpcMethod?.Trim('/');
this.activitySource.Start(activity, ActivityKind.Client, ActivitySource);
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource);
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client);
if (activity.IsAllDataRequested)
{
@ -158,8 +152,6 @@ namespace OpenTelemetry.Instrumentation.GrpcNetClient.Implementation
}
}
}
this.activitySource.Stop(activity);
}
}
}

View File

@ -16,6 +16,7 @@
using System;
using OpenTelemetry.Instrumentation.GrpcNetClient;
using OpenTelemetry.Instrumentation.GrpcNetClient.Implementation;
namespace OpenTelemetry.Trace
{
@ -43,7 +44,10 @@ namespace OpenTelemetry.Trace
var grpcOptions = new GrpcClientInstrumentationOptions();
configure?.Invoke(grpcOptions);
builder.AddDiagnosticSourceInstrumentation((activitySource) => new GrpcClientInstrumentation(activitySource, grpcOptions));
builder.AddInstrumentation(() => new GrpcClientInstrumentation(grpcOptions));
builder.AddSource(GrpcClientDiagnosticListener.ActivitySourceName);
builder.AddLegacyActivity("Grpc.Net.Client.GrpcOut");
return builder;
}
}

View File

@ -29,11 +29,10 @@ namespace OpenTelemetry.Instrumentation.Http
/// <summary>
/// Initializes a new instance of the <see cref="HttpClientInstrumentation"/> class.
/// </summary>
/// <param name="activitySourceAdapter">ActivitySource adapter instance.</param>
/// <param name="options">Configuration options for HTTP client instrumentation.</param>
public HttpClientInstrumentation(ActivitySourceAdapter activitySourceAdapter, HttpClientInstrumentationOptions options)
public HttpClientInstrumentation(HttpClientInstrumentationOptions options)
{
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerDiagnosticListener(options, activitySourceAdapter), null);
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerDiagnosticListener(options), null);
this.diagnosticSourceSubscriber.Subscribe();
}

View File

@ -37,7 +37,6 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
private static readonly Regex CoreAppMajorVersionCheckRegex = new Regex("^\\.NETCoreApp,Version=v(\\d+)\\.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly ActivitySourceAdapter activitySource;
private readonly PropertyFetcher<HttpRequestMessage> startRequestFetcher = new PropertyFetcher<HttpRequestMessage>("Request");
private readonly PropertyFetcher<HttpResponseMessage> stopResponseFetcher = new PropertyFetcher<HttpResponseMessage>("Response");
private readonly PropertyFetcher<Exception> stopExceptionFetcher = new PropertyFetcher<Exception>("Exception");
@ -45,7 +44,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
private readonly bool httpClientSupportsW3C;
private readonly HttpClientInstrumentationOptions options;
public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options, ActivitySourceAdapter activitySource)
public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options)
: base("HttpHandlerDiagnosticListener")
{
var framework = Assembly
@ -64,7 +63,6 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
}
this.options = options;
this.activitySource = activitySource;
}
public override void OnStartActivity(Activity activity, object payload)
@ -107,7 +105,8 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method);
this.activitySource.Start(activity, ActivityKind.Client, ActivitySource);
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource);
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client);
if (activity.IsAllDataRequested)
{
@ -178,8 +177,6 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
}
}
}
this.activitySource.Stop(activity);
}
public override void OnException(Activity activity, object payload)

View File

@ -16,9 +16,7 @@
using System;
using OpenTelemetry.Instrumentation.Http;
#if NETFRAMEWORK
using OpenTelemetry.Instrumentation.Http.Implementation;
#endif
namespace OpenTelemetry.Trace
{
@ -60,7 +58,9 @@ namespace OpenTelemetry.Trace
configureHttpClientInstrumentationOptions?.Invoke(httpClientOptions);
builder.AddDiagnosticSourceInstrumentation((activitySource) => new HttpClientInstrumentation(activitySource, httpClientOptions));
builder.AddInstrumentation(() => new HttpClientInstrumentation(httpClientOptions));
builder.AddSource(HttpHandlerDiagnosticListener.ActivitySourceName);
builder.AddLegacyActivity("System.Net.Http.HttpRequestOut");
#if NETFRAMEWORK
builder.AddHttpWebRequestInstrumentation(configureHttpWebRequestInstrumentationOptions);

View File

@ -1 +1,2 @@
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool

View File

@ -1 +1,2 @@
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool

View File

@ -1 +1,2 @@
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool

View File

@ -1 +1,2 @@
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddLegacyActivity(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderExtensions.ForceFlush(this OpenTelemetry.Trace.TracerProvider provider, int timeoutMilliseconds = -1) -> bool

View File

@ -9,7 +9,14 @@ please check the latest changes
## Unreleased
* Added `ForceFlush` to `TracerProvider`. ([#1837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1837))
* Added `ForceFlush` to `TracerProvider`.
([#1837](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1837))
* Added a TracerProvierBuilder extension method called
`AddLegacyActivityOperationName` which is used by instrumentation libraries
that use DiagnosticSource to get activities processed without
ActivitySourceAdapter.
[#1836](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1836)
## 1.0.1
@ -57,8 +64,8 @@ Released 2021-Jan-29
invalid attributes we now throw an exception instead of logging an error.
([#1720](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1720))
* Merging "this" resource with an "other" resource now prioritizes the "other"
resource's attributes in a conflict. We've rectified to follow a recent
change to the spec. We previously prioritized "this" resource's tags.
resource's attributes in a conflict. We've rectified to follow a recent change
to the spec. We previously prioritized "this" resource's tags.
([#1728](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1728))
* `BatchExportProcessor` will now flush any remaining spans left in a `Batch`
after the export operation has completed.

View File

@ -0,0 +1,44 @@
// <copyright file="ActivityInstrumentationHelper.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 System.Linq.Expressions;
namespace OpenTelemetry.Instrumentation
{
internal static class ActivityInstrumentationHelper
{
internal static readonly Action<Activity, ActivityKind> SetKindProperty = CreateActivityKindSetter();
internal static readonly Action<Activity, ActivitySource> SetActivitySourceProperty = CreateActivitySourceSetter();
private static Action<Activity, ActivitySource> CreateActivitySourceSetter()
{
ParameterExpression instance = Expression.Parameter(typeof(Activity), "instance");
ParameterExpression propertyValue = Expression.Parameter(typeof(ActivitySource), "propertyValue");
var body = Expression.Assign(Expression.Property(instance, "Source"), propertyValue);
return Expression.Lambda<Action<Activity, ActivitySource>>(body, instance, propertyValue).Compile();
}
private static Action<Activity, ActivityKind> CreateActivityKindSetter()
{
ParameterExpression instance = Expression.Parameter(typeof(Activity), "instance");
ParameterExpression propertyValue = Expression.Parameter(typeof(ActivityKind), "propertyValue");
var body = Expression.Assign(Expression.Property(instance, "Kind"), propertyValue);
return Expression.Lambda<Action<Activity, ActivityKind>>(body, instance, propertyValue).Compile();
}
}
}

View File

@ -1,193 +0,0 @@
// <copyright file="ActivitySourceAdapter.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 System.Linq.Expressions;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Trace
{
/// <summary>
/// This class encapsulates the logic for performing ActivitySource actions
/// on Activities that are created using default ActivitySource.
/// All activities created without using ActivitySource will have a
/// default ActivitySource assigned to them with their name as empty string.
/// This class is to be used by instrumentation adapters which converts/augments
/// activies created without ActivitySource, into something which closely
/// matches the one created using ActivitySource.
/// </summary>
/// <remarks>
/// This class is meant to be only used when writing new Instrumentation for
/// libraries which are already instrumented with DiagnosticSource/Activity
/// following this doc:
/// https://github.com/dotnet/runtime/blob/master/src/libraries/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md.
/// </remarks>
internal class ActivitySourceAdapter
{
private static readonly Action<Activity, ActivityKind> SetKindProperty = CreateActivityKindSetter();
private static readonly Action<Activity, ActivitySource> SetActivitySourceProperty = CreateActivitySourceSetter();
private readonly Sampler sampler;
private readonly Action<Activity> getRequestedDataAction;
private BaseProcessor<Activity> activityProcessor;
internal ActivitySourceAdapter(Sampler sampler, BaseProcessor<Activity> activityProcessor)
{
this.sampler = sampler ?? throw new ArgumentNullException(nameof(sampler));
if (this.sampler is AlwaysOnSampler)
{
this.getRequestedDataAction = this.RunGetRequestedDataAlwaysOnSampler;
}
else if (this.sampler is AlwaysOffSampler)
{
this.getRequestedDataAction = this.RunGetRequestedDataAlwaysOffSampler;
}
else
{
this.getRequestedDataAction = this.RunGetRequestedDataOtherSampler;
}
this.activityProcessor = activityProcessor;
}
private ActivitySourceAdapter()
{
}
/// <summary>
/// Method that starts an <see cref="Activity"/>.
/// </summary>
/// <param name="activity"><see cref="Activity"/> to be started.</param>
/// <param name="kind">ActivityKind to be set of the activity.</param>
/// <param name="source">ActivitySource to be set of the activity.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")]
public void Start(Activity activity, ActivityKind kind, ActivitySource source)
{
OpenTelemetrySdkEventSource.Log.ActivityStarted(activity);
SetActivitySourceProperty(activity, source);
SetKindProperty(activity, kind);
this.getRequestedDataAction(activity);
if (activity.IsAllDataRequested)
{
this.activityProcessor?.OnStart(activity);
}
}
/// <summary>
/// Method that stops an <see cref="Activity"/>.
/// </summary>
/// <param name="activity"><see cref="Activity"/> to be stopped.</param>
public void Stop(Activity activity)
{
OpenTelemetrySdkEventSource.Log.ActivityStopped(activity);
if (activity?.IsAllDataRequested ?? false)
{
this.activityProcessor?.OnEnd(activity);
}
}
internal void UpdateProcessor(BaseProcessor<Activity> processor)
{
this.activityProcessor = processor;
}
private static Action<Activity, ActivitySource> CreateActivitySourceSetter()
{
ParameterExpression instance = Expression.Parameter(typeof(Activity), "instance");
ParameterExpression propertyValue = Expression.Parameter(typeof(ActivitySource), "propertyValue");
var body = Expression.Assign(Expression.Property(instance, "Source"), propertyValue);
return Expression.Lambda<Action<Activity, ActivitySource>>(body, instance, propertyValue).Compile();
}
private static Action<Activity, ActivityKind> CreateActivityKindSetter()
{
ParameterExpression instance = Expression.Parameter(typeof(Activity), "instance");
ParameterExpression propertyValue = Expression.Parameter(typeof(ActivityKind), "propertyValue");
var body = Expression.Assign(Expression.Property(instance, "Kind"), propertyValue);
return Expression.Lambda<Action<Activity, ActivityKind>>(body, instance, propertyValue).Compile();
}
private void RunGetRequestedDataAlwaysOnSampler(Activity activity)
{
activity.IsAllDataRequested = true;
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
}
private void RunGetRequestedDataAlwaysOffSampler(Activity activity)
{
activity.IsAllDataRequested = false;
}
private void RunGetRequestedDataOtherSampler(Activity activity)
{
ActivityContext parentContext;
// Check activity.ParentId alone is sufficient to normally determine if a activity is root or not. But if one uses activity.SetParentId to override the TraceId (without intending to set an actual parent), then additional check of parentspanid being empty is required to confirm if an activity is root or not.
// This checker can be removed, once Activity exposes an API to customize ID Generation (https://github.com/dotnet/runtime/issues/46704) or issue https://github.com/dotnet/runtime/issues/46706 is addressed.
if (string.IsNullOrEmpty(activity.ParentId) || activity.ParentSpanId.ToHexString() == "0000000000000000")
{
parentContext = default;
}
else if (activity.Parent != null)
{
parentContext = activity.Parent.Context;
}
else
{
parentContext = new ActivityContext(
activity.TraceId,
activity.ParentSpanId,
activity.ActivityTraceFlags,
activity.TraceStateString,
isRemote: true);
}
var samplingParameters = new SamplingParameters(
parentContext,
activity.TraceId,
activity.DisplayName,
activity.Kind,
activity.TagObjects,
activity.Links);
var samplingResult = this.sampler.ShouldSample(samplingParameters);
switch (samplingResult.Decision)
{
case SamplingDecision.Drop:
activity.IsAllDataRequested = false;
break;
case SamplingDecision.RecordOnly:
activity.IsAllDataRequested = true;
break;
case SamplingDecision.RecordAndSample:
activity.IsAllDataRequested = true;
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
break;
}
if (samplingResult.Decision != SamplingDecision.Drop)
{
foreach (var att in samplingResult.Attributes)
{
activity.SetTag(att.Key, att.Value);
}
}
}
}
}

View File

@ -71,6 +71,22 @@ namespace OpenTelemetry.Trace
return tracerProviderBuilder;
}
/// <summary>
/// Adds activity with a given operation name to the list of subscribed activities. This is only for legacy activities (i.e. activities created without using ActivitySource API).
/// </summary>
/// <param name="tracerProviderBuilder">TracerProviderBuilder instance.</param>
/// <param name="operationName">OperationName to add.</param>
/// <returns>Returns <see cref="TracerProviderBuilder"/> for chaining.</returns>
public static TracerProviderBuilder AddLegacyActivity(this TracerProviderBuilder tracerProviderBuilder, string operationName)
{
if (tracerProviderBuilder is TracerProviderBuilderSdk tracerProviderBuilderSdk)
{
tracerProviderBuilderSdk.AddLegacyActivity(operationName);
}
return tracerProviderBuilder;
}
public static TracerProvider Build(this TracerProviderBuilder tracerProviderBuilder)
{
if (tracerProviderBuilder is TracerProviderBuilderSdk tracerProviderBuilderSdk)
@ -80,32 +96,5 @@ namespace OpenTelemetry.Trace
return null;
}
/// <summary>
/// Adds a DiagnosticSource based instrumentation.
/// This is required for libraries which is already instrumented with
/// DiagnosticSource and Activity, without using ActivitySource.
/// </summary>
/// <typeparam name="TInstrumentation">Type of instrumentation class.</typeparam>
/// <param name="tracerProviderBuilder">TracerProviderBuilder instance.</param>
/// <param name="instrumentationFactory">Function that builds instrumentation.</param>
/// <returns>Returns <see cref="TracerProviderBuilder"/> for chaining.</returns>
internal static TracerProviderBuilder AddDiagnosticSourceInstrumentation<TInstrumentation>(
this TracerProviderBuilder tracerProviderBuilder,
Func<ActivitySourceAdapter, TInstrumentation> instrumentationFactory)
where TInstrumentation : class
{
if (instrumentationFactory == null)
{
throw new ArgumentNullException(nameof(instrumentationFactory));
}
if (tracerProviderBuilder is TracerProviderBuilderSdk tracerProviderBuilderSdk)
{
tracerProviderBuilderSdk.AddDiagnosticSourceInstrumentation(instrumentationFactory);
}
return tracerProviderBuilder;
}
}
}

View File

@ -26,11 +26,11 @@ namespace OpenTelemetry.Trace
/// </summary>
internal class TracerProviderBuilderSdk : TracerProviderBuilder
{
private readonly List<DiagnosticSourceInstrumentationFactory> diagnosticSourceInstrumentationFactories = new List<DiagnosticSourceInstrumentationFactory>();
private readonly List<InstrumentationFactory> instrumentationFactories = new List<InstrumentationFactory>();
private readonly List<BaseProcessor<Activity>> processors = new List<BaseProcessor<Activity>>();
private readonly List<string> sources = new List<string>();
private readonly Dictionary<string, bool> legacyActivityOperationNames = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private ResourceBuilder resourceBuilder = ResourceBuilder.CreateDefault();
private Sampler sampler = new ParentBasedSampler(new AlwaysOnSampler());
@ -129,55 +129,32 @@ namespace OpenTelemetry.Trace
return this;
}
/// <summary>
/// Adds activity with a given operation name to the list of subscribed activities. This is only for legacy activities (i.e. activities created without using ActivitySource API).
/// </summary>
/// <param name="operationName">OperationName to add.</param>
/// <returns>Returns <see cref="TracerProviderBuilder"/> for chaining.</returns>
internal TracerProviderBuilder AddLegacyActivity(string operationName)
{
if (string.IsNullOrWhiteSpace(operationName))
{
throw new ArgumentException($"{nameof(operationName)} contains null or whitespace string.");
}
this.legacyActivityOperationNames[operationName] = true;
return this;
}
internal TracerProvider Build()
{
return new TracerProviderSdk(
this.resourceBuilder.Build(),
this.sources,
this.diagnosticSourceInstrumentationFactories,
this.instrumentationFactories,
this.sampler,
this.processors);
}
/// <summary>
/// Adds a DiagnosticSource based instrumentation.
/// This is required for libraries which is already instrumented with
/// DiagnosticSource and Activity, without using ActivitySource.
/// </summary>
/// <typeparam name="TInstrumentation">Type of instrumentation class.</typeparam>
/// <param name="instrumentationFactory">Function that builds instrumentation.</param>
/// <returns>Returns <see cref="TracerProviderBuilder"/> for chaining.</returns>
internal TracerProviderBuilder AddDiagnosticSourceInstrumentation<TInstrumentation>(
Func<ActivitySourceAdapter, TInstrumentation> instrumentationFactory)
where TInstrumentation : class
{
if (instrumentationFactory == null)
{
throw new ArgumentNullException(nameof(instrumentationFactory));
}
this.diagnosticSourceInstrumentationFactories.Add(
new DiagnosticSourceInstrumentationFactory(
typeof(TInstrumentation).Name,
"semver:" + typeof(TInstrumentation).Assembly.GetName().Version,
instrumentationFactory));
return this;
}
internal readonly struct DiagnosticSourceInstrumentationFactory
{
public readonly string Name;
public readonly string Version;
public readonly Func<ActivitySourceAdapter, object> Factory;
internal DiagnosticSourceInstrumentationFactory(string name, string version, Func<ActivitySourceAdapter, object> factory)
{
this.Name = name;
this.Version = version;
this.Factory = factory;
}
this.processors,
this.legacyActivityOperationNames);
}
internal readonly struct InstrumentationFactory

View File

@ -20,7 +20,6 @@ using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading;
using OpenTelemetry.Internal;
using OpenTelemetry.Resources;
@ -33,34 +32,29 @@ namespace OpenTelemetry.Trace
private readonly List<object> instrumentations = new List<object>();
private readonly ActivityListener listener;
private readonly Sampler sampler;
private readonly ActivitySourceAdapter adapter;
private readonly Dictionary<string, bool> legacyActivityOperationNames;
private BaseProcessor<Activity> processor;
private Action<Activity> getRequestedDataAction;
private bool supportLegacyActivity;
internal TracerProviderSdk(
Resource resource,
IEnumerable<string> sources,
IEnumerable<TracerProviderBuilderSdk.DiagnosticSourceInstrumentationFactory> diagnosticSourceInstrumentationFactories,
IEnumerable<TracerProviderBuilderSdk.InstrumentationFactory> instrumentationFactories,
Sampler sampler,
List<BaseProcessor<Activity>> processors)
List<BaseProcessor<Activity>> processors,
Dictionary<string, bool> legacyActivityOperationNames)
{
this.Resource = resource;
this.sampler = sampler;
this.legacyActivityOperationNames = legacyActivityOperationNames;
this.supportLegacyActivity = legacyActivityOperationNames.Count > 0;
foreach (var processor in processors)
{
this.AddProcessor(processor);
}
if (diagnosticSourceInstrumentationFactories.Any())
{
this.adapter = new ActivitySourceAdapter(sampler, this.processor);
foreach (var instrumentationFactory in diagnosticSourceInstrumentationFactories)
{
this.instrumentations.Add(instrumentationFactory.Factory(this.adapter));
}
}
if (instrumentationFactories.Any())
{
foreach (var instrumentationFactory in instrumentationFactories)
@ -76,6 +70,21 @@ namespace OpenTelemetry.Trace
{
OpenTelemetrySdkEventSource.Log.ActivityStarted(activity);
if (this.supportLegacyActivity && string.IsNullOrEmpty(activity.Source.Name))
{
// We have a legacy activity in hand now
if (legacyActivityOperationNames.ContainsKey(activity.OperationName))
{
// Legacy activity matches the user configured list. Call sampler for the legacy activity
this.getRequestedDataAction(activity);
}
else
{
// Legacy activity doesn't match the user configured list. No need to proceed further.
return;
}
}
if (!activity.IsAllDataRequested)
{
return;
@ -92,6 +101,16 @@ namespace OpenTelemetry.Trace
{
OpenTelemetrySdkEventSource.Log.ActivityStopped(activity);
if (this.supportLegacyActivity && string.IsNullOrEmpty(activity.Source.Name))
{
// We have a legacy activity in hand now
if (!legacyActivityOperationNames.ContainsKey(activity.OperationName))
{
// Legacy activity doesn't match the user configured list. No need to proceed further.
return;
}
}
if (!activity.IsAllDataRequested)
{
return;
@ -116,17 +135,20 @@ namespace OpenTelemetry.Trace
{
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ActivitySamplingResult.AllDataAndRecorded : ActivitySamplingResult.None;
this.getRequestedDataAction = this.RunGetRequestedDataAlwaysOnSampler;
}
else if (sampler is AlwaysOffSampler)
{
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? PropagateOrIgnoreData(options.Parent.TraceId) : ActivitySamplingResult.None;
this.getRequestedDataAction = this.RunGetRequestedDataAlwaysOffSampler;
}
else
{
// This delegate informs ActivitySource about sampling decision when the parent context is an ActivityContext.
listener.Sample = (ref ActivityCreationOptions<ActivityContext> options) =>
!Sdk.SuppressInstrumentation ? ComputeActivitySamplingResult(options, sampler) : ActivitySamplingResult.None;
this.getRequestedDataAction = this.RunGetRequestedDataOtherSampler;
}
if (sources.Any())
@ -153,7 +175,10 @@ namespace OpenTelemetry.Trace
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
listener.ShouldListenTo = (activitySource) => regex.IsMatch(activitySource.Name);
listener.ShouldListenTo = (activitySource) =>
this.supportLegacyActivity ?
string.IsNullOrEmpty(activitySource.Name) || regex.IsMatch(activitySource.Name) :
regex.IsMatch(activitySource.Name);
}
else
{
@ -164,11 +189,23 @@ namespace OpenTelemetry.Trace
activitySources[name] = true;
}
if (this.supportLegacyActivity)
{
activitySources[string.Empty] = true;
}
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
listener.ShouldListenTo = (activitySource) => activitySources.ContainsKey(activitySource.Name);
}
}
else
{
if (this.supportLegacyActivity)
{
listener.ShouldListenTo = (activitySource) => string.IsNullOrEmpty(activitySource.Name);
}
}
ActivitySource.AddActivityListener(listener);
this.listener = listener;
@ -202,8 +239,6 @@ namespace OpenTelemetry.Trace
});
}
this.adapter?.UpdateProcessor(this.processor);
return this;
}
@ -318,5 +353,73 @@ namespace OpenTelemetry.Trace
? ActivitySamplingResult.PropagationData
: ActivitySamplingResult.None;
}
private void RunGetRequestedDataAlwaysOnSampler(Activity activity)
{
activity.IsAllDataRequested = true;
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
}
private void RunGetRequestedDataAlwaysOffSampler(Activity activity)
{
activity.IsAllDataRequested = false;
}
private void RunGetRequestedDataOtherSampler(Activity activity)
{
ActivityContext parentContext;
// Check activity.ParentId alone is sufficient to normally determine if a activity is root or not. But if one uses activity.SetParentId to override the TraceId (without intending to set an actual parent), then additional check of parentspanid being empty is required to confirm if an activity is root or not.
// This checker can be removed, once Activity exposes an API to customize ID Generation (https://github.com/dotnet/runtime/issues/46704) or issue https://github.com/dotnet/runtime/issues/46706 is addressed.
if (string.IsNullOrEmpty(activity.ParentId) || activity.ParentSpanId.ToHexString() == "0000000000000000")
{
parentContext = default;
}
else if (activity.Parent != null)
{
parentContext = activity.Parent.Context;
}
else
{
parentContext = new ActivityContext(
activity.TraceId,
activity.ParentSpanId,
activity.ActivityTraceFlags,
activity.TraceStateString,
isRemote: true);
}
var samplingParameters = new SamplingParameters(
parentContext,
activity.TraceId,
activity.DisplayName,
activity.Kind,
activity.TagObjects,
activity.Links);
var samplingResult = this.sampler.ShouldSample(samplingParameters);
switch (samplingResult.Decision)
{
case SamplingDecision.Drop:
activity.IsAllDataRequested = false;
break;
case SamplingDecision.RecordOnly:
activity.IsAllDataRequested = true;
break;
case SamplingDecision.RecordAndSample:
activity.IsAllDataRequested = true;
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
break;
}
if (samplingResult.Decision != SamplingDecision.Drop)
{
foreach (var att in samplingResult.Attributes)
{
activity.SetTag(att.Key, att.Value);
}
}
}
}
}

View File

@ -1,78 +0,0 @@
// <copyright file="ActivitySourceAdapterBenchmark.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.Diagnostics;
using BenchmarkDotNet.Attributes;
using OpenTelemetry;
using OpenTelemetry.Trace;
namespace Benchmarks.Trace
{
[MemoryDiagnoser]
public class ActivitySourceAdapterBenchmark
{
private TestInstrumentation testInstrumentation = null;
private TracerProvider tracerProvider;
[GlobalSetup]
public void GlobalSetup()
{
this.tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddDiagnosticSourceInstrumentation((adapter) =>
{
this.testInstrumentation = new TestInstrumentation(adapter);
return this.testInstrumentation;
})
.Build();
}
[GlobalCleanup]
public void GlobalCleanup()
{
this.tracerProvider.Dispose();
}
[Benchmark]
public void ActivitySourceAdapterStartStop()
{
var activity = new Activity("test").Start();
this.testInstrumentation.Start(activity);
this.testInstrumentation.Stop(activity);
activity.Stop();
}
private class TestInstrumentation
{
internal static ActivitySource ActivitySource = new ActivitySource("test", "1.0.0");
private ActivitySourceAdapter adapter;
public TestInstrumentation(ActivitySourceAdapter adapter)
{
this.adapter = adapter;
}
public void Start(Activity activity)
{
this.adapter.Start(activity, ActivityKind.Internal, ActivitySource);
}
public void Stop(Activity activity)
{
this.adapter.Stop(activity);
}
}
}
}

View File

@ -31,6 +31,8 @@ namespace Benchmarks.Trace
private readonly ActivitySource sourceWithOneProcessor = new ActivitySource("Benchmark.OneProcessor");
private readonly ActivitySource sourceWithTwoProcessors = new ActivitySource("Benchmark.TwoProcessors");
private readonly ActivitySource sourceWithThreeProcessors = new ActivitySource("Benchmark.ThreeProcessors");
private readonly ActivitySource sourceWithOneLegacyActivityOperationNameSubscription = new ActivitySource("Benchmark.OneInstrumentation");
private readonly ActivitySource sourceWithTwoLegacyActivityOperationNameSubscriptions = new ActivitySource("Benchmark.TwoInstrumentations");
public TraceBenchmarks()
{
@ -80,6 +82,21 @@ namespace Benchmarks.Trace
.AddProcessor(new DummyActivityProcessor())
.AddProcessor(new DummyActivityProcessor())
.Build();
Sdk.CreateTracerProviderBuilder()
.SetSampler(new AlwaysOnSampler())
.AddSource(this.sourceWithOneLegacyActivityOperationNameSubscription.Name)
.AddLegacyActivity("TestOperationName")
.AddProcessor(new DummyActivityProcessor())
.Build();
Sdk.CreateTracerProviderBuilder()
.SetSampler(new AlwaysOnSampler())
.AddSource(this.sourceWithTwoLegacyActivityOperationNameSubscriptions.Name)
.AddLegacyActivity("TestOperationName1")
.AddLegacyActivity("TestOperationName2")
.AddProcessor(new DummyActivityProcessor())
.Build();
}
[Benchmark]
@ -142,6 +159,22 @@ namespace Benchmarks.Trace
}
}
[Benchmark]
public void OneInstrumentation()
{
using (var activity = this.sourceWithOneLegacyActivityOperationNameSubscription.StartActivity("Benchmark"))
{
}
}
[Benchmark]
public void TwoInstrumentations()
{
using (var activity = this.sourceWithTwoLegacyActivityOperationNameSubscriptions.StartActivity("Benchmark"))
{
}
}
internal class DummyActivityProcessor : BaseProcessor<Activity>
{
}

View File

@ -202,8 +202,8 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}")
{
// only SetParentProvider/Shutdown/Dispose are called because request was filtered.
Assert.Equal(3, activityProcessor.Invocations.Count);
// only SetParentProvider/Shutdown/Dispose/OnStart are called because request was filtered.
Assert.Equal(4, activityProcessor.Invocations.Count);
return;
}
@ -211,7 +211,60 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
var currentActivity = Activity.Current;
Activity span;
Assert.Equal(5, activityProcessor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
if (carrierFormat == "CustomContextNonmatchParent")
{
Assert.Equal(6, activityProcessor.Invocations.Count); // SetParentProvider/OnStart(framework activity)/OnStart(sibling activity)/OnEnd(sibling activity)/OnShutdown/Dispose called.
var startedActivities = activityProcessor.Invocations.Where(invo => invo.Method.Name == "OnStart");
var stoppedActivities = activityProcessor.Invocations.Where(invo => invo.Method.Name == "OnEnd");
Assert.Equal(2, startedActivities.Count());
Assert.Single(stoppedActivities);
// The activity created by the framework and the sibling activity are both sent to Processor.OnStart
Assert.Contains(startedActivities, item =>
{
var startedActivity = item.Arguments[0] as Activity;
return startedActivity.OperationName == HttpInListener.ActivityOperationName;
});
Assert.Contains(startedActivities, item =>
{
var startedActivity = item.Arguments[0] as Activity;
return startedActivity.OperationName == HttpInListener.ActivityNameByHttpInListener;
});
// Only the sibling activity is sent to Processor.OnEnd
Assert.Contains(stoppedActivities, item =>
{
var stoppedActivity = item.Arguments[0] as Activity;
return stoppedActivity.OperationName == HttpInListener.ActivityNameByHttpInListener;
});
}
else
{
Assert.Equal(5, activityProcessor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called.
var startedActivities = activityProcessor.Invocations.Where(invo => invo.Method.Name == "OnStart");
var stoppedActivities = activityProcessor.Invocations.Where(invo => invo.Method.Name == "OnEnd");
// There is no sibling activity created
Assert.Single(startedActivities);
Assert.Single(stoppedActivities);
Assert.Contains(startedActivities, item =>
{
var startedActivity = item.Arguments[0] as Activity;
return startedActivity.OperationName == HttpInListener.ActivityOperationName;
});
// Only the sibling activity is sent to Processor.OnEnd
Assert.Contains(stoppedActivities, item =>
{
var stoppedActivity = item.Arguments[0] as Activity;
return stoppedActivity.OperationName == HttpInListener.ActivityOperationName;
});
}
span = (Activity)activityProcessor.Invocations[2].Arguments[0];
Assert.Equal(

View File

@ -88,7 +88,10 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
}
Assert.Equal(3, activityProcessor.Invocations.Count); // begin and end was called
var activity = (Activity)activityProcessor.Invocations[2].Arguments[0];
// we should only call Processor.OnEnd for the "/api/values" request
Assert.Single(activityProcessor.Invocations, invo => invo.Method.Name == "OnEnd");
var activity = activityProcessor.Invocations.FirstOrDefault(invo => invo.Method.Name == "OnEnd").Arguments[0] as Activity;
Assert.Equal(200, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
@ -168,8 +171,15 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
WaitForProcessorInvocations(activityProcessor, 3);
}
Assert.Equal(3, activityProcessor.Invocations.Count); // begin and end was called
var activity = (Activity)activityProcessor.Invocations[2].Arguments[0];
// List of invocations
// 1. SetParentProvider for TracerProviderSdk
// 2. OnStart for the activity created by AspNetCore with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// 3. OnStart for the sibling activity created by the instrumentation library with the OperationName: ActivityCreatedByHttpInListener
// 4. OnEnd for the sibling activity created by the instrumentation library with the OperationName: ActivityCreatedByHttpInListener
// we should only call Processor.OnEnd once for the sibling activity with the OperationName ActivityCreatedByHttpInListener
Assert.Single(activityProcessor.Invocations, invo => invo.Method.Name == "OnEnd");
var activity = activityProcessor.Invocations.FirstOrDefault(invo => invo.Method.Name == "OnEnd").Arguments[0] as Activity;
#if !NETCOREAPP2_1
// ASP.NET Core after 2.x is W3C aware and hence Activity created by it
@ -221,14 +231,42 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
var response = await client.GetAsync("/api/values/2");
response.EnsureSuccessStatusCode(); // Status Code 200-299
WaitForProcessorInvocations(activityProcessor, 3);
WaitForProcessorInvocations(activityProcessor, 4);
}
// begin and end was called once each.
Assert.Equal(3, activityProcessor.Invocations.Count);
var activity = (Activity)activityProcessor.Invocations[2].Arguments[0];
Assert.Equal("ActivityCreatedByHttpInListener", activity.OperationName);
// List of invocations on the processor
// 1. SetParentProvider for TracerProviderSdk
// 2. OnStart for the activity created by AspNetCore with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// 3. OnStart for the sibling activity created by the instrumentation library with the OperationName: ActivityCreatedByHttpInListener
// 4. OnEnd for the sibling activity created by the instrumentation library with the OperationName: ActivityCreatedByHttpInListener
Assert.Equal(4, activityProcessor.Invocations.Count);
var startedActivities = activityProcessor.Invocations.Where(invo => invo.Method.Name == "OnStart");
var stoppedActivities = activityProcessor.Invocations.Where(invo => invo.Method.Name == "OnEnd");
Assert.Equal(2, startedActivities.Count());
Assert.Single(stoppedActivities);
// The activity created by the framework and the sibling activity are both sent to Processor.OnStart
Assert.Contains(startedActivities, item =>
{
var startedActivity = item.Arguments[0] as Activity;
return startedActivity.OperationName == HttpInListener.ActivityOperationName;
});
Assert.Contains(startedActivities, item =>
{
var startedActivity = item.Arguments[0] as Activity;
return startedActivity.OperationName == HttpInListener.ActivityNameByHttpInListener;
});
// Only the sibling activity is sent to Processor.OnEnd
Assert.Contains(stoppedActivities, item =>
{
var stoppedActivity = item.Arguments[0] as Activity;
return stoppedActivity.OperationName == HttpInListener.ActivityNameByHttpInListener;
});
var activity = activityProcessor.Invocations.FirstOrDefault(invo => invo.Method.Name == "OnEnd").Arguments[0] as Activity;
Assert.Equal(ActivityKind.Server, activity.Kind);
Assert.True(activity.Duration != TimeSpan.Zero);
Assert.Equal("api/Values/{id}", activity.DisplayName);
@ -271,12 +309,18 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
response1.EnsureSuccessStatusCode(); // Status Code 200-299
response2.EnsureSuccessStatusCode(); // Status Code 200-299
WaitForProcessorInvocations(activityProcessor, 3);
WaitForProcessorInvocations(activityProcessor, 4);
}
// we should only create one span and never call processor with another
Assert.Equal(3, activityProcessor.Invocations.Count); // begin and end was called
var activity = (Activity)activityProcessor.Invocations[2].Arguments[0];
// 1. SetParentProvider for TracerProviderSdk
// 2. OnStart for the activity created by AspNetCore for "/api/values" with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// 3. OnEnd for the activity created by AspNetCore for "/api/values" with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// 4. OnStart for the activity created by AspNetCore for "/api/values/2" with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
Assert.Equal(4, activityProcessor.Invocations.Count);
// we should only call Processor.OnEnd for the "/api/values" request
Assert.Single(activityProcessor.Invocations, invo => invo.Method.Name == "OnEnd");
var activity = activityProcessor.Invocations.FirstOrDefault(invo => invo.Method.Name == "OnEnd").Arguments[0] as Activity;
Assert.Equal(ActivityKind.Server, activity.Kind);
Assert.Equal("/api/values", activity.GetTagValue(SpanAttributeConstants.HttpPathKey) as string);
@ -328,8 +372,16 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
// As InstrumentationFilter threw, we continue as if the
// InstrumentationFilter did not exist.
Assert.Equal(3, activityProcessor.Invocations.Count); // begin and end was called
var activity = (Activity)activityProcessor.Invocations[2].Arguments[0];
// List of invocations on the processor
// 1. SetParentProvider for TracerProviderSdk
// 2. OnStart for the activity created by AspNetCore for "/api/values" with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// 3. OnEnd for the activity created by AspNetCore for "/api/values" with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// 4. OnStart for the activity created by AspNetCore for "/api/values/2" with the OperationName: Microsoft.AspNetCore.Hosting.HttpRequestIn
// we should only call Processor.OnEnd for the "/api/values" request
Assert.Single(activityProcessor.Invocations, invo => invo.Method.Name == "OnEnd");
var activity = activityProcessor.Invocations.FirstOrDefault(invo => invo.Method.Name == "OnEnd").Arguments[0] as Activity;
Assert.Equal(ActivityKind.Server, activity.Kind);
Assert.Equal("/api/values", activity.GetTagValue(SpanAttributeConstants.HttpPathKey) as string);

View File

@ -232,7 +232,7 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
await c.SendAsync(request);
}
Assert.Equal(3, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose called.
Assert.Equal(4, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose/OnStart called.
}
[Fact]
@ -249,7 +249,7 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
await c.GetAsync(this.url);
}
Assert.Equal(3, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose called.
Assert.Equal(4, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose/OnStart called.
}
[Fact]
@ -270,7 +270,7 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
}
}
Assert.Equal(3, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose called.
Assert.Equal(4, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose/OnStart called.
}
[Fact]

View File

@ -0,0 +1,53 @@
// <copyright file="ActivityInstrumentationHelperTest.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.Diagnostics;
using Xunit;
namespace OpenTelemetry.Instrumentation.Tests
{
public class ActivityInstrumentationHelperTest
{
[Theory]
[InlineData("TestActivitySource", null)]
[InlineData("TestActivitySource", "1.0.0")]
public void SetActivitySource(string name, string version)
{
var activity = new Activity("Test");
var activitySource = new ActivitySource(name, version);
activity.Start();
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, activitySource);
Assert.Equal(activitySource.Name, activity.Source.Name);
Assert.Equal(activitySource.Version, activity.Source.Version);
activity.Stop();
}
[Theory]
[InlineData(ActivityKind.Client)]
[InlineData(ActivityKind.Consumer)]
[InlineData(ActivityKind.Internal)]
[InlineData(ActivityKind.Producer)]
[InlineData(ActivityKind.Server)]
public void SetActivityKind(ActivityKind activityKind)
{
var activity = new Activity("Test");
activity.Start();
ActivityInstrumentationHelper.SetKindProperty(activity, activityKind);
Assert.Equal(activityKind, activity.Kind);
}
}
}

View File

@ -1,267 +0,0 @@
// <copyright file="ActivitySourceAdapterTest.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.Collections.Generic;
using System.Diagnostics;
using OpenTelemetry.Tests;
using Xunit;
namespace OpenTelemetry.Trace.Tests
{
public class ActivitySourceAdapterTest : IDisposable
{
private TestSampler testSampler;
private TestActivityProcessor testProcessor;
private ActivitySourceAdapter activitySourceAdapter;
static ActivitySourceAdapterTest()
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
}
public ActivitySourceAdapterTest()
{
this.testSampler = new TestSampler();
this.testProcessor = new TestActivityProcessor();
this.activitySourceAdapter = new ActivitySourceAdapter(this.testSampler, this.testProcessor);
}
[Fact]
public void ActivitySourceAdapterValidatesConstructor()
{
// Sampler null
Assert.Throws<ArgumentNullException>(() => new ActivitySourceAdapter(null, this.testProcessor));
// Processor null. This is not expected to throw as processor can
// be null and can be later added.
var adapter = new ActivitySourceAdapter(this.testSampler, null);
}
[Theory]
[InlineData(ActivityKind.Client)]
[InlineData(ActivityKind.Consumer)]
[InlineData(ActivityKind.Internal)]
[InlineData(ActivityKind.Producer)]
[InlineData(ActivityKind.Server)]
public void ActivitySourceAdapterSetsKind(ActivityKind kind)
{
var activity = new Activity("test");
activity.Start();
this.activitySourceAdapter.Start(activity, kind, new ActivitySource("test", "1.0.0"));
Assert.Equal(kind, activity.Kind);
}
[Theory]
[InlineData(SamplingDecision.Drop)]
[InlineData(SamplingDecision.RecordOnly)]
[InlineData(SamplingDecision.RecordAndSample)]
public void ActivitySourceAdapterCallsStartStopActivityProcessor1(SamplingDecision decision)
{
this.testSampler.SamplingAction = (samplingParameters) =>
{
return new SamplingResult(decision);
};
bool startCalled = false;
bool endCalled = false;
this.testProcessor.StartAction =
(a) =>
{
startCalled = true;
// If start is called, that means activity is sampled,
// and TraceFlag is set to Recorded.
Assert.Equal(decision == SamplingDecision.RecordOnly || decision == SamplingDecision.RecordAndSample, a.IsAllDataRequested);
Assert.Equal(decision == SamplingDecision.RecordAndSample ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None, a.ActivityTraceFlags);
Assert.Equal(decision == SamplingDecision.RecordAndSample, a.Recorded);
};
this.testProcessor.EndAction =
(a) =>
{
endCalled = true;
};
var activity = new Activity("test");
activity.Start();
this.activitySourceAdapter.Start(activity, ActivityKind.Producer, new ActivitySource("test", "1.0.0"));
activity.Stop();
this.activitySourceAdapter.Stop(activity);
Assert.Equal(ActivityKind.Producer, activity.Kind);
Assert.Equal(activity.IsAllDataRequested, startCalled);
Assert.Equal(activity.IsAllDataRequested, endCalled);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ActivitySourceAdapterCallsStartStopActivityProcessor2(bool isSampled)
{
this.testSampler.SamplingAction = (samplingParameters) =>
{
return new SamplingResult(isSampled);
};
bool startCalled = false;
bool endCalled = false;
this.testProcessor.StartAction =
(a) =>
{
startCalled = true;
// If start is called, that means activity is sampled,
// and TraceFlag is set to Recorded.
Assert.Equal(isSampled, a.IsAllDataRequested);
Assert.Equal(isSampled ? ActivityTraceFlags.Recorded : ActivityTraceFlags.None, a.ActivityTraceFlags);
Assert.Equal(isSampled, a.Recorded);
};
this.testProcessor.EndAction =
(a) =>
{
endCalled = true;
};
var activity = new Activity("test");
activity.Start();
this.activitySourceAdapter.Start(activity, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
activity.Stop();
this.activitySourceAdapter.Stop(activity);
Assert.Equal(isSampled, startCalled);
Assert.Equal(isSampled, endCalled);
}
[Theory]
[InlineData(SamplingDecision.Drop)]
[InlineData(SamplingDecision.RecordOnly)]
[InlineData(SamplingDecision.RecordAndSample)]
public void ActivitySourceAdapterPopulatesSamplingAttributesToActivity(SamplingDecision sampling)
{
this.testSampler.SamplingAction = (samplingParams) =>
{
var attributes = new Dictionary<string, object>();
attributes.Add("tagkeybysampler", "tagvalueaddedbysampler");
return new SamplingResult(sampling, attributes);
};
var activity = new Activity("test");
activity.Start();
this.activitySourceAdapter.Start(activity, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
if (sampling != SamplingDecision.Drop)
{
Assert.Contains(new KeyValuePair<string, object>("tagkeybysampler", "tagvalueaddedbysampler"), activity.TagObjects);
}
activity.Stop();
}
[Fact]
public void ActivitySourceAdapterPopulatesSamplingParamsCorrectlyForRootActivity()
{
this.testSampler.SamplingAction = (samplingParameters) =>
{
Assert.Equal(default, samplingParameters.ParentContext);
return new SamplingResult(SamplingDecision.RecordAndSample);
};
// Start activity without setting parent. i.e it'll have null parent
// and becomes root activity
var activity = new Activity("test");
activity.Start();
this.activitySourceAdapter.Start(activity, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
activity.Stop();
this.activitySourceAdapter.Stop(activity);
}
[Theory]
[InlineData(ActivityTraceFlags.None)]
[InlineData(ActivityTraceFlags.Recorded)]
public void ActivitySourceAdapterPopulatesSamplingParamsCorrectlyForActivityWithRemoteParent(ActivityTraceFlags traceFlags)
{
var parentTraceId = ActivityTraceId.CreateRandom();
var parentSpanId = ActivitySpanId.CreateRandom();
var parentTraceFlag = (traceFlags == ActivityTraceFlags.Recorded) ? "01" : "00";
string remoteParentId = $"00-{parentTraceId}-{parentSpanId}-{parentTraceFlag}";
string tracestate = "a=b;c=d";
this.testSampler.SamplingAction = (samplingParameters) =>
{
Assert.Equal(parentTraceId, samplingParameters.ParentContext.TraceId);
Assert.Equal(parentSpanId, samplingParameters.ParentContext.SpanId);
Assert.Equal(traceFlags, samplingParameters.ParentContext.TraceFlags);
Assert.Equal(tracestate, samplingParameters.ParentContext.TraceState);
return new SamplingResult(SamplingDecision.RecordAndSample);
};
// Create an activity with remote parent id.
// The sampling parameters are expected to be that of the
// parent context i.e the remote parent.
var activity = new Activity("test").SetParentId(remoteParentId);
activity.TraceStateString = tracestate;
activity.Start();
this.activitySourceAdapter.Start(activity, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
activity.Stop();
this.activitySourceAdapter.Stop(activity);
}
[Theory]
[InlineData(ActivityTraceFlags.None)]
[InlineData(ActivityTraceFlags.Recorded)]
public void ActivitySourceAdapterPopulatesSamplingParamsCorrectlyForActivityWithInProcParent(ActivityTraceFlags traceFlags)
{
// Create some parent activity.
string tracestate = "a=b;c=d";
var activityLocalParent = new Activity("testParent");
activityLocalParent.ActivityTraceFlags = traceFlags;
activityLocalParent.TraceStateString = tracestate;
activityLocalParent.Start();
this.testSampler.SamplingAction = (samplingParameters) =>
{
Assert.Equal(activityLocalParent.TraceId, samplingParameters.ParentContext.TraceId);
Assert.Equal(activityLocalParent.SpanId, samplingParameters.ParentContext.SpanId);
Assert.Equal(activityLocalParent.ActivityTraceFlags, samplingParameters.ParentContext.TraceFlags);
Assert.Equal(tracestate, samplingParameters.ParentContext.TraceState);
Assert.Equal(ActivityKind.Client, samplingParameters.Kind);
return new SamplingResult(SamplingDecision.RecordAndSample);
};
// This activity will have a inproc parent.
// activity.Parent will be equal to the activity created at the beginning of this test.
// Sampling parameters are expected to be that of the parentContext.
// i.e of the parent Activity
var activity = new Activity("test");
activity.Start();
this.activitySourceAdapter.Start(activity, ActivityKind.Client, new ActivitySource("test", "1.0.0"));
activity.Stop();
this.activitySourceAdapter.Stop(activity);
activityLocalParent.Stop();
}
public void Dispose()
{
Activity.Current = null;
this.testProcessor.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,61 @@
// <copyright file="TracerProviderBuilderExtensionsTest.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.Trace;
using Xunit;
namespace OpenTelemetry.Trace.Tests
{
public class TracerProviderBuilderExtensionsTest
{
[Fact]
public void AddLegacyOperationName_NullBuilder_Noop()
{
TracerProviderBuilder builder = null;
// No exception is thrown on executing this line
builder.AddLegacyActivity("TestOperationName");
using var provider = builder.Build();
var emptyActivitySource = new ActivitySource(string.Empty);
Assert.False(emptyActivitySource.HasListeners()); // Check if AddLegacyOperationName was noop after TracerProviderBuilder.Build
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void AddLegacyOperationName_BadArgs(string operationName)
{
var builder = Sdk.CreateTracerProviderBuilder();
Assert.Throws<ArgumentException>(() => builder.AddLegacyActivity(operationName));
}
[Fact]
public void AddLegacyOperationNameAddsActivityListenerForEmptyActivitySource()
{
var emptyActivitySource = new ActivitySource(string.Empty);
var builder = Sdk.CreateTracerProviderBuilder();
builder.AddLegacyActivity("TestOperationName");
Assert.False(emptyActivitySource.HasListeners());
using var provider = builder.Build();
Assert.True(emptyActivitySource.HasListeners());
}
}
}

View File

@ -18,6 +18,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenTelemetry.Instrumentation;
using OpenTelemetry.Resources;
using OpenTelemetry.Tests;
using Xunit;
@ -28,6 +29,11 @@ namespace OpenTelemetry.Trace.Tests
{
private const string ActivitySourceName = "TraceSdkTest";
public TracerProviderSdkTest()
{
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
}
[Fact]
public void TracerProviderSdkInvokesSamplingWithCorrectParameters()
{
@ -252,8 +258,10 @@ namespace OpenTelemetry.Trace.Tests
Assert.False(endCalled);
}
// Test to check that TracerProvider does not call Processor.OnStart or Processor.OnEnd for a legacy activity when no legacy OperationName is
// provided to TracerProviderBuilder.
[Fact]
public void TracerProvideSdkCreatesActivitySource()
public void SdkDoesNotProcessLegacyActivityWithNoAdditionalConfig()
{
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
@ -263,38 +271,301 @@ namespace OpenTelemetry.Trace.Tests
testActivityProcessor.StartAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalled = true;
};
testActivityProcessor.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalled = true;
};
TestInstrumentation testInstrumentation = null;
var emptyActivitySource = new ActivitySource(string.Empty);
Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet
// No AddLegacyOperationName chained to TracerProviderBuilder
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddProcessor(testActivityProcessor)
.AddDiagnosticSourceInstrumentation((adapter) =>
{
testInstrumentation = new TestInstrumentation(adapter);
return testInstrumentation;
})
.Build();
var adapter = testInstrumentation.Adapter;
Activity activity = new Activity("test");
Assert.False(emptyActivitySource.HasListeners()); // No listener for empty ActivitySource even after build
Activity activity = new Activity("Test");
activity.Start();
activity.Stop();
Assert.False(startCalled); // Processor.OnStart is not called since we did not add any legacy OperationName
Assert.False(endCalled); // Processor.OnEnd is not called since we did not add any legacy OperationName
}
// Test to check that TracerProvider samples a legacy activity using a custom Sampler and calls Processor.OnStart and Processor.OnEnd for the
// legacy activity when the correct legacy OperationName is provided to TracerProviderBuilder.
[Fact]
public void SdkSamplesAndProcessesLegacyActivityWithRightConfig()
{
bool samplerCalled = false;
var sampler = new TestSampler
{
SamplingAction =
(samplingParameters) =>
{
samplerCalled = true;
return new SamplingResult(SamplingDecision.RecordAndSample);
},
};
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
testActivityProcessor.StartAction =
(a) =>
{
Assert.True(samplerCalled);
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalled = true;
};
testActivityProcessor.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalled = true;
};
var emptyActivitySource = new ActivitySource(string.Empty);
Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet
var operationNameForLegacyActivity = "TestOperationName";
// AddLegacyOperationName chained to TracerProviderBuilder
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(sampler)
.AddProcessor(testActivityProcessor)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
activity.Stop();
Assert.True(startCalled); // Processor.OnStart is called since we added a legacy OperationName
Assert.True(endCalled); // Processor.OnEnd is called since we added a legacy OperationName
}
// Test to check that TracerProvider samples a legacy activity using a custom Sampler and calls Processor.OnStart and Processor.OnEnd for the
// legacy activity when the correct legacy OperationName is provided to TracerProviderBuilder and a wildcard Source is added
[Fact]
public void SdkSamplesAndProcessesLegacyActivityWithRightConfigOnWildCardMode()
{
bool samplerCalled = false;
var sampler = new TestSampler
{
SamplingAction =
(samplingParameters) =>
{
samplerCalled = true;
return new SamplingResult(SamplingDecision.RecordAndSample);
},
};
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
testActivityProcessor.StartAction =
(a) =>
{
Assert.True(samplerCalled);
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalled = true;
};
testActivityProcessor.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalled = true;
};
var emptyActivitySource = new ActivitySource(string.Empty);
Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet
var operationNameForLegacyActivity = "TestOperationName";
// AddLegacyOperationName chained to TracerProviderBuilder
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(sampler)
.AddSource("ABCCompany.XYZProduct.*") // Adding a wild card source
.AddProcessor(testActivityProcessor)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
activity.Stop();
Assert.True(startCalled); // Processor.OnStart is called since we added a legacy OperationName
Assert.True(endCalled); // Processor.OnEnd is called since we added a legacy OperationName
}
// Test to check that TracerProvider does not call Processor.OnEnd for a legacy activity whose ActivitySource got updated before Activity.Stop and
// the updated source was not added to the Provider
[Fact]
public void SdkCallsOnlyProcessorOnStartForLegacyActivityWhenActivitySourceIsUpdatedWithoutAddSource()
{
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
testActivityProcessor.StartAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalled = true;
};
testActivityProcessor.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalled = true;
};
var emptyActivitySource = new ActivitySource(string.Empty);
Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet
var operationNameForLegacyActivity = "TestOperationName";
var activitySourceForLegacyActvity = new ActivitySource("TestActivitySource", "1.0.0");
// AddLegacyOperationName chained to TracerProviderBuilder
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddLegacyActivity(operationNameForLegacyActivity)
.AddProcessor(testActivityProcessor)
.Build();
Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, activitySourceForLegacyActvity);
activity.Stop();
Assert.True(startCalled); // Processor.OnStart is called since we provided the legacy OperationName
Assert.False(endCalled); // Processor.OnEnd is not called since the ActivitySource is updated and the updated source name is not added as a Source to the provider
}
// Test to check that TracerProvider calls Processor.OnStart and Processor.OnEnd for a legacy activity whose ActivitySource got updated before Activity.Stop and
// the updated source was added to the Provider
[Fact]
public void SdkProcessesLegacyActivityWhenActivitySourceIsUpdatedWithAddSource()
{
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
testActivityProcessor.StartAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalled = true;
};
testActivityProcessor.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalled = true;
};
var emptyActivitySource = new ActivitySource(string.Empty);
Assert.False(emptyActivitySource.HasListeners()); // No ActivityListener for empty ActivitySource added yet
var operationNameForLegacyActivity = "TestOperationName";
var activitySourceForLegacyActvity = new ActivitySource("TestActivitySource", "1.0.0");
// AddLegacyOperationName chained to TracerProviderBuilder
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(activitySourceForLegacyActvity.Name) // Add the updated ActivitySource as a Source
.AddLegacyActivity(operationNameForLegacyActivity)
.AddProcessor(testActivityProcessor)
.Build();
Assert.True(emptyActivitySource.HasListeners()); // Listener for empty ActivitySource added after TracerProvider build
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
ActivityInstrumentationHelper.SetActivitySourceProperty(activity, activitySourceForLegacyActvity);
activity.Stop();
Assert.True(startCalled); // Processor.OnStart is called since we provided the legacy OperationName
Assert.True(endCalled); // Processor.OnEnd is not called since the ActivitySource is updated and the updated source name is added as a Source to the provider
}
// Test to check that TracerProvider continues to process legacy activities even after a new Processor is added after the building the provider.
[Fact]
public void SdkProcessesLegacyActivityEvenAfterAddingNewProcessor()
{
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
testActivityProcessor.StartAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalled = true;
};
testActivityProcessor.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalled = true;
};
var operationNameForLegacyActivity = "TestOperationName";
// AddLegacyOperationName chained to TracerProviderBuilder
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddProcessor(testActivityProcessor)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
adapter.Start(activity, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
adapter.Stop(activity);
activity.Stop();
Assert.True(startCalled);
Assert.True(endCalled);
// As Processors can be added anytime after Provider construction,
// the following validates that updated processors are reflected
// in ActivitySourceAdapter.
// As Processors can be added anytime after Provider construction, the following validates
// the following validates that updated processors are processing the legacy activities created from here on.
TestActivityProcessor testActivityProcessorNew = new TestActivityProcessor();
bool startCalledNew = false;
@ -303,20 +574,23 @@ namespace OpenTelemetry.Trace.Tests
testActivityProcessorNew.StartAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Proccessor.OnStart is called, activity's IsAllDataRequested is set to true
startCalledNew = true;
};
testActivityProcessorNew.EndAction =
(a) =>
{
Assert.False(Sdk.SuppressInstrumentation);
Assert.True(a.IsAllDataRequested); // If Processor.OnEnd is called, activity's IsAllDataRequested is set to true
endCalledNew = true;
};
tracerProvider.AddProcessor(testActivityProcessorNew);
Activity activityNew = new Activity("test");
Activity activityNew = new Activity(operationNameForLegacyActivity); // Create a new Activity with the same operation name
activityNew.Start();
adapter.Start(activityNew, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
adapter.Stop(activityNew);
activityNew.Stop();
Assert.True(startCalledNew);
@ -324,28 +598,164 @@ namespace OpenTelemetry.Trace.Tests
}
[Fact]
public void TracerProvideSdkCreatesActivitySourceWhenNoProcessor()
public void SdkSamplesLegacyActivityWithAlwaysOnSampler()
{
TestInstrumentation testInstrumentation = null;
var operationNameForLegacyActivity = "TestOperationName";
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddDiagnosticSourceInstrumentation((adapter) =>
{
testInstrumentation = new TestInstrumentation(adapter);
return testInstrumentation;
})
.SetSampler(new AlwaysOnSampler())
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
var adapter = testInstrumentation.Adapter;
Activity activity = new Activity("test");
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
adapter.Start(activity, ActivityKind.Internal, new ActivitySource("test", "1.0.0"));
adapter.Stop(activity);
activity.Stop();
// No asserts here. Validates that no exception
// gets thrown when processors are not added,
// TODO: Refactor to have more proper unit test
// to target each individual classes.
Assert.True(activity.IsAllDataRequested);
Assert.True(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));
activity.Stop();
}
[Fact]
public void SdkSamplesLegacyActivityWithAlwaysOffSampler()
{
var operationNameForLegacyActivity = "TestOperationName";
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(new AlwaysOffSampler())
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
Assert.False(activity.IsAllDataRequested);
Assert.False(activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));
activity.Stop();
}
[Theory]
[InlineData(SamplingDecision.Drop, false, false)]
[InlineData(SamplingDecision.RecordOnly, true, false)]
[InlineData(SamplingDecision.RecordAndSample, true, true)]
public void SdkSamplesLegacyActivityWithCustomSampler(SamplingDecision samplingDecision, bool isAllDataRequested, bool hasRecordedFlag)
{
var operationNameForLegacyActivity = "TestOperationName";
var sampler = new TestSampler() { SamplingAction = (samplingParameters) => new SamplingResult(samplingDecision) };
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(sampler)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
Assert.Equal(isAllDataRequested, activity.IsAllDataRequested);
Assert.Equal(hasRecordedFlag, activity.ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));
activity.Stop();
}
[Fact]
public void SdkPopulatesSamplingParamsCorrectlyForRootLegacyActivity()
{
var operationNameForLegacyActivity = "TestOperationName";
var sampler = new TestSampler()
{
SamplingAction = (samplingParameters) =>
{
Assert.Equal(default, samplingParameters.ParentContext);
return new SamplingResult(SamplingDecision.RecordAndSample);
},
};
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(sampler)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
// Start activity without setting parent. i.e it'll have null parent
// and becomes root activity
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
activity.Stop();
}
[Theory]
[InlineData(ActivityTraceFlags.None)]
[InlineData(ActivityTraceFlags.Recorded)]
public void SdkPopulatesSamplingParamsCorrectlyForLegacyActivityWithRemoteParent(ActivityTraceFlags traceFlags)
{
var parentTraceId = ActivityTraceId.CreateRandom();
var parentSpanId = ActivitySpanId.CreateRandom();
var parentTraceFlag = (traceFlags == ActivityTraceFlags.Recorded) ? "01" : "00";
string remoteParentId = $"00-{parentTraceId}-{parentSpanId}-{parentTraceFlag}";
string tracestate = "a=b;c=d";
var operationNameForLegacyActivity = "TestOperationName";
var sampler = new TestSampler()
{
SamplingAction = (samplingParameters) =>
{
Assert.Equal(parentTraceId, samplingParameters.ParentContext.TraceId);
Assert.Equal(parentSpanId, samplingParameters.ParentContext.SpanId);
Assert.Equal(traceFlags, samplingParameters.ParentContext.TraceFlags);
Assert.Equal(tracestate, samplingParameters.ParentContext.TraceState);
return new SamplingResult(SamplingDecision.RecordAndSample);
},
};
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(sampler)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
// Create an activity with remote parent id.
// The sampling parameters are expected to be that of the
// parent context i.e the remote parent.
Activity activity = new Activity(operationNameForLegacyActivity).SetParentId(remoteParentId);
activity.TraceStateString = tracestate;
activity.Start();
activity.Stop();
}
[Theory]
[InlineData(ActivityTraceFlags.None)]
[InlineData(ActivityTraceFlags.Recorded)]
public void SdkPopulatesSamplingParamsCorrectlyForLegacyActivityWithInProcParent(ActivityTraceFlags traceFlags)
{
// Create some parent activity.
string tracestate = "a=b;c=d";
var activityLocalParent = new Activity("TestParent");
activityLocalParent.ActivityTraceFlags = traceFlags;
activityLocalParent.TraceStateString = tracestate;
activityLocalParent.Start();
var operationNameForLegacyActivity = "TestOperationName";
var sampler = new TestSampler()
{
SamplingAction = (samplingParameters) =>
{
Assert.Equal(activityLocalParent.TraceId, samplingParameters.ParentContext.TraceId);
Assert.Equal(activityLocalParent.SpanId, samplingParameters.ParentContext.SpanId);
Assert.Equal(activityLocalParent.ActivityTraceFlags, samplingParameters.ParentContext.TraceFlags);
Assert.Equal(tracestate, samplingParameters.ParentContext.TraceState);
return new SamplingResult(SamplingDecision.RecordAndSample);
},
};
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetSampler(sampler)
.AddLegacyActivity(operationNameForLegacyActivity)
.Build();
// This activity will have a inproc parent.
// activity.Parent will be equal to the activity created at the beginning of this test.
// Sampling parameters are expected to be that of the parentContext.
// i.e of the parent Activity
Activity activity = new Activity(operationNameForLegacyActivity);
activity.Start();
activity.Stop();
}
[Fact]
@ -353,16 +763,14 @@ namespace OpenTelemetry.Trace.Tests
{
TestInstrumentation testInstrumentation = null;
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddDiagnosticSourceInstrumentation((adapter) =>
.AddInstrumentation(() =>
{
testInstrumentation = new TestInstrumentation(adapter);
testInstrumentation = new TestInstrumentation();
return testInstrumentation;
})
.Build();
Assert.NotNull(testInstrumentation);
var adapter = testInstrumentation.Adapter;
Assert.NotNull(adapter);
Assert.False(testInstrumentation.IsDisposed);
tracerProvider.Dispose();
Assert.True(testInstrumentation.IsDisposed);
@ -421,11 +829,9 @@ namespace OpenTelemetry.Trace.Tests
private class TestInstrumentation : IDisposable
{
public bool IsDisposed;
public ActivitySourceAdapter Adapter;
public TestInstrumentation(ActivitySourceAdapter adapter)
public TestInstrumentation()
{
this.Adapter = adapter;
this.IsDisposed = false;
}