diff --git a/Directory.Packages.props b/Directory.Packages.props index 626320dbe96..f7775366744 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,6 +13,7 @@ + diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 7c75ed0c9b2..b6fbf4d3081 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -267,6 +267,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A49299FB-C5CD-4E0E-B7E1-B7867BBD67CC}" ProjectSection(SolutionItems) = preProject src\Shared\ActivityInstrumentationHelper.cs = src\Shared\ActivityInstrumentationHelper.cs + src\Shared\HttpSemanticConventionHelper.cs = src\Shared\HttpSemanticConventionHelper.cs EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DiagnosticSourceInstrumentation", "DiagnosticSourceInstrumentation", "{28F3EC79-660C-4659-8B73-F90DC1173316}" @@ -336,10 +337,6 @@ Global {A38AC295-2745-4B85-8B6B-DCA864CEDD5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {A38AC295-2745-4B85-8B6B-DCA864CEDD5B}.Release|Any CPU.ActiveCfg = Release|Any CPU {A38AC295-2745-4B85-8B6B-DCA864CEDD5B}.Release|Any CPU.Build.0 = Release|Any CPU - {56A34828-621A-478B-A0B8-C065FE938383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56A34828-621A-478B-A0B8-C065FE938383}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56A34828-621A-478B-A0B8-C065FE938383}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56A34828-621A-478B-A0B8-C065FE938383}.Release|Any CPU.Build.0 = Release|Any CPU {1AFFF251-3B0C-47CA-BE94-937083732C0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1AFFF251-3B0C-47CA-BE94-937083732C0A}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AFFF251-3B0C-47CA-BE94-937083732C0A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -356,10 +353,6 @@ Global {305E9DFD-E73B-4A28-8769-795C25551020}.Debug|Any CPU.Build.0 = Debug|Any CPU {305E9DFD-E73B-4A28-8769-795C25551020}.Release|Any CPU.ActiveCfg = Release|Any CPU {305E9DFD-E73B-4A28-8769-795C25551020}.Release|Any CPU.Build.0 = Release|Any CPU - {98F9556B-116F-49B5-9211-BB1D418446FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98F9556B-116F-49B5-9211-BB1D418446FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98F9556B-116F-49B5-9211-BB1D418446FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98F9556B-116F-49B5-9211-BB1D418446FF}.Release|Any CPU.Build.0 = Release|Any CPU {FF3E6E08-E8E4-4523-B526-847CD989279F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FF3E6E08-E8E4-4523-B526-847CD989279F}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF3E6E08-E8E4-4523-B526-847CD989279F}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs index 283126e488a..cca902e2c74 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs @@ -16,6 +16,8 @@ using System.Diagnostics; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.AspNetCore { @@ -24,6 +26,23 @@ namespace OpenTelemetry.Instrumentation.AspNetCore /// public class AspNetCoreInstrumentationOptions { + internal readonly HttpSemanticConvention HttpSemanticConvention; + + /// + /// Initializes a new instance of the class. + /// + public AspNetCoreInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal AspNetCoreInstrumentationOptions(IConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration was null"); + + this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration); + } + /// /// Gets or sets a filter function that determines whether or not to /// collect telemetry on a per request basis. diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs index 23abdeec670..4b2bbb0cb31 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetricsInstrumentationOptions.cs @@ -16,6 +16,8 @@ using System.Diagnostics; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.AspNetCore { @@ -24,6 +26,23 @@ namespace OpenTelemetry.Instrumentation.AspNetCore /// public class AspNetCoreMetricsInstrumentationOptions { + internal readonly HttpSemanticConvention HttpSemanticConvention; + + /// + /// Initializes a new instance of the class. + /// + public AspNetCoreMetricsInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal AspNetCoreMetricsInstrumentationOptions(IConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration was null"); + + this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration); + } + /// /// Delegate for enrichment of recorded metric with additional tags. /// diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 809473ad3aa..bbf749d5414 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -63,7 +63,6 @@ internal class HttpInListener : ListenerHandler #endif private readonly PropertyFetcher stopExceptionFetcher = new("Exception"); private readonly AspNetCoreInstrumentationOptions options; - private readonly HttpSemanticConvention httpSemanticConvention; public HttpInListener(AspNetCoreInstrumentationOptions options) : base(DiagnosticSourceName) @@ -71,8 +70,6 @@ public HttpInListener(AspNetCoreInstrumentationOptions options) Guard.ThrowIfNull(options); this.options = options; - - this.httpSemanticConvention = GetSemanticConventionOptIn(); } public override void OnEventWritten(string name, object payload) @@ -198,7 +195,7 @@ public void OnStartActivity(Activity activity, object payload) activity.DisplayName = path; // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { if (request.Host.HasValue) { @@ -227,7 +224,7 @@ public void OnStartActivity(Activity activity, object payload) } // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { if (request.Host.HasValue) { @@ -284,12 +281,12 @@ public void OnStopActivity(Activity activity, object payload) var response = context.Response; - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { activity.SetTag(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)); } - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)); } @@ -488,12 +485,12 @@ private void AddGrpcAttributes(Activity activity, string grpcMethod, HttpContext activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString()); } - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort); } - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { activity.SetTag(SemanticConventions.AttributeServerPort, context.Connection.RemotePort); } diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs index 17d9e77738e..981d4bc26b0 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs @@ -35,16 +35,12 @@ internal sealed class HttpInMetricsListener : ListenerHandler private readonly AspNetCoreMetricsInstrumentationOptions options; private readonly Histogram httpServerDuration; - private readonly HttpSemanticConvention httpSemanticConvention; - internal HttpInMetricsListener(string name, Meter meter, AspNetCoreMetricsInstrumentationOptions options) : base(name) { this.meter = meter; this.options = options; this.httpServerDuration = meter.CreateHistogram(HttpServerDurationMetricName, "ms", "Measures the duration of inbound HTTP requests."); - - this.httpSemanticConvention = GetSemanticConventionOptIn(); } public override void OnEventWritten(string name, object payload) @@ -83,7 +79,7 @@ public override void OnEventWritten(string name, object payload) TagList tags = default; // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol))); tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpScheme, context.Request.Scheme)); @@ -102,7 +98,7 @@ public override void OnEventWritten(string name, object payload) } // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { tags.Add(new KeyValuePair(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol))); tags.Add(new KeyValuePair(SemanticConventions.AttributeUrlScheme, context.Request.Scheme)); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs index 47cbcdf3fc0..59d65896635 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs @@ -66,10 +66,15 @@ public static MeterProviderBuilder AddAspNetCoreInstrumentation( name ??= Options.DefaultName; - if (configureAspNetCoreInstrumentationOptions != null) + builder.ConfigureServices(services => { - builder.ConfigureServices(services => services.Configure(name, configureAspNetCoreInstrumentationOptions)); - } + if (configureAspNetCoreInstrumentationOptions != null) + { + services.Configure(name, configureAspNetCoreInstrumentationOptions); + } + + services.RegisterOptionsFactory(configuration => new AspNetCoreMetricsInstrumentationOptions(configuration)); + }); builder.AddMeter(AspNetCoreMetrics.InstrumentationName); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj b/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj index f2881a4ca1e..262e88c8c63 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/OpenTelemetry.Instrumentation.AspNetCore.csproj @@ -14,7 +14,9 @@ - + + + diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs index fde28c82ad5..b8872d5912d 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs @@ -68,10 +68,15 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation( name ??= Options.DefaultName; - if (configureAspNetCoreInstrumentationOptions != null) + builder.ConfigureServices(services => { - builder.ConfigureServices(services => services.Configure(name, configureAspNetCoreInstrumentationOptions)); - } + if (configureAspNetCoreInstrumentationOptions != null) + { + services.Configure(name, configureAspNetCoreInstrumentationOptions); + } + + services.RegisterOptionsFactory(configuration => new AspNetCoreInstrumentationOptions(configuration)); + }); if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) { diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/GrpcClientInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.GrpcNetClient/GrpcClientInstrumentationOptions.cs index d03c882aa6f..4ea44b69e59 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/GrpcClientInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/GrpcClientInstrumentationOptions.cs @@ -15,6 +15,8 @@ // using System.Diagnostics; +using Microsoft.Extensions.Configuration; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.GrpcNetClient { @@ -23,6 +25,23 @@ namespace OpenTelemetry.Instrumentation.GrpcNetClient /// public class GrpcClientInstrumentationOptions { + internal readonly HttpSemanticConvention HttpSemanticConvention; + + /// + /// Initializes a new instance of the class. + /// + public GrpcClientInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal GrpcClientInstrumentationOptions(IConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration was null"); + + this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration); + } + /// /// Gets or sets a value indicating whether down stream instrumentation is suppressed (disabled). /// diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs index 04183129d15..73b7cd98f6f 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/Implementation/GrpcClientDiagnosticListener.cs @@ -19,7 +19,6 @@ using OpenTelemetry.Context.Propagation; using OpenTelemetry.Instrumentation.Http; using OpenTelemetry.Trace; -using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.GrpcNetClient.Implementation { @@ -37,14 +36,10 @@ internal sealed class GrpcClientDiagnosticListener : ListenerHandler private readonly PropertyFetcher startRequestFetcher = new("Request"); private readonly PropertyFetcher stopRequestFetcher = new("Response"); - private readonly HttpSemanticConvention httpSemanticConvention; - public GrpcClientDiagnosticListener(GrpcClientInstrumentationOptions options) : base("Grpc.Net.Client") { this.options = options; - - this.httpSemanticConvention = GetSemanticConventionOptIn(); } public override void OnEventWritten(string name, object payload) diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/OpenTelemetry.Instrumentation.GrpcNetClient.csproj b/src/OpenTelemetry.Instrumentation.GrpcNetClient/OpenTelemetry.Instrumentation.GrpcNetClient.csproj index 11ef412972d..08c37511099 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/OpenTelemetry.Instrumentation.GrpcNetClient.csproj +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/OpenTelemetry.Instrumentation.GrpcNetClient.csproj @@ -13,7 +13,9 @@ - + + + diff --git a/src/OpenTelemetry.Instrumentation.GrpcNetClient/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.GrpcNetClient/TracerProviderBuilderExtensions.cs index 00b5b55d505..504a242ac26 100644 --- a/src/OpenTelemetry.Instrumentation.GrpcNetClient/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.GrpcNetClient/TracerProviderBuilderExtensions.cs @@ -63,10 +63,15 @@ public static TracerProviderBuilder AddGrpcClientInstrumentation( name ??= Options.DefaultName; - if (configure != null) + builder.ConfigureServices(services => { - builder.ConfigureServices(services => services.Configure(name, configure)); - } + if (configure != null) + { + services.Configure(name, configure); + } + + services.RegisterOptionsFactory(configuration => new GrpcClientInstrumentationOptions(configuration)); + }); builder.AddSource(GrpcClientDiagnosticListener.ActivitySourceName); builder.AddLegacySource("Grpc.Net.Client.GrpcOut"); diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs index 1084bbdc44d..a4dd10a0639 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs @@ -20,7 +20,9 @@ using System.Net.Http; #endif using System.Runtime.CompilerServices; +using Microsoft.Extensions.Configuration; using OpenTelemetry.Instrumentation.Http.Implementation; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.Http { @@ -29,6 +31,23 @@ namespace OpenTelemetry.Instrumentation.Http /// public class HttpClientInstrumentationOptions { + internal readonly HttpSemanticConvention HttpSemanticConvention; + + /// + /// Initializes a new instance of the class. + /// + public HttpClientInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal HttpClientInstrumentationOptions(IConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration was null"); + + this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration); + } + /// /// Gets or sets a filter function that determines whether or not to /// collect telemetry on a per request basis. diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetricInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetricInstrumentationOptions.cs new file mode 100644 index 00000000000..57ce8b3a6b2 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetricInstrumentationOptions.cs @@ -0,0 +1,42 @@ +// +// 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. +// + +using System.Diagnostics; +using Microsoft.Extensions.Configuration; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; + +namespace OpenTelemetry.Instrumentation.Http +{ + internal sealed class HttpClientMetricInstrumentationOptions + { + internal readonly HttpSemanticConvention HttpSemanticConvention; + + /// + /// Initializes a new instance of the class. + /// + public HttpClientMetricInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal HttpClientMetricInstrumentationOptions(IConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration was null"); + + this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration); + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs index 3bf57029d8a..8901643faf0 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs @@ -44,10 +44,11 @@ internal sealed class HttpClientMetrics : IDisposable /// /// Initializes a new instance of the class. /// - public HttpClientMetrics() + /// HttpClient metric instrumentation options. + public HttpClientMetrics(HttpClientMetricInstrumentationOptions options) { this.meter = new Meter(InstrumentationName, InstrumentationVersion); - this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener", this.meter), this.isEnabled); + this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener", this.meter, options), this.isEnabled); this.diagnosticSourceSubscriber.Subscribe(); } diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index 7197d772f1f..6248407b7a5 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -46,8 +46,6 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler private readonly PropertyFetcher stopRequestStatusFetcher = new("RequestTaskStatus"); private readonly HttpClientInstrumentationOptions options; - private readonly HttpSemanticConvention httpSemanticConvention; - static HttpHandlerDiagnosticListener() { try @@ -64,8 +62,6 @@ public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options) : base("HttpHandlerDiagnosticListener") { this.options = options; - - this.httpSemanticConvention = GetSemanticConventionOptIn(); } public override void OnEventWritten(string name, object payload) @@ -163,7 +159,7 @@ public void OnStartActivity(Activity activity, object payload) } // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { activity.SetTag(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme); activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)); @@ -178,7 +174,7 @@ public void OnStartActivity(Activity activity, object payload) } // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { activity.SetTag(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme); activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, HttpTagHelper.GetNameForHttpMethod(request.Method)); @@ -242,12 +238,12 @@ public void OnStopActivity(Activity activity, object payload) if (this.stopResponseFetcher.TryFetch(payload, out HttpResponseMessage response) && response != null) { - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { activity.SetTag(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)); } - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)); } diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs index 338a55dbf92..55d2e1edf74 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs @@ -31,15 +31,13 @@ internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler private readonly PropertyFetcher stopResponseFetcher = new("Response"); private readonly PropertyFetcher stopRequestFetcher = new("Request"); private readonly Histogram httpClientDuration; + private readonly HttpClientMetricInstrumentationOptions options; - private readonly HttpSemanticConvention httpSemanticConvention; - - public HttpHandlerMetricsDiagnosticListener(string name, Meter meter) + public HttpHandlerMetricsDiagnosticListener(string name, Meter meter, HttpClientMetricInstrumentationOptions options) : base(name) { this.httpClientDuration = meter.CreateHistogram("http.client.duration", "ms", "Measures the duration of outbound HTTP requests."); - - this.httpSemanticConvention = GetSemanticConventionOptIn(); + this.options = options; } public override void OnEventWritten(string name, object payload) @@ -57,7 +55,7 @@ public override void OnEventWritten(string name, object payload) TagList tags = default; // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old)) { tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method))); tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpScheme, request.RequestUri.Scheme)); @@ -76,7 +74,7 @@ public override void OnEventWritten(string name, object payload) } // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/http.md - if (this.httpSemanticConvention.HasFlag(HttpSemanticConvention.New)) + if (this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New)) { tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, HttpTagHelper.GetNameForHttpMethod(request.Method))); tags.Add(new KeyValuePair(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme)); diff --git a/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs index b78c800dee7..0912cc6c967 100644 --- a/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/MeterProviderBuilderExtensions.cs @@ -14,6 +14,8 @@ // limitations under the License. // +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using OpenTelemetry.Instrumentation.Http; using OpenTelemetry.Instrumentation.Http.Implementation; using OpenTelemetry.Internal; @@ -38,6 +40,11 @@ public static MeterProviderBuilder AddHttpClientInstrumentation( // Note: Warm-up the status code mapping. _ = TelemetryHelper.BoxedStatusCodes; + builder.ConfigureServices(services => + { + services.RegisterOptionsFactory(configuration => new HttpClientMetricInstrumentationOptions(configuration)); + }); + // TODO: Implement an IDeferredMeterProviderBuilder // TODO: Handle HttpClientInstrumentationOptions @@ -46,9 +53,9 @@ public static MeterProviderBuilder AddHttpClientInstrumentation( // Enrich - do we want a similar kind of functionality for metrics? // RecordException - probably doesn't make sense for metric instrumentation - var instrumentation = new HttpClientMetrics(); builder.AddMeter(HttpClientMetrics.InstrumentationName); - return builder.AddInstrumentation(() => instrumentation); + return builder.AddInstrumentation(sp => new HttpClientMetrics( + sp.GetRequiredService>().CurrentValue)); } } } diff --git a/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj b/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj index 23a8c183a25..21f527297a8 100644 --- a/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj +++ b/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj @@ -13,7 +13,9 @@ - + + + diff --git a/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs index bebfd21eb9e..237a737a4c7 100644 --- a/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs @@ -65,10 +65,15 @@ public static TracerProviderBuilder AddHttpClientInstrumentation( name ??= Options.DefaultName; - if (configureHttpClientInstrumentationOptions != null) + builder.ConfigureServices(services => { - builder.ConfigureServices(services => services.Configure(name, configureHttpClientInstrumentationOptions)); - } + if (configureHttpClientInstrumentationOptions != null) + { + services.Configure(name, configureHttpClientInstrumentationOptions); + } + + services.RegisterOptionsFactory(configuration => new HttpClientInstrumentationOptions(configuration)); + }); #if NETFRAMEWORK builder.AddSource(HttpWebRequestActivitySource.ActivitySourceName); diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs index fe17f8e907d..8849f218b72 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlClientDiagnosticListener.cs @@ -17,7 +17,6 @@ using System.Data; using System.Diagnostics; using OpenTelemetry.Trace; -using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.SqlClient.Implementation { @@ -41,14 +40,10 @@ internal sealed class SqlClientDiagnosticListener : ListenerHandler private readonly PropertyFetcher exceptionFetcher = new("Exception"); private readonly SqlClientInstrumentationOptions options; - private readonly HttpSemanticConvention httpSemanticConvention; - public SqlClientDiagnosticListener(string sourceName, SqlClientInstrumentationOptions options) : base(sourceName) { this.options = options ?? new SqlClientInstrumentationOptions(); - - this.httpSemanticConvention = GetSemanticConventionOptIn(); } public override bool SupportsNullActivity => true; diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj b/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj index 9a47a00608f..af7c4576c4e 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj +++ b/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj @@ -13,7 +13,9 @@ - + + + @@ -22,6 +24,7 @@ + diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentationOptions.cs index fdb0e29eff4..ed02895b391 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentationOptions.cs @@ -18,7 +18,9 @@ using System.Data; using System.Diagnostics; using System.Text.RegularExpressions; +using Microsoft.Extensions.Configuration; using OpenTelemetry.Trace; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; namespace OpenTelemetry.Instrumentation.SqlClient { @@ -30,6 +32,8 @@ namespace OpenTelemetry.Instrumentation.SqlClient /// public class SqlClientInstrumentationOptions { + internal readonly HttpSemanticConvention HttpSemanticConvention; + /* * Match... * protocol[ ]:[ ]serverName @@ -63,6 +67,21 @@ public class SqlClientInstrumentationOptions private static readonly ConcurrentDictionary ConnectionDetailCache = new(StringComparer.OrdinalIgnoreCase); + /// + /// Initializes a new instance of the class. + /// + public SqlClientInstrumentationOptions() + : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) + { + } + + internal SqlClientInstrumentationOptions(IConfiguration configuration) + { + Debug.Assert(configuration != null, "configuration was null"); + + this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration); + } + /// /// Gets or sets a value indicating whether or not the should add the names of { - builder.ConfigureServices(services => services.Configure(name, configureSqlClientInstrumentationOptions)); - } + if (configureSqlClientInstrumentationOptions != null) + { + services.Configure(name, configureSqlClientInstrumentationOptions); + } + + services.RegisterOptionsFactory(configuration => new SqlClientInstrumentationOptions(configuration)); + }); builder.AddInstrumentation(sp => { diff --git a/src/OpenTelemetry.Api/Internal/HttpSemanticConventionHelper.cs b/src/Shared/HttpSemanticConventionHelper.cs similarity index 84% rename from src/OpenTelemetry.Api/Internal/HttpSemanticConventionHelper.cs rename to src/Shared/HttpSemanticConventionHelper.cs index 1774cbd2069..61d002482c8 100644 --- a/src/OpenTelemetry.Api/Internal/HttpSemanticConventionHelper.cs +++ b/src/Shared/HttpSemanticConventionHelper.cs @@ -14,6 +14,11 @@ // limitations under the License. // +#nullable enable + +using System.Diagnostics; +using Microsoft.Extensions.Configuration; + namespace OpenTelemetry.Internal; /// @@ -27,8 +32,10 @@ namespace OpenTelemetry.Internal; /// internal static class HttpSemanticConventionHelper { + public const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN"; + [Flags] - internal enum HttpSemanticConvention + public enum HttpSemanticConvention { /// /// Instructs an instrumentation library to emit the old experimental HTTP attributes. @@ -46,11 +53,13 @@ internal enum HttpSemanticConvention Dupe = Old | New, } - public static HttpSemanticConvention GetSemanticConventionOptIn() + public static HttpSemanticConvention GetSemanticConventionOptIn(IConfiguration configuration) { + Debug.Assert(configuration != null, "configuration was null"); + try { - var envVarValue = Environment.GetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN"); + var envVarValue = configuration![SemanticConventionOptInKeyName]; return envVarValue?.ToLowerInvariant() switch { "http" => HttpSemanticConvention.New, diff --git a/test/OpenTelemetry.Api.Tests/Internal/HttpSemanticConventionHelperTest.cs b/test/OpenTelemetry.Api.Tests/Internal/HttpSemanticConventionHelperTest.cs deleted file mode 100644 index 48a92230ab7..00000000000 --- a/test/OpenTelemetry.Api.Tests/Internal/HttpSemanticConventionHelperTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// 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. -// - -using Xunit; -using static OpenTelemetry.Internal.HttpSemanticConventionHelper; - -namespace OpenTelemetry.Api.Tests.Internal; - -public class HttpSemanticConventionHelperTest -{ - [Fact] - public void VerifyFlags() - { - var testValue = HttpSemanticConvention.Dupe; - Assert.True(testValue.HasFlag(HttpSemanticConvention.Old)); - Assert.True(testValue.HasFlag(HttpSemanticConvention.New)); - - testValue = HttpSemanticConvention.Old; - Assert.True(testValue.HasFlag(HttpSemanticConvention.Old)); - Assert.False(testValue.HasFlag(HttpSemanticConvention.New)); - - testValue = HttpSemanticConvention.New; - Assert.False(testValue.HasFlag(HttpSemanticConvention.Old)); - Assert.True(testValue.HasFlag(HttpSemanticConvention.New)); - } - - [Fact] - public void VerifyGetSemanticConventionOptIn() - { - this.RunTestWithEnvironmentVariable(null, HttpSemanticConvention.Old); - this.RunTestWithEnvironmentVariable(string.Empty, HttpSemanticConvention.Old); - this.RunTestWithEnvironmentVariable("junk", HttpSemanticConvention.Old); - this.RunTestWithEnvironmentVariable("none", HttpSemanticConvention.Old); - this.RunTestWithEnvironmentVariable("NONE", HttpSemanticConvention.Old); - this.RunTestWithEnvironmentVariable("http", HttpSemanticConvention.New); - this.RunTestWithEnvironmentVariable("HTTP", HttpSemanticConvention.New); - this.RunTestWithEnvironmentVariable("http/dup", HttpSemanticConvention.Dupe); - this.RunTestWithEnvironmentVariable("HTTP/DUP", HttpSemanticConvention.Dupe); - } - - private void RunTestWithEnvironmentVariable(string value, HttpSemanticConvention expected) - { - try - { - Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", value); - - Assert.Equal(expected, GetSemanticConventionOptIn()); - } - finally - { - Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null); - } - } -} diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index 4c67f8f2fe3..88c75b48306 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/test/OpenTelemetry.Tests/Shared/HttpSemanticConventionHelperTest.cs b/test/OpenTelemetry.Tests/Shared/HttpSemanticConventionHelperTest.cs new file mode 100644 index 00000000000..c68c0dc0c82 --- /dev/null +++ b/test/OpenTelemetry.Tests/Shared/HttpSemanticConventionHelperTest.cs @@ -0,0 +1,91 @@ +// +// 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. +// + +using Microsoft.Extensions.Configuration; +using Xunit; +using static OpenTelemetry.Internal.HttpSemanticConventionHelper; + +namespace OpenTelemetry.Tests.Shared; + +public class HttpSemanticConventionHelperTest +{ + [Fact] + public void VerifyFlags() + { + var testValue = HttpSemanticConvention.Dupe; + Assert.True(testValue.HasFlag(HttpSemanticConvention.Old)); + Assert.True(testValue.HasFlag(HttpSemanticConvention.New)); + + testValue = HttpSemanticConvention.Old; + Assert.True(testValue.HasFlag(HttpSemanticConvention.Old)); + Assert.False(testValue.HasFlag(HttpSemanticConvention.New)); + + testValue = HttpSemanticConvention.New; + Assert.False(testValue.HasFlag(HttpSemanticConvention.Old)); + Assert.True(testValue.HasFlag(HttpSemanticConvention.New)); + } + + [Fact] + public void VerifyGetSemanticConventionOptIn() + { + RunTestWithEnvironmentVariable(null, HttpSemanticConvention.Old); + RunTestWithEnvironmentVariable(string.Empty, HttpSemanticConvention.Old); + RunTestWithEnvironmentVariable("junk", HttpSemanticConvention.Old); + RunTestWithEnvironmentVariable("none", HttpSemanticConvention.Old); + RunTestWithEnvironmentVariable("NONE", HttpSemanticConvention.Old); + RunTestWithEnvironmentVariable("http", HttpSemanticConvention.New); + RunTestWithEnvironmentVariable("HTTP", HttpSemanticConvention.New); + RunTestWithEnvironmentVariable("http/dup", HttpSemanticConvention.Dupe); + RunTestWithEnvironmentVariable("HTTP/DUP", HttpSemanticConvention.Dupe); + } + + [Fact] + public void VerifyGetSemanticConventionOptInUsingIConfiguration() + { + RunTestWithIConfiguration(null, HttpSemanticConvention.Old); + RunTestWithIConfiguration(string.Empty, HttpSemanticConvention.Old); + RunTestWithIConfiguration("junk", HttpSemanticConvention.Old); + RunTestWithIConfiguration("none", HttpSemanticConvention.Old); + RunTestWithIConfiguration("NONE", HttpSemanticConvention.Old); + RunTestWithIConfiguration("http", HttpSemanticConvention.New); + RunTestWithIConfiguration("HTTP", HttpSemanticConvention.New); + RunTestWithIConfiguration("http/dup", HttpSemanticConvention.Dupe); + RunTestWithIConfiguration("HTTP/DUP", HttpSemanticConvention.Dupe); + } + + private static void RunTestWithEnvironmentVariable(string value, HttpSemanticConvention expected) + { + try + { + Environment.SetEnvironmentVariable(SemanticConventionOptInKeyName, value); + + Assert.Equal(expected, GetSemanticConventionOptIn(new ConfigurationBuilder().AddEnvironmentVariables().Build())); + } + finally + { + Environment.SetEnvironmentVariable(SemanticConventionOptInKeyName, null); + } + } + + private static void RunTestWithIConfiguration(string value, HttpSemanticConvention expected) + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { [SemanticConventionOptInKeyName] = value }) + .Build(); + + Assert.Equal(expected, GetSemanticConventionOptIn(configuration)); + } +}