From 9211cee8bf332bd0542795645f8e903443626e74 Mon Sep 17 00:00:00 2001 From: Vishwesh Bankwar Date: Thu, 30 Nov 2023 20:11:41 -0800 Subject: [PATCH 01/23] [HttpClient & ASP.NET Core] Document request durations (#5079) --- .../README.md | 17 +++++++++++++++++ .../README.md | 7 +++++++ 2 files changed, 24 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md index 5f32153a44..c21322a972 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md @@ -235,6 +235,23 @@ This instrumentation automatically sets Activity Status to Error if an unhandled exception is thrown. Additionally, `RecordException` feature may be turned on, to store the exception to the Activity itself as ActivityEvent. +## Activity Duration and http.server.request.duration metric calculation + +`Activity.Duration` and `http.server.request.duration` values represents the +time used to handle an inbound HTTP request as measured at the hosting layer of +ASP.NET Core. The time measurement starts once the underlying web host has: + +* Sufficiently parsed the HTTP request headers on the inbound network stream to + identify the new request. +* Initialized the context data structures such as the + [HttpContext](https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.http.httpcontext). + +The time ends when: + +* The ASP.NET Core handler pipeline is finished executing. +* All response data has been sent. +* The context data structures for the request are being disposed. + ## Troubleshooting This component uses an diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md index 8e502ad796..b3e60eac41 100644 --- a/src/OpenTelemetry.Instrumentation.Http/README.md +++ b/src/OpenTelemetry.Instrumentation.Http/README.md @@ -312,6 +312,13 @@ This instrumentation automatically sets Activity Status to Error if the Http StatusCode is >= 400. Additionally, `RecordException` feature may be turned on, to store the exception to the Activity itself as ActivityEvent. +## Activity Duration and http.client.request.duration metric calculation + +`Activity.Duration` and `http.client.request.duration` values represents the +time the underlying client handler takes to complete the request. Completing the +request includes the time up to reading response headers from the network +stream. It doesn't include the time spent reading the response body. + ## Troubleshooting This component uses an From 61c6c748ad148312b2255cdde5bfaf6919108cb7 Mon Sep 17 00:00:00 2001 From: Vishwesh Bankwar Date: Fri, 1 Dec 2023 11:35:36 -0800 Subject: [PATCH 02/23] Rename HttpClientInstrumentationOptions (#5109) --- .../.publicApi/PublicAPI.Unshipped.txt | 40 +++++++++---------- .../CHANGELOG.md | 4 ++ .../HttpClientInstrumentation.cs | 2 +- ... HttpClientTraceInstrumentationOptions.cs} | 4 +- .../HttpHandlerDiagnosticListener.cs | 4 +- .../HttpWebRequestActivitySource.netfx.cs | 6 +-- .../README.md | 6 +-- .../TracerProviderBuilderExtensions.cs | 20 +++++----- .../HttpClientTests.Basic.cs | 6 +-- ...HttpWebRequestActivitySourceTests.netfx.cs | 2 +- .../HttpWebRequestTests.Basic.cs | 4 +- 11 files changed, 51 insertions(+), 47 deletions(-) rename src/OpenTelemetry.Instrumentation.Http/{HttpClientInstrumentationOptions.cs => HttpClientTraceInstrumentationOptions.cs} (97%) diff --git a/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Unshipped.txt index 2dd55551b0..caa4690c8d 100644 --- a/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Unshipped.txt @@ -1,24 +1,24 @@ -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithException.get -> System.Action -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithException.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpRequestMessage.get -> System.Action -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpRequestMessage.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpResponseMessage.get -> System.Action -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpResponseMessage.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpWebRequest.get -> System.Action -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpWebRequest.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpWebResponse.get -> System.Action -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.EnrichWithHttpWebResponse.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.FilterHttpRequestMessage.get -> System.Func -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.FilterHttpRequestMessage.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.FilterHttpWebRequest.get -> System.Func -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.FilterHttpWebRequest.set -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.HttpClientInstrumentationOptions() -> void -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.RecordException.get -> bool -OpenTelemetry.Instrumentation.Http.HttpClientInstrumentationOptions.RecordException.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithException.get -> System.Action +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithException.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpRequestMessage.get -> System.Action +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpRequestMessage.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpResponseMessage.get -> System.Action +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpResponseMessage.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebRequest.get -> System.Action +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebRequest.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebResponse.get -> System.Action +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebResponse.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.FilterHttpRequestMessage.get -> System.Func +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.FilterHttpRequestMessage.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.FilterHttpWebRequest.get -> System.Func +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.FilterHttpWebRequest.set -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.HttpClientTraceInstrumentationOptions() -> void +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.RecordException.get -> bool +OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.RecordException.set -> void OpenTelemetry.Metrics.MeterProviderBuilderExtensions OpenTelemetry.Trace.TracerProviderBuilderExtensions static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configureHttpClientInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configureHttpClientInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configureHttpClientTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configureHttpClientTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md index 5ef8ca9eb8..43b5267542 100644 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md @@ -19,6 +19,10 @@ [issue](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5092). ([#5077](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5077)) +* **Breaking Change**: Renamed `HttpClientInstrumentationOptions` to + `HttpClientTraceInstrumentationOptions`. + ([#5109](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5109)) + ## 1.6.0-beta.3 Released 2023-Nov-17 diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs index be7b7a1dc1..da8d744084 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs @@ -47,7 +47,7 @@ internal sealed class HttpClientInstrumentation : IDisposable /// Initializes a new instance of the class. /// /// Configuration options for HTTP client instrumentation. - public HttpClientInstrumentation(HttpClientInstrumentationOptions options) + public HttpClientInstrumentation(HttpClientTraceInstrumentationOptions options) { // For .NET7.0 activity will be created using activitySource. // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs similarity index 97% rename from src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs rename to src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs index f0cd19691e..cbaa9422ae 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ namespace OpenTelemetry.Instrumentation.Http; /// /// Options for HttpClient instrumentation. /// -public class HttpClientInstrumentationOptions +public class HttpClientTraceInstrumentationOptions { /// /// Gets or sets a filter function that determines whether or not to diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index f71707b37a..33b3a3d139 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -47,7 +47,7 @@ internal sealed class HttpHandlerDiagnosticListener : ListenerHandler private static readonly PropertyFetcher StopResponseFetcher = new("Response"); private static readonly PropertyFetcher StopExceptionFetcher = new("Exception"); private static readonly PropertyFetcher StopRequestStatusFetcher = new("RequestTaskStatus"); - private readonly HttpClientInstrumentationOptions options; + private readonly HttpClientTraceInstrumentationOptions options; static HttpHandlerDiagnosticListener() { @@ -61,7 +61,7 @@ static HttpHandlerDiagnosticListener() } } - public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options) + public HttpHandlerDiagnosticListener(HttpClientTraceInstrumentationOptions options) : base("HttpHandlerDiagnosticListener") { this.options = options; diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs index 4adcff269c..1b73409a01 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs @@ -50,7 +50,7 @@ internal static class HttpWebRequestActivitySource private static readonly Meter WebRequestMeter = new(MeterName, Version); private static readonly Histogram HttpClientRequestDuration = WebRequestMeter.CreateHistogram("http.client.request.duration", "s", "Measures the duration of outbound HTTP requests."); - private static HttpClientInstrumentationOptions tracingOptions; + private static HttpClientTraceInstrumentationOptions tracingOptions; // Fields for reflection private static FieldInfo connectionGroupListField; @@ -87,7 +87,7 @@ static HttpWebRequestActivitySource() PrepareReflectionObjects(); PerformInjection(); - TracingOptions = new HttpClientInstrumentationOptions(); + TracingOptions = new HttpClientTraceInstrumentationOptions(); } catch (Exception ex) { @@ -96,7 +96,7 @@ static HttpWebRequestActivitySource() } } - internal static HttpClientInstrumentationOptions TracingOptions + internal static HttpClientTraceInstrumentationOptions TracingOptions { get => tracingOptions; set diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md index b3e60eac41..4eb4e2adf7 100644 --- a/src/OpenTelemetry.Instrumentation.Http/README.md +++ b/src/OpenTelemetry.Instrumentation.Http/README.md @@ -148,7 +148,7 @@ newer versions. ## Advanced configuration This instrumentation can be configured to change the default behavior by using -`HttpClientInstrumentationOptions`. It is important to note that there are +`HttpClientTraceInstrumentationOptions`. It is important to note that there are differences between .NET Framework and newer .NET/.NET Core runtimes which govern what options are used. On .NET Framework, `HttpClient` uses the `HttpWebRequest` API. On .NET & .NET Core, `HttpWebRequest` uses the @@ -196,7 +196,7 @@ enrich the activity with additional information. These actions are called only when `activity.IsAllDataRequested` is `true`. It contains the activity itself (which can be enriched) and the actual raw object. -`HttpClientInstrumentationOptions` provides 3 enrich options: +`HttpClientTraceInstrumentationOptions` provides 3 enrich options: `EnrichWithHttpRequestMessage`, `EnrichWithHttpResponseMessage` and `EnrichWithException`. These are based on the raw object that is passed in to the action to enrich the activity. @@ -269,7 +269,7 @@ enrich the activity with additional information. These actions are called only when `activity.IsAllDataRequested` is `true`. It contains the activity itself (which can be enriched) and the actual raw object. -`HttpClientInstrumentationOptions` provides 3 enrich options: +`HttpClientTraceInstrumentationOptions` provides 3 enrich options: `EnrichWithHttpWebRequest`, `EnrichWithHttpWebResponse` and `EnrichWithException`. These are based on the raw object that is passed in to the action to enrich the activity. diff --git a/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs index cf0863091f..f19ea5ce54 100644 --- a/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/TracerProviderBuilderExtensions.cs @@ -33,30 +33,30 @@ public static class TracerProviderBuilderExtensions /// being configured. /// The instance of to chain the calls. public static TracerProviderBuilder AddHttpClientInstrumentation(this TracerProviderBuilder builder) - => AddHttpClientInstrumentation(builder, name: null, configureHttpClientInstrumentationOptions: null); + => AddHttpClientInstrumentation(builder, name: null, configureHttpClientTraceInstrumentationOptions: null); /// /// Enables HttpClient instrumentation. /// /// being configured. - /// Callback action for configuring . + /// Callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddHttpClientInstrumentation( this TracerProviderBuilder builder, - Action configureHttpClientInstrumentationOptions) - => AddHttpClientInstrumentation(builder, name: null, configureHttpClientInstrumentationOptions); + Action configureHttpClientTraceInstrumentationOptions) + => AddHttpClientInstrumentation(builder, name: null, configureHttpClientTraceInstrumentationOptions); /// /// Enables HttpClient instrumentation. /// /// being configured. /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddHttpClientInstrumentation( this TracerProviderBuilder builder, string name, - Action configureHttpClientInstrumentationOptions) + Action configureHttpClientTraceInstrumentationOptions) { Guard.ThrowIfNull(builder); @@ -68,9 +68,9 @@ public static TracerProviderBuilder AddHttpClientInstrumentation( builder.ConfigureServices(services => { - if (configureHttpClientInstrumentationOptions != null) + if (configureHttpClientTraceInstrumentationOptions != null) { - services.Configure(name, configureHttpClientInstrumentationOptions); + services.Configure(name, configureHttpClientTraceInstrumentationOptions); } }); @@ -81,7 +81,7 @@ public static TracerProviderBuilder AddHttpClientInstrumentation( { deferredTracerProviderBuilder.Configure((sp, builder) => { - var options = sp.GetRequiredService>().Get(name); + var options = sp.GetRequiredService>().Get(name); HttpWebRequestActivitySource.TracingOptions = options; }); @@ -91,7 +91,7 @@ public static TracerProviderBuilder AddHttpClientInstrumentation( builder.AddInstrumentation(sp => { - var options = sp.GetRequiredService>().Get(name); + var options = sp.GetRequiredService>().Get(name); return new HttpClientInstrumentation(options); }); diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs index 1fe6ccee83..49b1edca02 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs @@ -96,12 +96,12 @@ public void AddHttpClientInstrumentation_NamedOptions() using var tracerProvider = Sdk.CreateTracerProviderBuilder() .ConfigureServices(services => { - services.Configure(o => defaultExporterOptionsConfigureOptionsInvocations++); + services.Configure(o => defaultExporterOptionsConfigureOptionsInvocations++); - services.Configure("Instrumentation2", o => namedExporterOptionsConfigureOptionsInvocations++); + services.Configure("Instrumentation2", o => namedExporterOptionsConfigureOptionsInvocations++); }) .AddHttpClientInstrumentation() - .AddHttpClientInstrumentation("Instrumentation2", configureHttpClientInstrumentationOptions: null) + .AddHttpClientInstrumentation("Instrumentation2", configureHttpClientTraceInstrumentationOptions: null) .Build(); Assert.Equal(1, defaultExporterOptionsConfigureOptionsInvocations); diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs index be4f6a2909..e09cef101b 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs @@ -37,7 +37,7 @@ public class HttpWebRequestActivitySourceTests : IDisposable static HttpWebRequestActivitySourceTests() { - HttpClientInstrumentationOptions options = new() + HttpClientTraceInstrumentationOptions options = new() { EnrichWithHttpWebRequest = (activity, httpWebRequest) => { diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs index be35cedaee..0beac0d983 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs @@ -312,11 +312,11 @@ public void AddHttpClientInstrumentationUsesOptionsApi(string name) using var tracerProvider = Sdk.CreateTracerProviderBuilder() .ConfigureServices(services => { - services.Configure(name, o => configurationDelegateInvocations++); + services.Configure(name, o => configurationDelegateInvocations++); }) .AddHttpClientInstrumentation(name, options => { - Assert.IsType(options); + Assert.IsType(options); }) .Build(); From 94c20c684b732b4f38e6fad2b9d54f5f9695bf0c Mon Sep 17 00:00:00 2001 From: Vishwesh Bankwar Date: Fri, 1 Dec 2023 14:43:05 -0800 Subject: [PATCH 03/23] Remove userAgent tag from HttpClient activity (#5110) --- src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md | 3 +++ .../Implementation/HttpHandlerDiagnosticListener.cs | 9 --------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md index 43b5267542..e698306f06 100644 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md @@ -23,6 +23,9 @@ `HttpClientTraceInstrumentationOptions`. ([#5109](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5109)) +* **Breaking Change**: Removed `http.user_agent` tag from HttpClient activity. + ([#5110](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5110)) + ## 1.6.0-beta.3 Released 2023-Nov-17 diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index 33b3a3d139..ff3fa2022e 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -167,15 +167,6 @@ public void OnStartActivity(Activity activity, object payload) activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri)); - if (request.Headers.TryGetValues("User-Agent", out var userAgentValues)) - { - var userAgent = userAgentValues.FirstOrDefault(); - if (!string.IsNullOrEmpty(userAgent)) - { - activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent); - } - } - try { this.options.EnrichWithHttpRequestMessage?.Invoke(activity, request); From c48c014b5059d693a6cd1fdf7ae8e31fe065baf8 Mon Sep 17 00:00:00 2001 From: Vishwesh Bankwar Date: Fri, 1 Dec 2023 15:19:05 -0800 Subject: [PATCH 04/23] Rename AspNetCoreInstrumentationOptions (#5108) --- examples/AspNetCore/Program.cs | 2 +- .../.publicApi/PublicAPI.Unshipped.txt | 28 +++++++++---------- ... AspNetCoreTraceInstrumentationOptions.cs} | 4 +-- .../CHANGELOG.md | 4 +++ .../Implementation/HttpInListener.cs | 4 +-- .../README.md | 12 ++++---- .../TracerProviderBuilderExtensions.cs | 18 ++++++------ .../BasicTests.cs | 10 +++---- .../DependencyInjectionConfigTests.cs | 4 +-- 9 files changed, 46 insertions(+), 40 deletions(-) rename src/OpenTelemetry.Instrumentation.AspNetCore/{AspNetCoreInstrumentationOptions.cs => AspNetCoreTraceInstrumentationOptions.cs} (96%) diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index ad841f993a..9ecff725e8 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -63,7 +63,7 @@ .AddAspNetCoreInstrumentation(); // Use IConfiguration binding for AspNetCore instrumentation options. - appBuilder.Services.Configure(appBuilder.Configuration.GetSection("AspNetCoreInstrumentation")); + appBuilder.Services.Configure(appBuilder.Configuration.GetSection("AspNetCoreInstrumentation")); switch (tracingExporter) { diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index 6033fda11c..859e71b936 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -1,18 +1,18 @@ -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.AspNetCoreInstrumentationOptions() -> void -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.EnrichWithException.get -> System.Action -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.EnrichWithException.set -> void -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.EnrichWithHttpRequest.get -> System.Action -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.EnrichWithHttpRequest.set -> void -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.EnrichWithHttpResponse.get -> System.Action -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.EnrichWithHttpResponse.set -> void -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.Filter.get -> System.Func -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.Filter.set -> void -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.RecordException.get -> bool -OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreInstrumentationOptions.RecordException.set -> void +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.AspNetCoreTraceInstrumentationOptions() -> void +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithException.get -> System.Action +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithException.set -> void +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithHttpRequest.get -> System.Action +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithHttpRequest.set -> void +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithHttpResponse.get -> System.Action +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithHttpResponse.set -> void +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.Filter.get -> System.Func +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.Filter.set -> void +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.RecordException.get -> bool +OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.RecordException.set -> void OpenTelemetry.Metrics.MeterProviderBuilderExtensions OpenTelemetry.Trace.TracerProviderBuilderExtensions static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configureAspNetCoreInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configureAspNetCoreInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configureAspNetCoreTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddAspNetCoreInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configureAspNetCoreTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs similarity index 96% rename from src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs rename to src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs index 4b4e6f8e71..69fe58f042 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreTraceInstrumentationOptions.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore; /// /// Options for requests instrumentation. /// -public class AspNetCoreInstrumentationOptions +public class AspNetCoreTraceInstrumentationOptions { /// /// Gets or sets a filter function that determines whether or not to diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md index 14066d82cc..b59a49e140 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md @@ -16,6 +16,10 @@ [#5098](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5098) ([#5097](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5097)) +* **Breaking Change** : Renamed `AspNetCoreInstrumentationOptions` to + `AspNetCoreTraceInstrumentationOptions`. + ([#5108](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5108)) + ## 1.6.0-beta.3 Released 2023-Nov-17 diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 93bcf74080..b10c8a6c4c 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -64,9 +64,9 @@ internal class HttpInListener : ListenerHandler private readonly PropertyFetcher beforeActionAttributeRouteInfoFetcher = new("AttributeRouteInfo"); private readonly PropertyFetcher beforeActionTemplateFetcher = new("Template"); #endif - private readonly AspNetCoreInstrumentationOptions options; + private readonly AspNetCoreTraceInstrumentationOptions options; - public HttpInListener(AspNetCoreInstrumentationOptions options) + public HttpInListener(AspNetCoreTraceInstrumentationOptions options) : base(DiagnosticSourceName) { Guard.ThrowIfNull(options); diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md index c21322a972..38269610e4 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/README.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/README.md @@ -139,17 +139,19 @@ newer versions. ## Advanced configuration This instrumentation can be configured to change the default behavior by using -`AspNetCoreInstrumentationOptions`, which allows adding [`Filter`](#filter), +`AspNetCoreTraceInstrumentationOptions`, which allows adding [`Filter`](#filter), [`Enrich`](#enrich) as explained below. // TODO: This section could be refined. -When used with [`OpenTelemetry.Extensions.Hosting`](../OpenTelemetry.Extensions.Hosting/README.md), -all configurations to `AspNetCoreInstrumentationOptions` can be done in the `ConfigureServices` +When used with +[`OpenTelemetry.Extensions.Hosting`](../OpenTelemetry.Extensions.Hosting/README.md), +all configurations to `AspNetCoreTraceInstrumentationOptions` can be done in the +`ConfigureServices` method of you applications `Startup` class as shown below. ```csharp // Configure -services.Configure(options => +services.Configure(options => { options.Filter = (httpContext) => { @@ -168,7 +170,7 @@ services.AddOpenTelemetry() This instrumentation by default collects all the incoming http requests. It allows filtering of requests by using the `Filter` function in -`AspNetCoreInstrumentationOptions`. This defines the condition for allowable +`AspNetCoreTraceInstrumentationOptions`. This defines the condition for allowable requests. The Filter receives the `HttpContext` of the incoming request, and does not collect telemetry about the request if the Filter returns false or throws exception. diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs index 7038d5a75c..357cbad6f0 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs @@ -36,30 +36,30 @@ public static class TracerProviderBuilderExtensions /// being configured. /// The instance of to chain the calls. public static TracerProviderBuilder AddAspNetCoreInstrumentation(this TracerProviderBuilder builder) - => AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreInstrumentationOptions: null); + => AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreTraceInstrumentationOptions: null); /// /// Enables the incoming requests automatic data collection for ASP.NET Core. /// /// being configured. - /// Callback action for configuring . + /// Callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddAspNetCoreInstrumentation( this TracerProviderBuilder builder, - Action configureAspNetCoreInstrumentationOptions) - => AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreInstrumentationOptions); + Action configureAspNetCoreTraceInstrumentationOptions) + => AddAspNetCoreInstrumentation(builder, name: null, configureAspNetCoreTraceInstrumentationOptions); /// /// Enables the incoming requests automatic data collection for ASP.NET Core. /// /// being configured. /// Name which is used when retrieving options. - /// Callback action for configuring . + /// Callback action for configuring . /// The instance of to chain the calls. public static TracerProviderBuilder AddAspNetCoreInstrumentation( this TracerProviderBuilder builder, string name, - Action configureAspNetCoreInstrumentationOptions) + Action configureAspNetCoreTraceInstrumentationOptions) { Guard.ThrowIfNull(builder); @@ -71,9 +71,9 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation( builder.ConfigureServices(services => { - if (configureAspNetCoreInstrumentationOptions != null) + if (configureAspNetCoreTraceInstrumentationOptions != null) { - services.Configure(name, configureAspNetCoreInstrumentationOptions); + services.Configure(name, configureAspNetCoreTraceInstrumentationOptions); } }); @@ -87,7 +87,7 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation( return builder.AddInstrumentation(sp => { - var options = sp.GetRequiredService>().Get(name); + var options = sp.GetRequiredService>().Get(name); return new AspNetCoreInstrumentation( new HttpInListener(options)); diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 30d829f438..11ebcbc8d7 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -496,7 +496,7 @@ void ConfigureTestServices(IServiceCollection services) { this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddAspNetCoreInstrumentation( - new TestHttpInListener(new AspNetCoreInstrumentationOptions()) + new TestHttpInListener(new AspNetCoreTraceInstrumentationOptions()) { OnEventWrittenCallback = (name, payload) => { @@ -836,7 +836,7 @@ public async Task DiagnosticSourceCallbacksAreReceivedOnlyForSubscribedEvents() this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddAspNetCoreInstrumentation( - new TestHttpInListener(new AspNetCoreInstrumentationOptions()) + new TestHttpInListener(new AspNetCoreTraceInstrumentationOptions()) { OnEventWrittenCallback = (name, payload) => { @@ -892,7 +892,7 @@ public async Task DiagnosticSourceExceptionCallbackIsReceivedForUnHandledExcepti this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddAspNetCoreInstrumentation( - new TestHttpInListener(new AspNetCoreInstrumentationOptions()) + new TestHttpInListener(new AspNetCoreTraceInstrumentationOptions()) { OnEventWrittenCallback = (name, payload) => { @@ -968,7 +968,7 @@ public async Task DiagnosticSourceExceptionCallBackIsNotReceivedForExceptionsHan // configure SDK this.tracerProvider = Sdk.CreateTracerProviderBuilder() .AddAspNetCoreInstrumentation( - new TestHttpInListener(new AspNetCoreInstrumentationOptions()) + new TestHttpInListener(new AspNetCoreTraceInstrumentationOptions()) { OnEventWrittenCallback = (name, payload) => { @@ -1134,7 +1134,7 @@ public override SamplingResult ShouldSample(in SamplingParameters samplingParame } } - private class TestHttpInListener(AspNetCoreInstrumentationOptions options) : HttpInListener(options) + private class TestHttpInListener(AspNetCoreTraceInstrumentationOptions options) : HttpInListener(options) { public Action OnEventWrittenCallback; diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs index ec78facc67..fef23ae73e 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/DependencyInjectionConfigTests.cs @@ -46,9 +46,9 @@ void ConfigureTestServices(IServiceCollection services) { services.AddOpenTelemetry() .WithTracing(builder => builder - .AddAspNetCoreInstrumentation(name, configureAspNetCoreInstrumentationOptions: null)); + .AddAspNetCoreInstrumentation(name, configureAspNetCoreTraceInstrumentationOptions: null)); - services.Configure(name, options => + services.Configure(name, options => { optionsPickedFromDI = true; }); From c5f5dd7dbd07cdba192c9aaac6c568df8da3dcbf Mon Sep 17 00:00:00 2001 From: Vishwesh Bankwar Date: Fri, 1 Dec 2023 15:47:01 -0800 Subject: [PATCH 05/23] [HttpWebRequest] Add additional error types (#5111) --- src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md | 4 ++++ .../Implementation/HttpWebRequestActivitySource.netfx.cs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md index e698306f06..1df45ecef5 100644 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md @@ -26,6 +26,10 @@ * **Breaking Change**: Removed `http.user_agent` tag from HttpClient activity. ([#5110](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5110)) +* `HttpWebRequest` : Introduced additional values for `error.type` tag on + activity and `http.client.request.duration` metric. + ([#5111](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5111)) + ## 1.6.0-beta.3 Released 2023-Nov-17 diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs index 1b73409a01..1223711476 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs @@ -166,13 +166,21 @@ private static string GetErrorType(Exception exception) { WebExceptionStatus.NameResolutionFailure => "name_resolution_failure", WebExceptionStatus.ConnectFailure => "connect_failure", + WebExceptionStatus.ReceiveFailure => "receive_failure", WebExceptionStatus.SendFailure => "send_failure", + WebExceptionStatus.PipelineFailure => "pipeline_failure", WebExceptionStatus.RequestCanceled => "request_cancelled", + WebExceptionStatus.ProtocolError => "protocol_error", + WebExceptionStatus.ConnectionClosed => "connection_closed", WebExceptionStatus.TrustFailure => "trust_failure", WebExceptionStatus.SecureChannelFailure => "secure_channel_failure", WebExceptionStatus.ServerProtocolViolation => "server_protocol_violation", + WebExceptionStatus.KeepAliveFailure => "keep_alive_failure", WebExceptionStatus.Timeout => "timeout", + WebExceptionStatus.ProxyNameResolutionFailure => "proxy_name_resolution_failure", WebExceptionStatus.MessageLengthLimitExceeded => "message_length_limit_exceeded", + WebExceptionStatus.RequestProhibitedByCachePolicy => "request_prohibited_by_cache_policy", + WebExceptionStatus.RequestProhibitedByProxy => "request_prohibited_by_proxy", _ => wexc.GetType().FullName, }; } From 9dbd22dbfe4b568a59d9594a36f60612a5c23c42 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:42:34 -0800 Subject: [PATCH 06/23] [Instrumentation.AspNetCore] Use static Meter and Histogram (#5112) --- .../AspNetCoreMetrics.cs | 11 +---------- .../Implementation/HttpInMetricsListener.cs | 17 +++++++++++------ .../MeterProviderBuilderExtensions.cs | 2 +- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs index 53e4f8f17e..7d8dce970a 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreMetrics.cs @@ -15,8 +15,6 @@ // #if !NET8_0_OR_GREATER -using System.Diagnostics.Metrics; -using System.Reflection; using OpenTelemetry.Instrumentation.AspNetCore.Implementation; namespace OpenTelemetry.Instrumentation.AspNetCore; @@ -26,10 +24,6 @@ namespace OpenTelemetry.Instrumentation.AspNetCore; /// internal sealed class AspNetCoreMetrics : IDisposable { - internal static readonly AssemblyName AssemblyName = typeof(HttpInListener).Assembly.GetName(); - internal static readonly string InstrumentationName = AssemblyName.Name; - internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString(); - private static readonly HashSet DiagnosticSourceEvents = new() { "Microsoft.AspNetCore.Hosting.HttpRequestIn", @@ -43,12 +37,10 @@ internal sealed class AspNetCoreMetrics : IDisposable => DiagnosticSourceEvents.Contains(eventName); private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; - private readonly Meter meter; internal AspNetCoreMetrics() { - this.meter = new Meter(InstrumentationName, InstrumentationVersion); - var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore", this.meter); + var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore"); this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(metricsListener, this.isEnabled, AspNetCoreInstrumentationEventSource.Log.UnknownErrorProcessingEvent); this.diagnosticSourceSubscriber.Subscribe(); } @@ -57,7 +49,6 @@ internal AspNetCoreMetrics() public void Dispose() { this.diagnosticSourceSubscriber?.Dispose(); - this.meter?.Dispose(); } } #endif diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs index a47ee19858..b3631d0ef2 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInMetricsListener.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using System.Reflection; using Microsoft.AspNetCore.Http; using OpenTelemetry.Internal; @@ -33,21 +34,25 @@ internal sealed class HttpInMetricsListener : ListenerHandler internal const string OnUnhandledHostingExceptionEvent = "Microsoft.AspNetCore.Hosting.UnhandledException"; internal const string OnUnhandledDiagnosticsExceptionEvent = "Microsoft.AspNetCore.Diagnostics.UnhandledException"; + + internal static readonly AssemblyName AssemblyName = typeof(HttpInListener).Assembly.GetName(); + internal static readonly string InstrumentationName = AssemblyName.Name; + internal static readonly string InstrumentationVersion = AssemblyName.Version.ToString(); + internal static readonly Meter Meter = new(InstrumentationName, InstrumentationVersion); + private const string OnStopEvent = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop"; + private const string EventName = "OnStopActivity"; private const string NetworkProtocolName = "http"; private static readonly PropertyFetcher ExceptionPropertyFetcher = new("Exception"); private static readonly PropertyFetcher HttpContextPropertyFetcher = new("HttpContext"); private static readonly object ErrorTypeHttpContextItemsKey = new(); - private readonly Meter meter; - private readonly Histogram httpServerRequestDuration; + private static readonly Histogram HttpServerRequestDuration = Meter.CreateHistogram(HttpServerRequestDurationMetricName, "s", "Duration of HTTP server requests."); - internal HttpInMetricsListener(string name, Meter meter) + internal HttpInMetricsListener(string name) : base(name) { - this.meter = meter; - this.httpServerRequestDuration = meter.CreateHistogram(HttpServerRequestDurationMetricName, "s", "Duration of HTTP server requests."); } public override void OnEventWritten(string name, object payload) @@ -130,6 +135,6 @@ public void OnStopEventWritten(string name, object payload) // We are relying here on ASP.NET Core to set duration before writing the stop event. // https://github.com/dotnet/aspnetcore/blob/d6fa351048617ae1c8b47493ba1abbe94c3a24cf/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L449 // TODO: Follow up with .NET team if we can continue to rely on this behavior. - this.httpServerRequestDuration.Record(Activity.Current.Duration.TotalSeconds, tags); + HttpServerRequestDuration.Record(Activity.Current.Duration.TotalSeconds, tags); } } diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs index c394a3590e..06e41eedc1 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/MeterProviderBuilderExtensions.cs @@ -44,7 +44,7 @@ public static MeterProviderBuilder AddAspNetCoreInstrumentation( _ = TelemetryHelper.BoxedStatusCodes; _ = RequestMethodHelper.KnownMethods; - builder.AddMeter(AspNetCoreMetrics.InstrumentationName); + builder.AddMeter(HttpInMetricsListener.InstrumentationName); builder.AddInstrumentation(new AspNetCoreMetrics()); From 4a8f99ee7163b604c4338936c25c40e689ca9895 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Fri, 1 Dec 2023 17:00:23 -0800 Subject: [PATCH 07/23] Update CHANGELOG for 1.6.0-rc.1 release of AspNetCore and HttpClient instrumentation libraries (#5113) --- src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md | 4 ++++ src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md index b59a49e140..d6dc29b65c 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.6.0-rc.1 + +Released 2023-Dec-01 + * Removed support for `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. The library will now emit only the [stable](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http) diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md index 1df45ecef5..af38a5eab6 100644 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.6.0-rc.1 + +Released 2023-Dec-01 + * Removed support for `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. The library will now emit only the [stable](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http) From 55f76f60d8028094dcaebcb4943e5e28cd6e73f5 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Mon, 4 Dec 2023 19:31:55 +0100 Subject: [PATCH 08/23] Removed Moq dependency from Api.Tests (#5117) --- .../AssemblyInfo.cs | 2 -- src/OpenTelemetry.Api/AssemblyInfo.cs | 3 --- test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj | 1 - 3 files changed, 6 deletions(-) diff --git a/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs b/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs index 678ad1e5c9..ac99c40c8e 100644 --- a/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs +++ b/src/OpenTelemetry.Api.ProviderBuilderExtensions/AssemblyInfo.cs @@ -29,12 +29,10 @@ file static class AssemblyInfo { public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; - public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; } #else file static class AssemblyInfo { public const string PublicKey = ""; - public const string MoqPublicKey = ""; } #endif diff --git a/src/OpenTelemetry.Api/AssemblyInfo.cs b/src/OpenTelemetry.Api/AssemblyInfo.cs index 3709e7402a..353b015fab 100644 --- a/src/OpenTelemetry.Api/AssemblyInfo.cs +++ b/src/OpenTelemetry.Api/AssemblyInfo.cs @@ -21,7 +21,6 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Api.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Shims.OpenTracing.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] #if !EXPOSE_EXPERIMENTAL_FEATURES [assembly: InternalsVisibleTo("OpenTelemetry.Api.ProviderBuilderExtensions.Tests" + AssemblyInfo.PublicKey)] @@ -37,12 +36,10 @@ file static class AssemblyInfo { public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; - public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; } #else file static class AssemblyInfo { public const string PublicKey = ""; - public const string MoqPublicKey = ""; } #endif diff --git a/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj b/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj index 31c6c8eb3d..b2e0fa8b6d 100644 --- a/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj +++ b/test/OpenTelemetry.Api.Tests/OpenTelemetry.Api.Tests.csproj @@ -21,7 +21,6 @@ - runtime; build; native; contentfiles; analyzers From 8d2162ff4d8eacf073524dd3a80f1ba12bb71b21 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Mon, 4 Dec 2023 20:00:23 +0100 Subject: [PATCH 09/23] Removed Moq dependency from Exporter.Prometheus.HttpListener.Tests (#5120) --- .../OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj index 148f6c843c..7ab05160d7 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj @@ -10,7 +10,6 @@ - runtime; build; native; contentfiles; analyzers From fba99e1254d2d87b7c0e9560453740f6ace4c6e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 11:31:40 -0800 Subject: [PATCH 10/23] Bump actions/setup-dotnet from 3 to 4 (#5127) --- .github/workflows/ci-aot.yml | 2 +- .github/workflows/ci-concurrency.yml | 2 +- .github/workflows/ci-instrumentation-libraries.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/code-coverage.yml | 2 +- .github/workflows/dotnet-format.yml | 4 ++-- .github/workflows/package-validation.yml | 4 ++-- .github/workflows/publish-packages-1.0.yml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-aot.yml b/.github/workflows/ci-aot.yml index 1d2149b775..54c991a921 100644 --- a/.github/workflows/ci-aot.yml +++ b/.github/workflows/ci-aot.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: publish AOT testApp, assert static analysis warning count, and run the app shell: pwsh diff --git a/.github/workflows/ci-concurrency.yml b/.github/workflows/ci-concurrency.yml index 82060ae39f..2734a1df94 100644 --- a/.github/workflows/ci-concurrency.yml +++ b/.github/workflows/ci-concurrency.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: Run Coyote Tests shell: pwsh diff --git a/.github/workflows/ci-instrumentation-libraries.yml b/.github/workflows/ci-instrumentation-libraries.yml index a1f195fc68..0dcb600cfe 100644 --- a/.github/workflows/ci-instrumentation-libraries.yml +++ b/.github/workflows/ci-instrumentation-libraries.yml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: dotnet restore run: dotnet restore ./build/InstrumentationLibraries.proj -p:RunningDotNetPack=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3387af214c..8b583dde64 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: Install dependencies run: dotnet restore @@ -56,7 +56,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index b4a9744c3c..3248b94fd1 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/dotnet-format.yml b/.github/workflows/dotnet-format.yml index ddc4f42fab..47e3d0eee4 100644 --- a/.github/workflows/dotnet-format.yml +++ b/.github/workflows/dotnet-format.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v4 - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: dotnet restore run: dotnet restore @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@v4 - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: dotnet restore run: dotnet restore diff --git a/.github/workflows/package-validation.yml b/.github/workflows/package-validation.yml index 4df06f48b4..8a7bfcead7 100644 --- a/.github/workflows/package-validation.yml +++ b/.github/workflows/package-validation.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: Pack run: dotnet pack OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=false @@ -30,7 +30,7 @@ jobs: fetch-depth: 0 # fetching all - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: Pack run: dotnet pack OpenTelemetry.proj --configuration Release /p:EnablePackageValidation=true /p:ExposeExperimentalFeatures=true diff --git a/.github/workflows/publish-packages-1.0.yml b/.github/workflows/publish-packages-1.0.yml index dbb2990238..41cad730f4 100644 --- a/.github/workflows/publish-packages-1.0.yml +++ b/.github/workflows/publish-packages-1.0.yml @@ -24,7 +24,7 @@ jobs: ref: ${{ github.ref || 'main' }} - name: Setup dotnet - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 - name: dotnet restore run: dotnet restore OpenTelemetry.proj -p:RunningDotNetPack=true From a3d2fec0f23f340659e359611de62b651cd51402 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Mon, 4 Dec 2023 20:43:13 +0100 Subject: [PATCH 11/23] Removed Moq dependency from Instrumentation.SqlClient.Tests (#5125) --- ...try.Instrumentation.SqlClient.Tests.csproj | 1 - .../SqlEventSourceTests.netfx.cs | 38 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj index b6d4f7670e..2075d0eba8 100644 --- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/OpenTelemetry.Instrumentation.SqlClient.Tests.csproj @@ -17,7 +17,6 @@ - diff --git a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs index 2df68ac8d3..f64dc359b9 100644 --- a/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs +++ b/test/OpenTelemetry.Instrumentation.SqlClient.Tests/SqlEventSourceTests.netfx.cs @@ -19,7 +19,6 @@ using System.Data.SqlClient; using System.Diagnostics; using System.Diagnostics.Tracing; -using Moq; using OpenTelemetry.Instrumentation.SqlClient.Implementation; using OpenTelemetry.Tests; using OpenTelemetry.Trace; @@ -48,9 +47,9 @@ To use Docker... [InlineData(CommandType.StoredProcedure, "sp_who", true)] public async Task SuccessfulCommandTest(CommandType commandType, string commandText, bool captureText, bool isFailure = false) { - var activityProcessor = new Mock>(); + var exportedItems = new List(); using var shutdownSignal = Sdk.CreateTracerProviderBuilder() - .AddProcessor(activityProcessor.Object) + .AddInMemoryExporter(exportedItems) .AddSqlClientInstrumentation(options => { options.SetDbStatementForText = captureText; @@ -78,9 +77,8 @@ public async Task SuccessfulCommandTest(CommandType commandType, string commandT { } - Assert.Equal(3, activityProcessor.Invocations.Count); - - var activity = (Activity)activityProcessor.Invocations[1].Arguments[0]; + Assert.Single(exportedItems); + var activity = exportedItems[0]; VerifyActivityData(commandText, captureText, isFailure, dataSource, activity); } @@ -105,9 +103,9 @@ public void EventSourceFakeTests( { using IFakeBehavingSqlEventSource fakeSqlEventSource = (IFakeBehavingSqlEventSource)Activator.CreateInstance(eventSourceType); - var activityProcessor = new Mock>(); + var exportedItems = new List(); using var shutdownSignal = Sdk.CreateTracerProviderBuilder() - .AddProcessor(activityProcessor.Object) + .AddInMemoryExporter(exportedItems) .AddSqlClientInstrumentation(options => { options.SetDbStatementForText = captureText; @@ -132,9 +130,9 @@ public void EventSourceFakeTests( fakeSqlEventSource.WriteEndExecuteEvent(objectId, compositeState, sqlExceptionNumber); shutdownSignal.Dispose(); - Assert.Equal(5, activityProcessor.Invocations.Count); // SetTracerProvider/OnStart/OnEnd/OnShutdown/Dispose called. + Assert.Single(exportedItems); - var activity = (Activity)activityProcessor.Invocations[2].Arguments[0]; + var activity = exportedItems[0]; VerifyActivityData(commandText, captureText, isFailure, "127.0.0.1", activity, enableConnectionLevelAttributes); } @@ -146,9 +144,9 @@ public void EventSourceFakeUnknownEventWithNullPayloadTest(Type eventSourceType) { using IFakeMisbehavingSqlEventSource fakeSqlEventSource = (IFakeMisbehavingSqlEventSource)Activator.CreateInstance(eventSourceType); - var activityProcessor = new Mock>(); + var exportedItems = new List(); using var shutdownSignal = Sdk.CreateTracerProviderBuilder() - .AddProcessor(activityProcessor.Object) + .AddInMemoryExporter(exportedItems) .AddSqlClientInstrumentation() .Build(); @@ -156,7 +154,7 @@ public void EventSourceFakeUnknownEventWithNullPayloadTest(Type eventSourceType) shutdownSignal.Dispose(); - Assert.Equal(3, activityProcessor.Invocations.Count); // SetTracerProvider/OnShutdown/Dispose called. + Assert.Empty(exportedItems); } [Theory] @@ -166,9 +164,9 @@ public void EventSourceFakeInvalidPayloadTest(Type eventSourceType) { using IFakeMisbehavingSqlEventSource fakeSqlEventSource = (IFakeMisbehavingSqlEventSource)Activator.CreateInstance(eventSourceType); - var activityProcessor = new Mock>(); + var exportedItems = new List(); using var shutdownSignal = Sdk.CreateTracerProviderBuilder() - .AddProcessor(activityProcessor.Object) + .AddInMemoryExporter(exportedItems) .AddSqlClientInstrumentation() .Build(); @@ -177,7 +175,7 @@ public void EventSourceFakeInvalidPayloadTest(Type eventSourceType) fakeSqlEventSource.WriteEndExecuteEvent("arg1", "arg2", "arg3", "arg4"); shutdownSignal.Dispose(); - Assert.Equal(3, activityProcessor.Invocations.Count); // SetTracerProvider/OnShutdown/Dispose called. + Assert.Empty(exportedItems); } [Theory] @@ -187,9 +185,9 @@ public void DefaultCaptureTextFalse(Type eventSourceType) { using IFakeBehavingSqlEventSource fakeSqlEventSource = (IFakeBehavingSqlEventSource)Activator.CreateInstance(eventSourceType); - var activityProcessor = new Mock>(); + var exportedItems = new List(); using var shutdownSignal = Sdk.CreateTracerProviderBuilder() - .AddProcessor(activityProcessor.Object) + .AddInMemoryExporter(exportedItems) .AddSqlClientInstrumentation() .Build(); @@ -211,9 +209,9 @@ public void DefaultCaptureTextFalse(Type eventSourceType) fakeSqlEventSource.WriteEndExecuteEvent(objectId, compositeState, 0); shutdownSignal.Dispose(); - Assert.Equal(5, activityProcessor.Invocations.Count); // SetTracerProvider/OnStart/OnEnd/OnShutdown/Dispose called. + Assert.Single(exportedItems); - var activity = (Activity)activityProcessor.Invocations[2].Arguments[0]; + var activity = exportedItems[0]; const bool captureText = false; VerifyActivityData(commandText, captureText, false, "127.0.0.1", activity, false); From 4cec27415d4ae5b39fd90ec035f417609fc919a7 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Mon, 4 Dec 2023 21:08:19 +0100 Subject: [PATCH 12/23] Removed Moq dependency from Extensions.Hosting.Tests (#5122) --- src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs | 3 --- .../OpenTelemetry.Extensions.Hosting.Tests.csproj | 1 - 2 files changed, 4 deletions(-) diff --git a/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs b/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs index 9c25df52d8..22263c1bb2 100644 --- a/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs +++ b/src/OpenTelemetry.Extensions.Hosting/AssemblyInfo.cs @@ -17,18 +17,15 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] #if SIGNED file static class AssemblyInfo { public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; - public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; } #else file static class AssemblyInfo { public const string PublicKey = ""; - public const string MoqPublicKey = ""; } #endif diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj index e1d6d4fb49..8ae85542eb 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj @@ -36,7 +36,6 @@ - runtime; build; native; contentfiles; analyzers From 41d888373fda1b44ed80d50f86c29c4ec0e9a710 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Mon, 4 Dec 2023 21:23:09 +0100 Subject: [PATCH 13/23] Removed Moq dependency from Exporter.Zipkin.Tests (#5121) --- .../OpenTelemetry.Exporter.Zipkin.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj index 8c42997435..d277eb4d15 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj @@ -17,7 +17,6 @@ - runtime; build; native; contentfiles; analyzers From 8369c2090f859e5602aa440475b08719ed0c12db Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Mon, 4 Dec 2023 21:36:14 +0100 Subject: [PATCH 14/23] Removed Moq dependency from Exporter.Prometheus.AspNetCore.Tests (#5119) --- .../OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj index 4d01b88f6b..e3603d4bc5 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj @@ -11,7 +11,6 @@ - runtime; build; native; contentfiles; analyzers From bb3a91a61fbbd8d9094dd4a727f1d23fbd577414 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Tue, 5 Dec 2023 00:54:04 +0100 Subject: [PATCH 15/23] Removed Moq dependency from Benchmarks (#5116) --- test/Benchmarks/Benchmarks.csproj | 1 - .../Exporter/OtlpGrpcExporterBenchmarks.cs | 14 +-------- test/Benchmarks/TestTraceServiceClient.cs | 30 +++++++++++++++++++ 3 files changed, 31 insertions(+), 14 deletions(-) create mode 100644 test/Benchmarks/TestTraceServiceClient.cs diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index cb9be4d43f..a8b4e74879 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -15,7 +15,6 @@ - diff --git a/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs b/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs index f7ed007fc5..f87b6d17ba 100644 --- a/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs +++ b/test/Benchmarks/Exporter/OtlpGrpcExporterBenchmarks.cs @@ -19,14 +19,11 @@ using System.Diagnostics; using BenchmarkDotNet.Attributes; using Benchmarks.Helper; -using Grpc.Core; -using Moq; using OpenTelemetry; using OpenTelemetry.Internal; using OpenTelemetryProtocol::OpenTelemetry.Exporter; using OpenTelemetryProtocol::OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetryProtocol::OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; -using OtlpCollector = OpenTelemetryProtocol::OpenTelemetry.Proto.Collector.Trace.V1; namespace Benchmarks.Exporter; @@ -45,20 +42,11 @@ public class OtlpGrpcExporterBenchmarks [GlobalSetup] public void GlobalSetup() { - var mockClient = new Mock(); - mockClient - .Setup(m => m.Export( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(new OtlpCollector.ExportTraceServiceResponse()); - var options = new OtlpExporterOptions(); this.exporter = new OtlpTraceExporter( options, new SdkLimitOptions(), - new OtlpGrpcTraceExportClient(options, mockClient.Object)); + new OtlpGrpcTraceExportClient(options, new TestTraceServiceClient())); this.activity = ActivityHelper.CreateTestActivity(); this.activityBatch = new CircularBuffer(this.NumberOfSpans); diff --git a/test/Benchmarks/TestTraceServiceClient.cs b/test/Benchmarks/TestTraceServiceClient.cs new file mode 100644 index 0000000000..b8cf7f9d91 --- /dev/null +++ b/test/Benchmarks/TestTraceServiceClient.cs @@ -0,0 +1,30 @@ +// +// 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. +// + +extern alias OpenTelemetryProtocol; + +using Grpc.Core; +using OpenTelemetryProtocol::OpenTelemetry.Proto.Collector.Trace.V1; + +namespace Benchmarks; + +internal class TestTraceServiceClient : TraceService.TraceServiceClient +{ + public override ExportTraceServiceResponse Export(ExportTraceServiceRequest request, Metadata headers = null, DateTime? deadline = null, CancellationToken cancellationToken = default) + { + return new ExportTraceServiceResponse(); + } +} From d6171719517c589f33f5b02ed51c4922129e5a22 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Tue, 5 Dec 2023 19:08:41 +0100 Subject: [PATCH 16/23] Removed Moq dependency from OpenTelemetry.Instrumentation.Http.Tests (#5115) --- .../HttpClientTests.Basic.cs | 71 ++++++------------- .../HttpWebRequestTests.Basic.cs | 29 ++------ ...elemetry.Instrumentation.Http.Tests.csproj | 8 +-- .../Shared/CustomTextMapPropagator.cs | 64 +++++++++++++++++ 4 files changed, 98 insertions(+), 74 deletions(-) create mode 100644 test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs index 49b1edca02..240abb1180 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.cs @@ -16,11 +16,9 @@ using System.Diagnostics; #if NETFRAMEWORK -using System.Net; using System.Net.Http; #endif using Microsoft.Extensions.DependencyInjection; -using Moq; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Instrumentation.Http.Implementation; using OpenTelemetry.Metrics; @@ -224,13 +222,9 @@ public async Task InjectsHeadersAsync(bool shouldEnrich) [Fact] public async Task InjectsHeadersAsync_CustomFormat() { - var propagator = new Mock(); - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => - { - action(message, "custom_traceparent", $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); - action(message, "custom_tracestate", Activity.Current.TraceStateString); - }); + var propagator = new CustomTextMapPropagator(); + propagator.InjectValues.Add("custom_traceParent", context => $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); + propagator.InjectValues.Add("custom_traceState", context => Activity.Current.TraceStateString); var exportedItems = new List(); @@ -246,7 +240,7 @@ public async Task InjectsHeadersAsync_CustomFormat() parent.TraceStateString = "k1=v1,k2=v2"; parent.ActivityTraceFlags = ActivityTraceFlags.Recorded; - Sdk.SetDefaultTextMapPropagator(propagator.Object); + Sdk.SetDefaultTextMapPropagator(propagator); using (Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation() @@ -271,13 +265,13 @@ public async Task InjectsHeadersAsync_CustomFormat() // not the HttpRequestMessage passed to HttpClient. Assert.Empty(request.Headers); #else - Assert.True(request.Headers.TryGetValues("custom_traceparent", out var traceparents)); - Assert.True(request.Headers.TryGetValues("custom_tracestate", out var tracestates)); - Assert.Single(traceparents); - Assert.Single(tracestates); + Assert.True(request.Headers.TryGetValues("custom_traceParent", out var traceParents)); + Assert.True(request.Headers.TryGetValues("custom_traceState", out var traceStates)); + Assert.Single(traceParents); + Assert.Single(traceStates); - Assert.Equal($"00/{activity.Context.TraceId}/{activity.Context.SpanId}/01", traceparents.Single()); - Assert.Equal("k1=v1,k2=v2", tracestates.Single()); + Assert.Equal($"00/{activity.Context.TraceId}/{activity.Context.SpanId}/01", traceParents.Single()); + Assert.Equal("k1=v1,k2=v2", traceStates.Single()); #endif Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator(new TextMapPropagator[] @@ -292,13 +286,9 @@ public async Task RespectsSuppress() { try { - var propagator = new Mock(); - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => - { - action(message, "custom_traceparent", $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); - action(message, "custom_tracestate", Activity.Current.TraceStateString); - }); + var propagator = new CustomTextMapPropagator(); + propagator.InjectValues.Add("custom_traceParent", context => $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); + propagator.InjectValues.Add("custom_traceState", context => Activity.Current.TraceStateString); var exportedItems = new List(); @@ -314,7 +304,7 @@ public async Task RespectsSuppress() parent.TraceStateString = "k1=v1,k2=v2"; parent.ActivityTraceFlags = ActivityTraceFlags.Recorded; - Sdk.SetDefaultTextMapPropagator(propagator.Object); + Sdk.SetDefaultTextMapPropagator(propagator); using (Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation() @@ -331,8 +321,8 @@ public async Task RespectsSuppress() // If suppressed, activity is not emitted and // propagation is also not performed. Assert.Empty(exportedItems); - Assert.False(request.Headers.Contains("custom_traceparent")); - Assert.False(request.Headers.Contains("custom_tracestate")); + Assert.False(request.Headers.Contains("custom_traceParent")); + Assert.False(request.Headers.Contains("custom_traceState")); } finally { @@ -685,27 +675,12 @@ public async Task CustomPropagatorCalled(bool sample, bool createParentActivity) ActivityContext parentContext = default; ActivityContext contextFromPropagator = default; - var propagator = new Mock(); - -#if NETFRAMEWORK - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, carrier, setter) => - { - contextFromPropagator = context.ActivityContext; - - setter(carrier, "custom_traceparent", $"00/{contextFromPropagator.TraceId}/{contextFromPropagator.SpanId}/01"); - setter(carrier, "custom_tracestate", contextFromPropagator.TraceState); - }); -#else - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, carrier, setter) => - { - contextFromPropagator = context.ActivityContext; - - setter(carrier, "custom_traceparent", $"00/{contextFromPropagator.TraceId}/{contextFromPropagator.SpanId}/01"); - setter(carrier, "custom_tracestate", contextFromPropagator.TraceState); - }); -#endif + var propagator = new CustomTextMapPropagator + { + Injected = (context) => contextFromPropagator = context.ActivityContext, + }; + propagator.InjectValues.Add("custom_traceParent", context => $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); + propagator.InjectValues.Add("custom_traceState", context => Activity.Current.TraceStateString); var exportedItems = new List(); @@ -716,7 +691,7 @@ public async Task CustomPropagatorCalled(bool sample, bool createParentActivity) .Build()) { var previousDefaultTextMapPropagator = Propagators.DefaultTextMapPropagator; - Sdk.SetDefaultTextMapPropagator(propagator.Object); + Sdk.SetDefaultTextMapPropagator(propagator); Activity parent = null; if (createParentActivity) diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs index 0beac0d983..060b264f5f 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.cs @@ -18,7 +18,6 @@ using System.Net; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Moq; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Instrumentation.Http.Implementation; using OpenTelemetry.Tests; @@ -220,26 +219,12 @@ public async Task CustomPropagatorCalled(bool sample, bool createParentActivity) ActivityContext parentContext = default; ActivityContext contextFromPropagator = default; - var propagator = new Mock(); -#if NETFRAMEWORK - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, carrier, setter) => - { - contextFromPropagator = context.ActivityContext; - - setter(carrier, "traceparent", $"00/{contextFromPropagator.TraceId}/{contextFromPropagator.SpanId}/01"); - setter(carrier, "tracestate", contextFromPropagator.TraceState); - }); -#else - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, carrier, setter) => - { - contextFromPropagator = context.ActivityContext; - - setter(carrier, "traceparent", $"00/{contextFromPropagator.TraceId}/{contextFromPropagator.SpanId}/01"); - setter(carrier, "tracestate", contextFromPropagator.TraceState); - }); -#endif + var propagator = new CustomTextMapPropagator + { + Injected = (PropagationContext context) => contextFromPropagator = context.ActivityContext, + }; + propagator.InjectValues.Add("custom_traceParent", context => $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); + propagator.InjectValues.Add("custom_traceState", context => Activity.Current.TraceStateString); var exportedItems = new List(); @@ -250,7 +235,7 @@ public async Task CustomPropagatorCalled(bool sample, bool createParentActivity) .Build()) { var previousDefaultTextMapPropagator = Propagators.DefaultTextMapPropagator; - Sdk.SetDefaultTextMapPropagator(propagator.Object); + Sdk.SetDefaultTextMapPropagator(propagator); Activity parent = null; if (createParentActivity) diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj b/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj index 75e4f2f295..9bb4c1710c 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj @@ -7,16 +7,16 @@ - - + - + + + - diff --git a/test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs b/test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs new file mode 100644 index 0000000000..e22904fd12 --- /dev/null +++ b/test/OpenTelemetry.Tests/Shared/CustomTextMapPropagator.cs @@ -0,0 +1,64 @@ +// +// 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 OpenTelemetry.Context.Propagation; + +namespace OpenTelemetry.Tests; + +internal sealed class CustomTextMapPropagator : TextMapPropagator +{ + private static readonly PropagationContext DefaultPropagationContext = default; + + public ActivityTraceId TraceId { get; set; } + + public ActivitySpanId SpanId { get; set; } + + public Action Injected { get; set; } + + public override ISet Fields => null; + +#pragma warning disable SA1201 // Elements should appear in the correct order +#pragma warning disable SA1010 // Opening square brackets should be spaced correctly + public Dictionary> InjectValues = []; +#pragma warning restore SA1010 // Opening square brackets should be spaced correctly +#pragma warning restore SA1201 // Elements should appear in the correct order + + public override PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + { + if (this.TraceId != default && this.SpanId != default) + { + return new PropagationContext( + new ActivityContext( + this.TraceId, + this.SpanId, + ActivityTraceFlags.Recorded), + default); + } + + return DefaultPropagationContext; + } + + public override void Inject(PropagationContext context, T carrier, Action setter) + { + foreach (var kv in this.InjectValues) + { + setter(carrier, kv.Key, kv.Value.Invoke(context)); + } + + this.Injected?.Invoke(context); + } +} From 5ca11244664df597614f3a148b41c4910bca07bb Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Tue, 5 Dec 2023 19:21:29 +0100 Subject: [PATCH 17/23] Removed Moq dependency from Exporter.OpenTelemetryProtocol.Tests (#5118) --- .../AssemblyInfo.cs | 6 --- .../OtlpHttpTraceExportClientTests.cs | 49 ++++--------------- ...xporter.OpenTelemetryProtocol.Tests.csproj | 1 - .../OtlpLogExporterTests.cs | 24 +++------ .../OtlpTraceExporterTests.cs | 10 ++-- .../TestExportClient.cs | 45 +++++++++++++++++ .../TestHttpMessageHandler.cs | 47 ++++++++++++++++++ 7 files changed, 113 insertions(+), 69 deletions(-) create mode 100644 test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs create mode 100644 test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs index 2bc348cad9..b57075574b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/AssemblyInfo.cs @@ -20,14 +20,8 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] [assembly: InternalsVisibleTo("Benchmarks, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] [assembly: InternalsVisibleTo("MockOpenTelemetryCollector, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] - -// Used by Moq. -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] #else [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests")] [assembly: InternalsVisibleTo("Benchmarks")] [assembly: InternalsVisibleTo("MockOpenTelemetryCollector")] - -// Used by Moq. -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] #endif diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs index 240c8ad88c..548f38b96c 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Implementation/ExportClient/OtlpHttpTraceExportClientTests.cs @@ -18,8 +18,6 @@ #if !NET6_0_OR_GREATER using System.Net.Http; #endif -using Moq; -using Moq.Protected; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Resources; @@ -94,42 +92,11 @@ public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest( Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}", }; - var httpHandlerMock = new Mock(); + var testHttpHandler = new TestHttpMessageHandler(); - HttpRequestMessage httpRequest = null; var httpRequestContent = Array.Empty(); - httpHandlerMock.Protected() -#if NET6_0_OR_GREATER - .Setup("Send", ItExpr.IsAny(), ItExpr.IsAny()) - .Returns((HttpRequestMessage request, CancellationToken token) => - { - return new HttpResponseMessage(); - }) - .Callback(async (r, ct) => - { - httpRequest = r; - - // We have to capture content as it can't be accessed after request is disposed inside of SendExportRequest method - httpRequestContent = await r.Content.ReadAsByteArrayAsync(ct); - }) -#else - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ReturnsAsync((HttpRequestMessage request, CancellationToken token) => - { - return new HttpResponseMessage(); - }) - .Callback(async (r, ct) => - { - httpRequest = r; - - // We have to capture content as it can't be accessed after request is disposed inside of SendExportRequest method - httpRequestContent = await r.Content.ReadAsByteArrayAsync(); - }) -#endif - .Verifiable(); - - var exportClient = new OtlpHttpTraceExportClient(options, new HttpClient(httpHandlerMock.Object)); + var exportClient = new OtlpHttpTraceExportClient(options, new HttpClient(testHttpHandler)); var resourceBuilder = ResourceBuilder.CreateEmpty(); if (includeServiceNameInResource) @@ -137,8 +104,8 @@ public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest( resourceBuilder.AddAttributes( new List> { - new KeyValuePair(ResourceSemanticConventions.AttributeServiceName, "service_name"), - new KeyValuePair(ResourceSemanticConventions.AttributeServiceNamespace, "ns_1"), + new(ResourceSemanticConventions.AttributeServiceName, "service_name"), + new(ResourceSemanticConventions.AttributeServiceNamespace, "ns_1"), }); } @@ -166,7 +133,7 @@ public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest( processor.Shutdown(); - var batch = new Batch(exportedItems.ToArray(), exportedItems.Count); + var batch = new Batch([.. exportedItems], exportedItems.Count); RunTest(batch); void RunTest(Batch batch) @@ -178,6 +145,8 @@ void RunTest(Batch batch) // Act var result = exportClient.SendExportRequest(request); + var httpRequest = testHttpHandler.HttpRequestMessage; + // Assert Assert.True(result); Assert.NotNull(httpRequest); @@ -192,11 +161,11 @@ void RunTest(Batch batch) Assert.Contains(httpRequest.Headers, entry => entry.Key == OtlpExporterOptions.StandardHeaders[i].Key && entry.Value.First() == OtlpExporterOptions.StandardHeaders[i].Value); } - Assert.NotNull(httpRequest.Content); + Assert.NotNull(testHttpHandler.HttpRequestContent); Assert.IsType(httpRequest.Content); Assert.Contains(httpRequest.Content.Headers, h => h.Key == "Content-Type" && h.Value.First() == OtlpHttpTraceExportClient.MediaContentType); - var exportTraceRequest = OtlpCollector.ExportTraceServiceRequest.Parser.ParseFrom(httpRequestContent); + var exportTraceRequest = OtlpCollector.ExportTraceServiceRequest.Parser.ParseFrom(testHttpHandler.HttpRequestContent); Assert.NotNull(exportTraceRequest); Assert.Single(exportTraceRequest.ResourceSpans); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj index 2e9775519d..ad60a2385a 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -12,7 +12,6 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs index 090a950b75..5e0297e139 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs @@ -22,9 +22,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Moq; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; -using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Internal; using OpenTelemetry.Logs; using OpenTelemetry.Resources; @@ -589,37 +587,34 @@ public void CheckToOtlpLogRecordRespectsAttributeLimits() public void Export_WhenExportClientIsProvidedInCtor_UsesProvidedExportClient() { // Arrange. - var fakeExportClient = new Mock>(); + var testExportClient = new TestExportClient(); var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); var sut = new OtlpLogExporter( new OtlpExporterOptions(), new SdkLimitOptions(), new ExperimentalOptions(), - fakeExportClient.Object); + testExportClient); // Act. - var result = sut.Export(emptyBatch); + sut.Export(emptyBatch); // Assert. - fakeExportClient.Verify(x => x.SendExportRequest(It.IsAny(), default), Times.Once()); + Assert.True(testExportClient.SendExportRequestCalled); } [Fact] public void Export_WhenExportClientThrowsException_ReturnsExportResultFailure() { // Arrange. - var fakeExportClient = new Mock>(); + var testExportClient = new TestExportClient(throwException: true); var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); - fakeExportClient - .Setup(_ => _.SendExportRequest(It.IsAny(), default)) - .Throws(new Exception("Test Exception")); var sut = new OtlpLogExporter( new OtlpExporterOptions(), new SdkLimitOptions(), new ExperimentalOptions(), - fakeExportClient.Object); + testExportClient); // Act. var result = sut.Export(emptyBatch); @@ -632,17 +627,14 @@ public void Export_WhenExportClientThrowsException_ReturnsExportResultFailure() public void Export_WhenExportIsSuccessful_ReturnsExportResultSuccess() { // Arrange. - var fakeExportClient = new Mock>(); + var testExportClient = new TestExportClient(); var emptyLogRecords = Array.Empty(); var emptyBatch = new Batch(emptyLogRecords, emptyLogRecords.Length); - fakeExportClient - .Setup(_ => _.SendExportRequest(It.IsAny(), default)) - .Returns(true); var sut = new OtlpLogExporter( new OtlpExporterOptions(), new SdkLimitOptions(), new ExperimentalOptions(), - fakeExportClient.Object); + testExportClient); // Act. var result = sut.Export(emptyBatch); diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index 03fd86bdeb..8663177d88 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -17,9 +17,7 @@ using System.Diagnostics; using Google.Protobuf.Collections; using Microsoft.Extensions.DependencyInjection; -using Moq; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; -using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Tests; @@ -642,13 +640,13 @@ public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor( [Fact] public void Shutdown_ClientShutdownIsCalled() { - var exportClientMock = new Mock>(); + var exportClientMock = new TestExportClient(); - var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), DefaultSdkLimitOptions, exportClientMock.Object); + var exporter = new OtlpTraceExporter(new OtlpExporterOptions(), DefaultSdkLimitOptions, exportClientMock); - var result = exporter.Shutdown(); + exporter.Shutdown(); - exportClientMock.Verify(m => m.Shutdown(It.IsAny()), Times.Once()); + Assert.True(exportClientMock.ShutdownCalled); } [Fact] diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs new file mode 100644 index 0000000000..b7123187f6 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestExportClient.cs @@ -0,0 +1,45 @@ +// +// 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 OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +internal class TestExportClient(bool throwException = false) : IExportClient +{ + public bool SendExportRequestCalled { get; private set; } + + public bool ShutdownCalled { get; private set; } + + public bool ThrowException { get; set; } = throwException; + + public bool SendExportRequest(T request, CancellationToken cancellationToken = default) + { + if (this.ThrowException) + { + throw new Exception("Exception thrown from SendExportRequest"); + } + + this.SendExportRequestCalled = true; + return true; + } + + public bool Shutdown(int timeoutMilliseconds) + { + this.ShutdownCalled = true; + return true; + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs new file mode 100644 index 0000000000..f3006cf03d --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/TestHttpMessageHandler.cs @@ -0,0 +1,47 @@ +// +// 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. +// + +#if !NET6_0_OR_GREATER +using System.Net.Http; +#endif + +namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; + +internal class TestHttpMessageHandler : HttpMessageHandler +{ + public HttpRequestMessage HttpRequestMessage { get; private set; } + + public byte[] HttpRequestContent { get; private set; } + + public virtual HttpResponseMessage InternalSend(HttpRequestMessage request, CancellationToken cancellationToken) + { + this.HttpRequestMessage = request; + this.HttpRequestContent = request.Content.ReadAsByteArrayAsync().Result; + return new HttpResponseMessage(); + } + +#if NET6_0_OR_GREATER + protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) + { + return this.InternalSend(request, cancellationToken); + } +#endif + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(this.InternalSend(request, cancellationToken)); + } +} From f11801da9254f1d72decf13a947f2fa61fd40293 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Tue, 5 Dec 2023 19:39:27 +0100 Subject: [PATCH 18/23] Removed Moq dependency from Shims.OpenTracing.Tests (#5126) --- ...enTelemetry.Shims.OpenTracing.Tests.csproj | 1 - .../ScopeManagerShimTests.cs | 3 +- .../TestFormatTextMap.cs | 23 ++++ .../TestSpan.cs | 110 ++++++++++++++++++ .../TestSpanContext.cs | 31 +++++ .../TestTextMap.cs | 47 ++++++++ .../TracerShimTests.cs | 53 ++++----- 7 files changed, 235 insertions(+), 33 deletions(-) create mode 100644 test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs create mode 100644 test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs create mode 100644 test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs create mode 100644 test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj b/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj index fb6948c69e..7e08df554f 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/OpenTelemetry.Shims.OpenTracing.Tests.csproj @@ -8,7 +8,6 @@ - runtime; build; native; contentfiles; analyzers diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs index 4d0b464152..1ae49d3231 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/ScopeManagerShimTests.cs @@ -15,7 +15,6 @@ // using System.Diagnostics; -using Moq; using OpenTelemetry.Trace; using Xunit; @@ -56,7 +55,7 @@ public void Activate_SpanMustBeShim() { var shim = new ScopeManagerShim(); - Assert.Throws(() => shim.Activate(new Mock().Object, true)); + Assert.Throws(() => shim.Activate(new TestSpan(), true)); } [Fact] diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs new file mode 100644 index 0000000000..b16e3a50b7 --- /dev/null +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestFormatTextMap.cs @@ -0,0 +1,23 @@ +// +// 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 OpenTracing.Propagation; + +namespace OpenTelemetry.Shims.OpenTracing.Tests; + +internal class TestFormatTextMap : IFormat +{ +} diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs new file mode 100644 index 0000000000..f2e4e54f4b --- /dev/null +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpan.cs @@ -0,0 +1,110 @@ +// +// 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 OpenTracing; +using OpenTracing.Tag; + +namespace OpenTelemetry.Shims.OpenTracing.Tests; + +internal class TestSpan : ISpan +{ + public ISpanContext Context => throw new NotImplementedException(); + + public void Finish() + { + throw new NotImplementedException(); + } + + public void Finish(DateTimeOffset finishTimestamp) + { + throw new NotImplementedException(); + } + + public string GetBaggageItem(string key) + { + throw new NotImplementedException(); + } + + public ISpan Log(IEnumerable> fields) + { + throw new NotImplementedException(); + } + + public ISpan Log(DateTimeOffset timestamp, IEnumerable> fields) + { + throw new NotImplementedException(); + } + + public ISpan Log(string @event) + { + throw new NotImplementedException(); + } + + public ISpan Log(DateTimeOffset timestamp, string @event) + { + throw new NotImplementedException(); + } + + public ISpan SetBaggageItem(string key, string value) + { + throw new NotImplementedException(); + } + + public ISpan SetOperationName(string operationName) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(string key, string value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(string key, bool value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(string key, int value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(string key, double value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(BooleanTag tag, bool value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(IntOrStringTag tag, string value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(IntTag tag, int value) + { + throw new NotImplementedException(); + } + + public ISpan SetTag(StringTag tag, string value) + { + throw new NotImplementedException(); + } +} diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs new file mode 100644 index 0000000000..097a54aab4 --- /dev/null +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestSpanContext.cs @@ -0,0 +1,31 @@ +// +// 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 OpenTracing; + +namespace OpenTelemetry.Shims.OpenTracing.Tests; + +internal class TestSpanContext : ISpanContext +{ + public string TraceId => throw new NotImplementedException(); + + public string SpanId => throw new NotImplementedException(); + + public IEnumerable> GetBaggageItems() + { + throw new NotImplementedException(); + } +} diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs new file mode 100644 index 0000000000..f72f78b5f4 --- /dev/null +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TestTextMap.cs @@ -0,0 +1,47 @@ +// +// 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.Collections; +using OpenTracing.Propagation; + +namespace OpenTelemetry.Shims.OpenTracing.Tests; + +internal class TestTextMap : ITextMap +{ + public bool GetEnumeratorCalled { get; private set; } + + public bool SetCalled { get; private set; } + +#pragma warning disable IDE0028 // Simplify collection initialization + public Dictionary Items { get; } = new(); +#pragma warning restore IDE0028 // Simplify collection initialization + + public IEnumerator> GetEnumerator() + { + this.GetEnumeratorCalled = true; + return this.Items.GetEnumerator(); + } + + public void Set(string key, string value) + { + this.SetCalled = true; + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } +} diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs index 08f2147c7c..ec9cabfa41 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/TracerShimTests.cs @@ -16,7 +16,6 @@ using System.Collections; using System.Diagnostics; -using Moq; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Trace; using OpenTracing; @@ -31,10 +30,10 @@ public class TracerShimTests public void CtorArgumentValidation() { // null tracer provider and text format - Assert.Throws(() => new TracerShim((TracerProvider)null, null)); + Assert.Throws(() => new TracerShim(null, null)); // null tracer provider - Assert.Throws(() => new TracerShim((TracerProvider)null, new TraceContextPropagator())); + Assert.Throws(() => new TracerShim(null, new TraceContextPropagator())); } [Fact] @@ -61,13 +60,13 @@ public void Inject_ArgumentValidation() var shim = new TracerShim(TracerProvider.Default, new TraceContextPropagator()); var spanContextShim = new SpanContextShim(new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None)); - var mockFormat = new Mock>(); - var mockCarrier = new Mock(); + var testFormat = new TestFormatTextMap(); + var testCarrier = new TestTextMap(); - Assert.Throws(() => shim.Inject(null, mockFormat.Object, mockCarrier.Object)); - Assert.Throws(() => shim.Inject(new Mock().Object, mockFormat.Object, mockCarrier.Object)); - Assert.Throws(() => shim.Inject(spanContextShim, null, mockCarrier.Object)); - Assert.Throws(() => shim.Inject(spanContextShim, mockFormat.Object, null)); + Assert.Throws(() => shim.Inject(null, testFormat, testCarrier)); + Assert.Throws(() => shim.Inject(new TestSpanContext(), testFormat, testCarrier)); + Assert.Throws(() => shim.Inject(spanContextShim, null, testCarrier)); + Assert.Throws(() => shim.Inject(spanContextShim, testFormat, null)); } [Fact] @@ -78,11 +77,11 @@ public void Inject_UnknownFormatIgnored() var spanContextShim = new SpanContextShim(new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded)); // Only two specific types of ITextMap are supported, and neither is a Mock. - var mockCarrier = new Mock(); - shim.Inject(spanContextShim, new Mock>().Object, mockCarrier.Object); + var testCarrier = new TestTextMap(); + shim.Inject(spanContextShim, new TestFormatTextMap(), testCarrier); - // Verify that the carrier mock was never called. - mockCarrier.Verify(x => x.Set(It.IsAny(), It.IsAny()), Times.Never); + // Verify that the test carrier was never called. + Assert.False(testCarrier.SetCalled); } [Fact] @@ -90,23 +89,22 @@ public void Extract_ArgumentValidation() { var shim = new TracerShim(TracerProvider.Default, new TraceContextPropagator()); - Assert.Throws(() => shim.Extract(null, new Mock().Object)); - Assert.Throws(() => shim.Extract(new Mock>().Object, null)); + Assert.Throws(() => shim.Extract(null, new TestTextMap())); + Assert.Throws(() => shim.Extract(new TestFormatTextMap(), null)); } [Fact] public void Extract_UnknownFormatIgnored() { var shim = new TracerShim(TracerProvider.Default, new TraceContextPropagator()); - - var spanContextShim = new SpanContextShim(new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None)); + _ = new SpanContextShim(new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None)); // Only two specific types of ITextMap are supported, and neither is a Mock. - var mockCarrier = new Mock(); - var context = shim.Extract(new Mock>().Object, mockCarrier.Object); + var testCarrier = new TestTextMap(); + _ = shim.Extract(new TestFormatTextMap(), testCarrier); - // Verify that the carrier mock was never called. - mockCarrier.Verify(x => x.GetEnumerator(), Times.Never); + // Verify that the test carrier was never called. + Assert.False(testCarrier.SetCalled); } [Fact] @@ -114,20 +112,15 @@ public void Extract_InvalidTraceParent() { var shim = new TracerShim(TracerProvider.Default, new TraceContextPropagator()); - var mockCarrier = new Mock(); + var testCarrier = new TestTextMap(); // The ProxyTracer uses OpenTelemetry.Context.Propagation.TraceContextPropagator, so we need to satisfy the traceparent key at the least - var carrierMap = new Dictionary - { - // This is an invalid traceparent value - { "traceparent", "unused" }, - }; + testCarrier.Items["traceparent"] = "unused"; - mockCarrier.Setup(x => x.GetEnumerator()).Returns(carrierMap.GetEnumerator()); - var spanContextShim = shim.Extract(BuiltinFormats.TextMap, mockCarrier.Object) as SpanContextShim; + var spanContextShim = shim.Extract(BuiltinFormats.TextMap, testCarrier) as SpanContextShim; // Verify that the carrier was called - mockCarrier.Verify(x => x.GetEnumerator(), Times.Once); + Assert.True(testCarrier.GetEnumeratorCalled); Assert.Null(spanContextShim); } From 3619a6f2f14621cc26639258550e3944e8c006e1 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Wed, 6 Dec 2023 03:25:22 +0100 Subject: [PATCH 19/23] Removed Moq dependency from OpenTelemetry.Tests (#5128) --- .../SelfDiagnosticsEventListenerTest.cs | 53 ++++++++----------- .../OpenTelemetry.Tests.csproj | 1 - .../TestSelfDiagnosticsConfigRefresher.cs | 35 ++++++++++++ .../Trace/ParentBasedSamplerTests.cs | 47 +++++----------- 4 files changed, 70 insertions(+), 66 deletions(-) create mode 100644 test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs index f85ffb7a1f..706d81448a 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs @@ -17,7 +17,7 @@ using System.Diagnostics.Tracing; using System.IO.MemoryMappedFiles; using System.Text; -using Moq; +using OpenTelemetry.Tests; using Xunit; namespace OpenTelemetry.Internal.Tests; @@ -41,47 +41,40 @@ public void SelfDiagnosticsEventListener_constructor_Invalid_Input() [Fact] public void SelfDiagnosticsEventListener_EventSourceSetup_LowerSeverity() { - var configRefresherMock = new Mock(); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Emitting a Verbose event. Or any EventSource event with lower severity than Error. OpenTelemetrySdkEventSource.Log.ActivityStarted("Activity started", "1"); - configRefresherMock.Verify(refresher => refresher.TryGetLogStream(It.IsAny(), out It.Ref.IsAny, out It.Ref.IsAny), Times.Never()); + Assert.False(configRefresher.TryGetLogStreamCalled); } [Fact] public void SelfDiagnosticsEventListener_EventSourceSetup_HigherSeverity() { - var configRefresherMock = new Mock(); - configRefresherMock.Setup(configRefresher => configRefresher.TryGetLogStream(It.IsAny(), out It.Ref.IsAny, out It.Ref.IsAny)) - .Returns(true); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Emitting an Error event. Or any EventSource event with higher than or equal to to Error severity. OpenTelemetrySdkEventSource.Log.TracerProviderException("TestEvent", "Exception Details"); - configRefresherMock.Verify(refresher => refresher.TryGetLogStream(It.IsAny(), out It.Ref.IsAny, out It.Ref.IsAny)); + Assert.True(configRefresher.TryGetLogStreamCalled); } [Fact] public void SelfDiagnosticsEventListener_WriteEvent() { // Arrange - var configRefresherMock = new Mock(); var memoryMappedFile = MemoryMappedFile.CreateFromFile(LOGFILEPATH, FileMode.Create, null, 1024); Stream stream = memoryMappedFile.CreateViewStream(); + var configRefresher = new TestSelfDiagnosticsConfigRefresher(stream); string eventMessage = "Event Message"; - int timestampPrefixLength = "2020-08-14T20:33:24.4788109Z:".Length; - byte[] bytes = Encoding.UTF8.GetBytes(eventMessage); - int availableByteCount = 100; - configRefresherMock.Setup(configRefresher => configRefresher.TryGetLogStream(timestampPrefixLength + bytes.Length + 1, out stream, out availableByteCount)) - .Returns(true); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Act: call WriteEvent method directly listener.WriteEvent(eventMessage, null); // Assert - configRefresherMock.Verify(refresher => refresher.TryGetLogStream(timestampPrefixLength + bytes.Length + 1, out stream, out availableByteCount)); + Assert.True(configRefresher.TryGetLogStreamCalled); stream.Dispose(); memoryMappedFile.Dispose(); AssertFileOutput(LOGFILEPATH, eventMessage); @@ -90,18 +83,18 @@ public void SelfDiagnosticsEventListener_WriteEvent() [Fact] public void SelfDiagnosticsEventListener_DateTimeGetBytes() { - var configRefresherMock = new Mock(); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + var configRefresher = new TestSelfDiagnosticsConfigRefresher(); + var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Check DateTimeKind of Utc, Local, and Unspecified - DateTime[] datetimes = new DateTime[] - { + DateTime[] datetimes = + [ DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Utc), DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Local), DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Unspecified), DateTime.UtcNow, DateTime.Now, - }; + ]; // Expect to match output string from DateTime.ToString("O") string[] expected = new string[datetimes.Length]; @@ -129,18 +122,16 @@ public void SelfDiagnosticsEventListener_DateTimeGetBytes() public void SelfDiagnosticsEventListener_EmitEvent_OmitAsConfigured() { // Arrange - var configRefresherMock = new Mock(); + var configRefresher = new TestSelfDiagnosticsConfigRefresher(); var memoryMappedFile = MemoryMappedFile.CreateFromFile(LOGFILEPATH, FileMode.Create, null, 1024); Stream stream = memoryMappedFile.CreateViewStream(); - configRefresherMock.Setup(configRefresher => configRefresher.TryGetLogStream(It.IsAny(), out stream, out It.Ref.IsAny)) - .Returns(true); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Act: emit an event with severity lower than configured OpenTelemetrySdkEventSource.Log.ActivityStarted("ActivityStart", "123"); // Assert - configRefresherMock.Verify(refresher => refresher.TryGetLogStream(It.IsAny(), out stream, out It.Ref.IsAny), Times.Never()); + Assert.False(configRefresher.TryGetLogStreamCalled); stream.Dispose(); memoryMappedFile.Dispose(); @@ -154,18 +145,16 @@ public void SelfDiagnosticsEventListener_EmitEvent_OmitAsConfigured() public void SelfDiagnosticsEventListener_EmitEvent_CaptureAsConfigured() { // Arrange - var configRefresherMock = new Mock(); var memoryMappedFile = MemoryMappedFile.CreateFromFile(LOGFILEPATH, FileMode.Create, null, 1024); Stream stream = memoryMappedFile.CreateViewStream(); - configRefresherMock.Setup(configRefresher => configRefresher.TryGetLogStream(It.IsAny(), out stream, out It.Ref.IsAny)) - .Returns(true); - var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + var configRefresher = new TestSelfDiagnosticsConfigRefresher(stream); + _ = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresher); // Act: emit an event with severity equal to configured OpenTelemetrySdkEventSource.Log.TracerProviderException("TestEvent", "Exception Details"); // Assert - configRefresherMock.Verify(refresher => refresher.TryGetLogStream(It.IsAny(), out stream, out It.Ref.IsAny)); + Assert.True(configRefresher.TryGetLogStreamCalled); stream.Dispose(); memoryMappedFile.Dispose(); diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index e7b5c8a03e..0679b5d365 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -31,7 +31,6 @@ - runtime; build; native; contentfiles; analyzers diff --git a/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs b/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs new file mode 100644 index 0000000000..f8e4170fdc --- /dev/null +++ b/test/OpenTelemetry.Tests/TestSelfDiagnosticsConfigRefresher.cs @@ -0,0 +1,35 @@ +// +// 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.CodeAnalysis; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Tests; + +internal class TestSelfDiagnosticsConfigRefresher(Stream stream = null) : SelfDiagnosticsConfigRefresher +{ + private readonly Stream stream = stream; + + public bool TryGetLogStreamCalled { get; private set; } + + public override bool TryGetLogStream(int byteCount, [NotNullWhen(true)] out Stream stream, out int availableByteCount) + { + this.TryGetLogStreamCalled = true; + stream = this.stream; + availableByteCount = 0; + return true; + } +} diff --git a/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs b/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs index f6a7d9b7df..e7660a7b5b 100644 --- a/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs +++ b/test/OpenTelemetry.Tests/Trace/ParentBasedSamplerTests.cs @@ -15,7 +15,7 @@ // using System.Diagnostics; -using Moq; +using OpenTelemetry.Tests; using Xunit; namespace OpenTelemetry.Trace.Tests; @@ -105,47 +105,28 @@ public void DoNotExamineLinks() [InlineData(false, false)] public void CustomSamplers(bool parentIsRemote, bool parentIsSampled) { - var mockRepository = new MockRepository(MockBehavior.Strict); - var remoteParentSampled = mockRepository.Create(); - var remoteParentNotSampled = mockRepository.Create(); - var localParentSampled = mockRepository.Create(); - var localParentNotSampled = mockRepository.Create(); + var remoteParentSampled = new TestSampler(); + var remoteParentNotSampled = new TestSampler(); + var localParentSampled = new TestSampler(); + var localParentNotSampled = new TestSampler(); var samplerUnderTest = new ParentBasedSampler( new AlwaysOnSampler(), // root - remoteParentSampled.Object, - remoteParentNotSampled.Object, - localParentSampled.Object, - localParentNotSampled.Object); + remoteParentSampled, + remoteParentNotSampled, + localParentSampled, + localParentNotSampled); var samplingParams = MakeTestParameters(parentIsRemote, parentIsSampled); - - Mock invokedSampler; - if (parentIsRemote && parentIsSampled) - { - invokedSampler = remoteParentSampled; - } - else if (parentIsRemote && !parentIsSampled) - { - invokedSampler = remoteParentNotSampled; - } - else if (!parentIsRemote && parentIsSampled) - { - invokedSampler = localParentSampled; - } - else - { - invokedSampler = localParentNotSampled; - } - var expectedResult = new SamplingResult(SamplingDecision.RecordAndSample); - invokedSampler.Setup(sampler => sampler.ShouldSample(samplingParams)).Returns(expectedResult); - var actualResult = samplerUnderTest.ShouldSample(samplingParams); - mockRepository.VerifyAll(); + Assert.Equal(parentIsRemote && parentIsSampled, remoteParentSampled.LatestSamplingParameters.Equals(samplingParams)); + Assert.Equal(parentIsRemote && !parentIsSampled, remoteParentNotSampled.LatestSamplingParameters.Equals(samplingParams)); + Assert.Equal(!parentIsRemote && parentIsSampled, localParentSampled.LatestSamplingParameters.Equals(samplingParams)); + Assert.Equal(!parentIsRemote && !parentIsSampled, localParentNotSampled.LatestSamplingParameters.Equals(samplingParams)); + Assert.Equal(expectedResult, actualResult); - mockRepository.VerifyNoOtherCalls(); } [Fact] From cd5b753785ac6d3b7211cff514466874d3da7be2 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Wed, 6 Dec 2023 03:42:59 +0100 Subject: [PATCH 20/23] Removed Moq dependency from Instrumentation.Grpc.Tests (#5124) --- .../GrpcTests.client.cs | 172 +++++++----------- .../GrpcTests.server.cs | 25 --- ...elemetry.Instrumentation.Grpc.Tests.csproj | 3 +- 3 files changed, 70 insertions(+), 130 deletions(-) diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs index 53328008bf..3a7c513aa3 100644 --- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs +++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.client.cs @@ -23,9 +23,9 @@ using Grpc.Net.Client; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Moq; #if !NETFRAMEWORK using OpenTelemetry.Context.Propagation; +using OpenTelemetry.Tests; #endif using OpenTelemetry.Instrumentation.Grpc.Tests.GrpcTestHelpers; using OpenTelemetry.Instrumentation.GrpcNetClient; @@ -61,7 +61,7 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho return response; }); - var processor = new Mock>(); + var exportedItems = new List(); using var parent = new Activity("parent") .SetIdFormat(ActivityIdFormat.W3C) @@ -77,7 +77,7 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho options.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => { enrichWithHttpResponseMessageCalled = true; }; } }) - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions @@ -88,8 +88,8 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho var rs = client.SayHello(new HelloRequest()); } - Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called. - var activity = (Activity)processor.Invocations[2].Arguments[0]; + Assert.Single(exportedItems); + var activity = exportedItems[0]; ValidateGrpcActivity(activity); Assert.Equal(parent.TraceId, activity.Context.TraceId); @@ -137,7 +137,7 @@ public void GrpcClientCallsAreCollectedSuccessfully(string baseAddress, bool sho [InlineData("http://[::1]", false)] public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool shouldEnrich = true) { - KeyValuePair[] config = new KeyValuePair[] { new KeyValuePair("OTEL_SEMCONV_STABILITY_OPT_IN", "http") }; + var config = new KeyValuePair[] { new("OTEL_SEMCONV_STABILITY_OPT_IN", "http") }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(config) .Build(); @@ -156,7 +156,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool return response; }); - var processor = new Mock>(); + var exportedItems = new List(); using var parent = new Activity("parent") .SetIdFormat(ActivityIdFormat.W3C) @@ -173,7 +173,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool options.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => { enrichWithHttpResponseMessageCalled = true; }; } }) - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions @@ -184,8 +184,8 @@ public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool var rs = client.SayHello(new HelloRequest()); } - Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called. - var activity = (Activity)processor.Invocations[2].Arguments[0]; + Assert.Single(exportedItems); + var activity = exportedItems[0]; ValidateGrpcActivity(activity); Assert.Equal(parent.TraceId, activity.Context.TraceId); @@ -233,7 +233,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_New(string baseAddress, bool [InlineData("http://[::1]", false)] public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, bool shouldEnrich = true) { - KeyValuePair[] config = new KeyValuePair[] { new KeyValuePair("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") }; + var config = new KeyValuePair[] { new("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(config) .Build(); @@ -252,7 +252,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, boo return response; }); - var processor = new Mock>(); + var exportedItems = new List(); using var parent = new Activity("parent") .SetIdFormat(ActivityIdFormat.W3C) @@ -269,7 +269,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, boo options.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => { enrichWithHttpResponseMessageCalled = true; }; } }) - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions @@ -280,8 +280,8 @@ public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, boo var rs = client.SayHello(new HelloRequest()); } - Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called. - var activity = (Activity)processor.Invocations[2].Arguments[0]; + Assert.Single(exportedItems); + var activity = exportedItems[0]; ValidateGrpcActivity(activity); Assert.Equal(parent.TraceId, activity.Context.TraceId); @@ -332,12 +332,7 @@ public void GrpcClientCallsAreCollectedSuccessfully_Dupe(string baseAddress, boo public void GrpcAndHttpClientInstrumentationIsInvoked(bool shouldEnrich) { var uri = new Uri($"http://localhost:{this.server.Port}"); - var processor = new Mock>(); - processor.Setup(x => x.OnStart(It.IsAny())).Callback(c => - { - c.SetTag("enrichedWithHttpRequestMessage", "no"); - c.SetTag("enrichedWithHttpResponseMessage", "no"); - }); + var exportedItems = new List(); using var parent = new Activity("parent") .Start(); @@ -360,7 +355,7 @@ public void GrpcAndHttpClientInstrumentationIsInvoked(bool shouldEnrich) } }) .AddHttpClientInstrumentation() - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { // With net5, based on the grpc changes, the quantity of default activities changed. @@ -374,9 +369,9 @@ public void GrpcAndHttpClientInstrumentationIsInvoked(bool shouldEnrich) var rs = client.SayHello(new HelloRequest()); } - Assert.Equal(7, processor.Invocations.Count); // SetParentProvider + OnStart/OnEnd (gRPC) + OnStart/OnEnd (HTTP) + OnShutdown/Dispose called. - var httpSpan = (Activity)processor.Invocations[3].Arguments[0]; - var grpcSpan = (Activity)processor.Invocations[4].Arguments[0]; + Assert.Equal(2, exportedItems.Count); + var httpSpan = exportedItems.Single(activity => activity.OperationName == OperationNameHttpOut); + var grpcSpan = exportedItems.Single(activity => activity.OperationName == OperationNameGrpcOut); ValidateGrpcActivity(grpcSpan); Assert.Equal($"greet.Greeter/SayHello", grpcSpan.DisplayName); @@ -384,17 +379,23 @@ public void GrpcAndHttpClientInstrumentationIsInvoked(bool shouldEnrich) Assert.Equal("POST", httpSpan.DisplayName); Assert.Equal(grpcSpan.SpanId, httpSpan.ParentSpanId); - Assert.NotEmpty(grpcSpan.Tags.Where(tag => tag.Key == "enrichedWithHttpRequestMessage")); - Assert.NotEmpty(grpcSpan.Tags.Where(tag => tag.Key == "enrichedWithHttpResponseMessage")); - Assert.Equal(shouldEnrich ? "yes" : "no", grpcSpan.Tags.Where(tag => tag.Key == "enrichedWithHttpRequestMessage").FirstOrDefault().Value); - Assert.Equal(shouldEnrich ? "yes" : "no", grpcSpan.Tags.Where(tag => tag.Key == "enrichedWithHttpResponseMessage").FirstOrDefault().Value); + if (shouldEnrich) + { + Assert.Single(grpcSpan.Tags, tag => tag.Key == "enrichedWithHttpRequestMessage" && tag.Value == "yes"); + Assert.Single(grpcSpan.Tags, tag => tag.Key == "enrichedWithHttpResponseMessage" && tag.Value == "yes"); + } + else + { + Assert.Empty(grpcSpan.Tags.Where(tag => tag.Key == "enrichedWithHttpRequestMessage")); + Assert.Empty(grpcSpan.Tags.Where(tag => tag.Key == "enrichedWithHttpResponseMessage")); + } } [Fact(Skip = "https://github.com/open-telemetry/opentelemetry-dotnet/issues/5092")] public void GrpcAndHttpClientInstrumentationWithSuppressInstrumentation() { var uri = new Uri($"http://localhost:{this.server.Port}"); - var processor = new Mock>(); + var exportedItems = new List(); using var parent = new Activity("parent") .Start(); @@ -403,7 +404,7 @@ public void GrpcAndHttpClientInstrumentationWithSuppressInstrumentation() .SetSampler(new AlwaysOnSampler()) .AddGrpcClientInstrumentation(o => o.SuppressDownstreamInstrumentation = true) .AddHttpClientInstrumentation() - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { Parallel.ForEach( @@ -420,11 +421,11 @@ public void GrpcAndHttpClientInstrumentationWithSuppressInstrumentation() }); } - Assert.Equal(11, processor.Invocations.Count); // SetParentProvider + OnStart/OnEnd (gRPC) * 4 + OnShutdown/Dispose called. - var grpcSpan1 = (Activity)processor.Invocations[2].Arguments[0]; - var grpcSpan2 = (Activity)processor.Invocations[4].Arguments[0]; - var grpcSpan3 = (Activity)processor.Invocations[6].Arguments[0]; - var grpcSpan4 = (Activity)processor.Invocations[8].Arguments[0]; + Assert.Equal(4, exportedItems.Count); + var grpcSpan1 = exportedItems[0]; + var grpcSpan2 = exportedItems[1]; + var grpcSpan3 = exportedItems[2]; + var grpcSpan4 = exportedItems[3]; ValidateGrpcActivity(grpcSpan1); Assert.Equal($"greet.Greeter/SayHello", grpcSpan1.DisplayName); @@ -449,21 +450,17 @@ public void GrpcPropagatesContextWithSuppressInstrumentationOptionSetToTrue() try { var uri = new Uri($"http://localhost:{this.server.Port}"); - var processor = new Mock>(); + var exportedItems = new List(); using var source = new ActivitySource("test-source"); - var propagator = new Mock(); - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => - { - action(message, "customField", "customValue"); - }); + var propagator = new CustomTextMapPropagator(); + propagator.InjectValues.Add("customField", context => "customValue"); Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), - propagator.Object, + propagator, })); using (Sdk.CreateTracerProviderBuilder() @@ -480,37 +477,21 @@ public void GrpcPropagatesContextWithSuppressInstrumentationOptionSetToTrue() activity.SetCustomProperty("customField", request.Headers["customField"].ToString()); }; }) // Instrumenting the server side as well - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { - using (var activity = source.StartActivity("parent")) - { - Assert.NotNull(activity); - var channel = GrpcChannel.ForAddress(uri); - var client = new Greeter.GreeterClient(channel); - var rs = client.SayHello(new HelloRequest()); - } - - WaitForProcessorInvocations(processor, 7); + using var activity = source.StartActivity("parent"); + Assert.NotNull(activity); + var channel = GrpcChannel.ForAddress(uri); + var client = new Greeter.GreeterClient(channel); + var rs = client.SayHello(new HelloRequest()); } - Assert.Equal(9, processor.Invocations.Count); // SetParentProvider + (OnStart + OnEnd) * 3 (parent, gRPC client, and server) + Shutdown + Dispose called. - - Assert.Single(processor.Invocations, invo => invo.Method.Name == "SetParentProvider"); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnStart), "parent")); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnStart), OperationNameGrpcOut)); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnStart), OperationNameHttpRequestIn)); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnEnd), OperationNameHttpRequestIn)); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnEnd), OperationNameGrpcOut)); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnEnd), "parent")); - Assert.Single(processor.Invocations, invo => invo.Method.Name == "OnShutdown"); - Assert.Single(processor.Invocations, invo => invo.Method.Name == nameof(processor.Object.Dispose)); - - var serverActivity = GetActivityFromProcessorInvocation(processor, nameof(processor.Object.OnEnd), OperationNameHttpRequestIn); - var clientActivity = GetActivityFromProcessorInvocation(processor, nameof(processor.Object.OnEnd), OperationNameGrpcOut); + var serverActivity = exportedItems.Single(activity => activity.OperationName == OperationNameHttpRequestIn); + var clientActivity = exportedItems.Single(activity => activity.OperationName == OperationNameGrpcOut); Assert.Equal($"greet.Greeter/SayHello", clientActivity.DisplayName); - Assert.Equal($"greet.Greeter/SayHello", serverActivity.DisplayName); + Assert.Equal($"POST /greet.Greeter/SayHello", serverActivity.DisplayName); Assert.Equal(clientActivity.TraceId, serverActivity.TraceId); Assert.Equal(clientActivity.SpanId, serverActivity.ParentSpanId); Assert.Equal(0, clientActivity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode)); @@ -532,19 +513,16 @@ public void GrpcDoesNotPropagateContextWithSuppressInstrumentationOptionSetToFal try { var uri = new Uri($"http://localhost:{this.server.Port}"); - var processor = new Mock>(); - + var exportedItems = new List(); using var source = new ActivitySource("test-source"); bool isPropagatorCalled = false; - var propagator = new Mock(); - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => - { - isPropagatorCalled = true; - }); + var propagator = new CustomTextMapPropagator + { + Injected = (context) => isPropagatorCalled = true, + }; - Sdk.SetDefaultTextMapPropagator(propagator.Object); + Sdk.SetDefaultTextMapPropagator(propagator); var headers = new Metadata(); @@ -554,7 +532,7 @@ public void GrpcDoesNotPropagateContextWithSuppressInstrumentationOptionSetToFal { o.SuppressDownstreamInstrumentation = false; }) - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { using var activity = source.StartActivity("parent"); @@ -563,15 +541,12 @@ public void GrpcDoesNotPropagateContextWithSuppressInstrumentationOptionSetToFal var rs = client.SayHello(new HelloRequest(), headers); } - Assert.Equal(7, processor.Invocations.Count); // SetParentProvider/OnShutdown/Dispose called. + Assert.Equal(2, exportedItems.Count); - Assert.Single(processor.Invocations, invo => invo.Method.Name == "SetParentProvider"); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnStart), "parent")); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnStart), OperationNameGrpcOut)); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnEnd), OperationNameGrpcOut)); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnEnd), "parent")); - Assert.Single(processor.Invocations, invo => invo.Method.Name == "OnShutdown"); - Assert.Single(processor.Invocations, invo => invo.Method.Name == nameof(processor.Object.Dispose)); + var parentActivity = exportedItems.Single(activity => activity.OperationName == "parent"); + var clientActivity = exportedItems.Single(activity => activity.OperationName == OperationNameGrpcOut); + + Assert.Equal(clientActivity.ParentSpanId, parentActivity.SpanId); // Propagator is not called Assert.False(isPropagatorCalled); @@ -592,22 +567,18 @@ public void GrpcClientInstrumentationRespectsSdkSuppressInstrumentation() try { var uri = new Uri($"http://localhost:{this.server.Port}"); - var processor = new Mock>(); + var exportedItems = new List(); using var source = new ActivitySource("test-source"); bool isPropagatorCalled = false; - var propagator = new Mock(); - propagator.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => - { - isPropagatorCalled = true; - }); + var propagator = new CustomTextMapPropagator(); + propagator.Injected = (context) => isPropagatorCalled = true; Sdk.SetDefaultTextMapPropagator(new CompositeTextMapPropagator(new TextMapPropagator[] { new TraceContextPropagator(), - propagator.Object, + propagator, })); using (Sdk.CreateTracerProviderBuilder() @@ -616,7 +587,7 @@ public void GrpcClientInstrumentationRespectsSdkSuppressInstrumentation() { o.SuppressDownstreamInstrumentation = true; }) - .AddProcessor(processor.Object) + .AddInMemoryExporter(exportedItems) .Build()) { using var activity = source.StartActivity("parent"); @@ -630,9 +601,7 @@ public void GrpcClientInstrumentationRespectsSdkSuppressInstrumentation() // If suppressed, activity is not emitted and // propagation is also not performed. - Assert.Equal(5, processor.Invocations.Count); // SetParentProvider + (OnStart + OnEnd) * 3 for parent + OnShutdown + Dispose called. - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnStart), "parent")); - Assert.Single(processor.Invocations, GeneratePredicateForMoqProcessorActivity(nameof(processor.Object.OnEnd), "parent")); + Assert.Single(exportedItems); Assert.False(isPropagatorCalled); } finally @@ -680,9 +649,4 @@ private static void ValidateGrpcActivity(Activity activityToValidate) Assert.Equal(GrpcClientDiagnosticListener.Version.ToString(), activityToValidate.Source.Version); Assert.Equal(ActivityKind.Client, activityToValidate.Kind); } - - private static Predicate GeneratePredicateForMoqProcessorActivity(string methodName, string activityOperationName) - { - return invo => invo.Method.Name == methodName && (invo.Arguments[0] as Activity)?.OperationName == activityOperationName; - } } diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs index 73ce23b4c0..e874cfe316 100644 --- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs +++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/GrpcTests.server.cs @@ -16,7 +16,6 @@ #if NET6_0_OR_GREATER using System.Diagnostics; -using Moq; using OpenTelemetry.Instrumentation.Grpc.Services.Tests; using Xunit; @@ -217,29 +216,5 @@ private static void WaitForExporterToReceiveItems(List itemsReceived, }, TimeSpan.FromSeconds(1))); } - - private static void WaitForProcessorInvocations(Mock> spanProcessor, int invocationCount) - { - // We need to let End callback execute as it is executed AFTER response was returned. - // In unit tests environment there may be a lot of parallel unit tests executed, so - // giving some breezing room for the End callback to complete - Assert.True(SpinWait.SpinUntil( - () => - { - Thread.Sleep(10); - return spanProcessor.Invocations.Count >= invocationCount; - }, - TimeSpan.FromSeconds(1))); - } - - private static Activity GetActivityFromProcessorInvocation(Mock> processor, string methodName, string activityOperationName) - { - return processor.Invocations - .FirstOrDefault(invo => - { - return invo.Method.Name == methodName - && (invo.Arguments[0] as Activity)?.OperationName == activityOperationName; - })?.Arguments[0] as Activity; - } } #endif diff --git a/test/OpenTelemetry.Instrumentation.Grpc.Tests/OpenTelemetry.Instrumentation.Grpc.Tests.csproj b/test/OpenTelemetry.Instrumentation.Grpc.Tests/OpenTelemetry.Instrumentation.Grpc.Tests.csproj index 9c393cac71..c56cc50418 100644 --- a/test/OpenTelemetry.Instrumentation.Grpc.Tests/OpenTelemetry.Instrumentation.Grpc.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.Grpc.Tests/OpenTelemetry.Instrumentation.Grpc.Tests.csproj @@ -17,7 +17,6 @@ - runtime; build; native; contentfiles; analyzers @@ -46,7 +45,9 @@ + + From a963ec809aac676ce9418eb008c9a4ef592eca26 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Wed, 6 Dec 2023 03:55:47 +0100 Subject: [PATCH 21/23] Removed Moq dependency from Instrumentation.AspNetCore.Tests (#5123) --- .../BasicTests.cs | 16 ++++++---------- ...metry.Instrumentation.AspNetCore.Tests.csproj | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 11ebcbc8d7..b27565a0f4 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -24,7 +24,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Moq; using OpenTelemetry.Context.Propagation; using OpenTelemetry.Instrumentation.AspNetCore.Implementation; using OpenTelemetry.Tests; @@ -205,14 +204,11 @@ public async Task CustomPropagator(bool addSampler) var expectedTraceId = ActivityTraceId.CreateRandom(); var expectedSpanId = ActivitySpanId.CreateRandom(); - var propagator = new Mock(); - propagator.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns( - new PropagationContext( - new ActivityContext( - expectedTraceId, - expectedSpanId, - ActivityTraceFlags.Recorded), - default)); + var propagator = new CustomTextMapPropagator + { + TraceId = expectedTraceId, + SpanId = expectedSpanId, + }; // Arrange using (var testFactory = this.factory @@ -220,7 +216,7 @@ public async Task CustomPropagator(bool addSampler) { builder.ConfigureTestServices(services => { - Sdk.SetDefaultTextMapPropagator(propagator.Object); + Sdk.SetDefaultTextMapPropagator(propagator); var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder(); if (addSampler) diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj index 186b33a8f7..26722a5dd5 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/OpenTelemetry.Instrumentation.AspNetCore.Tests.csproj @@ -10,7 +10,6 @@ - runtime; build; native; contentfiles; analyzers @@ -33,6 +32,7 @@ + From 4cf2bf433ef24027566730a7a3c5ab183410e838 Mon Sep 17 00:00:00 2001 From: Nils Gruson Date: Wed, 6 Dec 2023 21:02:29 +0100 Subject: [PATCH 22/23] Moq cleanup (#5133) --- Directory.Packages.props | 1 - src/OpenTelemetry/AssemblyInfo.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 42cf425cee..a53d8cf720 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -85,7 +85,6 @@ - diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs index ddcbfd91a8..633ef4f832 100644 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ b/src/OpenTelemetry/AssemblyInfo.cs @@ -23,7 +23,6 @@ [assembly: InternalsVisibleTo("OpenTelemetry.Exporter.Prometheus.HttpListener.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Extensions.Hosting.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] [assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)] #if !EXPOSE_EXPERIMENTAL_FEATURES @@ -36,12 +35,10 @@ file static class AssemblyInfo { public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898"; - public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; } #else file static class AssemblyInfo { public const string PublicKey = ""; - public const string MoqPublicKey = ""; } #endif From 7419d854e6619422a3b5a521fc2f67990d2ec4b7 Mon Sep 17 00:00:00 2001 From: Robert Coltheart <13191652+robertcoltheart@users.noreply.github.com> Date: Thu, 7 Dec 2023 11:18:25 +1100 Subject: [PATCH 23/23] Export OpenMetrics format for prometheus exporters (#5107) --- .../CHANGELOG.md | 2 + ...etry.Exporter.Prometheus.AspNetCore.csproj | 1 + .../PrometheusExporterMiddleware.cs | 29 ++++++- .../CHANGELOG.md | 2 + .../Internal/PrometheusCollectionManager.cs | 17 +++-- .../Internal/PrometheusExporter.cs | 2 + .../Internal/PrometheusHeadersParser.cs | 60 +++++++++++++++ .../Internal/PrometheusSerializer.cs | 26 +++++++ .../Internal/PrometheusSerializerExt.cs | 12 ++- .../PrometheusHttpListener.cs | 20 ++++- .../PrometheusExporterMiddlewareTests.cs | 61 ++++++++++++--- .../PrometheusCollectionManagerTests.cs | 14 ++-- .../PrometheusHeadersParserTests.cs | 48 ++++++++++++ .../PrometheusHttpListenerTests.cs | 72 +++++++++++++++--- .../PrometheusSerializerTests.cs | 75 ++++++++++++++++++- 15 files changed, 395 insertions(+), 46 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs create mode 100644 test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHeadersParserTests.cs diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index ba0192f913..809994f353 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) + ## 1.7.0-rc.1 Released 2023-Nov-29 diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj index 3a421b0e63..a94c7fdb1a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -28,6 +28,7 @@ + diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index bfbb178783..72145bdaed 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; @@ -64,7 +65,9 @@ public async Task InvokeAsync(HttpContext httpContext) try { - var collectionResponse = await this.exporter.CollectionManager.EnterCollect().ConfigureAwait(false); + var openMetricsRequested = AcceptsOpenMetrics(httpContext.Request); + var collectionResponse = await this.exporter.CollectionManager.EnterCollect(openMetricsRequested).ConfigureAwait(false); + try { if (collectionResponse.View.Count > 0) @@ -75,7 +78,9 @@ public async Task InvokeAsync(HttpContext httpContext) #else response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); #endif - response.ContentType = "text/plain; charset=utf-8; version=0.0.4"; + response.ContentType = openMetricsRequested + ? "application/openmetrics-text; version=1.0.0; charset=utf-8" + : "text/plain; charset=utf-8; version=0.0.4"; await response.Body.WriteAsync(collectionResponse.View.Array, 0, collectionResponse.View.Count).ConfigureAwait(false); } @@ -102,4 +107,24 @@ public async Task InvokeAsync(HttpContext httpContext) this.exporter.OnExport = null; } + + private static bool AcceptsOpenMetrics(HttpRequest request) + { + var acceptHeader = request.Headers.Accept; + + if (StringValues.IsNullOrEmpty(acceptHeader)) + { + return false; + } + + foreach (var header in acceptHeader) + { + if (PrometheusHeadersParser.AcceptsOpenMetrics(header)) + { + return true; + } + } + + return false; + } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index 86cbc02b37..16185be88d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) + ## 1.7.0-rc.1 Released 2023-Nov-29 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 4d4ef30eda..c0356597b6 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -45,9 +45,9 @@ public PrometheusCollectionManager(PrometheusExporter exporter) } #if NET6_0_OR_GREATER - public ValueTask EnterCollect() + public ValueTask EnterCollect(bool openMetricsRequested) #else - public Task EnterCollect() + public Task EnterCollect(bool openMetricsRequested) #endif { this.EnterGlobalLock(); @@ -93,7 +93,7 @@ public Task EnterCollect() this.ExitGlobalLock(); CollectionResponse response; - var result = this.ExecuteCollect(); + var result = this.ExecuteCollect(openMetricsRequested); if (result) { this.previousDataViewGeneratedAtUtc = DateTime.UtcNow; @@ -168,9 +168,10 @@ private void WaitForReadersToComplete() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ExecuteCollect() + private bool ExecuteCollect(bool openMetricsRequested) { this.exporter.OnExport = this.onCollectRef; + this.exporter.OpenMetricsRequested = openMetricsRequested; var result = this.exporter.Collect(Timeout.Infinite); this.exporter.OnExport = null; return result; @@ -193,7 +194,13 @@ private ExportResult OnCollect(Batch metrics) { try { - cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric)); + cursor = PrometheusSerializer.WriteMetric( + this.buffer, + cursor, + metric, + this.GetPrometheusMetric(metric), + this.exporter.OpenMetricsRequested); + break; } catch (IndexOutOfRangeException) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index ddc3df494c..b02a3a64b6 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -63,6 +63,8 @@ internal Func, ExportResult> OnExport internal int ScrapeResponseCacheDurationMilliseconds { get; } + internal bool OpenMetricsRequested { get; set; } + /// public override ExportResult Export(in Batch metrics) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs new file mode 100644 index 0000000000..8548f6d16e --- /dev/null +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusHeadersParser.cs @@ -0,0 +1,60 @@ +// +// 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. +// + +namespace OpenTelemetry.Exporter.Prometheus; + +internal static class PrometheusHeadersParser +{ + private const string OpenMetricsMediaType = "application/openmetrics-text"; + + internal static bool AcceptsOpenMetrics(string contentType) + { + var value = contentType.AsSpan(); + + while (value.Length > 0) + { + var headerValue = SplitNext(ref value, ','); + var mediaType = SplitNext(ref headerValue, ';'); + + if (mediaType.Equals(OpenMetricsMediaType.AsSpan(), StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + private static ReadOnlySpan SplitNext(ref ReadOnlySpan span, char character) + { + var index = span.IndexOf(character); + + if (index == -1) + { + var part = span; + span = span.Slice(span.Length); + + return part; + } + else + { + var part = span.Slice(0, index); + span = span.Slice(index + 1); + + return part; + } + } +} diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 3bcd4998f1..631de4eab3 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -326,6 +326,32 @@ public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric return cursor; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool useOpenMetrics) + { + if (useOpenMetrics) + { + cursor = WriteLong(buffer, cursor, value / 1000); + buffer[cursor++] = unchecked((byte)'.'); + + long millis = value % 1000; + + if (millis < 100) + { + buffer[cursor++] = unchecked((byte)'0'); + } + + if (millis < 10) + { + buffer[cursor++] = unchecked((byte)'0'); + } + + return WriteLong(buffer, cursor, millis); + } + + return WriteLong(buffer, cursor, value); + } + private static string MapPrometheusType(PrometheusType type) { return type switch diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 0eb068b185..d606a5ced5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric) + public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -94,7 +94,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; } @@ -136,7 +136,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteLong(buffer, cursor, totalCount); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; } @@ -163,7 +163,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum()); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; @@ -189,14 +189,12 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount()); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; } } - buffer[cursor++] = ASCII_LINEFEED; - return cursor; } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 112bbf2620..914f448df9 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -110,6 +110,18 @@ public void Dispose() } } + private static bool AcceptsOpenMetrics(HttpListenerRequest request) + { + var acceptHeader = request.Headers["Accept"]; + + if (string.IsNullOrEmpty(acceptHeader)) + { + return false; + } + + return PrometheusHeadersParser.AcceptsOpenMetrics(acceptHeader); + } + private void WorkerProc() { this.httpListener.Start(); @@ -148,7 +160,9 @@ private async Task ProcessRequestAsync(HttpListenerContext context) { try { - var collectionResponse = await this.exporter.CollectionManager.EnterCollect().ConfigureAwait(false); + var openMetricsRequested = AcceptsOpenMetrics(context.Request); + var collectionResponse = await this.exporter.CollectionManager.EnterCollect(openMetricsRequested).ConfigureAwait(false); + try { context.Response.Headers.Add("Server", string.Empty); @@ -156,7 +170,9 @@ private async Task ProcessRequestAsync(HttpListenerContext context) { context.Response.StatusCode = 200; context.Response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); - context.Response.ContentType = "text/plain; charset=utf-8; version=0.0.4"; + context.Response.ContentType = openMetricsRequested + ? "application/openmetrics-text; version=1.0.0; charset=utf-8" + : "text/plain; charset=utf-8; version=0.0.4"; await context.Response.OutputStream.WriteAsync(collectionResponse.View.Array, 0, collectionResponse.View.Count).ConfigureAwait(false); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index ea38fb59cf..072b3b4511 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -238,6 +238,24 @@ await RunPrometheusExporterMiddlewareIntegrationTest( registerMeterProvider: false); } + [Fact] + public Task PrometheusExporterMiddlewareIntegration_TextPlainResponse() + { + return RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + acceptHeader: "text/plain"); + } + + [Fact] + public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader() + { + return RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + acceptHeader: "application/openmetrics-text; version=1.0.0"); + } + private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string path, Action configure, @@ -245,8 +263,11 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( Action validateResponse = null, bool registerMeterProvider = true, Action configureOptions = null, - bool skipMetrics = false) + bool skipMetrics = false, + string acceptHeader = "application/openmetrics-text") { + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + using var host = await new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder .UseTestServer() @@ -284,7 +305,14 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( counter.Add(0.99D, tags); } - using var response = await host.GetTestClient().GetAsync(path); + using var client = host.GetTestClient(); + + if (!string.IsNullOrEmpty(acceptHeader)) + { + client.DefaultRequestHeaders.Add("Accept", acceptHeader); + } + + using var response = await client.GetAsync(path); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -292,22 +320,31 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Content.Headers.Contains("Last-Modified")); - Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + + if (requestOpenMetrics) + { + Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType.ToString()); + } + else + { + Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + } string content = await response.Content.ReadAsStringAsync(); - var matches = Regex.Matches( - content, - ("^" - + "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "\n" - + "# EOF\n" - + "$").Replace('\'', '"')); + string expected = requestOpenMetrics + ? "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" + + "# EOF\n" + : "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" + + "# EOF\n"; + + var matches = Regex.Matches(content, ("^" + expected + "$").Replace('\'', '"')); Assert.Single(matches); - var timestamp = long.Parse(matches[0].Groups[1].Value); + var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty)); Assert.True(beginTimestamp <= timestamp && timestamp <= endTimestamp); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 70b886b520..2611126379 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -24,11 +24,13 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests; public sealed class PrometheusCollectionManagerTests { [Theory] - [InlineData(0)] // disable cache, default value for HttpListener + [InlineData(0, true)] // disable cache, default value for HttpListener + [InlineData(0, false)] // disable cache, default value for HttpListener #if PROMETHEUS_ASPNETCORE - [InlineData(300)] // default value for AspNetCore, no possibility to set on HttpListener + [InlineData(300, true)] // default value for AspNetCore, no possibility to set on HttpListener + [InlineData(300, false)] // default value for AspNetCore, no possibility to set on HttpListener #endif - public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMilliseconds) + public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMilliseconds, bool openMetricsRequested) { bool cacheEnabled = scrapeResponseCacheDurationMilliseconds != 0; using var meter = new Meter(Utils.GetCurrentMethodName()); @@ -65,7 +67,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(); + var response = await exporter.CollectionManager.EnterCollect(openMetricsRequested); try { return new Response @@ -98,7 +100,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon counter.Add(100); // This should use the cache and ignore the second counter update. - var task = exporter.CollectionManager.EnterCollect(); + var task = exporter.CollectionManager.EnterCollect(openMetricsRequested); Assert.True(task.IsCompleted); var response = await task; try @@ -129,7 +131,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(); + var response = await exporter.CollectionManager.EnterCollect(openMetricsRequested); try { return new Response diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHeadersParserTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHeadersParserTests.cs new file mode 100644 index 0000000000..bc82596f3c --- /dev/null +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHeadersParserTests.cs @@ -0,0 +1,48 @@ +// +// 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; + +namespace OpenTelemetry.Exporter.Prometheus.Tests; + +public class PrometheusHeadersParserTests +{ + [Theory] + [InlineData("application/openmetrics-text")] + [InlineData("application/openmetrics-text; version=1.0.0")] + [InlineData("application/openmetrics-text; version=1.0.0; charset=utf-8")] + [InlineData("text/plain,application/openmetrics-text; version=1.0.0; charset=utf-8")] + [InlineData("text/plain; charset=utf-8,application/openmetrics-text; version=1.0.0; charset=utf-8")] + [InlineData("text/plain, */*;q=0.8,application/openmetrics-text; version=1.0.0; charset=utf-8")] + public void ParseHeader_AcceptHeaders_OpenMetricsValid(string header) + { + var result = PrometheusHeadersParser.AcceptsOpenMetrics(header); + + Assert.True(result); + } + + [Theory] + [InlineData("text/plain")] + [InlineData("text/plain; charset=utf-8")] + [InlineData("text/plain; charset=utf-8; version=0.0.4")] + [InlineData("*/*;q=0.8,text/plain; charset=utf-8; version=0.0.4")] + public void ParseHeader_AcceptHeaders_OtherHeadersInvalid(string header) + { + var result = PrometheusHeadersParser.AcceptsOpenMetrics(header); + + Assert.False(result); + } +} diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index d964432009..e9e270dd5b 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -90,14 +90,28 @@ public async Task PrometheusExporterHttpServerIntegration_NoMetrics() await this.RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); } - private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false) + [Fact] + public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics() + { + await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); + } + + [Fact] + public async Task PrometheusExporterHttpServerIntegration_UseOpenMetricsVersionHeader() { + await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); + } + + private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text") + { + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + Random random = new Random(); int retryAttempts = 5; int port = 0; string address = null; - MeterProvider provider; + MeterProvider provider = null; using var meter = new Meter(this.meterName); while (retryAttempts-- != 0) @@ -105,10 +119,24 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri port = random.Next(2000, 5000); address = $"http://localhost:{port}/"; - provider = Sdk.CreateMeterProviderBuilder() - .AddMeter(meter.Name) - .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { address }) - .Build(); + try + { + provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { address }) + .Build(); + + break; + } + catch + { + // ignored + } + } + + if (provider == null) + { + throw new InvalidOperationException("HttpListener could not be started"); } var tags = new KeyValuePair[] @@ -125,21 +153,45 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri } using HttpClient client = new HttpClient(); + + if (!string.IsNullOrEmpty(acceptHeader)) + { + client.DefaultRequestHeaders.Add("Accept", acceptHeader); + } + using var response = await client.GetAsync($"{address}metrics"); if (!skipMetrics) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Content.Headers.Contains("Last-Modified")); - Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); - Assert.Matches( - "^# TYPE counter_double_total counter\ncounter_double_total{key1='value1',key2='value2'} 101.17 \\d+\n\n# EOF\n$".Replace('\'', '"'), - await response.Content.ReadAsStringAsync()); + if (requestOpenMetrics) + { + Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType.ToString()); + } + else + { + Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + } + + var content = await response.Content.ReadAsStringAsync(); + + var expected = requestOpenMetrics + ? "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 \\d+\\.\\d{3}\n" + + "# EOF\n" + : "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 \\d+\n" + + "# EOF\n"; + + Assert.Matches(("^" + expected + "$").Replace('\'', '"'), content); } else { Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + + provider.Dispose(); } } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 53617606cf..ee837e516c 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -512,8 +512,79 @@ public void ExponentialHistogramIsIgnoredForNow() Assert.False(PrometheusSerializer.CanWriteMetric(metrics[0])); } - private static int WriteMetric(byte[] buffer, int cursor, Metric metric) + [Fact] + public void SumWithOpenMetricsFormat() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var counter = meter.CreateUpDownCounter("test_updown_counter"); + counter.Add(10); + counter.Add(-11); + + provider.ForceFlush(); + + var cursor = WriteMetric(buffer, 0, metrics[0], true); + Assert.Matches( + ("^" + + "# TYPE test_updown_counter gauge\n" + + "test_updown_counter -1 \\d+\\.\\d{3}\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramOneDimensionWithScopeInfo() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); + + provider.ForceFlush(); + + var cursor = WriteMetric(buffer, 0, metrics[0], true); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{x='1',le='0'} 0 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='5'} 0 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='10'} 0 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='25'} 1 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='50'} 1 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='75'} 1 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='100'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='250'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='500'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='750'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='1000'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='2500'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='5000'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='7500'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='10000'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{x='1',le='\\+Inf'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_sum{x='1'} 118 \\d+\\.\\d{3}\n" + + "test_histogram_count{x='1'} 2 \\d+\\.\\d{3}\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false) { - return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric)); + return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics); } }