diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index fa4e163abb6..23c03fbf947 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -130,8 +130,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.AspNetCore", "exam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{DE9130A4-F30A-49D7-8834-41DE3021218B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.Http", "src\OpenTelemetry.Instrumentation.Http\OpenTelemetry.Instrumentation.Http.csproj", "{412C64D1-43D6-4E4C-8AD8-E20E63B415BD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.GrpcNetClient", "src\OpenTelemetry.Instrumentation.GrpcNetClient\OpenTelemetry.Instrumentation.GrpcNetClient.csproj", "{0246BFC4-8AAF-45E1-A127-DB43D6E345BB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{7C87CAF9-79D7-4C26-9FFB-F3F1FB6911F1}" @@ -430,10 +428,6 @@ Global {DE9130A4-F30A-49D7-8834-41DE3021218B}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE9130A4-F30A-49D7-8834-41DE3021218B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE9130A4-F30A-49D7-8834-41DE3021218B}.Release|Any CPU.Build.0 = Release|Any CPU - {412C64D1-43D6-4E4C-8AD8-E20E63B415BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {412C64D1-43D6-4E4C-8AD8-E20E63B415BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {412C64D1-43D6-4E4C-8AD8-E20E63B415BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {412C64D1-43D6-4E4C-8AD8-E20E63B415BD}.Release|Any CPU.Build.0 = Release|Any CPU {0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0246BFC4-8AAF-45E1-A127-DB43D6E345BB}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Shipped.txt b/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Shipped.txt deleted file mode 100644 index a082f2c5bec..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Shipped.txt +++ /dev/null @@ -1,24 +0,0 @@ -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.HttpClientInstrumentationMeterProviderBuilderExtensions -OpenTelemetry.Trace.HttpClientInstrumentationTracerProviderBuilderExtensions -static OpenTelemetry.Metrics.HttpClientInstrumentationMeterProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder -static OpenTelemetry.Trace.HttpClientInstrumentationTracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.HttpClientInstrumentationTracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configureHttpClientTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder -static OpenTelemetry.Trace.HttpClientInstrumentationTracerProviderBuilderExtensions.AddHttpClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action configureHttpClientTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Instrumentation.Http/.publicApi/PublicAPI.Unshipped.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/OpenTelemetry.Instrumentation.Http/AssemblyInfo.cs b/src/OpenTelemetry.Instrumentation.Http/AssemblyInfo.cs deleted file mode 100644 index d1d2362bb31..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Runtime.CompilerServices; - -#if SIGNED -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.Http.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")] -#else -[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.Http.Tests")] -#endif diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md deleted file mode 100644 index 829e5952d79..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ /dev/null @@ -1,544 +0,0 @@ -# Changelog - -## 1.8.1 - -Released 2024-Apr-12 - -* **Breaking Change**: Fixed tracing instrumentation so that by default any - values detected in the query string component of requests are replaced with - the text `Redacted` when building the `url.full` tag. For example, - `?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can - disable this redaction by setting the environment variable - `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` to `true`. - ([#5532](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5532)) - -## 1.8.0 - -Released 2024-Apr-04 - -* Fixed an issue for spans when `server.port` attribute was not set with - `server.address` when it has default values (`80` for `HTTP` and - `443` for `HTTPS` protocol). - ([#5419](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5419)) - -* Fixed an issue where the `http.request.method_original` attribute was not set - on activity. Now, when `http.request.method` is set and the original method - is converted to its canonical form (e.g., `Get` is converted to `GET`), - the original value `Get` will be stored in `http.request.method_original`. - The attribute is not set on .NET Framework for non canonical form of `CONNECT`, - `GET`, `HEAD`, `PUT`, and `POST`. HTTP Client is converting these values - to canonical form. - ([#5471](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5471)) - -## 1.7.1 - -Released 2024-Feb-09 - -* .NET Framework - fix description for `http.client.request.duration` metric. - ([#5234](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5234)) - -## 1.7.0 - -Released 2023-Dec-13 - -## 1.6.0 - First stable release of this library - -Released 2023-Dec-13 - -## 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) - semantic conventions. - ([#5068](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5068)) - -* Update activity DisplayName as per the specification. - ([#5078](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5078)) - -* Removed reference to `OpenTelemetry` package. This is a **breaking change** - for users relying on - [SuppressDownstreamInstrumentation](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.GrpcNetClient#suppressdownstreaminstrumentation) - option in `OpenTelemetry.Instrumentation.GrpcNetClient`. For details, check - out this - [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)) - -* **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 - -* Removed the Activity Status Description that was being set during - exceptions. Activity Status will continue to be reported as `Error`. - This is a **breaking change**. `EnrichWithException` can be leveraged - to restore this behavior. - ([#5025](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5025)) - -* Updated `http.request.method` to match specification guidelines. - * For activity, if the method does not belong to one of the [known - values](https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#:~:text=http.request.method%20has%20the%20following%20list%20of%20well%2Dknown%20values) - then the request method will be set on an additional tag - `http.request.method.original` and `http.request.method` will be set to - `_OTHER`. - * For metrics, if the original method does not belong to one of the [known - values](https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-spans.md#:~:text=http.request.method%20has%20the%20following%20list%20of%20well%2Dknown%20values) - then `http.request.method` on `http.client.request.duration` metric will be - set to `_OTHER` - - `http.request.method` is set on `http.client.request.duration` metric or - activity when `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable is set to - `http` or `http/dup`. - ([#5003](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5003)) - -* An additional attribute `error.type` will be added to activity and - `http.client.request.duration` metric in case of failed requests as per the - [specification](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes). - - Users moving to `net8.0` or newer frameworks from lower versions will see - difference in values in case of an exception. `net8.0` or newer frameworks add - the ability to further drill down the exceptions to a specific type through - [HttpRequestError](https://learn.microsoft.com/dotnet/api/system.net.http.httprequesterror?view=net-8.0) - enum. For lower versions, the individual types will be rolled in to a single - type. This could be a **breaking change** if alerts are set based on the values. - - The attribute will only be added when `OTEL_SEMCONV_STABILITY_OPT_IN` - environment variable is set to `http` or `http/dup`. - - ([#5005](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5005)) - ([#5034](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5034)) - -* Fixed `network.protocol.version` attribute values to match the specification. - ([#5006](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5006)) - -* Set `network.protocol.version` value using the protocol version on the - received response. If the request fails without response, then - `network.protocol.version` attribute will not be set on Activity and - `http.client.request.duration` metric. - ([#5043](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5043)) - -## 1.6.0-beta.2 - -Released 2023-Oct-26 - -* Introduced a new metric for `HttpClient`, `http.client.request.duration` - measured in seconds. The OTel SDK (starting with version 1.6.0) - [applies custom histogram buckets](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820) - for this metric to comply with the - [Semantic Convention for Http Metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md). - This new metric is only available for users who opt-in to the new - semantic convention by configuring the `OTEL_SEMCONV_STABILITY_OPT_IN` - environment variable to either `http` (to emit only the new metric) or - `http/dup` (to emit both the new and old metrics). - ([#4870](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4870)) - - * New metric: `http.client.request.duration` - * Unit: `s` (seconds) - * Histogram Buckets: `0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, - 0.75, 1, 2.5, 5, 7.5, 10` - * Old metric: `http.client.duration` - * Unit: `ms` (milliseconds) - * Histogram Buckets: `0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, - 5000, 7500, 10000` - - Note: The older `http.client.duration` metric and - `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable will eventually be - removed after the HTTP semantic conventions are marked stable. At which time - this instrumentation can publish a stable release. Refer to the specification - for more information regarding the new HTTP semantic conventions: - * [http-spans](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-spans.md) - * [http-metrics](https://github.com/open-telemetry/semantic-conventions/blob/2bad9afad58fbd6b33cc683d1ad1f006e35e4a5d/docs/http/http-metrics.md) - -* Added support for publishing `http.client.duration` & - `http.client.request.duration` metrics on .NET Framework for `HttpWebRequest`. - ([#4870](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4870)) - -* Following `HttpClient` metrics will now be enabled by default when targeting - `.NET8.0` framework or newer. - - * **Meter** : `System.Net.Http` - * `http.client.request.duration` - * `http.client.active_requests` - * `http.client.open_connections` - * `http.client.connection.duration` - * `http.client.request.time_in_queue` - - * **Meter** : `System.Net.NameResolution` - * `dns.lookups.duration` - - For details about each individual metric check [System.Net metrics - docs - page](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-system-net). - - **NOTES**: - * When targeting `.NET8.0` framework or newer, `http.client.request.duration` metric - will only follow - [v1.22.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.22.0/docs/http/http-metrics.md#metric-httpclientrequestduration) - semantic conventions specification. Ability to switch behavior to older - conventions using `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable is - not available. - * Users can opt-out of metrics that are not required using - [views](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/metrics/customizing-the-sdk#drop-an-instrument). - - ([#4931](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4931)) - -* Added `url.scheme` attribute to `http.client.request.duration` metric. The - metric will be emitted when `OTEL_SEMCONV_STABILITY_OPT_IN` environment - variable is set to `http` or `http/dup`. - ([#4989](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4989)) - -* Updated description for `http.client.request.duration` metrics to match spec - definition. - ([#4990](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4990)) - -* `dns.lookups.duration` metric is renamed to `dns.lookup.duration`. This change - impacts only users on `.NET8.0` or newer framework. - ([#5049](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5049)) - -## 1.5.1-beta.1 - -Released 2023-Jul-20 - -* The new HTTP and network semantic conventions can be opted in to by setting - the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This allows for a - transition period for users to experiment with the new semantic conventions - and adapt as necessary. The environment variable supports the following - values: - * `http` - emit the new, frozen (proposed for stable) HTTP and networking - attributes, and stop emitting the old experimental HTTP and networking - attributes that the instrumentation emitted previously. - * `http/dup` - emit both the old and the frozen (proposed for stable) HTTP - and networking attributes, allowing for a more seamless transition. - * The default behavior (in the absence of one of these values) is to continue - emitting the same HTTP and network semantic conventions that were emitted in - `1.5.0-beta.1`. - * Note: this option will eventually be removed after the new HTTP and - network semantic conventions are marked stable. At which time this - instrumentation can receive a stable release, and the old HTTP and - network semantic conventions will no longer be supported. Refer to the - specification for more information regarding the new HTTP and network - semantic conventions for both - [spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md) - and - [metrics](https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-metrics.md). - ([#4538](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4538), - [#4639](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4639)) - -## 1.5.0-beta.1 - -Released 2023-Jun-05 - -* Bumped the package version to `1.5.0-beta.1` to keep its major and minor - version in sync with that of the core packages. This would make it more - intuitive for users to figure out what version of core packages would work - with a given version of this package. The pre-release identifier has also been - changed from `rc` to `beta` as we believe this more accurately reflects the - status of this package. We believe the `rc` identifier will be more - appropriate as semantic conventions reach stability. - -* Fixed an issue of missing `http.client.duration` metric data in case of - network failures (when response is not available). - ([#4098](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4098)) - -* Improve perf by avoiding boxing of common status codes values. - ([#4361](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4361), - [#4363](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4363)) - -## 1.0.0-rc9.14 - -Released 2023-Feb-24 - -* Updated OTel SDK dependency to 1.4.0 - -## 1.4.0-rc9.13 - -Released 2023-Feb-10 - -## 1.0.0-rc9.12 - -Released 2023-Feb-01 - -## 1.0.0-rc9.11 - -Released 2023-Jan-09 - -## 1.0.0-rc9.10 - -Released 2022-Dec-12 - -* Added `net.peer.name` and `net.peer.port` as dimensions on - `http.client.duration` metric. - ([#3907](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3907)) - -* **Breaking change** `http.host` will no longer be populated on activity. - `net.peer.name` and `net.peer.port` attributes will be populated instead. - ([#3832](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3832)) - -## 1.0.0-rc9.9 - -Released 2022-Nov-07 - -* Added back `netstandard2.0` target. - ([#3787](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3787)) - -* **Breaking change**: The `Enrich` callback option has been removed. For better - usability, it has been replaced by three separate options: In case of - `HttpClient` the new options are `EnrichWithHttpRequestMessage`, - `EnrichWithHttpResponseMessage` and `EnrichWithException` and in case of - `HttpWebRequest` the new options are `EnrichWithHttpWebRequest`, - `EnrichWithHttpWebResponse` and `EnrichWithException`. Previously, the single - `Enrich` callback required the consumer to detect which event triggered the - callback to be invoked (e.g., request start, response end, or an exception) - and then cast the object received to the appropriate type: - `HttpRequestMessage`, `HttpResponsemessage`, or `Exception` in case of - `HttpClient` and `HttpWebRequest`,`HttpWebResponse` and `Exception` in case of - `HttpWebRequest`. The separate callbacks make it clear what event triggers - them and there is no longer the need to cast the argument to the expected - type. - ([#3792](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3792)) - -* Fixed an issue which prevented custom propagators from being called on .NET 7+ - runtimes for non-sampled outgoing `HttpClient` spans. - ([#3828](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3828)) - -* **Breaking change**: The same API is now exposed for `net462` and - `netstandard2.0` targets. The `Filter` property on options is now exposed as - `FilterHttpRequestMessage` (called for .NET & .NET Core) and - `FilterHttpWebRequest` (called for .NET Framework). - ([#3793](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3793)) - -## 1.0.0-rc9.8 - -Released 2022-Oct-17 - -* In case of .NET Core, additional spans created during retries will now be -exported. -([[#3729](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3729)]) - -## 1.0.0-rc9.7 - -Released 2022-Sep-29 - -* Dropped `netstandard2.0` target and added `net6.0`. .NET 5 reached EOL - in May 2022 and .NET Core 3.1 reaches EOL in December 2022. End of support - dates for .NET are published - [here](https://dotnet.microsoft.com/download/dotnet). - The instrumentation for HttpClient now requires .NET 6 or later. - This does not affect applications targeting .NET Framework. - ([#3664](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3664)) - -* Added overloads which accept a name to the `TracerProviderBuilder` - `AddHttpClientInstrumentation` extension to allow for more fine-grained - options management - ([#3664](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3664), - [#3667](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3667)) - -## 1.0.0-rc9.6 - -Released 2022-Aug-18 - -* Updated to use Activity native support from `System.Diagnostics.DiagnosticSource` - to set activity status. - ([#3118](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3118)) - ([#3555](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3555)) - -* Changed activity source name from `OpenTelemetry.HttpWebRequest` - to `OpenTelemetry.Instrumentation.Http.HttpWebRequest` for `HttpWebRequest`s - and from `OpenTelemetry.Instrumentation.Http` - to `OpenTelemetry.Instrumentation.Http.HttpClient` for `HttpClient`. - ([#3515](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3515)) - -## 1.0.0-rc9.5 - -Released 2022-Aug-02 - -* Added `http.scheme` tag to tracing instrumentation. - ([#3464](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3464)) - -* [Breaking] Removes `SetHttpFlavor` option. "http.flavor" is - now always automatically populated. - To remove this tag, set "http.flavor" to null using `ActivityProcessor`. - ([#3380](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3380)) - -* Fix `Enrich` not getting invoked when SocketException due to HostNotFound - occurs. - ([#3407](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3407)) - -## 1.0.0-rc9.4 - -Released 2022-Jun-03 - -## 1.0.0-rc9.3 - -Released 2022-Apr-15 - -* Removes .NET Framework 4.6.1. The minimum .NET Framework - version supported is .NET 4.6.2. ([#3190](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3190)) - -## 1.0.0-rc9.2 - -Released 2022-Apr-12 - -## 1.0.0-rc9.1 - -Released 2022-Mar-30 - -* Updated `TracerProviderBuilderExtensions.AddHttpClientInstrumentation` to support - `IDeferredTracerProviderBuilder` and `IOptions` - ([#3051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3051)) - -## 1.0.0-rc10 (broken. use 1.0.0-rc9.1 and newer) - -Released 2022-Mar-04 - -## 1.0.0-rc9 - -Released 2022-Feb-02 - -* Fixed an issue with `Filter` and `Enrich` callbacks not firing under certain - conditions when gRPC is used - ([#2698](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2698)) - -## 1.0.0-rc8 - -Released 2021-Oct-08 - -* Removes .NET Framework 4.5.2 support. The minimum .NET Framework - version supported is .NET 4.6.1. ([#2138](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2138)) - -* `HttpClient` instances created before `AddHttpClientInstrumentation` is called - on .NET Framework will now also be instrumented - ([#2364](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2364)) - -## 1.0.0-rc7 - -Released 2021-Jul-12 - -## 1.0.0-rc6 - -Released 2021-Jun-25 - -## 1.0.0-rc5 - -Released 2021-Jun-09 - -## 1.0.0-rc4 - -Released 2021-Apr-23 - -* Sanitize `http.url` attribute. - ([#1961](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1961)) -* Added `RecordException` to HttpClientInstrumentationOptions and - HttpWebRequestInstrumentationOptions which allows Exception to be reported as - ActivityEvent. -* Update `AddHttpClientInstrumentation` extension method for .NET Framework to - use only use `HttpWebRequestInstrumentationOptions` - ([#1982](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1982)) - -## 1.0.0-rc3 - -Released 2021-Mar-19 - -* Leverages added AddLegacySource API from OpenTelemetry SDK to trigger Samplers - and ActivityProcessors. Samplers, ActivityProcessor.OnStart will now get the - Activity before any enrichment done by the instrumentation. - ([#1836](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1836)) -* Performance optimization by leveraging sampling decision and short circuiting - activity enrichment. - ([#1894](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1894)) - -## 1.0.0-rc2 - -Released 2021-Jan-29 - -* `otel.status_description` tag will no longer be set to the http status - description/reason phrase for outgoing http spans. - ([#1579](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1579)) - -* Moved the DiagnosticListener filtering logic from HttpClientInstrumentation - ctor to OnStartActivity method of HttpHandlerDiagnosticListener.cs; Updated - the logic of OnStartActivity to inject propagation data into Headers for - filtered out events as well. - ([#1707](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1707)) - -## 1.0.0-rc1.1 - -Released 2020-Nov-17 - -* HttpInstrumentation sets ActivitySource to activities created outside - ActivitySource. - ([#1515](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1515/)) - -## 0.8.0-beta.1 - -Released 2020-Nov-5 - -* Instrumentation for `HttpWebRequest` no longer store raw objects like - `HttpWebRequest` in Activity.CustomProperty. To enrich activity, use the - Enrich action on the instrumentation. - ([#1407](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1407)) -* Renamed TextMapPropagator to TraceContextPropagator, CompositePropagator to - CompositeTextMapPropagator. IPropagator is renamed to TextMapPropagator and - changed from interface to abstract class. - ([#1427](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1427)) -* Propagators.DefaultTextMapPropagator will be used as the default Propagator - ([#1427](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1428)) -* Removed Propagator from Instrumentation Options. Instrumentation now always - respect the Propagator.DefaultTextMapPropagator. - ([#1448](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1448)) - -## 0.7.0-beta.1 - -Released 2020-Oct-16 - -* Instrumentation no longer store raw objects like `HttpRequestMessage` in - Activity.CustomProperty. To enrich activity, use the Enrich action on the - instrumentation. - ([#1261](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1261)) -* Span Status is populated as per new spec - ([#1313](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1313)) - -## 0.6.0-beta.1 - -Released 2020-Sep-15 - -## 0.5.0-beta.2 - -Released 2020-08-28 - -* Rename FilterFunc to Filter. - -* HttpClient/HttpWebRequest instrumentation will now add the raw Request, - Response, and/or Exception objects to the Activity it creates - ([#1099](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1099)) -* Changed the default propagation to support W3C Baggage - ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) - * The default ITextFormat is now `CompositePropagator(TraceContextFormat, - BaggageFormat)`. Outgoing Http request will now send Baggage using the [W3C - Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md) - header. Previously Baggage was sent using the `Correlation-Context` header, - which is now outdated. -* Removed `AddHttpInstrumentation` and `AddHttpWebRequestInstrumentation` (.NET - Framework) `TracerProviderBuilderExtensions`. `AddHttpClientInstrumentation` - will now register `HttpClient` instrumentation on .NET Core and `HttpClient` + - `HttpWebRequest` instrumentation on .NET Framework. -* Renamed `ITextPropagator` to `IPropagator` - ([#1190](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1190)) - -## 0.3.0-beta - -Released 2020-07-23 - -* Initial release diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs deleted file mode 100644 index 80a84bb57c3..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentation.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using OpenTelemetry.Instrumentation.Http.Implementation; - -namespace OpenTelemetry.Instrumentation.Http; - -/// -/// HttpClient instrumentation. -/// -internal sealed class HttpClientInstrumentation : IDisposable -{ - private static readonly HashSet ExcludedDiagnosticSourceEventsNet7OrGreater = new() - { - "System.Net.Http.Request", - "System.Net.Http.Response", - "System.Net.Http.HttpRequestOut", - }; - - private static readonly HashSet ExcludedDiagnosticSourceEvents = new() - { - "System.Net.Http.Request", - "System.Net.Http.Response", - }; - - private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; - - private readonly Func isEnabled = (eventName, _, _) - => !ExcludedDiagnosticSourceEvents.Contains(eventName); - - private readonly Func isEnabledNet7OrGreater = (eventName, _, _) - => !ExcludedDiagnosticSourceEventsNet7OrGreater.Contains(eventName); - - /// - /// Initializes a new instance of the class. - /// - /// Configuration options for HTTP client instrumentation. - 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 - // However, in case when activity creation returns null (due to sampling) - // the framework will fall back to creating activity anyways due to active diagnostic source listener - // To prevent this, isEnabled is implemented which will return false always - // so that the sampler's decision is respected. - if (HttpHandlerDiagnosticListener.IsNet7OrGreater) - { - this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerDiagnosticListener(options), this.isEnabledNet7OrGreater, HttpInstrumentationEventSource.Log.UnknownErrorProcessingEvent); - } - else - { - this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpHandlerDiagnosticListener(options), this.isEnabled, HttpInstrumentationEventSource.Log.UnknownErrorProcessingEvent); - } - - this.diagnosticSourceSubscriber.Subscribe(); - } - - /// - public void Dispose() - { - this.diagnosticSourceSubscriber?.Dispose(); - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationMeterProviderBuilderExtensions.cs deleted file mode 100644 index 24b9524696f..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationMeterProviderBuilderExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !NET8_0_OR_GREATER -#if !NETFRAMEWORK -using OpenTelemetry.Instrumentation.Http; -#endif -using OpenTelemetry.Instrumentation.Http.Implementation; -#endif - -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Metrics; - -/// -/// Extension methods to simplify registering of HttpClient instrumentation. -/// -public static class HttpClientInstrumentationMeterProviderBuilderExtensions -{ - /// - /// Enables HttpClient instrumentation. - /// - /// being configured. - /// The instance of to chain the calls. - public static MeterProviderBuilder AddHttpClientInstrumentation( - this MeterProviderBuilder builder) - { - Guard.ThrowIfNull(builder); - -#if NET8_0_OR_GREATER - return builder - .AddMeter("System.Net.Http") - .AddMeter("System.Net.NameResolution"); -#else - // Note: Warm-up the status code and method mapping. - _ = TelemetryHelper.BoxedStatusCodes; - _ = RequestMethodHelper.KnownMethods; - -#if NETFRAMEWORK - builder.AddMeter(HttpWebRequestActivitySource.MeterName); -#else - builder.AddMeter(HttpHandlerMetricsDiagnosticListener.MeterName); - - builder.AddInstrumentation(new HttpClientMetrics()); -#endif - return builder; -#endif - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs deleted file mode 100644 index 5f97d0342fc..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationTracerProviderBuilderExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using OpenTelemetry.Instrumentation.Http; -using OpenTelemetry.Instrumentation.Http.Implementation; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Trace; - -/// -/// Extension methods to simplify registering of HttpClient instrumentation. -/// -public static class HttpClientInstrumentationTracerProviderBuilderExtensions -{ - /// - /// Enables HttpClient instrumentation. - /// - /// being configured. - /// The instance of to chain the calls. - public static TracerProviderBuilder AddHttpClientInstrumentation(this TracerProviderBuilder builder) - => AddHttpClientInstrumentation(builder, name: null, configureHttpClientTraceInstrumentationOptions: null); - - /// - /// Enables HttpClient instrumentation. - /// - /// being configured. - /// Callback action for configuring . - /// The instance of to chain the calls. - public static TracerProviderBuilder AddHttpClientInstrumentation( - this TracerProviderBuilder builder, - Action configureHttpClientTraceInstrumentationOptions) - => AddHttpClientInstrumentation(builder, name: null, configureHttpClientTraceInstrumentationOptions); - - /// - /// Enables HttpClient instrumentation. - /// - /// being configured. - /// Name which is used when retrieving options. - /// Callback action for configuring . - /// The instance of to chain the calls. - public static TracerProviderBuilder AddHttpClientInstrumentation( - this TracerProviderBuilder builder, - string name, - Action configureHttpClientTraceInstrumentationOptions) - { - Guard.ThrowIfNull(builder); - - // Note: Warm-up the status code and method mapping. - _ = TelemetryHelper.BoxedStatusCodes; - _ = RequestMethodHelper.KnownMethods; - - name ??= Options.DefaultName; - - builder.ConfigureServices(services => - { - if (configureHttpClientTraceInstrumentationOptions != null) - { - services.Configure(name, configureHttpClientTraceInstrumentationOptions); - } - - services.RegisterOptionsFactory(configuration => new HttpClientTraceInstrumentationOptions(configuration)); - }); - -#if NETFRAMEWORK - builder.AddSource(HttpWebRequestActivitySource.ActivitySourceName); - - if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) - { - deferredTracerProviderBuilder.Configure((sp, builder) => - { - var options = sp.GetRequiredService>().Get(name); - - HttpWebRequestActivitySource.TracingOptions = options; - }); - } -#else - AddHttpClientInstrumentationSource(builder); - - builder.AddInstrumentation(sp => - { - var options = sp.GetRequiredService>().Get(name); - - return new HttpClientInstrumentation(options); - }); -#endif - return builder; - } - -#if !NETFRAMEWORK - internal static void AddHttpClientInstrumentationSource( - this TracerProviderBuilder builder) - { - if (HttpHandlerDiagnosticListener.IsNet7OrGreater) - { - builder.AddSource(HttpHandlerDiagnosticListener.HttpClientActivitySourceName); - } - else - { - builder.AddSource(HttpHandlerDiagnosticListener.ActivitySourceName); - builder.AddLegacySource("System.Net.Http.HttpRequestOut"); - } - } -#endif -} diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs deleted file mode 100644 index f3773ed8fdc..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientMetrics.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using OpenTelemetry.Instrumentation.Http.Implementation; - -namespace OpenTelemetry.Instrumentation.Http; - -/// -/// HttpClient instrumentation. -/// -internal sealed class HttpClientMetrics : IDisposable -{ - private static readonly HashSet ExcludedDiagnosticSourceEvents = new() - { - "System.Net.Http.Request", - "System.Net.Http.Response", - }; - - private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber; - - private readonly Func isEnabled = (activityName, obj1, obj2) - => !ExcludedDiagnosticSourceEvents.Contains(activityName); - - /// - /// Initializes a new instance of the class. - /// - public HttpClientMetrics() - { - this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber( - new HttpHandlerMetricsDiagnosticListener("HttpHandlerDiagnosticListener"), - this.isEnabled, - HttpInstrumentationEventSource.Log.UnknownErrorProcessingEvent); - this.diagnosticSourceSubscriber.Subscribe(); - } - - /// - public void Dispose() - { - this.diagnosticSourceSubscriber?.Dispose(); - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs deleted file mode 100644 index a5fbec23cdc..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientTraceInstrumentationOptions.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Diagnostics; -using System.Net; -using System.Runtime.CompilerServices; -#if NETFRAMEWORK -using System.Net.Http; -#endif -using Microsoft.Extensions.Configuration; -using OpenTelemetry.Instrumentation.Http.Implementation; - -namespace OpenTelemetry.Instrumentation.Http; - -/// -/// Options for HttpClient instrumentation. -/// -public class HttpClientTraceInstrumentationOptions -{ - /// - /// Initializes a new instance of the class. - /// - public HttpClientTraceInstrumentationOptions() - : this(new ConfigurationBuilder().AddEnvironmentVariables().Build()) - { - } - - internal HttpClientTraceInstrumentationOptions(IConfiguration configuration) - { - Debug.Assert(configuration != null, "configuration was null"); - - if (configuration.TryGetBoolValue( - HttpInstrumentationEventSource.Log, - "OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION", - out var disableUrlQueryRedaction)) - { - this.DisableUrlQueryRedaction = disableUrlQueryRedaction; - } - } - - /// - /// Gets or sets a filter function that determines whether or not to - /// collect telemetry on a per request basis. - /// - /// - /// FilterHttpRequestMessage is only executed on .NET and .NET - /// Core runtimes. and on .NET and .NET Core are both implemented - /// using . - /// Notes: - /// - /// The return value for the filter function is interpreted as: - /// - /// If filter returns , the request is - /// collected. - /// If filter returns or throws an - /// exception the request is NOT collected. - /// - /// - /// - public Func FilterHttpRequestMessage { get; set; } - - /// - /// Gets or sets an action to enrich an with . - /// - /// - /// EnrichWithHttpRequestMessage is only executed on .NET and .NET - /// Core runtimes. and on .NET and .NET Core are both implemented - /// using . - /// - public Action EnrichWithHttpRequestMessage { get; set; } - - /// - /// Gets or sets an action to enrich an with . - /// - /// - /// EnrichWithHttpResponseMessage is only executed on .NET and .NET - /// Core runtimes. and on .NET and .NET Core are both implemented - /// using . - /// - public Action EnrichWithHttpResponseMessage { get; set; } - - /// - /// Gets or sets an action to enrich an with . - /// - /// - /// EnrichWithException is called for all runtimes. - /// - public Action EnrichWithException { get; set; } - - /// - /// Gets or sets a filter function that determines whether or not to - /// collect telemetry on a per request basis. - /// - /// - /// FilterHttpWebRequest is only executed on .NET Framework - /// runtimes. and - /// on .NET Framework are both implemented using . - /// Notes: - /// - /// The return value for the filter function is interpreted as: - /// - /// If filter returns , the request is - /// collected. - /// If filter returns or throws an - /// exception the request is NOT collected. - /// - /// - /// - public Func FilterHttpWebRequest { get; set; } - - /// - /// Gets or sets an action to enrich an with . - /// - /// - /// EnrichWithHttpWebRequest is only executed on .NET Framework - /// runtimes. and - /// on .NET Framework are both implemented using . - /// - public Action EnrichWithHttpWebRequest { get; set; } - - /// - /// Gets or sets an action to enrich an with . - /// - /// - /// EnrichWithHttpWebResponse is only executed on .NET Framework - /// runtimes. and - /// on .NET Framework are both implemented using . - /// - public Action EnrichWithHttpWebResponse { get; set; } - - /// - /// Gets or sets a value indicating whether exception will be recorded - /// as an or not. Default value: . - /// - /// - /// RecordException is supported on all runtimes. - /// For specification details see: . - /// - public bool RecordException { get; set; } - - /// - /// Gets or sets a value indicating whether the url query value should be redacted or not. - /// - /// - /// The query parameter values are redacted with value set as Redacted. - /// e.g. `?key1=value1` is set as `?key1=Redacted`. - /// The redaction can be disabled by setting this property to . - /// - internal bool DisableUrlQueryRedaction { get; set; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool EventFilterHttpRequestMessage(string activityName, object arg1) - { - try - { - return - this.FilterHttpRequestMessage == null || - !TryParseHttpRequestMessage(activityName, arg1, out HttpRequestMessage requestMessage) || - this.FilterHttpRequestMessage(requestMessage); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.RequestFilterException(ex); - return false; - } - } - - internal bool EventFilterHttpWebRequest(HttpWebRequest request) - { - try - { - return this.FilterHttpWebRequest?.Invoke(request) ?? true; - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.RequestFilterException(ex); - return false; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryParseHttpRequestMessage(string activityName, object arg1, out HttpRequestMessage requestMessage) - { - return (requestMessage = arg1 as HttpRequestMessage) != null && activityName == "System.Net.Http.HttpRequestOut"; - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpRequestMessageContextPropagation.cs b/src/OpenTelemetry.Instrumentation.Http/HttpRequestMessageContextPropagation.cs deleted file mode 100644 index 91d2a5c18a7..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/HttpRequestMessageContextPropagation.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if NETFRAMEWORK -using System.Net.Http; -#endif - -namespace OpenTelemetry.Instrumentation.Http; - -internal static class HttpRequestMessageContextPropagation -{ - internal static Action HeaderValueSetter => (request, name, value) => - { - request.Headers.Remove(name); - request.Headers.Add(name, value); - }; -} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs deleted file mode 100644 index 8dda9051812..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Diagnostics; -#if NET6_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif -#if NETFRAMEWORK -using System.Net.Http; -#endif -using System.Reflection; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Internal; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.Http.Implementation; - -internal sealed class HttpHandlerDiagnosticListener : ListenerHandler -{ - internal static readonly AssemblyName AssemblyName = typeof(HttpHandlerDiagnosticListener).Assembly.GetName(); - internal static readonly bool IsNet7OrGreater; - - // https://github.com/dotnet/runtime/blob/7d034ddbbbe1f2f40c264b323b3ed3d6b3d45e9a/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L19 - internal static readonly string HttpClientActivitySourceName = "System.Net.Http"; - internal static readonly string ActivitySourceName = AssemblyName.Name + ".HttpClient"; - internal static readonly Version Version = AssemblyName.Version; - internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString()); - - private const string OnStartEvent = "System.Net.Http.HttpRequestOut.Start"; - private const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop"; - private const string OnUnhandledExceptionEvent = "System.Net.Http.Exception"; - - private static readonly PropertyFetcher StartRequestFetcher = new("Request"); - private static readonly PropertyFetcher StopResponseFetcher = new("Response"); - private static readonly PropertyFetcher StopExceptionFetcher = new("Exception"); - private static readonly PropertyFetcher StopRequestStatusFetcher = new("RequestTaskStatus"); - private readonly HttpClientTraceInstrumentationOptions options; - - static HttpHandlerDiagnosticListener() - { - try - { - IsNet7OrGreater = typeof(HttpClient).Assembly.GetName().Version.Major >= 7; - } - catch (Exception) - { - IsNet7OrGreater = false; - } - } - - public HttpHandlerDiagnosticListener(HttpClientTraceInstrumentationOptions options) - : base("HttpHandlerDiagnosticListener") - { - this.options = options; - } - - public override void OnEventWritten(string name, object payload) - { - switch (name) - { - case OnStartEvent: - { - this.OnStartActivity(Activity.Current, payload); - } - - break; - case OnStopEvent: - { - this.OnStopActivity(Activity.Current, payload); - } - - break; - case OnUnhandledExceptionEvent: - { - this.OnException(Activity.Current, payload); - } - - break; - } - } - - public void OnStartActivity(Activity activity, object payload) - { - // The overall flow of what HttpClient library does is as below: - // Activity.Start() - // DiagnosticSource.WriteEvent("Start", payload) - // DiagnosticSource.WriteEvent("Stop", payload) - // Activity.Stop() - - // This method is in the WriteEvent("Start", payload) path. - // By this time, samplers have already run and - // activity.IsAllDataRequested populated accordingly. - - if (!TryFetchRequest(payload, out HttpRequestMessage request)) - { - HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity)); - return; - } - - // Propagate context irrespective of sampling decision - var textMapPropagator = Propagators.DefaultTextMapPropagator; - if (textMapPropagator is not TraceContextPropagator) - { - textMapPropagator.Inject(new PropagationContext(activity.Context, Baggage.Current), request, HttpRequestMessageContextPropagation.HeaderValueSetter); - } - - // For .NET7.0 or higher versions, activity is created using activity source. - // However the framework will fallback to creating activity if the sampler's decision is to drop and there is a active diagnostic listener. - // To prevent processing such activities we first check the source name to confirm if it was created using - // activity source or not. - if (IsNet7OrGreater && string.IsNullOrEmpty(activity.Source.Name)) - { - activity.IsAllDataRequested = false; - } - - // enrich Activity from payload only if sampling decision - // is favorable. - if (activity.IsAllDataRequested) - { - try - { - if (this.options.EventFilterHttpRequestMessage(activity.OperationName, request) == false) - { - HttpInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); - activity.IsAllDataRequested = false; - activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; - return; - } - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.RequestFilterException(ex); - activity.IsAllDataRequested = false; - activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; - return; - } - - RequestMethodHelper.SetActivityDisplayName(activity, request.Method.Method); - - if (!IsNet7OrGreater) - { - ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource); - ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Client); - } - - // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md - RequestMethodHelper.SetHttpMethodTag(activity, request.Method.Method); - - activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host); - activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port); - - activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, this.options.DisableUrlQueryRedaction)); - - try - { - this.options.EnrichWithHttpRequestMessage?.Invoke(activity, request); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.EnrichmentException(ex); - } - } - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] -#endif - static bool TryFetchRequest(object payload, out HttpRequestMessage request) - { - if (!StartRequestFetcher.TryFetch(payload, out request) || request == null) - { - return false; - } - - return true; - } - } - - public void OnStopActivity(Activity activity, object payload) - { - if (activity.IsAllDataRequested) - { - var requestTaskStatus = GetRequestStatus(payload); - - ActivityStatusCode currentStatusCode = activity.Status; - if (requestTaskStatus != TaskStatus.RanToCompletion) - { - if (requestTaskStatus == TaskStatus.Canceled) - { - if (currentStatusCode == ActivityStatusCode.Unset) - { - activity.SetStatus(ActivityStatusCode.Error); - } - } - else if (requestTaskStatus != TaskStatus.Faulted) - { - if (currentStatusCode == ActivityStatusCode.Unset) - { - // Faults are handled in OnException and should already have a span.Status of Error w/ Description. - activity.SetStatus(ActivityStatusCode.Error); - } - } - } - - if (TryFetchResponse(payload, out HttpResponseMessage response)) - { - if (currentStatusCode == ActivityStatusCode.Unset) - { - activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, (int)response.StatusCode)); - } - - activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(response.Version)); - activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)); - if (activity.Status == ActivityStatusCode.Error) - { - activity.SetTag(SemanticConventions.AttributeErrorType, TelemetryHelper.GetStatusCodeString(response.StatusCode)); - } - - try - { - this.options.EnrichWithHttpResponseMessage?.Invoke(activity, response); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.EnrichmentException(ex); - } - } - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] -#endif - static TaskStatus GetRequestStatus(object payload) - { - // requestTaskStatus (type is TaskStatus) is a non-nullable enum so we don't need to have a null check here. - // See: https://github.com/dotnet/runtime/blob/79c021d65c280020246d1035b0e87ae36f2d36a9/src/libraries/System.Net.Http/src/HttpDiagnosticsGuide.md?plain=1#L69 - _ = StopRequestStatusFetcher.TryFetch(payload, out var requestTaskStatus); - - return requestTaskStatus; - } - } - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] -#endif - static bool TryFetchResponse(object payload, out HttpResponseMessage response) - { - if (StopResponseFetcher.TryFetch(payload, out response) && response != null) - { - return true; - } - - return false; - } - } - - public void OnException(Activity activity, object payload) - { - if (activity.IsAllDataRequested) - { - if (!TryFetchException(payload, out Exception exc)) - { - HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException)); - return; - } - - activity.SetTag(SemanticConventions.AttributeErrorType, GetErrorType(exc)); - - if (this.options.RecordException) - { - activity.RecordException(exc); - } - - if (exc is HttpRequestException) - { - activity.SetStatus(ActivityStatusCode.Error); - } - - try - { - this.options.EnrichWithException?.Invoke(activity, exc); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.EnrichmentException(ex); - } - } - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The event source guarantees that top-level properties are preserved")] -#endif - static bool TryFetchException(object payload, out Exception exc) - { - if (!StopExceptionFetcher.TryFetch(payload, out exc) || exc == null) - { - return false; - } - - return true; - } - } - - private static string GetErrorType(Exception exc) - { -#if NET8_0_OR_GREATER - // For net8.0 and above exception type can be found using HttpRequestError. - // https://learn.microsoft.com/dotnet/api/system.net.http.httprequesterror?view=net-8.0 - if (exc is HttpRequestException httpRequestException) - { - return httpRequestException.HttpRequestError switch - { - HttpRequestError.NameResolutionError => "name_resolution_error", - HttpRequestError.ConnectionError => "connection_error", - HttpRequestError.SecureConnectionError => "secure_connection_error", - HttpRequestError.HttpProtocolError => "http_protocol_error", - HttpRequestError.ExtendedConnectNotSupported => "extended_connect_not_supported", - HttpRequestError.VersionNegotiationError => "version_negotiation_error", - HttpRequestError.UserAuthenticationError => "user_authentication_error", - HttpRequestError.ProxyTunnelError => "proxy_tunnel_error", - HttpRequestError.InvalidResponse => "invalid_response", - HttpRequestError.ResponseEnded => "response_ended", - HttpRequestError.ConfigurationLimitExceeded => "configuration_limit_exceeded", - - // Fall back to the exception type name in case of HttpRequestError.Unknown - _ => exc.GetType().FullName, - }; - } -#endif - return exc.GetType().FullName; - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs deleted file mode 100644 index c6a710e2dd0..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerMetricsDiagnosticListener.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Diagnostics; -#if NET6_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif -using System.Diagnostics.Metrics; -#if NETFRAMEWORK -using System.Net.Http; -#endif -using System.Reflection; -using OpenTelemetry.Internal; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.Http.Implementation; - -internal sealed class HttpHandlerMetricsDiagnosticListener : ListenerHandler -{ - internal const string OnStopEvent = "System.Net.Http.HttpRequestOut.Stop"; - - internal static readonly AssemblyName AssemblyName = typeof(HttpClientMetrics).Assembly.GetName(); - internal static readonly string MeterName = AssemblyName.Name; - internal static readonly string MeterVersion = AssemblyName.Version.ToString(); - internal static readonly Meter Meter = new(MeterName, MeterVersion); - private const string OnUnhandledExceptionEvent = "System.Net.Http.Exception"; - private static readonly Histogram HttpClientRequestDuration = Meter.CreateHistogram("http.client.request.duration", "s", "Duration of HTTP client requests."); - - private static readonly PropertyFetcher StopRequestFetcher = new("Request"); - private static readonly PropertyFetcher StopResponseFetcher = new("Response"); - private static readonly PropertyFetcher StopExceptionFetcher = new("Exception"); - private static readonly PropertyFetcher RequestFetcher = new("Request"); -#if NET6_0_OR_GREATER - private static readonly HttpRequestOptionsKey HttpRequestOptionsErrorKey = new(SemanticConventions.AttributeErrorType); -#endif - - public HttpHandlerMetricsDiagnosticListener(string name) - : base(name) - { - } - - public static void OnStopEventWritten(Activity activity, object payload) - { - if (TryFetchRequest(payload, out HttpRequestMessage request)) - { - // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md - TagList tags = default; - - var httpMethod = RequestMethodHelper.GetNormalizedHttpMethod(request.Method.Method); - tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod)); - - tags.Add(new KeyValuePair(SemanticConventions.AttributeServerAddress, request.RequestUri.Host)); - tags.Add(new KeyValuePair(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme)); - - if (!request.RequestUri.IsDefaultPort) - { - tags.Add(new KeyValuePair(SemanticConventions.AttributeServerPort, request.RequestUri.Port)); - } - - if (TryFetchResponse(payload, out HttpResponseMessage response)) - { - tags.Add(new KeyValuePair(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(response.Version))); - tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode))); - - // Set error.type to status code for failed requests - // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes - if (SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)response.StatusCode) == ActivityStatusCode.Error) - { - tags.Add(new KeyValuePair(SemanticConventions.AttributeErrorType, TelemetryHelper.GetStatusCodeString(response.StatusCode))); - } - } - - if (response == null) - { -#if !NET6_0_OR_GREATER - request.Properties.TryGetValue(SemanticConventions.AttributeErrorType, out var errorType); -#else - request.Options.TryGetValue(HttpRequestOptionsErrorKey, out var errorType); -#endif - - // Set error.type to exception type if response was not received. - // https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md#common-attributes - if (errorType != null) - { - tags.Add(new KeyValuePair(SemanticConventions.AttributeErrorType, errorType)); - } - } - - // We are relying here on HttpClient library to set duration before writing the stop event. - // https://github.com/dotnet/runtime/blob/90603686d314147017c8bbe1fa8965776ce607d0/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L178 - // TODO: Follow up with .NET team if we can continue to rely on this behavior. - HttpClientRequestDuration.Record(activity.Duration.TotalSeconds, tags); - } - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The System.Net.Http library guarantees that top-level properties are preserved")] -#endif - static bool TryFetchRequest(object payload, out HttpRequestMessage request) => - StopRequestFetcher.TryFetch(payload, out request) && request != null; - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The System.Net.Http library guarantees that top-level properties are preserved")] -#endif - static bool TryFetchResponse(object payload, out HttpResponseMessage response) => - StopResponseFetcher.TryFetch(payload, out response) && response != null; - } - - public static void OnExceptionEventWritten(Activity activity, object payload) - { - if (!TryFetchException(payload, out Exception exc) || !TryFetchRequest(payload, out HttpRequestMessage request)) - { - HttpInstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerMetricsDiagnosticListener), nameof(OnExceptionEventWritten)); - return; - } - -#if !NET6_0_OR_GREATER - request.Properties.Add(SemanticConventions.AttributeErrorType, exc.GetType().FullName); -#else - request.Options.Set(HttpRequestOptionsErrorKey, exc.GetType().FullName); -#endif - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The System.Net.Http library guarantees that top-level properties are preserved")] -#endif - static bool TryFetchException(object payload, out Exception exc) - { - if (!StopExceptionFetcher.TryFetch(payload, out exc) || exc == null) - { - return false; - } - - return true; - } - - // The AOT-annotation DynamicallyAccessedMembers in System.Net.Http library ensures that top-level properties on the payload object are always preserved. - // see https://github.com/dotnet/runtime/blob/f9246538e3d49b90b0e9128d7b1defef57cd6911/src/libraries/System.Net.Http/src/System/Net/Http/DiagnosticsHandler.cs#L325 -#if NET6_0_OR_GREATER - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "The System.Net.Http library guarantees that top-level properties are preserved")] -#endif - static bool TryFetchRequest(object payload, out HttpRequestMessage request) - { - if (!RequestFetcher.TryFetch(payload, out request) || request == null) - { - return false; - } - - return true; - } - } - - public override void OnEventWritten(string name, object payload) - { - if (name == OnStopEvent) - { - OnStopEventWritten(Activity.Current, payload); - } - else if (name == OnUnhandledExceptionEvent) - { - OnExceptionEventWritten(Activity.Current, payload); - } - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs deleted file mode 100644 index 4f04f9b6671..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpInstrumentationEventSource.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Diagnostics.Tracing; -using Microsoft.Extensions.Configuration; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Instrumentation.Http.Implementation; - -/// -/// EventSource events emitted from the project. -/// -[EventSource(Name = "OpenTelemetry-Instrumentation-Http")] -internal sealed class HttpInstrumentationEventSource : EventSource, IConfigurationExtensionsLogger -{ - public static HttpInstrumentationEventSource Log = new(); - - [NonEvent] - public void FailedProcessResult(Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.FailedProcessResult(ex.ToInvariantString()); - } - } - - [NonEvent] - public void RequestFilterException(Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.RequestFilterException(ex.ToInvariantString()); - } - } - - [NonEvent] - public void ExceptionInitializingInstrumentation(string instrumentationType, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.ExceptionInitializingInstrumentation(instrumentationType, ex.ToInvariantString()); - } - } - - [NonEvent] - public void UnknownErrorProcessingEvent(string handlerName, string eventName, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.UnknownErrorProcessingEvent(handlerName, eventName, ex.ToInvariantString()); - } - } - - [Event(1, Message = "Failed to process result: '{0}'", Level = EventLevel.Error)] - public void FailedProcessResult(string ex) - { - this.WriteEvent(1, ex); - } - - [Event(2, Message = "Error initializing instrumentation type {0}. Exception : {1}", Level = EventLevel.Error)] - public void ExceptionInitializingInstrumentation(string instrumentationType, string ex) - { - this.WriteEvent(2, instrumentationType, ex); - } - - [Event(3, Message = "Payload is NULL in event '{1}' from handler '{0}', span will not be recorded.", Level = EventLevel.Warning)] - public void NullPayload(string handlerName, string eventName) - { - this.WriteEvent(3, handlerName, eventName); - } - - [Event(4, Message = "Filter threw exception. Request will not be collected. Exception {0}.", Level = EventLevel.Error)] - public void RequestFilterException(string exception) - { - this.WriteEvent(4, exception); - } - - [NonEvent] - public void EnrichmentException(Exception ex) - { - if (this.IsEnabled(EventLevel.Error, EventKeywords.All)) - { - this.EnrichmentException(ex.ToInvariantString()); - } - } - - [Event(5, Message = "Enrich threw exception. Exception {0}.", Level = EventLevel.Error)] - public void EnrichmentException(string exception) - { - this.WriteEvent(5, exception); - } - - [Event(6, Message = "Request is filtered out.", Level = EventLevel.Verbose)] - public void RequestIsFilteredOut(string eventName) - { - this.WriteEvent(6, eventName); - } - - [Event(7, Message = "Unknown error processing event '{1}' from handler '{0}', Exception: {2}", Level = EventLevel.Error)] - public void UnknownErrorProcessingEvent(string handlerName, string eventName, string ex) - { - this.WriteEvent(7, handlerName, eventName, ex); - } - - [Event(8, Message = "Configuration key '{0}' has an invalid value: '{1}'", Level = EventLevel.Warning)] - public void InvalidConfigurationValue(string key, string value) - { - this.WriteEvent(8, key, value); - } - - void IConfigurationExtensionsLogger.LogInvalidConfigurationValue(string key, string value) - { - this.InvalidConfigurationValue(key, value); - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs deleted file mode 100644 index 0b5d7184fd0..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpTagHelper.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Instrumentation.Http.Implementation; - -/// -/// A collection of helper methods to be used when building Http activities. -/// -internal static class HttpTagHelper -{ - /// - /// Gets the OpenTelemetry standard uri tag value for a span based on its request . - /// - /// . - /// Indicates whether query parameter should be redacted or not. - /// Span uri value. - public static string GetUriTagValueFromRequestUri(Uri uri, bool disableQueryRedaction) - { - if (string.IsNullOrEmpty(uri.UserInfo) && disableQueryRedaction) - { - return uri.OriginalString; - } - - var query = disableQueryRedaction ? uri.Query : RedactionHelper.GetRedactedQueryString(uri.Query); - - return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.AbsolutePath, query, uri.Fragment); - } - - public static string GetProtocolVersionString(Version httpVersion) => (httpVersion.Major, httpVersion.Minor) switch - { - (1, 0) => "1.0", - (1, 1) => "1.1", - (2, 0) => "2", - (3, 0) => "3", - _ => httpVersion.ToString(), - }; -} diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs deleted file mode 100644 index 1418bc0f5ae..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs +++ /dev/null @@ -1,1196 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if NETFRAMEWORK -using System.Collections; -using System.Diagnostics; -using System.Diagnostics.Metrics; -using System.Net; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Internal; -using OpenTelemetry.Trace; - -namespace OpenTelemetry.Instrumentation.Http.Implementation; - -/// -/// Hooks into the class reflectively and writes diagnostic events as requests are processed. -/// -/// -/// Inspired from the System.Diagnostics.DiagnosticSource.HttpHandlerDiagnosticListener class which has some bugs and feature gaps. -/// See https://github.com/dotnet/runtime/pull/33732 for details. -/// -internal static class HttpWebRequestActivitySource -{ - internal static readonly AssemblyName AssemblyName = typeof(HttpWebRequestActivitySource).Assembly.GetName(); - internal static readonly string ActivitySourceName = AssemblyName.Name + ".HttpWebRequest"; - internal static readonly string ActivityName = ActivitySourceName + ".HttpRequestOut"; - internal static readonly string MeterName = AssemblyName.Name; - - internal static readonly Func> HttpWebRequestHeaderValuesGetter = (request, name) => request.Headers.GetValues(name); - internal static readonly Action HttpWebRequestHeaderValuesSetter = (request, name, value) => request.Headers.Add(name, value); - - private static readonly string Version = AssemblyName.Version.ToString(); - private static readonly ActivitySource WebRequestActivitySource = new(ActivitySourceName, Version); - private static readonly Meter WebRequestMeter = new(MeterName, Version); - private static readonly Histogram HttpClientRequestDuration = WebRequestMeter.CreateHistogram("http.client.request.duration", "s", "Duration of HTTP client requests."); - - private static HttpClientTraceInstrumentationOptions tracingOptions; - - // Fields for reflection - private static FieldInfo connectionGroupListField; - private static Type connectionGroupType; - private static FieldInfo connectionListField; - private static Type connectionType; - private static FieldInfo writeListField; - private static Func writeAResultAccessor; - private static Func readAResultAccessor; - - // LazyAsyncResult & ContextAwareResult - private static Func asyncCallbackAccessor; - private static Action asyncCallbackModifier; - private static Func asyncStateAccessor; - private static Action asyncStateModifier; - private static Func endCalledAccessor; - private static Func resultAccessor; - private static Func isContextAwareResultChecker; - - // HttpWebResponse - private static Func httpWebResponseCtor; - private static Func uriAccessor; - private static Func verbAccessor; - private static Func mediaTypeAccessor; - private static Func usesProxySemanticsAccessor; - private static Func coreResponseDataAccessor; - private static Func isWebSocketResponseAccessor; - private static Func connectionGroupNameAccessor; - - static HttpWebRequestActivitySource() - { - try - { - PrepareReflectionObjects(); - PerformInjection(); - - TracingOptions = new HttpClientTraceInstrumentationOptions(); - } - catch (Exception ex) - { - // If anything went wrong, just no-op. Write an event so at least we can find out. - HttpInstrumentationEventSource.Log.ExceptionInitializingInstrumentation(typeof(HttpWebRequestActivitySource).FullName, ex); - } - } - - internal static HttpClientTraceInstrumentationOptions TracingOptions - { - get => tracingOptions; - set - { - tracingOptions = value; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddRequestTagsAndInstrumentRequest(HttpWebRequest request, Activity activity) - { - RequestMethodHelper.SetActivityDisplayName(activity, request.Method); - - if (activity.IsAllDataRequested) - { - // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md - RequestMethodHelper.SetHttpMethodTag(activity, request.Method); - - activity.SetTag(SemanticConventions.AttributeServerAddress, request.RequestUri.Host); - activity.SetTag(SemanticConventions.AttributeServerPort, request.RequestUri.Port); - - activity.SetTag(SemanticConventions.AttributeUrlFull, HttpTagHelper.GetUriTagValueFromRequestUri(request.RequestUri, tracingOptions.DisableUrlQueryRedaction)); - - try - { - TracingOptions.EnrichWithHttpWebRequest?.Invoke(activity, request); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.EnrichmentException(ex); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddResponseTags(HttpWebResponse response, Activity activity) - { - Debug.Assert(activity != null, "Activity must not be null"); - - if (activity.IsAllDataRequested) - { - activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(response.ProtocolVersion)); - activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode)); - - try - { - TracingOptions.EnrichWithHttpWebResponse?.Invoke(activity, response); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.EnrichmentException(ex); - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetErrorType(Exception exception) - { - if (exception is WebException wexc) - { - // TODO: consider other Status values from - // https://learn.microsoft.com/dotnet/api/system.net.webexceptionstatus?view=netframework-4.6.2 - return wexc.Status switch - { - 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, - }; - } - - return exception.GetType().FullName; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void AddExceptionEvent(Exception exception, Activity activity) - { - Debug.Assert(activity != null, "Activity must not be null"); - - if (!activity.IsAllDataRequested) - { - return; - } - - if (TracingOptions.RecordException) - { - activity.RecordException(exception); - } - - try - { - TracingOptions.EnrichWithException?.Invoke(activity, exception); - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.EnrichmentException(ex); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void InstrumentRequest(HttpWebRequest request, ActivityContext activityContext) - => Propagators.DefaultTextMapPropagator.Inject(new PropagationContext(activityContext, Baggage.Current), request, HttpWebRequestHeaderValuesSetter); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsRequestInstrumented(HttpWebRequest request) - => Propagators.DefaultTextMapPropagator.Extract(default, request, HttpWebRequestHeaderValuesGetter) != default; - - private static void ProcessRequest(HttpWebRequest request) - { - // There are subscribers to the ActivitySource and no user-provided filter is - // filtering this request. - var enableTracing = WebRequestActivitySource.HasListeners() - && TracingOptions.EventFilterHttpWebRequest(request); - - if (!enableTracing && !HttpClientRequestDuration.Enabled) - { - // Tracing and metrics are not enabled, so we can skip generating signals - // Propagation must still be done in such cases, to allow - // downstream services to continue from parent context, if any. - // Eg: Parent could be the Asp.Net activity. - InstrumentRequest(request, Activity.Current?.Context ?? default); - return; - } - - if (IsRequestInstrumented(request)) - { - // This request was instrumented by previous - // ProcessRequest, such is the case with redirect responses where the same request is sent again. - return; - } - - Activity activity = enableTracing - ? WebRequestActivitySource.StartActivity(ActivityName, ActivityKind.Client) - : null; - - var activityContext = Activity.Current?.Context ?? default; - - // Propagation must still be done in all cases, to allow - // downstream services to continue from parent context, if any. - // Eg: Parent could be the Asp.Net activity. - InstrumentRequest(request, activityContext); - - IAsyncResult asyncContext = writeAResultAccessor(request); - if (asyncContext != null) - { - // Flow here is for [Begin]GetRequestStream[Async]. - - AsyncCallbackWrapper callback = new AsyncCallbackWrapper(request, activity, asyncCallbackAccessor(asyncContext), Stopwatch.GetTimestamp()); - asyncCallbackModifier(asyncContext, callback.AsyncCallback); - } - else - { - // Flow here is for [Begin]GetResponse[Async] without a prior call to [Begin]GetRequestStream[Async]. - - asyncContext = readAResultAccessor(request); - AsyncCallbackWrapper callback = new AsyncCallbackWrapper(request, activity, asyncCallbackAccessor(asyncContext), Stopwatch.GetTimestamp()); - asyncCallbackModifier(asyncContext, callback.AsyncCallback); - } - - if (activity != null) - { - AddRequestTagsAndInstrumentRequest(request, activity); - } - } - - private static void HookOrProcessResult(HttpWebRequest request) - { - IAsyncResult writeAsyncContext = writeAResultAccessor(request); - if (writeAsyncContext == null || asyncCallbackAccessor(writeAsyncContext)?.Target is not AsyncCallbackWrapper writeAsyncContextCallback) - { - // If we already hooked into the read result during ProcessRequest or we hooked up after the fact already we don't need to do anything here. - return; - } - - // If we got here it means the user called [Begin]GetRequestStream[Async] and we have to hook the read result after the fact. - - IAsyncResult readAsyncContext = readAResultAccessor(request); - if (readAsyncContext == null) - { - // We're still trying to establish the connection (no read has started). - return; - } - - // Clear our saved callback so we know not to process again. - asyncCallbackModifier(writeAsyncContext, null); - - if (endCalledAccessor.Invoke(readAsyncContext) || readAsyncContext.CompletedSynchronously) - { - // We need to process the result directly because the read callback has already fired. Force a copy because response has likely already been disposed. - ProcessResult(readAsyncContext, null, writeAsyncContextCallback.Activity, resultAccessor(readAsyncContext), true, request, writeAsyncContextCallback.StartTimestamp); - return; - } - - // Hook into the result callback if it hasn't already fired. - AsyncCallbackWrapper callback = new AsyncCallbackWrapper(writeAsyncContextCallback.Request, writeAsyncContextCallback.Activity, asyncCallbackAccessor(readAsyncContext), Stopwatch.GetTimestamp()); - asyncCallbackModifier(readAsyncContext, callback.AsyncCallback); - } - - private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncCallback, Activity activity, object result, bool forceResponseCopy, HttpWebRequest request, long startTimestamp) - { - HttpStatusCode? httpStatusCode = null; - string errorType = null; - Version protocolVersion = null; - ActivityStatusCode activityStatus = ActivityStatusCode.Unset; - - // Activity may be null if we are not tracing in these cases: - // 1. No listeners - // 2. Request was filtered out - // 3. Request was not sampled - // We could be executing on a different thread now so restore the activity if needed. - if (activity != null && Activity.Current != activity) - { - Activity.Current = activity; - } - - try - { - if (result is Exception ex) - { - errorType = GetErrorType(ex); - if (ex is WebException wexc && wexc.Response is HttpWebResponse response) - { - httpStatusCode = response.StatusCode; - protocolVersion = response.ProtocolVersion; - activityStatus = SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)response.StatusCode); - if (activityStatus == ActivityStatusCode.Error) - { - // override the errorType to statusCode for failures. - errorType = TelemetryHelper.GetStatusCodeString(response.StatusCode); - } - - if (activity != null) - { - AddResponseTags(response, activity); - AddExceptionEvent(ex, activity); - } - } - else - { - activityStatus = ActivityStatusCode.Error; - if (activity != null) - { - AddExceptionEvent(ex, activity); - } - } - } - else - { - HttpWebResponse response = (HttpWebResponse)result; - - if (forceResponseCopy || (asyncCallback == null && isContextAwareResultChecker(asyncResult))) - { - // For async calls (where asyncResult is ContextAwareResult)... - // If no callback was set assume the user is manually calling BeginGetResponse & EndGetResponse - // in which case they could dispose the HttpWebResponse before our listeners have a chance to work with it. - // Disposed HttpWebResponse throws when accessing properties, so let's make a copy of the data to ensure that doesn't happen. - - HttpWebResponse responseCopy = httpWebResponseCtor( - new object[] - { - uriAccessor(response), verbAccessor(response), coreResponseDataAccessor(response), mediaTypeAccessor(response), - usesProxySemanticsAccessor(response), DecompressionMethods.None, - isWebSocketResponseAccessor(response), connectionGroupNameAccessor(response), - }); - - if (activity != null) - { - AddResponseTags(responseCopy, activity); - } - - httpStatusCode = responseCopy.StatusCode; - protocolVersion = responseCopy.ProtocolVersion; - } - else - { - if (activity != null) - { - AddResponseTags(response, activity); - } - - httpStatusCode = response.StatusCode; - protocolVersion = response.ProtocolVersion; - } - - activityStatus = SpanHelper.ResolveSpanStatusForHttpStatusCode(ActivityKind.Client, (int)httpStatusCode.Value); - - if (activityStatus == ActivityStatusCode.Error) - { - // set the errorType to statusCode for failures. - errorType = TelemetryHelper.GetStatusCodeString(httpStatusCode.Value); - } - } - } - catch (Exception ex) - { - HttpInstrumentationEventSource.Log.FailedProcessResult(ex); - } - - if (activity != null && activity.IsAllDataRequested) - { - activity.SetStatus(activityStatus); - if (errorType != null) - { - activity.SetTag(SemanticConventions.AttributeErrorType, errorType); - } - } - - activity?.Stop(); - - if (HttpClientRequestDuration.Enabled) - { - double durationS; - if (activity != null) - { - durationS = activity.Duration.TotalSeconds; - } - else - { - var endTimestamp = Stopwatch.GetTimestamp(); - durationS = (endTimestamp - startTimestamp) / (double)Stopwatch.Frequency; - } - - TagList tags = default; - - var httpMethod = RequestMethodHelper.GetNormalizedHttpMethod(request.Method); - tags.Add(new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, httpMethod)); - - tags.Add(SemanticConventions.AttributeServerAddress, request.RequestUri.Host); - tags.Add(SemanticConventions.AttributeUrlScheme, request.RequestUri.Scheme); - if (protocolVersion != null) - { - tags.Add(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetProtocolVersionString(protocolVersion)); - } - - if (!request.RequestUri.IsDefaultPort) - { - tags.Add(SemanticConventions.AttributeServerPort, request.RequestUri.Port); - } - - if (httpStatusCode.HasValue) - { - tags.Add(SemanticConventions.AttributeHttpResponseStatusCode, (int)httpStatusCode.Value); - } - - if (errorType != null) - { - tags.Add(SemanticConventions.AttributeErrorType, errorType); - } - - HttpClientRequestDuration.Record(durationS, tags); - } - } - - private static void PrepareReflectionObjects() - { - // At any point, if the operation failed, it should just throw. The caller should catch all exceptions and swallow. - - Type servicePointType = typeof(ServicePoint); - Assembly systemNetHttpAssembly = servicePointType.Assembly; - connectionGroupListField = servicePointType.GetField("m_ConnectionGroupList", BindingFlags.Instance | BindingFlags.NonPublic); - connectionGroupType = systemNetHttpAssembly?.GetType("System.Net.ConnectionGroup"); - connectionListField = connectionGroupType?.GetField("m_ConnectionList", BindingFlags.Instance | BindingFlags.NonPublic); - connectionType = systemNetHttpAssembly?.GetType("System.Net.Connection"); - writeListField = connectionType?.GetField("m_WriteList", BindingFlags.Instance | BindingFlags.NonPublic); - - writeAResultAccessor = CreateFieldGetter(typeof(HttpWebRequest), "_WriteAResult", BindingFlags.NonPublic | BindingFlags.Instance); - readAResultAccessor = CreateFieldGetter(typeof(HttpWebRequest), "_ReadAResult", BindingFlags.NonPublic | BindingFlags.Instance); - - // Double checking to make sure we have all the pieces initialized - if (connectionGroupListField == null || - connectionGroupType == null || - connectionListField == null || - connectionType == null || - writeListField == null || - writeAResultAccessor == null || - readAResultAccessor == null || - !PrepareAsyncResultReflectionObjects(systemNetHttpAssembly) || - !PrepareHttpWebResponseReflectionObjects(systemNetHttpAssembly)) - { - // If anything went wrong here, just return false. There is nothing we can do. - throw new InvalidOperationException("Unable to initialize all required reflection objects"); - } - } - - private static bool PrepareAsyncResultReflectionObjects(Assembly systemNetHttpAssembly) - { - Type lazyAsyncResultType = systemNetHttpAssembly?.GetType("System.Net.LazyAsyncResult"); - if (lazyAsyncResultType != null) - { - asyncCallbackAccessor = CreateFieldGetter(lazyAsyncResultType, "m_AsyncCallback", BindingFlags.NonPublic | BindingFlags.Instance); - asyncCallbackModifier = CreateFieldSetter(lazyAsyncResultType, "m_AsyncCallback", BindingFlags.NonPublic | BindingFlags.Instance); - asyncStateAccessor = CreateFieldGetter(lazyAsyncResultType, "m_AsyncState", BindingFlags.NonPublic | BindingFlags.Instance); - asyncStateModifier = CreateFieldSetter(lazyAsyncResultType, "m_AsyncState", BindingFlags.NonPublic | BindingFlags.Instance); - endCalledAccessor = CreateFieldGetter(lazyAsyncResultType, "m_EndCalled", BindingFlags.NonPublic | BindingFlags.Instance); - resultAccessor = CreateFieldGetter(lazyAsyncResultType, "m_Result", BindingFlags.NonPublic | BindingFlags.Instance); - } - - Type contextAwareResultType = systemNetHttpAssembly?.GetType("System.Net.ContextAwareResult"); - if (contextAwareResultType != null) - { - isContextAwareResultChecker = CreateTypeChecker(contextAwareResultType); - } - - return asyncCallbackAccessor != null - && asyncCallbackModifier != null - && asyncStateAccessor != null - && asyncStateModifier != null - && endCalledAccessor != null - && resultAccessor != null - && isContextAwareResultChecker != null; - } - - private static bool PrepareHttpWebResponseReflectionObjects(Assembly systemNetHttpAssembly) - { - Type knownHttpVerbType = systemNetHttpAssembly?.GetType("System.Net.KnownHttpVerb"); - Type coreResponseData = systemNetHttpAssembly?.GetType("System.Net.CoreResponseData"); - - if (knownHttpVerbType != null && coreResponseData != null) - { - var constructorParameterTypes = new Type[] - { - typeof(Uri), knownHttpVerbType, coreResponseData, typeof(string), - typeof(bool), typeof(DecompressionMethods), - typeof(bool), typeof(string), - }; - - ConstructorInfo ctor = typeof(HttpWebResponse).GetConstructor( - BindingFlags.NonPublic | BindingFlags.Instance, - null, - constructorParameterTypes, - null); - - if (ctor != null) - { - httpWebResponseCtor = CreateTypeInstance(ctor); - } - } - - uriAccessor = CreateFieldGetter("m_Uri", BindingFlags.NonPublic | BindingFlags.Instance); - verbAccessor = CreateFieldGetter("m_Verb", BindingFlags.NonPublic | BindingFlags.Instance); - mediaTypeAccessor = CreateFieldGetter("m_MediaType", BindingFlags.NonPublic | BindingFlags.Instance); - usesProxySemanticsAccessor = CreateFieldGetter("m_UsesProxySemantics", BindingFlags.NonPublic | BindingFlags.Instance); - coreResponseDataAccessor = CreateFieldGetter("m_CoreResponseData", BindingFlags.NonPublic | BindingFlags.Instance); - isWebSocketResponseAccessor = CreateFieldGetter("m_IsWebSocketResponse", BindingFlags.NonPublic | BindingFlags.Instance); - connectionGroupNameAccessor = CreateFieldGetter("m_ConnectionGroupName", BindingFlags.NonPublic | BindingFlags.Instance); - - return httpWebResponseCtor != null - && uriAccessor != null - && verbAccessor != null - && mediaTypeAccessor != null - && usesProxySemanticsAccessor != null - && coreResponseDataAccessor != null - && isWebSocketResponseAccessor != null - && connectionGroupNameAccessor != null; - } - - private static void PerformInjection() - { - FieldInfo servicePointTableField = typeof(ServicePointManager).GetField("s_ServicePointTable", BindingFlags.Static | BindingFlags.NonPublic) - ?? throw new InvalidOperationException("Unable to access the ServicePointTable field"); - - Hashtable originalTable = servicePointTableField.GetValue(null) as Hashtable; - ServicePointHashtable newTable = new ServicePointHashtable(originalTable ?? new Hashtable()); - - foreach (DictionaryEntry existingServicePoint in originalTable) - { - HookServicePoint(existingServicePoint.Value); - } - - servicePointTableField.SetValue(null, newTable); - } - - private static void HookServicePoint(object value) - { - if (value is WeakReference weakRef - && weakRef.IsAlive - && weakRef.Target is ServicePoint servicePoint) - { - // Replace the ConnectionGroup hashtable inside this ServicePoint object, - // which allows us to intercept each new ConnectionGroup object added under - // this ServicePoint. - Hashtable originalTable = connectionGroupListField.GetValue(servicePoint) as Hashtable; - ConnectionGroupHashtable newTable = new ConnectionGroupHashtable(originalTable ?? new Hashtable()); - - foreach (DictionaryEntry existingConnectionGroup in originalTable) - { - HookConnectionGroup(existingConnectionGroup.Value); - } - - connectionGroupListField.SetValue(servicePoint, newTable); - } - } - - private static void HookConnectionGroup(object value) - { - if (connectionGroupType.IsInstanceOfType(value)) - { - // Replace the Connection arraylist inside this ConnectionGroup object, - // which allows us to intercept each new Connection object added under - // this ConnectionGroup. - ArrayList originalArrayList = connectionListField.GetValue(value) as ArrayList; - ConnectionArrayList newArrayList = new ConnectionArrayList(originalArrayList ?? new ArrayList()); - - foreach (object connection in originalArrayList) - { - HookConnection(connection); - } - - connectionListField.SetValue(value, newArrayList); - } - } - - private static void HookConnection(object value) - { - if (connectionType.IsInstanceOfType(value)) - { - // Replace the HttpWebRequest arraylist inside this Connection object, - // which allows us to intercept each new HttpWebRequest object added under - // this Connection. - ArrayList originalArrayList = writeListField.GetValue(value) as ArrayList; - HttpWebRequestArrayList newArrayList = new HttpWebRequestArrayList(originalArrayList ?? new ArrayList()); - - writeListField.SetValue(value, newArrayList); - } - } - - private static Func CreateFieldGetter(string fieldName, BindingFlags flags) - where TClass : class - { - FieldInfo field = typeof(TClass).GetField(fieldName, flags); - if (field != null) - { - string methodName = field.ReflectedType.FullName + ".get_" + field.Name; - DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(TField), new[] { typeof(TClass) }, true); - ILGenerator generator = getterMethod.GetILGenerator(); - generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Ldfld, field); - generator.Emit(OpCodes.Ret); - return (Func)getterMethod.CreateDelegate(typeof(Func)); - } - - return null; - } - - /// - /// Creates getter for a field defined in private or internal type - /// repesented with classType variable. - /// - private static Func CreateFieldGetter(Type classType, string fieldName, BindingFlags flags) - { - FieldInfo field = classType.GetField(fieldName, flags); - if (field != null) - { - string methodName = classType.FullName + ".get_" + field.Name; - DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(TField), new[] { typeof(object) }, true); - ILGenerator generator = getterMethod.GetILGenerator(); - generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Castclass, classType); - generator.Emit(OpCodes.Ldfld, field); - generator.Emit(OpCodes.Ret); - - return (Func)getterMethod.CreateDelegate(typeof(Func)); - } - - return null; - } - - /// - /// Creates setter for a field defined in private or internal type - /// repesented with classType variable. - /// - private static Action CreateFieldSetter(Type classType, string fieldName, BindingFlags flags) - { - FieldInfo field = classType.GetField(fieldName, flags); - if (field != null) - { - string methodName = classType.FullName + ".set_" + field.Name; - DynamicMethod setterMethod = new DynamicMethod(methodName, null, new[] { typeof(object), typeof(TField) }, true); - ILGenerator generator = setterMethod.GetILGenerator(); - generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Castclass, classType); - generator.Emit(OpCodes.Ldarg_1); - generator.Emit(OpCodes.Stfld, field); - generator.Emit(OpCodes.Ret); - - return (Action)setterMethod.CreateDelegate(typeof(Action)); - } - - return null; - } - - /// - /// Creates an "is" method for the private or internal type. - /// - private static Func CreateTypeChecker(Type classType) - { - string methodName = classType.FullName + ".typeCheck"; - DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(bool), new[] { typeof(object) }, true); - ILGenerator generator = setterMethod.GetILGenerator(); - generator.Emit(OpCodes.Ldarg_0); - generator.Emit(OpCodes.Isinst, classType); - generator.Emit(OpCodes.Ldnull); - generator.Emit(OpCodes.Cgt_Un); - generator.Emit(OpCodes.Ret); - - return (Func)setterMethod.CreateDelegate(typeof(Func)); - } - - /// - /// Creates an instance of T using a private or internal ctor. - /// - private static Func CreateTypeInstance(ConstructorInfo constructorInfo) - { - Type classType = typeof(T); - string methodName = classType.FullName + ".ctor"; - DynamicMethod setterMethod = new DynamicMethod(methodName, classType, new Type[] { typeof(object[]) }, true); - ILGenerator generator = setterMethod.GetILGenerator(); - - ParameterInfo[] ctorParams = constructorInfo.GetParameters(); - for (int i = 0; i < ctorParams.Length; i++) - { - generator.Emit(OpCodes.Ldarg_0); - switch (i) - { - case 0: generator.Emit(OpCodes.Ldc_I4_0); break; - case 1: generator.Emit(OpCodes.Ldc_I4_1); break; - case 2: generator.Emit(OpCodes.Ldc_I4_2); break; - case 3: generator.Emit(OpCodes.Ldc_I4_3); break; - case 4: generator.Emit(OpCodes.Ldc_I4_4); break; - case 5: generator.Emit(OpCodes.Ldc_I4_5); break; - case 6: generator.Emit(OpCodes.Ldc_I4_6); break; - case 7: generator.Emit(OpCodes.Ldc_I4_7); break; - case 8: generator.Emit(OpCodes.Ldc_I4_8); break; - default: generator.Emit(OpCodes.Ldc_I4, i); break; - } - - generator.Emit(OpCodes.Ldelem_Ref); - Type paramType = ctorParams[i].ParameterType; - generator.Emit(paramType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, paramType); - } - - generator.Emit(OpCodes.Newobj, constructorInfo); - generator.Emit(OpCodes.Ret); - - return (Func)setterMethod.CreateDelegate(typeof(Func)); - } - - private class HashtableWrapper : Hashtable, IEnumerable - { - private readonly Hashtable table; - - internal HashtableWrapper(Hashtable table) - : base() - { - this.table = table; - } - - public override int Count => this.table.Count; - - public override bool IsReadOnly => this.table.IsReadOnly; - - public override bool IsFixedSize => this.table.IsFixedSize; - - public override bool IsSynchronized => this.table.IsSynchronized; - - public override object SyncRoot => this.table.SyncRoot; - - public override ICollection Keys => this.table.Keys; - - public override ICollection Values => this.table.Values; - - public override object this[object key] - { - get => this.table[key]; - set => this.table[key] = value; - } - - public override void Add(object key, object value) - { - this.table.Add(key, value); - } - - public override void Clear() - { - this.table.Clear(); - } - - public override bool Contains(object key) - { - return this.table.Contains(key); - } - - public override bool ContainsKey(object key) - { - return this.table.ContainsKey(key); - } - - public override bool ContainsValue(object key) - { - return this.table.ContainsValue(key); - } - - public override void CopyTo(Array array, int arrayIndex) - { - this.table.CopyTo(array, arrayIndex); - } - - public override object Clone() - { - return new HashtableWrapper((Hashtable)this.table.Clone()); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.table.GetEnumerator(); - } - - public override IDictionaryEnumerator GetEnumerator() - { - return this.table.GetEnumerator(); - } - - public override void Remove(object key) - { - this.table.Remove(key); - } - } - - /// - /// Helper class used for ServicePointManager.s_ServicePointTable. The goal here is to - /// intercept each new ServicePoint object being added to ServicePointManager.s_ServicePointTable - /// and replace its ConnectionGroupList hashtable field. - /// - private sealed class ServicePointHashtable : HashtableWrapper - { - public ServicePointHashtable(Hashtable table) - : base(table) - { - } - - public override object this[object key] - { - get => base[key]; - set - { - HookServicePoint(value); - base[key] = value; - } - } - } - - /// - /// Helper class used for ServicePoint.m_ConnectionGroupList. The goal here is to - /// intercept each new ConnectionGroup object being added to ServicePoint.m_ConnectionGroupList - /// and replace its m_ConnectionList arraylist field. - /// - private sealed class ConnectionGroupHashtable : HashtableWrapper - { - public ConnectionGroupHashtable(Hashtable table) - : base(table) - { - } - - public override object this[object key] - { - get => base[key]; - set - { - HookConnectionGroup(value); - base[key] = value; - } - } - } - - /// - /// Helper class used to wrap the array list object. This class itself doesn't actually - /// have the array elements, but rather access another array list that's given at - /// construction time. - /// - private class ArrayListWrapper : ArrayList - { - private ArrayList list; - - internal ArrayListWrapper(ArrayList list) - : base() - { - this.list = list; - } - - public override int Capacity - { - get => this.list.Capacity; - set => this.list.Capacity = value; - } - - public override int Count => this.list.Count; - - public override bool IsReadOnly => this.list.IsReadOnly; - - public override bool IsFixedSize => this.list.IsFixedSize; - - public override bool IsSynchronized => this.list.IsSynchronized; - - public override object SyncRoot => this.list.SyncRoot; - - public override object this[int index] - { - get => this.list[index]; - set => this.list[index] = value; - } - - public override int Add(object value) - { - return this.list.Add(value); - } - - public override void AddRange(ICollection c) - { - this.list.AddRange(c); - } - - public override int BinarySearch(object value) - { - return this.list.BinarySearch(value); - } - - public override int BinarySearch(object value, IComparer comparer) - { - return this.list.BinarySearch(value, comparer); - } - - public override int BinarySearch(int index, int count, object value, IComparer comparer) - { - return this.list.BinarySearch(index, count, value, comparer); - } - - public override void Clear() - { - this.list.Clear(); - } - - public override object Clone() - { - return new ArrayListWrapper((ArrayList)this.list.Clone()); - } - - public override bool Contains(object item) - { - return this.list.Contains(item); - } - - public override void CopyTo(Array array) - { - this.list.CopyTo(array); - } - - public override void CopyTo(Array array, int index) - { - this.list.CopyTo(array, index); - } - - public override void CopyTo(int index, Array array, int arrayIndex, int count) - { - this.list.CopyTo(index, array, arrayIndex, count); - } - - public override IEnumerator GetEnumerator() - { - return this.list.GetEnumerator(); - } - - public override IEnumerator GetEnumerator(int index, int count) - { - return this.list.GetEnumerator(index, count); - } - - public override int IndexOf(object value) - { - return this.list.IndexOf(value); - } - - public override int IndexOf(object value, int startIndex) - { - return this.list.IndexOf(value, startIndex); - } - - public override int IndexOf(object value, int startIndex, int count) - { - return this.list.IndexOf(value, startIndex, count); - } - - public override void Insert(int index, object value) - { - this.list.Insert(index, value); - } - - public override void InsertRange(int index, ICollection c) - { - this.list.InsertRange(index, c); - } - - public override int LastIndexOf(object value) - { - return this.list.LastIndexOf(value); - } - - public override int LastIndexOf(object value, int startIndex) - { - return this.list.LastIndexOf(value, startIndex); - } - - public override int LastIndexOf(object value, int startIndex, int count) - { - return this.list.LastIndexOf(value, startIndex, count); - } - - public override void Remove(object value) - { - this.list.Remove(value); - } - - public override void RemoveAt(int index) - { - this.list.RemoveAt(index); - } - - public override void RemoveRange(int index, int count) - { - this.list.RemoveRange(index, count); - } - - public override void Reverse(int index, int count) - { - this.list.Reverse(index, count); - } - - public override void SetRange(int index, ICollection c) - { - this.list.SetRange(index, c); - } - - public override ArrayList GetRange(int index, int count) - { - return this.list.GetRange(index, count); - } - - public override void Sort() - { - this.list.Sort(); - } - - public override void Sort(IComparer comparer) - { - this.list.Sort(comparer); - } - - public override void Sort(int index, int count, IComparer comparer) - { - this.list.Sort(index, count, comparer); - } - - public override object[] ToArray() - { - return this.list.ToArray(); - } - - public override Array ToArray(Type type) - { - return this.list.ToArray(type); - } - - public override void TrimToSize() - { - this.list.TrimToSize(); - } - - public ArrayList Swap() - { - ArrayList old = this.list; - this.list = new ArrayList(old.Capacity); - return old; - } - } - - /// - /// Helper class used for ConnectionGroup.m_ConnectionList. The goal here is to - /// intercept each new Connection object being added to ConnectionGroup.m_ConnectionList - /// and replace its m_WriteList arraylist field. - /// - private sealed class ConnectionArrayList : ArrayListWrapper - { - public ConnectionArrayList(ArrayList list) - : base(list) - { - } - - public override int Add(object value) - { - HookConnection(value); - return base.Add(value); - } - } - - /// - /// Helper class used for Connection.m_WriteList. The goal here is to - /// intercept all new HttpWebRequest objects being added to Connection.m_WriteList - /// and notify the listener about the HttpWebRequest that's about to send a request. - /// It also intercepts all HttpWebRequest objects that are about to get removed from - /// Connection.m_WriteList as they have completed the request. - /// - private sealed class HttpWebRequestArrayList : ArrayListWrapper - { - public HttpWebRequestArrayList(ArrayList list) - : base(list) - { - } - - public override int Add(object value) - { - // Add before firing events so if some user code cancels/aborts the request it will be found in the outstanding list. - int index = base.Add(value); - - if (value is HttpWebRequest request) - { - ProcessRequest(request); - } - - return index; - } - - public override void RemoveAt(int index) - { - object request = this[index]; - - base.RemoveAt(index); - - if (request is HttpWebRequest webRequest) - { - HookOrProcessResult(webRequest); - } - } - - public override void Clear() - { - ArrayList oldList = this.Swap(); - for (int i = 0; i < oldList.Count; i++) - { - if (oldList[i] is HttpWebRequest request) - { - HookOrProcessResult(request); - } - } - } - } - - /// - /// A closure object so our state is available when our callback executes. - /// - private sealed class AsyncCallbackWrapper - { - public AsyncCallbackWrapper(HttpWebRequest request, Activity activity, AsyncCallback originalCallback, long startTimestamp) - { - this.Request = request; - this.Activity = activity; - this.OriginalCallback = originalCallback; - this.StartTimestamp = startTimestamp; - } - - public HttpWebRequest Request { get; } - - public Activity Activity { get; } - - public AsyncCallback OriginalCallback { get; } - - public long StartTimestamp { get; } - - public void AsyncCallback(IAsyncResult asyncResult) - { - object result = resultAccessor(asyncResult); - if (result is Exception || result is HttpWebResponse) - { - ProcessResult( - asyncResult, - this.OriginalCallback, - this.Activity, - result, - forceResponseCopy: false, - this.Request, - this.StartTimestamp); - } - - this.OriginalCallback?.Invoke(asyncResult); - } - } -} -#endif diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/TelemetryHelper.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/TelemetryHelper.cs deleted file mode 100644 index 353a49b0e01..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/TelemetryHelper.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System.Net; - -namespace OpenTelemetry.Instrumentation.Http.Implementation; - -internal static class TelemetryHelper -{ - public static readonly (object, string)[] BoxedStatusCodes; - - static TelemetryHelper() - { - BoxedStatusCodes = new (object, string)[500]; - for (int i = 0, c = 100; i < BoxedStatusCodes.Length; i++, c++) - { - BoxedStatusCodes[i] = (c, c.ToString()); - } - } - - public static object GetBoxedStatusCode(HttpStatusCode statusCode) - { - int intStatusCode = (int)statusCode; - if (intStatusCode >= 100 && intStatusCode < 600) - { - return BoxedStatusCodes[intStatusCode - 100].Item1; - } - - return statusCode; - } - - public static string GetStatusCodeString(HttpStatusCode statusCode) - { - int intStatusCode = (int)statusCode; - if (intStatusCode >= 100 && intStatusCode < 600) - { - return BoxedStatusCodes[intStatusCode - 100].Item2; - } - - return statusCode.ToString(); - } -} diff --git a/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj b/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj deleted file mode 100644 index 686e415b942..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/OpenTelemetry.Instrumentation.Http.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - $(TargetFrameworksForLibraries) - Http instrumentation for OpenTelemetry .NET - $(PackageTags);distributed-tracing - Instrumentation.Http- - true - 1.8.1 - - - disable - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/OpenTelemetry.Instrumentation.Http/README.md b/src/OpenTelemetry.Instrumentation.Http/README.md deleted file mode 100644 index b3fec4ebe45..00000000000 --- a/src/OpenTelemetry.Instrumentation.Http/README.md +++ /dev/null @@ -1,362 +0,0 @@ -# HttpClient and HttpWebRequest instrumentation for OpenTelemetry - -[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.Http.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) -[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.Http.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) - -This is an [Instrumentation -Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library), -which instruments -[System.Net.Http.HttpClient](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient) -and -[System.Net.HttpWebRequest](https://docs.microsoft.com/dotnet/api/system.net.httpwebrequest) -and collects metrics and traces about outgoing HTTP requests. - -This component is based on the -[v1.23](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http) -of http semantic conventions. For details on the default set of attributes that -are added, checkout [Traces](#traces) and [Metrics](#metrics) sections below. - -## Steps to enable OpenTelemetry.Instrumentation.Http - -### Step 1: Install Package - -Add a reference to the -[`OpenTelemetry.Instrumentation.Http`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.Http) -package. Also, add any other instrumentations & exporters you will need. - -```shell -dotnet add package OpenTelemetry.Instrumentation.Http -``` - -### Step 2: Enable HTTP Instrumentation at application startup - -HTTP instrumentation must be enabled at application startup. - -#### Traces - -The following example demonstrates adding `HttpClient` instrumentation with the -extension method `.AddHttpClientInstrumentation()` on `TracerProviderBuilder` to -a console application. This example also sets up the OpenTelemetry Console -Exporter, which requires adding the package -[`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md) -to the application. - -```csharp -using OpenTelemetry; -using OpenTelemetry.Trace; - -public class Program -{ - public static void Main(string[] args) - { - using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddHttpClientInstrumentation() - .AddConsoleExporter() - .Build(); - } -} -``` - -Following list of attributes are added by default on activity. See -[http-spans](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http/http-spans.md) -for more details about each individual attribute: - -* `error.type` -* `http.request.method` -* `http.request.method_original` -* `http.response.status_code` -* `network.protocol.version` -* `server.address` -* `server.port` -* `url.full` - By default, the values in the query component of the url are - replaced with the text `Redacted`. For example, `?key1=value1&key2=value2` - becomes `?key1=Redacted&key2=Redacted`. You can disable this redaction by - setting the environment variable - `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` to `true`. - -[Enrich Api](#enrich-httpclient-api) can be used if any additional attributes are -required on activity. - -#### Metrics - -The following example demonstrates adding `HttpClient` instrumentation with the -extension method `.AddHttpClientInstrumentation()` on `MeterProviderBuilder` to -a console application. This example also sets up the OpenTelemetry Console -Exporter, which requires adding the package -[`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md) -to the application. - -```csharp -using OpenTelemetry; -using OpenTelemetry.Metrics; - -public class Program -{ - public static void Main(string[] args) - { - using var meterProvider = Sdk.CreateMeterProviderBuilder() - .AddHttpClientInstrumentation() - .AddConsoleExporter() - .Build(); - } -} -``` - -Refer to this [example](../../examples/AspNetCore/Program.cs) to see how to -enable this instrumentation in an ASP.NET core application. - -Refer to this -[example](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.AspNet/README.md) -to see how to enable this instrumentation in an ASP.NET application. - -Following list of attributes are added by default on -`http.client.request.duration` metric. See -[http-metrics](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http/http-metrics.md) -for more details about each individual attribute. `.NET8.0` and above supports -additional metrics, see [list of metrics produced](#list-of-metrics-produced) for -more details. - -* `error.type` -* `http.request.method` -* `http.response.status_code` -* `network.protocol.version` -* `server.address` -* `server.port` -* `url.scheme` - -#### List of metrics produced - -When the application targets `NETFRAMEWORK`, `.NET6.0` or `.NET7.0`, the -instrumentation emits the following metric: - -| Name | Details | -|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `http.client.request.duration` | [Specification](https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md#metric-httpclientrequestduration) | - -Starting from `.NET8.0`, metrics instrumentation is natively implemented, and -the HttpClient library has incorporated support for [built-in -metrics](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-system-net) -following the OpenTelemetry semantic conventions. The library includes additional -metrics beyond those defined in the -[specification](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-metrics.md), -covering additional scenarios for HttpClient users. When the application targets -`.NET8.0` and newer versions, the instrumentation library automatically enables -all `built-in` metrics by default. - -Note that the `AddHttpClientInstrumentation()` extension simplifies the process -of enabling all built-in metrics via a single line of code. Alternatively, for -more granular control over emitted metrics, you can utilize the `AddMeter()` -extension on `MeterProviderBuilder` for meters listed in -[built-in-metrics-system-net](https://learn.microsoft.com/dotnet/core/diagnostics/built-in-metrics-system-net). -Using `AddMeter()` for metrics activation eliminates the need to take dependency -on the instrumentation library package and calling -`AddHttpClientInstrumentation()`. - -If you utilize `AddHttpClientInstrumentation()` and wish to exclude unnecessary -metrics, you can utilize -[Views](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/metrics/customizing-the-sdk#drop-an-instrument) -to achieve this. - -> [!NOTE] -> There is no difference in features or emitted metrics when enabling metrics -using `AddMeter()` or `AddHttpClientInstrumentation()` on `.NET8.0` and newer -versions. - -> [!NOTE] -> The `http.client.request.duration` metric is emitted in `seconds` as per the -semantic convention. While the convention [recommends using custom histogram -buckets](https://github.com/open-telemetry/semantic-conventions/blob/release/v1.23.x/docs/http/http-metrics.md) -, this feature is not yet available via .NET Metrics API. A -[workaround](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4820) -has been included in OTel SDK starting version `1.6.0` which applies recommended -buckets by default for `http.client.request.duration`. This applies to all -targeted frameworks. - -## Advanced configuration - -### Tracing - -This instrumentation can be configured to change the default behavior by using -`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 -`HttpClient` API. As such, depending on the runtime, only one half of the -"filter" & "enrich" options are used. - -#### .NET & .NET Core - -##### Filter HttpClient API - -This instrumentation by default collects all the outgoing HTTP requests. It -allows filtering of requests by using the `FilterHttpRequestMessage` function -option. This defines the condition for allowable requests. The filter function -receives the request object (`HttpRequestMessage`) representing the outgoing -request and does not collect telemetry about the request if the filter function -returns `false` or throws an exception. - -The following code snippet shows how to use `FilterHttpRequestMessage` to only -allow GET requests. - -```csharp -using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddHttpClientInstrumentation( - // Note: Only called on .NET & .NET Core runtimes. - (options) => options.FilterHttpRequestMessage = - (httpRequestMessage) => - { - // Example: Only collect telemetry about HTTP GET requests. - return httpRequestMessage.Method.Equals(HttpMethod.Get); - }) - .AddConsoleExporter() - .Build(); -``` - -It is important to note that this `FilterHttpRequestMessage` option is specific -to this instrumentation. OpenTelemetry has a concept of a -[Sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling), -and the `FilterHttpRequestMessage` option does the filtering *after* the Sampler -is invoked. - -##### Enrich HttpClient API - -This instrumentation library provides options that can be used to -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. - -`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. - -Example: - -```csharp -using System.Net.Http; - -var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddHttpClientInstrumentation((options) => - { - // Note: Only called on .NET & .NET Core runtimes. - options.EnrichWithHttpRequestMessage = (activity, httpRequestMessage) => - { - activity.SetTag("requestVersion", httpRequestMessage.Version); - }; - // Note: Only called on .NET & .NET Core runtimes. - options.EnrichWithHttpResponseMessage = (activity, httpResponseMessage) => - { - activity.SetTag("responseVersion", httpResponseMessage.Version); - }; - // Note: Called for all runtimes. - options.EnrichWithException = (activity, exception) => - { - activity.SetTag("stackTrace", exception.StackTrace); - }; - }) - .Build(); -``` - -#### .NET Framework - -##### Filter HttpWebRequest API - -This instrumentation by default collects all the outgoing HTTP requests. It -allows filtering of requests by using the `FilterHttpWebRequest` function -option. This defines the condition for allowable requests. The filter function -receives the request object (`HttpWebRequest`) representing the outgoing request -and does not collect telemetry about the request if the filter function returns -`false` or throws an exception. - -The following code snippet shows how to use `FilterHttpWebRequest` to only allow -GET requests. - -```csharp -using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddHttpClientInstrumentation( - // Note: Only called on .NET Framework. - (options) => options.FilterHttpWebRequest = - (httpWebRequest) => - { - // Example: Only collect telemetry about HTTP GET requests. - return httpWebRequest.Method.Equals(HttpMethod.Get.Method); - }) - .AddConsoleExporter() - .Build(); -``` - -It is important to note that this `FilterHttpWebRequest` option is specific to -this instrumentation. OpenTelemetry has a concept of a -[Sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#sampling), -and the `FilterHttpWebRequest` option does the filtering *after* the Sampler is -invoked. - -##### Enrich HttpWebRequest API - -This instrumentation library provides options that can be used to -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. - -`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. - -Example: - -```csharp -using System.Net; - -var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddHttpClientInstrumentation((options) => - { - // Note: Only called on .NET Framework. - options.EnrichWithHttpWebRequest = (activity, httpWebRequest) => - { - activity.SetTag("requestVersion", httpWebRequest.Version); - }; - // Note: Only called on .NET Framework. - options.EnrichWithHttpWebResponse = (activity, httpWebResponse) => - { - activity.SetTag("responseVersion", httpWebResponse.Version); - }; - // Note: Called for all runtimes. - options.EnrichWithException = (activity, exception) => - { - activity.SetTag("stackTrace", exception.StackTrace); - }; - }) - .Build(); -``` - -[Processor](../../docs/trace/extending-the-sdk/README.md#processor), is the -general extensibility point to add additional properties to any activity. The -`Enrich` option is specific to this instrumentation, and is provided to get -access to raw request, response, and exception objects. - -#### RecordException - -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 -[EventSource](https://docs.microsoft.com/dotnet/api/system.diagnostics.tracing.eventsource) -with the name "OpenTelemetry-Instrumentation-Http" for its internal logging. -Please refer to [SDK -troubleshooting](../OpenTelemetry/README.md#troubleshooting) for instructions on -seeing these internal logs. - -## References - -* [OpenTelemetry Project](https://opentelemetry.io/)