Capture HTTP Headers/gRPC Metadata as span attributes (#3444)

This commit is contained in:
Piotr Kiełkowicz 2024-06-12 07:25:47 +02:00 committed by GitHub
parent b288cb35e3
commit 86a32ceb4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 910 additions and 96 deletions

View File

@ -5,6 +5,7 @@ bitness
bytecode
cmake
Codespaces
Contoso
coreutils
corhlpr
Couchbase
@ -28,8 +29,10 @@ MASSTRANSIT
metricsexporter
mkdir
mktemp
monocytogenes
MSMQ
myapp
mycompanymyproductmylibrary
MYSQLCONNECTOR
MYSQLDATA
NETRUNTIME

View File

@ -57,6 +57,9 @@ jobs:
with:
fetch-depth: 0 # fetching all, needed to correctly calculate version
- name: Set the GITHUB_RUNNER_SYSTEM environment variable
run: echo "GITHUB_RUNNER_SYSTEM=${{ matrix.machine }}" >> $GITHUB_ENV
- name: Setup ARM64 Environment Variables
if: ${{ matrix.machine == 'actuated-arm64-4cpu-8gb' }}
run: |

View File

@ -9,6 +9,20 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h
### Added
- Support for capturing HTTP headers for following traces instrumentations:
- ASP.NET, configurable by
`OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS`
and `OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS`,
- ASP.NET Core, configurable by
`OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS`
and `OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS`,
- HTTP Client, configurable by
`OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS`
and `OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS`.
- Support for capturing gRPC metadata for Grpc.Net.Client traces instrumentation.
It can by configured by
`OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_REQUEST_METADATA`
and `OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_RESPONSE_METADATA`.
- Support for [Oracle.ManagedDataAccess.Core](https://www.nuget.org/packages/Oracle.ManagedDataAccess.Core)
and [Oracle.ManagedDataAccess](https://www.nuget.org/packages/Oracle.ManagedDataAccess)
traces instrumentation from 23.4.0 together with support for
@ -23,6 +37,17 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h
for details.
- Do not create consumer spans related to `PartitionEOF` events
for `Confluent.Kafka` client instrumentation.
- Following properties can be set before calling plugins:
- `OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithHttpRequest`,
- `OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions.EnrichWithHttpResponse`,
- `OpenTelemetry.Instrumentation.AspNet.AspNetTraceInstrumentationOptions.EnrichWithHttpRequest`,
- `OpenTelemetry.Instrumentation.AspNet.AspNetTraceInstrumentationOptions.EnrichWithHttpResponse`,
- `OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientTraceInstrumentationOptions.EnrichWithHttpRequestMessage`,
- `OpenTelemetry.Instrumentation.GrpcNetClient.GrpcClientTraceInstrumentationOptions.EnrichWithHttpResponseMessage`,
- `OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpRequestMessage`,
- `OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebRequest`,
- `OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpResponseMessage`,
- `OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebResponse`.
#### Dependency updates

View File

@ -203,15 +203,23 @@ the `ASPNETCORE_HOSTINGSTARTUPASSEMBLIES` environment variable to
### Instrumentation options
| Environment variable | Description | Default value | Status |
|-------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `OTEL_DOTNET_AUTO_ENTITYFRAMEWORKCORE_SET_DBSTATEMENT_FOR_TEXT` | Whether the Entity Framework Core instrumentation can pass SQL statements through the `db.statement` attribute. Queries might contain sensitive information. If set to `false`, `db.statement` is recorded only for executing stored procedures. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_GRAPHQL_SET_DOCUMENT` | Whether the GraphQL instrumentation can pass raw queries through the `graphql.document` attribute. Queries might contain sensitive information. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_ORACLEMDA_SET_DBSTATEMENT_FOR_TEXT` | Whether the Oracle Client instrumentation can pass SQL statements through the `db.statement` attribute. Queries might contain sensitive information. If set to `false`, `db.statement` is recorded only for executing stored procedures. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_SQLCLIENT_SET_DBSTATEMENT_FOR_TEXT` | Whether the SQL Client instrumentation can pass SQL statements through the `db.statement` attribute. Queries might contain sensitive information. If set to `false`, `db.statement` is recorded only for executing stored procedures. **Not supported on .NET Framework for System.Data.SqlClient.** | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION` | Whether the ASP.NET Core instrumentation turns off redaction of the `url.query` attribute value. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` | Whether the HTTP client instrumentation turns off redaction of the `url.full` attribute value. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_EXPERIMENTAL_ASPNET_DISABLE_URL_QUERY_REDACTION` | Whether the ASP.NET instrumentation turns off redaction of the `url.query` attribute value. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| Environment variable | Description | Default value | Status |
|-----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `OTEL_DOTNET_AUTO_ENTITYFRAMEWORKCORE_SET_DBSTATEMENT_FOR_TEXT` | Whether the Entity Framework Core instrumentation can pass SQL statements through the `db.statement` attribute. Queries might contain sensitive information. If set to `false`, `db.statement` is recorded only for executing stored procedures. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_GRAPHQL_SET_DOCUMENT` | Whether the GraphQL instrumentation can pass raw queries through the `graphql.document` attribute. Queries might contain sensitive information. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_ORACLEMDA_SET_DBSTATEMENT_FOR_TEXT` | Whether the Oracle Client instrumentation can pass SQL statements through the `db.statement` attribute. Queries might contain sensitive information. If set to `false`, `db.statement` is recorded only for executing stored procedures. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_SQLCLIENT_SET_DBSTATEMENT_FOR_TEXT` | Whether the SQL Client instrumentation can pass SQL statements through the `db.statement` attribute. Queries might contain sensitive information. If set to `false`, `db.statement` is recorded only for executing stored procedures. **Not supported on .NET Framework for System.Data.SqlClient.** | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS` | A comma-separated list of HTTP header names. ASP.NET instrumentations will capture HTTP request header values for all configured header names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS` | A comma-separated list of HTTP header names. ASP.NET instrumentations will capture HTTP response header values for all configured header names. **Not supported on IIS Classic mode.** | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS` | A comma-separated list of HTTP header names. ASP.NET Core instrumentations will capture HTTP request header values for all configured header names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS` | A comma-separated list of HTTP header names. ASP.NET Core instrumentations will capture HTTP response header values for all configured header names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_REQUEST_METADATA` | A comma-separated list of gRPC metadata names. Grpc.Net.Client instrumentations will capture gRPC request metadata values for all configured metadata names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_RESPONSE_METADATA` | A comma-separated list of gRPC metadata names. Grpc.Net.Client instrumentations will capture gRPC response metadata values for all configured metadata names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS` | A comma-separated list of HTTP header names. HTTP Client instrumentations will capture HTTP request header values for all configured header names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS` | A comma-separated list of HTTP header names. HTTP Client instrumentations will capture HTTP response header values for all configured header names. | | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_EXPERIMENTAL_ASPNETCORE_DISABLE_URL_QUERY_REDACTION` | Whether the ASP.NET Core instrumentation turns off redaction of the `url.query` attribute value. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_EXPERIMENTAL_HTTPCLIENT_DISABLE_URL_QUERY_REDACTION` | Whether the HTTP client instrumentation turns off redaction of the `url.full` attribute value. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_DOTNET_EXPERIMENTAL_ASPNET_DISABLE_URL_QUERY_REDACTION` | Whether the ASP.NET instrumentation turns off redaction of the `url.query` attribute value. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
## Propagators

View File

@ -102,6 +102,12 @@ public class MyPlugin
}
```
> [!NOTE]
> Automatic Instrumentation can configure particular properties before calling
> `Configure{Signal}Methods`. It is plugin responsibility to not override this behavior.
> Example: `OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions.EnrichWithHttpWebRequest`
> is conditionally set by this project.
## Supported Options
### Tracing

View File

@ -25,4 +25,16 @@ internal static class ConfigurationExtensions
return enabledConfigurations;
}
public static IReadOnlyList<string> ParseList(this Configuration source, string key, char valueSeparator)
{
var values = source.GetString(key);
if (string.IsNullOrWhiteSpace(values))
{
return Array.Empty<string>();
}
return values!.Split(new[] { valueSeparator }, StringSplitOptions.RemoveEmptyEntries);
}
}

View File

@ -108,11 +108,60 @@ internal partial class ConfigurationKeys
/// </summary>
public static class InstrumentationOptions
{
#if NETFRAMEWORK
/// <summary>
/// Configuration key for ASP.NET instrumentation to enable capturing HTTP request headers as span tags.
/// </summary>
public const string AspNetInstrumentationCaptureRequestHeaders = "OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS";
/// <summary>
/// Configuration key for ASP.NET instrumentation to enable capturing HTTP response headers as span tags.
/// </summary>
public const string AspNetInstrumentationCaptureResponseHeaders = "OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS";
#endif
#if NET6_0_OR_GREATER
/// <summary>
/// Configuration key for ASP.NET Core instrumentation to enable capturing HTTP request headers as span tags.
/// </summary>
public const string AspNetCoreInstrumentationCaptureRequestHeaders = "OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS";
/// <summary>
/// Configuration key for ASP.NET Core instrumentation to enable capturing HTTP response headers as span tags.
/// </summary>
public const string AspNetCoreInstrumentationCaptureResponseHeaders = "OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS";
/// <summary>
/// Configuration key for Entity Framework Core instrumentation to enable passing text query as a db.statement attribute.
/// </summary>
public const string EntityFrameworkCoreSetDbStatementForText = "OTEL_DOTNET_AUTO_ENTITYFRAMEWORKCORE_SET_DBSTATEMENT_FOR_TEXT";
#endif
/// <summary>
/// Configuration key for GraphQL instrumentation to enable passing query as a document attribute.
/// </summary>
public const string GraphQLSetDocument = "OTEL_DOTNET_AUTO_GRAPHQL_SET_DOCUMENT";
/// <summary>
/// Configuration key for GrpcNetClient instrumentation to enable capturing request metadata as span tags.
/// </summary>
public const string GrpcNetClientInstrumentationCaptureRequestMetadata = "OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_REQUEST_METADATA";
/// <summary>
/// Configuration key for GrpcNetClient instrumentation to enable capturing response metadata as span tags.
/// </summary>
public const string GrpcNetClientInstrumentationCaptureResponseMetadata = "OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_RESPONSE_METADATA";
/// <summary>
/// Configuration key for HTTP instrumentation to enable capturing HTTP request headers as span tags.
/// </summary>
public const string HttpInstrumentationCaptureRequestHeaders = "OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS";
/// <summary>
/// Configuration key for HTTP instrumentation to enable capturing HTTP response headers as span tags.
/// </summary>
public const string HttpInstrumentationCaptureResponseHeaders = "OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS";
/// <summary>
/// Configuration key for Oracle Client instrumentation to enable passing text query as a db.statement attribute.
/// </summary>
@ -122,13 +171,6 @@ internal partial class ConfigurationKeys
/// Configuration key for SQL Client instrumentation to enable passing text query as a db.statement attribute.
/// </summary>
public const string SqlClientSetDbStatementForText = "OTEL_DOTNET_AUTO_SQLCLIENT_SET_DBSTATEMENT_FOR_TEXT";
#if NET6_0_OR_GREATER
/// <summary>
/// Configuration key for Entity Framework Core instrumentation to enable passing text query as a db.statement attribute.
/// </summary>
public const string EntityFrameworkCoreSetDbStatementForText = "OTEL_DOTNET_AUTO_ENTITYFRAMEWORKCORE_SET_DBSTATEMENT_FOR_TEXT";
#endif
}
}

View File

@ -14,30 +14,30 @@ internal static class DelayedInitialization
{
#if NETFRAMEWORK
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddAspNet(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
public static void AddAspNet(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
new AspNetInitializer(lazyInstrumentationLoader, pluginManager);
new AspNetInitializer(lazyInstrumentationLoader, pluginManager, tracerSettings);
}
#endif
#if NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddAspNetCore(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
public static void AddAspNetCore(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
lazyInstrumentationLoader.Add(new AspNetCoreInitializer(pluginManager));
lazyInstrumentationLoader.Add(new AspNetCoreInitializer(pluginManager, tracerSettings));
}
#endif
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddHttpClient(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
public static void AddHttpClient(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
new HttpClientInitializer(lazyInstrumentationLoader, pluginManager);
new HttpClientInitializer(lazyInstrumentationLoader, pluginManager, tracerSettings);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void AddGrpcClient(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
public static void AddGrpcClient(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
lazyInstrumentationLoader.Add(new GrpcClientInitializer(pluginManager));
lazyInstrumentationLoader.Add(new GrpcClientInitializer(pluginManager, tracerSettings));
}
[MethodImpl(MethodImplOptions.NoInlining)]

View File

@ -25,11 +25,11 @@ internal static class EnvironmentConfigurationTracerHelper
_ = enabledInstrumentation switch
{
#if NETFRAMEWORK
TracerInstrumentation.AspNet => Wrappers.AddAspNetInstrumentation(builder, pluginManager, lazyInstrumentationLoader),
TracerInstrumentation.AspNet => Wrappers.AddAspNetInstrumentation(builder, pluginManager, lazyInstrumentationLoader, settings),
TracerInstrumentation.WcfService => AddWcfIfNeeded(builder, pluginManager, lazyInstrumentationLoader, ref wcfInstrumentationAdded),
#endif
TracerInstrumentation.GrpcNetClient => Wrappers.AddGrpcClientInstrumentation(builder, pluginManager, lazyInstrumentationLoader),
TracerInstrumentation.HttpClient => Wrappers.AddHttpClientInstrumentation(builder, pluginManager, lazyInstrumentationLoader),
TracerInstrumentation.GrpcNetClient => Wrappers.AddGrpcClientInstrumentation(builder, pluginManager, lazyInstrumentationLoader, settings),
TracerInstrumentation.HttpClient => Wrappers.AddHttpClientInstrumentation(builder, pluginManager, lazyInstrumentationLoader, settings),
TracerInstrumentation.Npgsql => builder.AddSource("Npgsql"),
TracerInstrumentation.SqlClient => Wrappers.AddSqlClientInstrumentation(builder, pluginManager, lazyInstrumentationLoader, settings),
TracerInstrumentation.NServiceBus => builder.AddSource("NServiceBus.Core"),
@ -42,7 +42,7 @@ internal static class EnvironmentConfigurationTracerHelper
TracerInstrumentation.WcfClient => AddWcfIfNeeded(builder, pluginManager, lazyInstrumentationLoader, ref wcfInstrumentationAdded),
TracerInstrumentation.OracleMda => Wrappers.AddOracleMdaInstrumentation(builder, lazyInstrumentationLoader, settings),
#if NET6_0_OR_GREATER
TracerInstrumentation.AspNetCore => Wrappers.AddAspNetCoreInstrumentation(builder, pluginManager, lazyInstrumentationLoader),
TracerInstrumentation.AspNetCore => Wrappers.AddAspNetCoreInstrumentation(builder, pluginManager, lazyInstrumentationLoader, settings),
TracerInstrumentation.MassTransit => builder.AddSource("MassTransit"),
TracerInstrumentation.MySqlData => builder.AddSource("connector-net"),
TracerInstrumentation.StackExchangeRedis => builder.AddSource("OpenTelemetry.Instrumentation.StackExchangeRedis"),
@ -122,9 +122,9 @@ internal static class EnvironmentConfigurationTracerHelper
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static TracerProviderBuilder AddHttpClientInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader)
public static TracerProviderBuilder AddHttpClientInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader, TracerSettings tracerSettings)
{
DelayedInitialization.Traces.AddHttpClient(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddHttpClient(lazyInstrumentationLoader, pluginManager, tracerSettings);
#if NETFRAMEWORK
builder.AddSource("OpenTelemetry.Instrumentation.Http.HttpWebRequest");
@ -139,18 +139,18 @@ internal static class EnvironmentConfigurationTracerHelper
#if NETFRAMEWORK
[MethodImpl(MethodImplOptions.NoInlining)]
public static TracerProviderBuilder AddAspNetInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader)
public static TracerProviderBuilder AddAspNetInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader, TracerSettings tracerSettings)
{
DelayedInitialization.Traces.AddAspNet(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddAspNet(lazyInstrumentationLoader, pluginManager, tracerSettings);
return builder.AddSource(OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.AspNetSourceName);
}
#endif
#if NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.NoInlining)]
public static TracerProviderBuilder AddAspNetCoreInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader)
public static TracerProviderBuilder AddAspNetCoreInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader, TracerSettings tracerSettings)
{
DelayedInitialization.Traces.AddAspNetCore(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddAspNetCore(lazyInstrumentationLoader, pluginManager, tracerSettings);
if (Environment.Version.Major == 6)
{
@ -198,9 +198,9 @@ internal static class EnvironmentConfigurationTracerHelper
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static TracerProviderBuilder AddGrpcClientInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader)
public static TracerProviderBuilder AddGrpcClientInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader, TracerSettings tracerSettings)
{
DelayedInitialization.Traces.AddGrpcClient(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddGrpcClient(lazyInstrumentationLoader, pluginManager, tracerSettings);
builder.AddSource("OpenTelemetry.Instrumentation.GrpcNetClient");
builder.AddLegacySource("Grpc.Net.Client.GrpcOut");

View File

@ -1,6 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using OpenTelemetry.AutoInstrumentation.HeadersCapture;
namespace OpenTelemetry.AutoInstrumentation.Configurations;
/// <summary>
@ -10,19 +12,79 @@ internal class InstrumentationOptions
{
internal InstrumentationOptions(Configuration configuration)
{
GraphQLSetDocument = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.GraphQLSetDocument) ?? false;
OracleMdaSetDbStatementForText = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.OracleMdaSetDbStatementForText) ?? false;
SqlClientSetDbStatementForText = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.SqlClientSetDbStatementForText) ?? false;
#if NETFRAMEWORK
AspNetInstrumentationCaptureRequestHeaders = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.AspNetInstrumentationCaptureRequestHeaders, AdditionalTag.CreateHttpRequestCache);
AspNetInstrumentationCaptureResponseHeaders = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.AspNetInstrumentationCaptureResponseHeaders, AdditionalTag.CreateHttpResponseCache);
#endif
#if NET6_0_OR_GREATER
AspNetCoreInstrumentationCaptureRequestHeaders = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.AspNetCoreInstrumentationCaptureRequestHeaders, AdditionalTag.CreateHttpRequestCache);
AspNetCoreInstrumentationCaptureResponseHeaders = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.AspNetCoreInstrumentationCaptureResponseHeaders, AdditionalTag.CreateHttpResponseCache);
EntityFrameworkCoreSetDbStatementForText = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.EntityFrameworkCoreSetDbStatementForText) ?? false;
#endif
GraphQLSetDocument = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.GraphQLSetDocument) ?? false;
GrpcNetClientInstrumentationCaptureRequestMetadata = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.GrpcNetClientInstrumentationCaptureRequestMetadata, AdditionalTag.CreateGrpcRequestCache);
GrpcNetClientInstrumentationCaptureResponseMetadata = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.GrpcNetClientInstrumentationCaptureResponseMetadata, AdditionalTag.CreateGrpcResponseCache);
HttpInstrumentationCaptureRequestHeaders = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders, AdditionalTag.CreateHttpRequestCache);
HttpInstrumentationCaptureResponseHeaders = configuration.ParseHeaders(ConfigurationKeys.Traces.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders, AdditionalTag.CreateHttpResponseCache);
OracleMdaSetDbStatementForText = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.OracleMdaSetDbStatementForText) ?? false;
SqlClientSetDbStatementForText = configuration.GetBool(ConfigurationKeys.Traces.InstrumentationOptions.SqlClientSetDbStatementForText) ?? false;
}
#if NETFRAMEWORK
/// <summary>
/// Gets the list of HTTP request headers to be captured as the span tags by ASP.NET instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> AspNetInstrumentationCaptureRequestHeaders { get; }
/// <summary>
/// Gets the list of HTTP response headers to be captured as the span tags by ASP.NET instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> AspNetInstrumentationCaptureResponseHeaders { get; }
#endif
#if NET6_0_OR_GREATER
/// <summary>
/// Gets the list of HTTP request headers to be captured as the span tags by ASP.NET Core instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> AspNetCoreInstrumentationCaptureRequestHeaders { get; }
/// <summary>
/// Gets the list of HTTP response headers to be captured as the span tags by ASP.NET Core instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> AspNetCoreInstrumentationCaptureResponseHeaders { get; }
/// <summary>
/// Gets a value indicating whether text query in Entity Framework Core can be passed as a db.statement tag.
/// </summary>
public bool EntityFrameworkCoreSetDbStatementForText { get; }
#endif
/// <summary>
/// Gets a value indicating whether GraphQL query can be passed as a Document tag.
/// </summary>
public bool GraphQLSetDocument { get; }
/// <summary>
/// Gets the list of request metadata to be captured as the span tags by Grpc.Net.Client instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> GrpcNetClientInstrumentationCaptureRequestMetadata { get; }
/// <summary>
/// Gets the list of response metadata to be captured as the span tags by Grpc.Net.Client instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> GrpcNetClientInstrumentationCaptureResponseMetadata { get; }
/// <summary>
/// Gets the list of HTTP request headers to be captured as the span tags by HTTP instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> HttpInstrumentationCaptureRequestHeaders { get; }
/// <summary>
/// Gets the list of HTTP response headers to be captured as the span tags by HTTP instrumentation.
/// </summary>
public IReadOnlyList<AdditionalTag> HttpInstrumentationCaptureResponseHeaders { get; }
/// <summary>
/// Gets a value indicating whether text query in Oracle Client can be passed as a db.statement tag.
/// </summary>
@ -32,11 +94,4 @@ internal class InstrumentationOptions
/// Gets a value indicating whether text query in SQL Client can be passed as a db.statement tag.
/// </summary>
public bool SqlClientSetDbStatementForText { get; }
#if NET6_0_OR_GREATER
/// <summary>
/// Gets a value indicating whether text query in Entity Framework Core can be passed as a db.statement tag.
/// </summary>
public bool EntityFrameworkCoreSetDbStatementForText { get; }
#endif
}

View File

@ -12,6 +12,18 @@ internal static class Constants
public const string TelemetryDistroVersionAttributeName = "telemetry.distro.version";
}
public static class GrpcSpanAttributes
{
public const string AttributeGrpcRequestMetadataPrefix = "rpc.grpc.request.metadata";
public const string AttributeGrpcResponseMetadataPrefix = "rpc.grpc.response.metadata";
}
public static class HttpSpanAttributes
{
public const string AttributeHttpRequestHeaderPrefix = "http.request.header";
public const string AttributeHttpResponseHeaderPrefix = "http.response.header";
}
public static class ConfigurationValues
{
public const string None = "none";

View File

@ -0,0 +1,100 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Collections.Specialized;
using System.Diagnostics;
using System.Net.Http.Headers;
#if NET6_0_OR_GREATER
using Microsoft.AspNetCore.Http;
#endif
namespace OpenTelemetry.AutoInstrumentation.HeadersCapture;
internal static class ActivityExtensions
{
public static void AddHeadersAsTags(this Activity activity, IReadOnlyList<AdditionalTag> additionalTags, NameValueCollection headers)
{
if (!activity.IsAllDataRequested)
{
return;
}
for (var i = 0; i < additionalTags.Count; i++)
{
var additionalTag = additionalTags[i];
var headerValues = headers.GetValues(additionalTag.Key);
if (headerValues == null)
{
continue;
}
if (headerValues.Length == 1)
{
activity.SetTag(additionalTag.TagName, headerValues[0]);
}
else
{
activity.SetTag(additionalTag.TagName, headerValues);
}
}
}
#if NET6_0_OR_GREATER
public static void AddHeadersAsTags(this Activity activity, IReadOnlyList<AdditionalTag> additionalTags, IHeaderDictionary headers)
{
if (!activity.IsAllDataRequested)
{
return;
}
for (var i = 0; i < additionalTags.Count; i++)
{
var additionalTag = additionalTags[i];
if (!headers.TryGetValue(additionalTag.Key, out var headerValues))
{
continue;
}
if (headerValues.Count == 1)
{
activity.SetTag(additionalTag.TagName, headerValues[0]);
}
else
{
activity.SetTag(additionalTag.TagName, headerValues);
}
}
}
#endif
public static void AddHeadersAsTags(this Activity activity, IReadOnlyList<AdditionalTag> additionalTags, HttpHeaders headers)
{
if (!activity.IsAllDataRequested)
{
return;
}
for (var i = 0; i < additionalTags.Count; i++)
{
var additionalTag = additionalTags[i];
if (!headers.TryGetValues(additionalTag.Key, out var headerValues))
{
continue;
}
var headerValuesAsArray = headerValues.ToArray();
if (headerValuesAsArray.Length == 1)
{
activity.SetTag(additionalTag.TagName, headerValuesAsArray[0]);
}
else
{
activity.SetTag(additionalTag.TagName, headerValuesAsArray);
}
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
namespace OpenTelemetry.AutoInstrumentation.HeadersCapture;
internal class AdditionalTag
{
private AdditionalTag(string key, string spanTagPrefix)
{
Key = key;
TagName = $"{spanTagPrefix}.{HeaderNormalizer.Normalize(key)}";
}
public string Key { get; }
public string TagName { get; }
public static AdditionalTag CreateGrpcRequestCache(string key)
{
return new AdditionalTag(key, Constants.GrpcSpanAttributes.AttributeGrpcRequestMetadataPrefix);
}
public static AdditionalTag CreateGrpcResponseCache(string key)
{
return new AdditionalTag(key, Constants.GrpcSpanAttributes.AttributeGrpcResponseMetadataPrefix);
}
public static AdditionalTag CreateHttpRequestCache(string key)
{
return new AdditionalTag(key, Constants.HttpSpanAttributes.AttributeHttpRequestHeaderPrefix);
}
public static AdditionalTag CreateHttpResponseCache(string key)
{
return new AdditionalTag(key, Constants.HttpSpanAttributes.AttributeHttpResponseHeaderPrefix);
}
}

View File

@ -0,0 +1,21 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using OpenTelemetry.AutoInstrumentation.Configurations;
namespace OpenTelemetry.AutoInstrumentation.HeadersCapture;
internal static class HeaderConfigurationExtensions
{
public static IReadOnlyList<AdditionalTag> ParseHeaders(this Configuration source, string key, Func<string, AdditionalTag> stringToHeaderCacheConverter)
{
var headers = source.ParseList(key, ',');
if (headers.Count == 0)
{
return Array.Empty<AdditionalTag>();
}
return headers.Select(stringToHeaderCacheConverter).ToArray();
}
}

View File

@ -0,0 +1,12 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
namespace OpenTelemetry.AutoInstrumentation.HeadersCapture;
internal static class HeaderNormalizer
{
public static string Normalize(string httpHeaderName)
{
return httpHeaderName.ToLowerInvariant();
}
}

View File

@ -321,17 +321,17 @@ internal static class Instrumentation
{
#if NETFRAMEWORK
case TracerInstrumentation.AspNet:
DelayedInitialization.Traces.AddAspNet(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddAspNet(lazyInstrumentationLoader, pluginManager, tracerSettings);
break;
case TracerInstrumentation.WcfService:
AddWcfIfNeeded(lazyInstrumentationLoader, pluginManager, ref wcfInstrumentationAdded);
break;
#endif
case TracerInstrumentation.HttpClient:
DelayedInitialization.Traces.AddHttpClient(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddHttpClient(lazyInstrumentationLoader, pluginManager, tracerSettings);
break;
case TracerInstrumentation.GrpcNetClient:
DelayedInitialization.Traces.AddGrpcClient(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddGrpcClient(lazyInstrumentationLoader, pluginManager, tracerSettings);
break;
case TracerInstrumentation.SqlClient:
DelayedInitialization.Traces.AddSqlClient(lazyInstrumentationLoader, pluginManager, tracerSettings);
@ -344,7 +344,7 @@ internal static class Instrumentation
break;
#if NET6_0_OR_GREATER
case TracerInstrumentation.AspNetCore:
DelayedInitialization.Traces.AddAspNetCore(lazyInstrumentationLoader, pluginManager);
DelayedInitialization.Traces.AddAspNetCore(lazyInstrumentationLoader, pluginManager, tracerSettings);
break;
case TracerInstrumentation.MySqlData:
break;

View File

@ -2,6 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
#if NET6_0_OR_GREATER
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.HeadersCapture;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
@ -9,11 +13,13 @@ namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal class AspNetCoreInitializer : InstrumentationInitializer
{
private readonly PluginManager _pluginManager;
private readonly TracerSettings _tracerSettings;
public AspNetCoreInitializer(PluginManager pluginManager)
public AspNetCoreInitializer(PluginManager pluginManager, TracerSettings tracerSettings)
: base("Microsoft.AspNetCore.Http")
{
_pluginManager = pluginManager;
_tracerSettings = tracerSettings;
}
public override void Initialize(ILifespanManager lifespanManager)
@ -22,6 +28,17 @@ internal class AspNetCoreInitializer : InstrumentationInitializer
var httpInListenerType = Type.GetType("OpenTelemetry.Instrumentation.AspNetCore.Implementation.HttpInListener, OpenTelemetry.Instrumentation.AspNetCore")!;
var options = new OpenTelemetry.Instrumentation.AspNetCore.AspNetCoreTraceInstrumentationOptions();
if (_tracerSettings.InstrumentationOptions.AspNetCoreInstrumentationCaptureRequestHeaders.Count != 0)
{
options.EnrichWithHttpRequest = EnrichWithHttpRequest;
}
if (_tracerSettings.InstrumentationOptions.AspNetCoreInstrumentationCaptureResponseHeaders.Count != 0)
{
options.EnrichWithHttpResponse = EnrichWithHttpResponse;
}
_pluginManager.ConfigureTracesOptions(options);
var httpInListener = Activator.CreateInstance(httpInListenerType, args: options);
@ -29,5 +46,15 @@ internal class AspNetCoreInitializer : InstrumentationInitializer
lifespanManager.Track(instrumentation);
}
private void EnrichWithHttpRequest(Activity activity, HttpRequest httpRequest)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.AspNetCoreInstrumentationCaptureRequestHeaders, httpRequest.Headers);
}
private void EnrichWithHttpResponse(Activity activity, HttpResponse httpResponse)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.AspNetCoreInstrumentationCaptureResponseHeaders, httpResponse.Headers);
}
}
#endif

View File

@ -3,6 +3,10 @@
#if NETFRAMEWORK
using System.Diagnostics;
using System.Web;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.HeadersCapture;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
@ -10,12 +14,14 @@ namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal class AspNetInitializer
{
private readonly PluginManager _pluginManager;
private readonly TracerSettings _tracerSettings;
private int _initialized;
public AspNetInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
public AspNetInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
_pluginManager = pluginManager;
_tracerSettings = tracerSettings;
lazyInstrumentationLoader.Add(new AspNetMvcInitializer(InitializeOnFirstCall));
lazyInstrumentationLoader.Add(new AspNetWebApiInitializer(InitializeOnFirstCall));
}
@ -31,11 +37,32 @@ internal class AspNetInitializer
var instrumentationType = Type.GetType("OpenTelemetry.Instrumentation.AspNet.AspNetInstrumentation, OpenTelemetry.Instrumentation.AspNet");
var options = new OpenTelemetry.Instrumentation.AspNet.AspNetTraceInstrumentationOptions();
if (_tracerSettings.InstrumentationOptions.AspNetInstrumentationCaptureRequestHeaders.Count != 0)
{
options.EnrichWithHttpRequest = EnrichWithHttpRequest;
}
if (_tracerSettings.InstrumentationOptions.AspNetInstrumentationCaptureResponseHeaders.Count != 0)
{
options.EnrichWithHttpResponse = EnrichWithHttpResponse;
}
_pluginManager.ConfigureTracesOptions(options);
var instrumentation = Activator.CreateInstance(instrumentationType, args: options);
lifespanManager.Track(instrumentation);
}
private void EnrichWithHttpRequest(Activity activity, HttpRequest httpRequest)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.AspNetInstrumentationCaptureRequestHeaders, httpRequest.Headers);
}
private void EnrichWithHttpResponse(Activity activity, HttpResponse httpResponse)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.AspNetInstrumentationCaptureResponseHeaders, httpResponse.Headers);
}
}
#endif

View File

@ -1,7 +1,10 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using System.Diagnostics;
using System.Net.Http;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.HeadersCapture;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
@ -9,11 +12,13 @@ namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal class GrpcClientInitializer : InstrumentationInitializer
{
private readonly PluginManager _pluginManager;
private readonly TracerSettings _tracerSettings;
public GrpcClientInitializer(PluginManager pluginManager)
public GrpcClientInitializer(PluginManager pluginManager, TracerSettings tracerSettings)
: base("Grpc.Net.Client")
{
_pluginManager = pluginManager;
_tracerSettings = tracerSettings;
}
public override void Initialize(ILifespanManager lifespanManager)
@ -25,10 +30,30 @@ internal class GrpcClientInitializer : InstrumentationInitializer
SuppressDownstreamInstrumentation = !Instrumentation.TracerSettings.Value.EnabledInstrumentations.Contains(TracerInstrumentation.HttpClient)
};
if (_tracerSettings.InstrumentationOptions.GrpcNetClientInstrumentationCaptureRequestMetadata.Count != 0)
{
options.EnrichWithHttpRequestMessage = EnrichWithHttpRequestMessage;
}
if (_tracerSettings.InstrumentationOptions.GrpcNetClientInstrumentationCaptureResponseMetadata.Count != 0)
{
options.EnrichWithHttpResponseMessage = EnrichWithHttpResponseMessage;
}
_pluginManager.ConfigureTracesOptions(options);
var instrumentation = Activator.CreateInstance(instrumentationType, options)!;
lifespanManager.Track(instrumentation);
}
private void EnrichWithHttpRequestMessage(Activity activity, HttpRequestMessage httpRequestMessage)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.GrpcNetClientInstrumentationCaptureRequestMetadata, httpRequestMessage.Headers);
}
private void EnrichWithHttpResponseMessage(Activity activity, HttpResponseMessage httpResponseMessage)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.GrpcNetClientInstrumentationCaptureResponseMetadata, httpResponseMessage.Headers);
}
}

View File

@ -1,7 +1,14 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#if NETFRAMEWORK
using System.Reflection;
#endif
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using OpenTelemetry.AutoInstrumentation.Configurations;
using OpenTelemetry.AutoInstrumentation.HeadersCapture;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
@ -9,12 +16,14 @@ namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
internal class HttpClientInitializer
{
private readonly PluginManager _pluginManager;
private readonly TracerSettings _tracerSettings;
private int _initialized;
public HttpClientInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
public HttpClientInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager, TracerSettings tracerSettings)
{
_pluginManager = pluginManager;
_tracerSettings = tracerSettings;
lazyInstrumentationLoader.Add(new GenericInitializer("System.Net.Http", InitializeOnFirstCall));
@ -32,6 +41,19 @@ internal class HttpClientInitializer
}
var options = new OpenTelemetry.Instrumentation.Http.HttpClientTraceInstrumentationOptions();
if (_tracerSettings.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders.Count != 0)
{
options.EnrichWithHttpRequestMessage = EnrichWithHttpRequestMessage;
options.EnrichWithHttpWebRequest = EnrichWithHttpWebRequest;
}
if (_tracerSettings.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders.Count != 0)
{
options.EnrichWithHttpResponseMessage = EnrichWithHttpResponseMessage;
options.EnrichWithHttpWebResponse = EnrichWithHttpWebResponse;
}
_pluginManager.ConfigureTracesOptions(options);
#if NETFRAMEWORK
@ -45,4 +67,24 @@ internal class HttpClientInitializer
lifespanManager.Track(instrumentation);
#endif
}
private void EnrichWithHttpRequestMessage(Activity activity, HttpRequestMessage httpRequestMessage)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders, httpRequestMessage.Headers);
}
private void EnrichWithHttpWebRequest(Activity activity, HttpWebRequest httpWebRequest)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders, httpWebRequest.Headers);
}
private void EnrichWithHttpResponseMessage(Activity activity, HttpResponseMessage httpResponseMessage)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders, httpResponseMessage.Headers);
}
private void EnrichWithHttpWebResponse(Activity activity, HttpWebResponse httpWebResponse)
{
activity.AddHeadersAsTags(_tracerSettings.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders, httpWebResponse.Headers);
}
}

View File

@ -10,6 +10,7 @@
<PackageVersion Include="GraphQL.MicrosoftDI" Version="7.8.0" />
<PackageVersion Include="GraphQL.Server.Transports.AspNetCore" Version="7.7.1" />
<PackageVersion Include="GraphQL.Server.Ui.Playground" Version="7.7.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.63.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.63.0" />
<PackageVersion Include="Grpc.Net.Client.Web" Version="2.63.0" />
<PackageVersion Include="Grpc.Tools" Version="2.51.0" />

View File

@ -51,6 +51,72 @@ public class AspNetTests
collector.AssertExpectations();
}
[Theory]
[Trait("Category", "EndToEnd")]
[Trait("Containers", "Windows")]
[InlineData("Classic")]
[InlineData("Integrated")]
public async Task SubmitTracesCapturesHttpHeaders(string appPoolMode)
{
Assert.True(EnvironmentTools.IsWindowsAdministrator(), "This test requires Windows Administrator privileges.");
// Using "*" as host requires Administrator. This is needed to make the mock collector endpoint
// accessible to the Windows docker container where the test application is executed by binding
// the endpoint to all network interfaces. In order to do that it is necessary to open the port
// on the firewall.
using var collector = new MockSpansCollector(Output, host: "*");
using var fwPort = FirewallHelper.OpenWinPort(collector.Port, Output);
collector.Expect("OpenTelemetry.Instrumentation.AspNet.Telemetry", span => // Expect Mvc span
{
if (appPoolMode == "Classic")
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header3")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header1")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header3")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header2");
}
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header3")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header1" && x.Value.StringValue == "Test-Value4")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header3" && x.Value.StringValue == "Test-Value6")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header2");
});
collector.Expect("OpenTelemetry.Instrumentation.AspNet.Telemetry", span => // Expect WebApi span
{
if (appPoolMode == "Classic")
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header3")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header1")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header3")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header2");
}
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header3")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header1" && x.Value.StringValue == "Test-Value1")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header3" && x.Value.StringValue == "Test-Value3")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header2");
});
var collectorUrl = $"http://{DockerNetworkHelper.IntegrationTestsGateway}:{collector.Port}";
_environmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] = collectorUrl;
_environmentVariables["OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS"] = "Custom-Request-Test-Header2";
_environmentVariables["OTEL_DOTNET_AUTO_TRACES_ASPNET_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS"] = "Custom-Response-Test-Header1,Custom-Response-Test-Header3";
var webPort = TcpPortProvider.GetOpenPort();
await using var container = await StartContainerAsync(webPort, appPoolMode);
await CallTestApplicationEndpoint(webPort);
collector.AssertExpectations();
}
[Fact]
[Trait("Category", "EndToEnd")]
[Trait("Containers", "Windows")]
@ -156,6 +222,10 @@ public class AspNetTests
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Custom-Request-Test-Header1", "Test-Value1");
client.DefaultRequestHeaders.Add("Custom-Request-Test-Header2", "Test-Value2");
client.DefaultRequestHeaders.Add("Custom-Request-Test-Header3", "Test-Value3");
var response = await client.GetAsync($"http://localhost:{webPort}");
var content = await response.Content.ReadAsStringAsync();
Output.WriteLine("MVC Response:");

View File

@ -30,4 +30,38 @@ public class GrpcNetClientTests : TestHelper
collector.AssertExpectations();
}
[Theory]
[Trait("Category", "EndToEnd")]
[MemberData(nameof(LibraryVersion.GrpcNetClient), MemberType = typeof(LibraryVersion))]
public void SubmitTracesCapturesGrpcMetadata(string packageVersion)
{
using var collector = new MockSpansCollector(Output);
SetExporter(collector);
collector.Expect("OpenTelemetry.Instrumentation.GrpcNetClient", span =>
{
return span.Attributes.Any(x => x.Key == "rpc.grpc.request.metadata.custom-request-test-header1" && x.Value.StringValue == "Test-Value1")
&& span.Attributes.Any(x => x.Key == "rpc.grpc.request.metadata.custom-request-test-header3" && x.Value.StringValue == "Test-Value3")
&& span.Attributes.All(x => x.Key != "rpc.grpc.request.metadata.custom-request-test-header2")
#if NETFRAMEWORK
; // there is no .NET Framework server
#else
&& span.Attributes.Any(x => x.Key == "rpc.grpc.response.metadata.custom-response-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "rpc.grpc.response.metadata.custom-response-test-header1")
&& span.Attributes.All(x => x.Key != "rpc.grpc.response.metadata.custom-response-test-header3");
#endif
});
// Grpc.Net.Client is using various version of http communication under the hood.
// Enabling only GrpcNetClient instrumentation to have consistent set of spans.
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_INSTRUMENTATION_ENABLED", "false");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_ENABLED", "true");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_REQUEST_METADATA", "Custom-Request-Test-Header1,Custom-Request-Test-Header3");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_GRPCNETCLIENT_INSTRUMENTATION_CAPTURE_RESPONSE_METADATA", "Custom-Response-Test-Header2");
RunTestApplication(new TestSettings { PackageVersion = packageVersion });
collector.AssertExpectations();
}
}

View File

@ -27,5 +27,29 @@ public class HttpNetFrameworkTests : TestHelper
collector.AssertExpectations();
}
[Fact]
public void SubmitTracesCapturesHttpHeaders()
{
using var collector = new MockSpansCollector(Output);
SetExporter(collector);
collector.Expect("OpenTelemetry.Instrumentation.Http.HttpWebRequest", span =>
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header1" && x.Value.StringValue == "Test-Value1")
&& span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header3" && x.Value.StringValue == "Test-Value3")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header2")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header1")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header3");
});
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS", "Custom-Request-Test-Header1,Custom-Request-Test-Header3");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS", "Custom-Response-Test-Header2");
RunTestApplication();
collector.AssertExpectations();
}
}
#endif

View File

@ -36,6 +36,7 @@ public class HttpTests : TestHelper
clientSpan = span;
return true;
});
Span? serverSpan = null;
#if NET7_0_OR_GREATER
collector.Expect("Microsoft.AspNetCore", span =>
@ -46,6 +47,7 @@ public class HttpTests : TestHelper
serverSpan = span;
return true;
});
Span? manualSpan = null;
collector.Expect("TestApplication.Http", span =>
{
@ -67,6 +69,50 @@ public class HttpTests : TestHelper
}
}
[Fact]
public void SubmitTracesCapturesHttpHeaders()
{
using var collector = new MockSpansCollector(Output);
SetExporter(collector);
#if NET7_0_OR_GREATER
collector.Expect("System.Net.Http", span =>
#else
collector.Expect("OpenTelemetry.Instrumentation.Http.HttpClient", span =>
#endif
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header1" && x.Value.StringValue == "Test-Value1")
&& span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header3" && x.Value.StringValue == "Test-Value3")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header2")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header1")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header3");
});
#if NET7_0_OR_GREATER
collector.Expect("Microsoft.AspNetCore", span =>
#else
collector.Expect("OpenTelemetry.Instrumentation.AspNetCore", span =>
#endif
{
return span.Attributes.Any(x => x.Key == "http.request.header.custom-request-test-header2" && x.Value.StringValue == "Test-Value2")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header1")
&& span.Attributes.All(x => x.Key != "http.request.header.custom-request-test-header3")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header1" && x.Value.StringValue == "Test-Value1")
&& span.Attributes.Any(x => x.Key == "http.response.header.custom-response-test-header3" && x.Value.StringValue == "Test-Value3")
&& span.Attributes.All(x => x.Key != "http.response.header.custom-response-test-header2");
});
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS", "Custom-Request-Test-Header1,Custom-Request-Test-Header3");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_HTTP_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS", "Custom-Response-Test-Header2");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_REQUEST_HEADERS", "Custom-Request-Test-Header2");
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ASPNETCORE_INSTRUMENTATION_CAPTURE_RESPONSE_HEADERS", "Custom-Response-Test-Header1,Custom-Response-Test-Header3");
RunTestApplication();
collector.AssertExpectations();
}
[Fact]
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()

View File

@ -105,7 +105,7 @@ public class ConfigurationTests
}
[Fact]
public void ParseEmptyAsNull_CompositeConfigurationSource()
public void ParseEnabledEnumList_ParseEmptyAsNull_CompositeConfigurationSource()
{
var mockSource = Substitute.For<IConfigurationSource>();
mockSource.GetString(Arg.Is<string>(key => key == "TEST_NULL_VALUE")).Returns(_ => null);
@ -118,4 +118,53 @@ public class ConfigurationTests
compositeSource.GetString("TEST_EMPTY_VALUE").Should().BeNull();
}
}
[Fact]
public void ParseList_ParseSingleElement()
{
var source = new Configuration(false, new NameValueConfigurationSource(false, new NameValueCollection
{
{ "TEST_LIST", "Value1" },
}));
var list = source.ParseList("TEST_LIST", ',');
list.Should().Equal("Value1");
}
[Fact]
public void ParseList_ParseMultipleList()
{
var source = new Configuration(false, new NameValueConfigurationSource(false, new NameValueCollection
{
{ "TEST_LIST", "Value1,Value2" },
}));
var list = source.ParseList("TEST_LIST", ',');
list.Should().Equal("Value1", "Value2");
}
[Fact]
public void ParseList_ParseEmptyEntry()
{
var source = new Configuration(false, new NameValueConfigurationSource(false, new NameValueCollection
{
{ "TEST_LIST", "Value1,,Value2" },
}));
var list = source.ParseList("TEST_LIST", ',');
list.Should().Equal("Value1", "Value2");
}
[Fact]
public void ParseList_ParseNullAsEmpty()
{
var source = new Configuration(false, new NameValueConfigurationSource(false, new NameValueCollection()));
var list = source.ParseList("TEST_LIST", ',');
list.Should().BeEmpty();
}
}

View File

@ -55,12 +55,22 @@ public class SettingsTests : IDisposable
settings.AdditionalLegacySources.Should().BeEmpty();
// Instrumentation options tests
settings.InstrumentationOptions.GraphQLSetDocument.Should().BeFalse();
settings.InstrumentationOptions.OracleMdaSetDbStatementForText.Should().BeFalse();
settings.InstrumentationOptions.SqlClientSetDbStatementForText.Should().BeFalse();
#if NETFRAMEWORK
settings.InstrumentationOptions.AspNetInstrumentationCaptureRequestHeaders.Should().BeEmpty();
settings.InstrumentationOptions.AspNetInstrumentationCaptureResponseHeaders.Should().BeEmpty();
#endif
#if NET6_0_OR_GREATER
settings.InstrumentationOptions.AspNetCoreInstrumentationCaptureRequestHeaders.Should().BeEmpty();
settings.InstrumentationOptions.AspNetCoreInstrumentationCaptureResponseHeaders.Should().BeEmpty();
settings.InstrumentationOptions.EntityFrameworkCoreSetDbStatementForText.Should().BeFalse();
#endif
settings.InstrumentationOptions.GraphQLSetDocument.Should().BeFalse();
settings.InstrumentationOptions.GrpcNetClientInstrumentationCaptureRequestMetadata.Should().BeEmpty();
settings.InstrumentationOptions.GrpcNetClientInstrumentationCaptureResponseMetadata.Should().BeEmpty();
settings.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders.Should().BeEmpty();
settings.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders.Should().BeEmpty();
settings.InstrumentationOptions.OracleMdaSetDbStatementForText.Should().BeFalse();
settings.InstrumentationOptions.SqlClientSetDbStatementForText.Should().BeFalse();
}
}
@ -369,7 +379,6 @@ public class SettingsTests : IDisposable
}
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.Exporter, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.GraphQLSetDocument, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.ExporterOtlpProtocol, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.FlushOnUnhandledException, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.ResourceDetectorEnabled, null);
@ -381,5 +390,22 @@ public class SettingsTests : IDisposable
}
Environment.SetEnvironmentVariable(ConfigurationKeys.Sdk.Propagators, null);
#if NETFRAMEWORK
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.AspNetInstrumentationCaptureRequestHeaders, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.AspNetInstrumentationCaptureResponseHeaders, null);
#endif
#if NET6_0_OR_GREATER
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.AspNetCoreInstrumentationCaptureRequestHeaders, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.AspNetCoreInstrumentationCaptureResponseHeaders, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.GraphQLSetDocument, null);
#endif
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.GrpcNetClientInstrumentationCaptureRequestMetadata, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.GrpcNetClientInstrumentationCaptureResponseMetadata, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.HttpInstrumentationCaptureRequestHeaders, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.HttpInstrumentationCaptureResponseHeaders, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.OracleMdaSetDbStatementForText, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Traces.InstrumentationOptions.SqlClientSetDbStatementForText, null);
}
}

View File

@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>
using System;
using System.Linq;
using System.Web.Mvc;
using TestApplication.AspNet.NetFramework.Helpers;
@ -32,6 +33,19 @@ public class HomeController : Controller
ViewBag.TracerAssemblies = AssembliesHelper.GetLoadedTracesAssemblies();
ViewBag.AllAssemblies = AssembliesHelper.GetLoadedAssemblies();
try
{
var headers = HttpContext.Response.Headers;
headers.Add("Custom-Response-Test-Header1", "Test-Value4");
headers.Add("Custom-Response-Test-Header2", "Test-Value5");
headers.Add("Custom-Response-Test-Header3", "Test-Value6");
}
catch (PlatformNotSupportedException)
{
// do nothing, it can be raised on classic mode
}
return View();
}
}

View File

@ -14,7 +14,9 @@
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using System.Net.Http;
using System.Net;
using System.Text;
using System.Web.Http;
namespace TestApplication.AspNet.NetFramework.Controllers;
@ -22,9 +24,13 @@ namespace TestApplication.AspNet.NetFramework.Controllers;
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
public HttpResponseMessage Get()
{
return new string[] { "value1", "value2" };
var response = Request.CreateResponse(HttpStatusCode.OK, new[] { "value1", "value2" });
response.Headers.Add("Custom-Response-Test-Header1", "Test-Value1");
response.Headers.Add("Custom-Response-Test-Header2", "Test-Value2");
response.Headers.Add("Custom-Response-Test-Header3", "Test-Value3");
return response;
}
// GET api/values/5

View File

@ -9,7 +9,7 @@
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.9.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.3.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
@ -35,7 +35,7 @@
<system.web>
<compilation>
<assemblies>
<add assembly="System.Web.Mvc, Version=5.2.9.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Web.Mvc, Version=5.3.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</assemblies>
</compilation>
</system.web>

View File

@ -0,0 +1,27 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
using Greet;
using Grpc.Core;
namespace TestApplication.GrpcNetClient;
public class GreeterService : Greeter.GreeterBase
{
public override async Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
var headers = new Metadata
{
{ "Custom-Response-Test-Header1", "Test-Value1" },
{ "Custom-Response-Test-Header2", "Test-Value2" },
{ "Custom-Response-Test-Header3", "Test-Value3" }
};
await context.WriteResponseHeadersAsync(headers);
return new HelloReply
{
Message = "Hello " + request.Name
};
}
}

View File

@ -1,43 +1,78 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
#if NETFRAMEWORK
using System.Net;
using System.Net.Http;
#endif
using Greet;
using Grpc.Core;
using Grpc.Net.Client;
using IntegrationTests.Helpers;
#if !NETFRAMEWORK
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
#endif
using TestApplication.GrpcNetClient;
#if NETFRAMEWORK
using Grpc.Net.Client.Web;
#endif
using TestApplication.Shared;
namespace TestApplication.GrpcNetClient;
ConsoleHelper.WriteSplashScreen(args);
public static class Program
var port = TcpPortProvider.GetOpenPort();
#if !NETFRAMEWORK
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
public static async Task Main(string[] args)
serverOptions.ConfigureEndpointDefaults(listenOptions =>
{
ConsoleHelper.WriteSplashScreen(args);
listenOptions.Protocols = HttpProtocols.Http2;
});
serverOptions.Listen(IPAddress.Loopback, port);
});
const string uri = "http://dummyAdress";
#if NETFRAMEWORK
var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions
{
HttpHandler = new GrpcWebHandler(new HttpClientHandler())
});
#else
var channel = GrpcChannel.ForAddress(uri);
// Add services to the container.
builder.Services.AddGrpc();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<GreeterService>();
await app.StartAsync();
#endif
try
{
var greeterClient = new Greeter.GreeterClient(channel);
await greeterClient.SayHelloAsync(new HelloRequest());
}
catch (RpcException e)
{
Console.WriteLine(e);
}
}
var uri = $"http://localhost:{port}";
#if NETFRAMEWORK
var channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions
{
HttpHandler = new GrpcWebHandler(new HttpClientHandler())
});
#else
var channel = GrpcChannel.ForAddress(uri);
#endif
var headers = new Metadata
{
{ "Custom-Request-Test-Header1", "Test-Value1" },
{ "Custom-Request-Test-Header2", "Test-Value2" },
{ "Custom-Request-Test-Header3", "Test-Value3" }
};
try
{
var greeterClient = new Greeter.GreeterClient(channel);
await greeterClient.SayHelloAsync(new HelloRequest { Name = "Test user" }, headers);
}
catch (RpcException e)
{
Console.WriteLine(e);
}
#if !NETFRAMEWORK
app.Lifetime.StopApplication();
#endif

View File

@ -21,7 +21,9 @@ service Greeter {
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}

View File

@ -3,15 +3,19 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<!-- No warn is needed for autogenerated code by protobuf: CS8981 - The type name only contains lower-cased ascii characters. -->
<NoWarn Condition="'$(TargetFramework)' == 'net8.0' OR '$(TargetFramework)' == 'net7.0'">CS8981</NoWarn>
<NoWarn Condition="'$(TargetFramework)' == 'net8.0' OR '$(TargetFramework)' == 'net7.0'">CS8981;$(NoWarn)</NoWarn>
<!-- Allow to downgrade Grpc.Tools on MacOS 11. -->
<NoWarn Condition="'$(GITHUB_RUNNER_SYSTEM)' == 'macos-11'">NU1605;$(NoWarn)</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" VersionOverride="3.22.5" />
<PackageReference Include="Google.Protobuf" Condition="'$(TargetFramework)' == 'net462' or ( '$(LibraryVersion)' != '' and '$(LibraryVersion)' &lt;= '2.54.0' )" />
<PackageReference Include="Grpc.AspNetCore" Condition="'$(TargetFramework)' != 'net462'" VersionOverride="$(LibraryVersion)" />
<PackageReference Include="Grpc.Net.Client" VersionOverride="$(LibraryVersion)" />
<PackageReference Include="Grpc.Net.Client.Web" Condition="'$(TargetFramework)' == 'net462'" VersionOverride="$(LibraryVersion)" />
<PackageReference Include="Grpc.Tools" VersionOverride="2.44.0" />
<PackageReference Include="Grpc.Tools" Condition="'$(TargetFramework)' == 'net462' or '$(GITHUB_RUNNER_SYSTEM)' == 'macos-11'" />
<!-- Workaround! Microsoft.Extensions.Logging.Abstractions v.8.0.0 is minimal version supported by auto instrumentation.
Grpc.Net.Client references older version. It prevents to load required version from Additional Dependencies store-->
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" VersionOverride="8.0.0" />
@ -19,7 +23,12 @@
</ItemGroup>
<ItemGroup>
<Protobuf Include="Proto\greet.proto" GrpcServices="Client" />
<Protobuf Include="Proto\greet.proto" GrpcServices="Client,Server" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\IntegrationTests\Helpers\TcpPortProvider.cs">
<Link>TcpPortProvider.cs</Link>
</Compile>
</ItemGroup>
</Project>

View File

@ -16,8 +16,11 @@ public class Program
var request = (HttpWebRequest)WebRequest.Create($"{address}/test");
request.Method = "POST";
request.ContentType = "text/plain";
request.Headers.Add("Custom-Request-Test-Header1", "Test-Value1");
request.Headers.Add("Custom-Request-Test-Header2", "Test-Value2");
request.Headers.Add("Custom-Request-Test-Header3", "Test-Value3");
using (Stream requestStream = request.GetRequestStream())
using (var requestStream = request.GetRequestStream())
{
var content = Encoding.UTF8.GetBytes("Ping");

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
using System.Net;
using System.Runtime.Remoting.Contexts;
using System.Text;
using TestApplication.Http.NetFramework.Helpers;
@ -12,13 +13,13 @@ public class TestServer : IDisposable
private readonly HttpListener _listener;
private readonly Thread _listenerThread;
public TestServer(string sufix)
public TestServer(string suffix)
{
Port = TcpPortProvider.GetOpenPort();
_listener = new HttpListener();
_listener.Start();
var prefix = new UriBuilder("http", "localhost", Port, sufix).ToString();
var prefix = new UriBuilder("http", "localhost", Port, suffix).ToString();
_listener.Prefixes.Add(prefix);
Console.WriteLine($"[LISTENER] Listening on '{prefix}'");
@ -53,6 +54,9 @@ public class TestServer : IDisposable
// NOTE: HttpStreamRequest doesn't support Transfer-Encoding: Chunked
// (Setting content-length avoids that)
ctx.Response.Headers.Add("Custom-Response-Test-Header1", "Test-Value1");
ctx.Response.Headers.Add("Custom-Response-Test-Header2", "Test-Value2");
ctx.Response.Headers.Add("Custom-Response-Test-Header3", "Test-Value3");
ctx.Response.ContentType = "text/plain";
var buffer = Encoding.UTF8.GetBytes("Pong");
ctx.Response.ContentLength64 = buffer.LongLength;
@ -79,7 +83,7 @@ public class TestServer : IDisposable
}
catch (Exception ex)
{
// somethig unexpected happened
// something unexpected happened
// log instead of crashing the thread
Console.WriteLine("[EXCEPTION]: {0}", ex.Message);
Console.WriteLine(ex);

View File

@ -32,6 +32,9 @@ public class Program
var address = addressFeature?.Addresses.First();
var dnsAddress = address?.Replace("127.0.0.1", "localhost"); // needed to force DNS resolution to test metrics
using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Custom-Request-Test-Header1", "Test-Value1");
httpClient.DefaultRequestHeaders.Add("Custom-Request-Test-Header2", "Test-Value2");
httpClient.DefaultRequestHeaders.Add("Custom-Request-Test-Header3", "Test-Value3");
httpClient.GetAsync($"{dnsAddress}/test").Wait();
httpClient.GetAsync($"{dnsAddress}/exception").Wait();
#if NET8_0_OR_GREATER

View File

@ -40,6 +40,10 @@ public class Startup
activity?.SetTag("test_tag", "test_value");
}
context.Response.Headers.Append("Custom-Response-Test-Header1", "Test-Value1");
context.Response.Headers.Append("Custom-Response-Test-Header2", "Test-Value2");
context.Response.Headers.Append("Custom-Response-Test-Header3", "Test-Value3");
await context.Response.WriteAsync("Pong");
}))
.Map(