diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml
index dd360f38df..19e7d3b814 100644
--- a/.github/workflows/markdownlint.yml
+++ b/.github/workflows/markdownlint.yml
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: run markdownlint
- uses: DavidAnson/markdownlint-cli2-action@ce4853d43830c74c1753b39f3cf40f71c2031eb9 # v23.0.0
+ uses: DavidAnson/markdownlint-cli2-action@6b51ade7a9e4a75a7ad929842dd298a3804ebe8b # v23.1.0
with:
globs: |
**/*.md
diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml
index d67b6ae584..5c425887d2 100644
--- a/.github/workflows/publish-packages.yml
+++ b/.github/workflows/publish-packages.yml
@@ -135,7 +135,7 @@ jobs:
# renovate: datasource=nuget depName=dotnet-validate
DOTNET_VALIDATE_VERSION: '0.0.1-preview.582'
# renovate: datasource=nuget depName=Meziantou.Framework.NuGetPackageValidation.Tool
- MEZIANTOU_VALIDATE_NUGET_PACKAGE_VERSION: '1.0.47'
+ MEZIANTOU_VALIDATE_NUGET_PACKAGE_VERSION: '1.0.49'
run: |
dotnet tool install --global dotnet-validate --version ${env:DOTNET_VALIDATE_VERSION} --allow-roll-forward
dotnet tool install --global Meziantou.Framework.NuGetPackageValidation.Tool --version ${env:MEZIANTOU_VALIDATE_NUGET_PACKAGE_VERSION} --allow-roll-forward
@@ -179,7 +179,7 @@ jobs:
run: dotnet nuget push *.nupkg --api-key ${env:API_KEY} --skip-duplicate --source ${env:SOURCE}
- name: NuGet log in
- uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544 # v1.1.0
+ uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
env:
NUGET_USER_EXISTS: ${{ secrets.NUGET_USER != '' }}
id: nuget-login
diff --git a/.github/workflows/sanitycheck.yml b/.github/workflows/sanitycheck.yml
index 360f754bd5..76ae6e04fd 100644
--- a/.github/workflows/sanitycheck.yml
+++ b/.github/workflows/sanitycheck.yml
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: check for typos
- uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1
+ uses: crate-ci/typos@7c572958218557a3272c2d6719629443b5cc26fd # v1.45.2
run-sanitycheck:
runs-on: ubuntu-24.04
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3127a5fdba..892ae9682d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -33,8 +33,8 @@
-
-
+
+
@@ -87,17 +87,17 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
@@ -107,7 +107,7 @@
-
+
@@ -117,12 +117,12 @@
-
+
-
+
diff --git a/build/BannedSymbols.txt b/build/BannedSymbols.txt
index 2a2ab5b8c0..e3af14dac0 100644
--- a/build/BannedSymbols.txt
+++ b/build/BannedSymbols.txt
@@ -44,6 +44,8 @@ M:System.SByte.TryParse(System.ReadOnlySpan{System.Char},System.SByte@); Use ove
M:System.SByte.TryParse(System.String,System.SByte@); Use overloads that specify CultureInfo.InvariantCulture.
M:System.Single.TryParse(System.ReadOnlySpan{System.Char},System.Single@); Use overloads that specify CultureInfo.InvariantCulture.
M:System.Single.TryParse(System.String,System.Single@); Use overloads that specify CultureInfo.InvariantCulture.
+M:System.String.Equals(System.String); Use the static string.Equals(string, string) method instead to avoid potential NullReferenceException.
+M:System.String.Equals(System.String,System.StringComparison); Use the static string.Equals(string, string, StringComparison) method instead to avoid potential NullReferenceException.
M:System.TimeOnly.TryParse(System.ReadOnlySpan{System.Char},System.TimeOnly@); Use overloads that specify CultureInfo.InvariantCulture.
M:System.TimeOnly.TryParse(System.String,System.TimeOnly@); Use overloads that specify CultureInfo.InvariantCulture.
M:System.TimeSpan.TryParse(System.ReadOnlySpan{System.Char},System.TimeSpan@); Use overloads that specify CultureInfo.InvariantCulture.
diff --git a/opentelemetry-dotnet-contrib.slnx b/opentelemetry-dotnet-contrib.slnx
index f180f16c77..26029a8ef8 100644
--- a/opentelemetry-dotnet-contrib.slnx
+++ b/opentelemetry-dotnet-contrib.slnx
@@ -213,6 +213,7 @@
+
diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs
index e8c503a083..9213bf789b 100644
--- a/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs
+++ b/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs
@@ -76,16 +76,16 @@ public string EtwSession
}
public bool PrivatePreviewEnableTraceLoggingDynamic => this.parts.TryGetValue(nameof(this.PrivatePreviewEnableTraceLoggingDynamic), out var value)
- && bool.TrueString.Equals(value, StringComparison.OrdinalIgnoreCase);
+ && string.Equals(bool.TrueString, value, StringComparison.OrdinalIgnoreCase);
public bool PrivatePreviewEnableOtlpProtobufEncoding => this.parts.TryGetValue(nameof(this.PrivatePreviewEnableOtlpProtobufEncoding), out var value)
- && bool.TrueString.Equals(value, StringComparison.OrdinalIgnoreCase);
+ && string.Equals(bool.TrueString, value, StringComparison.OrdinalIgnoreCase);
public bool PrivatePreviewEnableUserEvents => this.parts.TryGetValue(nameof(this.PrivatePreviewEnableUserEvents), out var value)
- && bool.TrueString.Equals(value, StringComparison.OrdinalIgnoreCase);
+ && string.Equals(bool.TrueString, value, StringComparison.OrdinalIgnoreCase);
public bool PrivatePreviewEnableAFDCorrelationIdEnrichment => this.parts.TryGetValue(nameof(this.PrivatePreviewEnableAFDCorrelationIdEnrichment), out var value)
- && bool.TrueString.Equals(value, StringComparison.OrdinalIgnoreCase);
+ && string.Equals(bool.TrueString, value, StringComparison.OrdinalIgnoreCase);
public int PrivatePreviewLogMessagePackStringSizeLimit =>
!this.parts.TryGetValue(nameof(this.PrivatePreviewLogMessagePackStringSizeLimit), out var value)
diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterOptions.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterOptions.cs
index 6b4930cad9..1fc4e8b744 100644
--- a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterOptions.cs
+++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterOptions.cs
@@ -50,8 +50,8 @@ public IReadOnlyDictionary? PrepopulatedMetricDimensions
foreach (var entry in value)
{
- if (entry.Key.Equals(GenevaMetricExporter.DimensionKeyForCustomMonitoringAccount, StringComparison.OrdinalIgnoreCase) ||
- entry.Key.Equals(GenevaMetricExporter.DimensionKeyForCustomMetricsNamespace, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(entry.Key, GenevaMetricExporter.DimensionKeyForCustomMonitoringAccount, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(entry.Key, GenevaMetricExporter.DimensionKeyForCustomMetricsNamespace, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException($"The dimension: {entry.Key} is reserved and cannot be used as a prepopulated dimension.");
}
diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/TlvMetricExporter.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/TlvMetricExporter.cs
index ac0e55398a..38caf916b3 100644
--- a/src/OpenTelemetry.Exporter.Geneva/Metrics/TlvMetricExporter.cs
+++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/TlvMetricExporter.cs
@@ -646,8 +646,8 @@ private void SerializeDimensionsAndGetCustomAccountNamespace(in ReadOnlyTagColle
// TODO: Data Validation
}
- if (tag.Key.Equals(GenevaMetricExporter.DimensionKeyForCustomMonitoringAccount, StringComparison.OrdinalIgnoreCase) ||
- tag.Key.Equals(GenevaMetricExporter.DimensionKeyForCustomMetricsNamespace, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(tag.Key, GenevaMetricExporter.DimensionKeyForCustomMonitoringAccount, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(tag.Key, GenevaMetricExporter.DimensionKeyForCustomMetricsNamespace, StringComparison.OrdinalIgnoreCase))
{
reservedTags++;
continue;
@@ -667,7 +667,7 @@ private void SerializeDimensionsAndGetCustomAccountNamespace(in ReadOnlyTagColle
// Serialize MetricPoint Dimension values
foreach (var tag in tags)
{
- if (tag.Key.Equals(GenevaMetricExporter.DimensionKeyForCustomMonitoringAccount, StringComparison.OrdinalIgnoreCase) && tag.Value is string metricsAccount)
+ if (string.Equals(tag.Key, GenevaMetricExporter.DimensionKeyForCustomMonitoringAccount, StringComparison.OrdinalIgnoreCase) && tag.Value is string metricsAccount)
{
if (!string.IsNullOrWhiteSpace(metricsAccount))
{
@@ -677,7 +677,7 @@ private void SerializeDimensionsAndGetCustomAccountNamespace(in ReadOnlyTagColle
continue;
}
- if (tag.Key.Equals(GenevaMetricExporter.DimensionKeyForCustomMetricsNamespace, StringComparison.OrdinalIgnoreCase) && tag.Value is string metricsNamespace)
+ if (string.Equals(tag.Key, GenevaMetricExporter.DimensionKeyForCustomMetricsNamespace, StringComparison.OrdinalIgnoreCase) && tag.Value is string metricsNamespace)
{
if (!string.IsNullOrWhiteSpace(metricsNamespace))
{
diff --git a/src/OpenTelemetry.Exporter.OneCollector/CHANGELOG.md b/src/OpenTelemetry.Exporter.OneCollector/CHANGELOG.md
index 7aa93f468e..50f5618af3 100644
--- a/src/OpenTelemetry.Exporter.OneCollector/CHANGELOG.md
+++ b/src/OpenTelemetry.Exporter.OneCollector/CHANGELOG.md
@@ -7,7 +7,8 @@
Released 2026-Apr-21
* Limit how much of the response body is read when export fails using the HTTP
- JSON transport and informational logging is enabled.
+ JSON transport and informational logging is enabled to resolve
+ [GHSA-55m9-299j-53c7](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/security/advisories/GHSA-55m9-299j-53c7).
([#4117](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4117))
* Updated OpenTelemetry core component version(s) to `1.15.3`.
diff --git a/src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSServiceType.cs b/src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSServiceType.cs
index 98f64525ab..f19850653f 100644
--- a/src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSServiceType.cs
+++ b/src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSServiceType.cs
@@ -15,23 +15,23 @@ internal class AWSServiceType
internal const string BedrockRuntimeService = "Bedrock Runtime";
internal static bool IsDynamoDbService(string service)
- => DynamoDbService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(DynamoDbService, service, StringComparison.OrdinalIgnoreCase);
internal static bool IsSqsService(string service)
- => SQSService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(SQSService, service, StringComparison.OrdinalIgnoreCase);
internal static bool IsSnsService(string service)
- => SNSService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(SNSService, service, StringComparison.OrdinalIgnoreCase);
internal static bool IsBedrockService(string service)
- => BedrockService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(BedrockService, service, StringComparison.OrdinalIgnoreCase);
internal static bool IsBedrockAgentService(string service)
- => BedrockAgentService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(BedrockAgentService, service, StringComparison.OrdinalIgnoreCase);
internal static bool IsBedrockAgentRuntimeService(string service)
- => BedrockAgentRuntimeService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(BedrockAgentRuntimeService, service, StringComparison.OrdinalIgnoreCase);
internal static bool IsBedrockRuntimeService(string service)
- => BedrockRuntimeService.Equals(service, StringComparison.OrdinalIgnoreCase);
+ => string.Equals(BedrockRuntimeService, service, StringComparison.OrdinalIgnoreCase);
}
diff --git a/src/OpenTelemetry.Instrumentation.AWS/Implementation/Utils.cs b/src/OpenTelemetry.Instrumentation.AWS/Implementation/Utils.cs
index 3919ea16bd..a7dce8578e 100644
--- a/src/OpenTelemetry.Instrumentation.AWS/Implementation/Utils.cs
+++ b/src/OpenTelemetry.Instrumentation.AWS/Implementation/Utils.cs
@@ -11,7 +11,7 @@ internal class Utils
{
foreach (var tag in activity.TagObjects)
{
- if (tag.Key.Equals(tagName, StringComparison.Ordinal))
+ if (string.Equals(tag.Key, tagName, StringComparison.Ordinal))
{
return tag.Value;
}
@@ -20,15 +20,13 @@ internal class Utils
return null;
}
- internal static string RemoveSuffix(string originalString, string suffix)
- {
- return string.IsNullOrEmpty(originalString)
+ internal static string RemoveSuffix(string originalString, string suffix) =>
+ string.IsNullOrEmpty(originalString)
? string.Empty
: originalString.EndsWith(suffix, StringComparison.Ordinal)
?
originalString.Substring(0, originalString.Length - suffix.Length)
: originalString;
- }
///
/// Removes amazon prefix from service name. There are two type of service name.
@@ -39,14 +37,10 @@ internal static string RemoveSuffix(string originalString, string suffix)
/// Name of the service.
/// String after removing Amazon prefix.
internal static string RemoveAmazonPrefixFromServiceName(string serviceName)
- {
- return RemovePrefix(RemovePrefix(serviceName, "Amazon"), ".");
- }
+ => RemovePrefix(RemovePrefix(serviceName, "Amazon"), ".");
- private static string RemovePrefix(string originalString, string prefix)
- {
- return string.IsNullOrEmpty(originalString) ? string.Empty :
+ private static string RemovePrefix(string originalString, string prefix) =>
+ string.IsNullOrEmpty(originalString) ? string.Empty :
originalString.StartsWith(prefix, StringComparison.Ordinal) ? originalString.Substring(prefix.Length) :
originalString;
- }
}
diff --git a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md
index 3d30b5a60d..ee9ae16b5a 100644
--- a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md
@@ -163,6 +163,7 @@ Released 2024-Apr-17
`?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
disable this redaction by setting the environment variable
`OTEL_DOTNET_EXPERIMENTAL_ASPNET_DISABLE_URL_QUERY_REDACTION` to `true`.
+ Resolves [GHSA-vh2m-22xx-q94f](https://github.com/advisories/GHSA-vh2m-22xx-q94f).
([#1656](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1656))
## 1.8.0-beta.1
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs
index d7d02ecdc3..a9ec2c8939 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentation.cs
@@ -19,7 +19,7 @@ internal sealed class AspNetCoreInstrumentation : IDisposable
"Microsoft.AspNetCore.Hosting.UnhandledException"
];
- private readonly Func isEnabled = (eventName, _, _)
+ private readonly Func isEnabled = static (eventName, _, _)
=> DiagnosticSourceEvents.Contains(eventName);
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
@@ -32,7 +32,5 @@ public AspNetCoreInstrumentation(HttpInListener httpInListener)
///
public void Dispose()
- {
- this.diagnosticSourceSubscriber?.Dispose();
- }
+ => this.diagnosticSourceSubscriber?.Dispose();
}
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationTracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationTracerProviderBuilderExtensions.cs
index d18e17357a..8b2a728b5e 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationTracerProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationTracerProviderBuilderExtensions.cs
@@ -75,9 +75,7 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation(
return builder.AddInstrumentation(sp =>
{
var options = sp.GetRequiredService>().Get(name);
-
- return new AspNetCoreInstrumentation(
- new HttpInListener(options));
+ return new AspNetCoreInstrumentation(new HttpInListener(options));
});
}
@@ -102,9 +100,9 @@ private static void AddAspNetCoreInstrumentationSources(
string optionsName,
IServiceProvider? serviceProvider = null)
{
- // For .NET7.0 onwards activity will be created using activitySource.
+ // For .NET 7.0+ the activity will be created using activitySource.
// https://github.com/dotnet/aspnetcore/blob/bf3352f2422bf16fa3ca49021f0e31961ce525eb/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L327
- // For .NET6.0 and below, we will continue to use legacy way.
+ // For .NET 6.0 and below, we will continue to use legacy way.
if (HttpInListener.Net7OrGreater)
{
// TODO: Check with .NET team to see if this can be prevented
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
index 4c934aecb9..89bcc1fdfb 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md
@@ -2,6 +2,12 @@
## Unreleased
+* Avoid duplicative work to add tags to traces when they are already natively supported
+ by ASP.NET Core itself. When using ASP.NET Core 10, performance can be
+ improved by setting the `Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData`
+ AppContext switch to `false` (its default value is `true`).
+ ([#3993](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3993))
+
## 1.15.2
Released 2026-Apr-21
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
index c452c1fad9..8cea0212b7 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
@@ -30,6 +30,8 @@ internal class HttpInListener : ListenerHandler
#pragma warning restore IDE0370 // Suppression is unnecessary
internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString());
internal static readonly bool Net7OrGreater = Environment.Version.Major >= 7;
+ internal static readonly bool Net10OrGreater = Environment.Version.Major >= 10;
+ internal static readonly bool Net11OrGreater = Environment.Version.Major >= 11;
private const string DiagnosticSourceName = "Microsoft.AspNetCore";
@@ -47,6 +49,7 @@ internal class HttpInListener : ListenerHandler
private static readonly PropertyFetcher ExceptionPropertyFetcher = new("Exception");
private readonly AspNetCoreTraceInstrumentationOptions options;
+ private readonly bool nativeAspNetCoreOpenTelemetryEnabled;
public HttpInListener(AspNetCoreTraceInstrumentationOptions options)
: base(DiagnosticSourceName)
@@ -54,6 +57,7 @@ public HttpInListener(AspNetCoreTraceInstrumentationOptions options)
Guard.ThrowIfNull(options);
this.options = options;
+ this.nativeAspNetCoreOpenTelemetryEnabled = AspNetCoreHasNativeOpenTelemetryTags();
}
public override void OnEventWritten(string name, object? payload)
@@ -63,24 +67,18 @@ public override void OnEventWritten(string name, object? payload)
switch (name)
{
case OnStartEvent:
- {
- this.OnStartActivity(activity, payload);
- }
-
+ this.OnStartActivity(activity, payload);
break;
- case OnStopEvent:
- {
- this.OnStopActivity(activity, payload);
- }
+ case OnStopEvent:
+ this.OnStopActivity(activity, payload);
break;
+
case OnUnhandledHostingExceptionEvent:
case OnUnHandledDiagnosticsExceptionEvent:
- {
- this.OnException(activity, payload);
- }
-
+ this.OnException(activity, payload);
break;
+
default:
break;
}
@@ -104,6 +102,8 @@ public void OnStartActivity(Activity activity, object? payload)
return;
}
+ string? path = null;
+
// Ensure context extraction irrespective of sampling decision
var request = context.Request;
var textMapPropagator = Propagators.DefaultTextMapPropagator;
@@ -143,6 +143,14 @@ public void OnStartActivity(Activity activity, object? payload)
// Set IsAllDataRequested to false for the activity created by the framework to only export the sibling activity and not the framework activity
activity.IsAllDataRequested = false;
activity = newOne;
+
+ if (Net11OrGreater)
+ {
+ // ASP.NET Core 11 will set the url.path attribute, but only if the
+ // activity is sampled. As we skip setting it for ASP.NET Core 11
+ // we need to set it here otherwise it will not get set at all.
+ SetUrlPathAttribute(request, activity);
+ }
}
Baggage.Current = ctx.Baggage;
@@ -176,19 +184,40 @@ public void OnStartActivity(Activity activity, object? payload)
ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Server);
}
- var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
- TelemetryHelper.RequestDataHelper.SetActivityDisplayName(activity, request.Method);
+ if (!Net11OrGreater || !this.nativeAspNetCoreOpenTelemetryEnabled)
+ {
+ TelemetryHelper.RequestDataHelper.SetActivityDisplayName(activity, request.Method);
+ }
- // see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
+ // ASP.NET Core does not support OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS so we
+ // still need to set the display name and HTTP method tag so that any override
+ // by the user is honoured. See https://github.com/dotnet/aspnetcore/issues/65873.
+ TelemetryHelper.RequestDataHelper.SetHttpMethodTag(activity, request.Method);
- if (request.Host.HasValue)
+ if (!Net10OrGreater || !this.nativeAspNetCoreOpenTelemetryEnabled)
{
- activity.SetTag(SemanticConventions.AttributeServerAddress, request.Host.Host);
+ if (request.Host.HasValue)
+ {
+ activity.SetTag(SemanticConventions.AttributeServerAddress, request.Host.Value);
- if (request.Host.Port.HasValue)
+ if (request.Host.Port is { } port)
+ {
+ activity.SetTag(SemanticConventions.AttributeServerPort, port);
+ }
+ }
+
+ if (request.Headers.TryGetValue("User-Agent", out var values))
{
- activity.SetTag(SemanticConventions.AttributeServerPort, request.Host.Port.Value);
+ var userAgent = values.Count > 0 ? values[0] : null;
+ if (!string.IsNullOrEmpty(userAgent))
+ {
+ activity.SetTag(SemanticConventions.AttributeUserAgentOriginal, userAgent);
+ }
}
+
+ activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
+
+ SetUrlPathAttribute(request, activity);
}
if (request.QueryString.HasValue)
@@ -203,21 +232,8 @@ public void OnStartActivity(Activity activity, object? payload)
}
}
- TelemetryHelper.RequestDataHelper.SetHttpMethodTag(activity, request.Method);
-
- activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
- activity.SetTag(SemanticConventions.AttributeUrlPath, path);
activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, RequestDataHelper.GetHttpProtocolVersion(request.Protocol));
- if (request.Headers.TryGetValue("User-Agent", out var values))
- {
- var userAgent = values.Count > 0 ? values[0] : null;
- if (!string.IsNullOrEmpty(userAgent))
- {
- activity.SetTag(SemanticConventions.AttributeUserAgentOriginal, userAgent);
- }
- }
-
try
{
this.options.EnrichWithHttpRequest?.Invoke(activity, request);
@@ -227,6 +243,13 @@ public void OnStartActivity(Activity activity, object? payload)
AspNetCoreInstrumentationEventSource.Log.EnrichmentException(nameof(HttpInListener), nameof(this.OnStartActivity), activity.OperationName, ex);
}
}
+
+ void SetUrlPathAttribute(HttpRequest request, Activity activity)
+ {
+ // See the spec: https://github.com/open-telemetry/semantic-conventions/blob/v1.40.0/docs/http/http-spans.md
+ path ??= (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
+ activity.SetTag(SemanticConventions.AttributeUrlPath, path);
+ }
}
public void OnStopActivity(Activity activity, object? payload)
@@ -241,25 +264,28 @@ public void OnStopActivity(Activity activity, object? payload)
var response = context.Response;
-#if !NETSTANDARD
- var routePattern = context.GetHttpRoute();
- if (!string.IsNullOrEmpty(routePattern))
+ if (!Net11OrGreater || !this.nativeAspNetCoreOpenTelemetryEnabled)
{
- TelemetryHelper.RequestDataHelper.SetActivityDisplayName(activity, context.Request.Method, routePattern);
- activity.SetTag(SemanticConventions.AttributeHttpRoute, routePattern);
- }
+#if NET
+ var routePattern = context.GetHttpRoute();
+ if (!string.IsNullOrEmpty(routePattern))
+ {
+ TelemetryHelper.RequestDataHelper.SetActivityDisplayName(activity, context.Request.Method, routePattern);
+ activity.SetTag(SemanticConventions.AttributeHttpRoute, routePattern);
+ }
#endif
- activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
+ activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
- if (this.options.EnableGrpcAspNetCoreSupport && TryGetGrpcMethod(activity, out var grpcMethod))
- {
- AddGrpcAttributes(activity, grpcMethod, context);
+ if (activity.Status == ActivityStatusCode.Unset)
+ {
+ activity.SetStatus(SpanHelper.ResolveActivityStatusForHttpStatusCode(activity.Kind, response.StatusCode));
+ }
}
- if (activity.Status == ActivityStatusCode.Unset)
+ if (this.options.EnableGrpcAspNetCoreSupport && TryGetGrpcMethod(activity, out var grpcMethod))
{
- activity.SetStatus(SpanHelper.ResolveActivityStatusForHttpStatusCode(activity.Kind, response.StatusCode));
+ AddGrpcAttributes(activity, grpcMethod, context);
}
try
@@ -394,4 +420,28 @@ private static void AddGrpcAttributes(Activity activity, string grpcMethod, Http
}
}
}
+
+ // ASP.NET Core 10 does not generate OpenTelemetry tags by default so we can only take
+ // the optimal path if the user has explicitly opted-out of suppressing the OpenTelemetry data.
+ private static bool AspNetCoreHasNativeOpenTelemetryTags()
+ {
+#if NET10_0_OR_GREATER
+ if (AppContext.TryGetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", out var suppressed))
+ {
+ return !suppressed;
+ }
+#endif
+#if NET10_0
+ // In ASP.NET Core 10 OpenTelemetry tags are suppressed by default,
+ // see https://github.com/dotnet/aspnetcore/blob/7387de91234d3ef751fa50b3d1bfede4130213ff/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L59-L67.
+ return false;
+#elif NET11_0_OR_GREATER
+ // In ASP.NET Core 11+ OpenTelemetry tags are emitted by default,
+ // see https://github.com/dotnet/aspnetcore/blob/655f41d52f2fc75992eac41496b8e9cc119e1b54/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L59-L67.
+ return true;
+#else
+ // In ASP.NET Core 8 and 9 the feature switch does not exist and there are no native OpenTelemetry tags
+ return false;
+#endif
+ }
}
diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md
index 6a503bdea0..0200fbf7e8 100644
--- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+* Fix SQL query text sanitization for malformed bracketed identifiers in `FROM`
+ clauses to avoid leaking following literal values.
+ ([#4317](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4317))
+
## 1.15.1-beta.1
Released 2026-Apr-21
diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
index 17bb0ea404..49f4235554 100644
--- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md
@@ -108,6 +108,7 @@ Released 2024-Apr-12
`?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`.
+ Resolves [GHSA-vh2m-22xx-q94f](https://github.com/advisories/GHSA-vh2m-22xx-q94f).
([#5532](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5532))
## 1.8.0
diff --git a/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md
index 30b280b3b1..99335df9fb 100644
--- a/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md
@@ -2,6 +2,15 @@
## Unreleased
+* Add instrumentation scope version and schema URL to metrics.
+ ([#4088](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4088))
+
+* Removed the `process.cpu.count` metric as it is not part of
+ the semantic conventions. Use the
+ [`dotnet.process.cpu.count`](https://github.com/open-telemetry/semantic-conventions/blob/v1.41.0/docs/runtime/dotnet-metrics.md#metric-dotnetprocesscpucount)
+ metric as an alternative.
+ ([#4088](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4088))
+
## 1.15.1-beta.1
Released 2026-Apr-21
diff --git a/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs
index de2f9e97df..71160e78c3 100644
--- a/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs
@@ -21,7 +21,7 @@ public static MeterProviderBuilder AddProcessInstrumentation(
{
Guard.ThrowIfNull(builder);
- builder.AddMeter(ProcessMetrics.MeterName);
+ builder.AddMeter(ProcessMetrics.MeterInstance.Name);
return builder.AddInstrumentation(() => new ProcessMetrics());
}
}
diff --git a/src/OpenTelemetry.Instrumentation.Process/OpenTelemetry.Instrumentation.Process.csproj b/src/OpenTelemetry.Instrumentation.Process/OpenTelemetry.Instrumentation.Process.csproj
index 49f9e9cb35..fd75263cbc 100644
--- a/src/OpenTelemetry.Instrumentation.Process/OpenTelemetry.Instrumentation.Process.csproj
+++ b/src/OpenTelemetry.Instrumentation.Process/OpenTelemetry.Instrumentation.Process.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs
index 05142e8bc1..7f0d993495 100644
--- a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs
+++ b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs
@@ -2,21 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
using System.Diagnostics.Metrics;
-using System.Reflection;
-using OpenTelemetry.Internal;
using Diagnostics = System.Diagnostics;
namespace OpenTelemetry.Instrumentation.Process;
internal sealed class ProcessMetrics
{
- internal static readonly Assembly Assembly = typeof(ProcessMetrics).Assembly;
- internal static readonly AssemblyName AssemblyName = Assembly.GetName();
-#pragma warning disable IDE0370 // Suppression is unnecessary
- internal static readonly string MeterName = AssemblyName.Name!;
-#pragma warning restore IDE0370 // Suppression is unnecessary
-
- private static readonly Meter MeterInstance = new(MeterName, Assembly.GetPackageVersion());
+ internal static readonly Version SemanticConventionsVersion = new(1, 25, 0);
+ internal static readonly Meter MeterInstance = Metrics.MeterFactory.Create(SemanticConventionsVersion);
static ProcessMetrics()
{
@@ -54,15 +47,6 @@ static ProcessMetrics()
unit: "s",
description: "Total CPU seconds broken down by different states.");
- MeterInstance.CreateObservableUpDownCounter(
- "process.cpu.count",
- () =>
- {
- return Environment.ProcessorCount;
- },
- unit: "{processors}",
- description: "The number of processors (CPU cores) available to the current process.");
-
MeterInstance.CreateObservableUpDownCounter(
"process.thread.count",
() =>
diff --git a/src/OpenTelemetry.Instrumentation.Process/README.md b/src/OpenTelemetry.Instrumentation.Process/README.md
index 69b95a2cf0..6e17593a7c 100644
--- a/src/OpenTelemetry.Instrumentation.Process/README.md
+++ b/src/OpenTelemetry.Instrumentation.Process/README.md
@@ -118,24 +118,6 @@ Gets the user processor time for this process.
* [Process.PrivilegedProcessorTime](https://learn.microsoft.com/dotnet/api/system.diagnostics.process.privilegedprocessortime):
Gets the privileged processor time for this process.
-### process.cpu.count
-
-The number of processors (CPU cores) available to the current process.
-
-| Units | Instrument Type | Value Type |
-| ------------- | ----------------------- | ---------- |
-| `{processors}`| ObservableUpDownCounter | `Int32` |
-
-The API used to retrieve the value is
-[System.Environment.ProcessorCount](https://learn.microsoft.com/dotnet/api/system.environment.processorcount).
-
-> [!NOTE]
-> This metric is under
-> [discussion](https://github.com/open-telemetry/opentelemetry-specification/issues/3200)
-and not part of the [Process Metrics
-Spec](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/process-metrics.md)
-at this time.
-
### process.thread.count
Process threads count.
diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
index b49dcf7d71..1229991981 100644
--- a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md
@@ -9,6 +9,10 @@
* Add support for native AoT on .NET 8+.
([#4062](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4062))
+* Fix SQL query text sanitization for malformed bracketed identifiers in `FROM`
+ clauses to avoid leaking following literal values.
+ ([#4317](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4317))
+
## 1.15.2
Released 2026-Apr-21
diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlTelemetryHelper.cs b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlTelemetryHelper.cs
index a0d8f78011..554ea7a2ca 100644
--- a/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlTelemetryHelper.cs
+++ b/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlTelemetryHelper.cs
@@ -93,17 +93,5 @@ public static TagList GetTagListFromConnectionInfo(string? dataSource, string? d
}
internal static double CalculateDurationFromTimestamp(long begin)
- {
-#if NET
- var duration = Stopwatch.GetElapsedTime(begin);
-#else
- var end = Stopwatch.GetTimestamp();
- var timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
- var delta = end - begin;
- var ticks = (long)(timestampToTicks * delta);
- var duration = new TimeSpan(ticks);
-#endif
-
- return duration.TotalSeconds;
- }
+ => Stopwatch.GetElapsedTime(begin).TotalSeconds;
}
diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj b/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj
index 84763fa654..c26dad640c 100644
--- a/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj
+++ b/src/OpenTelemetry.Instrumentation.SqlClient/OpenTelemetry.Instrumentation.SqlClient.csproj
@@ -38,6 +38,7 @@
+
diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md
index c554b48d37..0ee7d06d9f 100644
--- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md
+++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md
@@ -6,6 +6,9 @@
the new database semantic conventions are enabled.
([#4245](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4245))
+* Add instrumentation scope version and schema URL to traces.
+ ([#4095](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4095))
+
## 1.15.1-beta.1
Released 2026-Apr-21
diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs
index 88cc27143f..fc4ac57a4e 100644
--- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs
+++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs
@@ -94,7 +94,14 @@ static bool GetCommandAndKey(
name = StackExchangeRedisConnectionInstrumentation.ActivityName;
}
- var activity = StackExchangeRedisConnectionInstrumentation.ActivitySource.StartActivity(
+ var activitySource =
+ options.EmitNewAttributes && options.EmitOldAttributes ?
+ StackExchangeRedisConnectionInstrumentation.ActivitySourceBoth :
+ options.EmitNewAttributes ?
+ StackExchangeRedisConnectionInstrumentation.ActivitySourceNew :
+ StackExchangeRedisConnectionInstrumentation.ActivitySource;
+
+ var activity = activitySource.StartActivity(
name,
ActivityKind.Client,
parentActivity?.Context ?? default,
diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj
index bfd6fb23b5..1b3ee6e5f4 100644
--- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj
+++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs
index 374c7395ce..ddcee2961a 100644
--- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs
+++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisConnectionInstrumentation.cs
@@ -3,7 +3,6 @@
using System.Collections.Concurrent;
using System.Diagnostics;
-using System.Reflection;
using OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
@@ -18,12 +17,17 @@ namespace OpenTelemetry.Instrumentation.StackExchangeRedis;
internal sealed class StackExchangeRedisConnectionInstrumentation : IDisposable
{
internal const string RedisDatabaseIndexKeyName = "db.redis.database_index";
- internal static readonly Assembly Assembly = typeof(StackExchangeRedisConnectionInstrumentation).Assembly;
-#pragma warning disable IDE0370 // Suppression is unnecessary
- internal static readonly string ActivitySourceName = Assembly.GetName().Name!;
-#pragma warning restore IDE0370 // Suppression is unnecessary
- internal static readonly string ActivityName = ActivitySourceName + ".Execute";
- internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Assembly.GetPackageVersion());
+
+ internal static readonly Version SemanticConventionsVersion = new(1, 23, 0);
+ internal static readonly ActivitySource ActivitySource = ActivitySourceFactory.Create(SemanticConventionsVersion);
+
+ internal static readonly Version SemanticConventionsVersionNew = new(1, 28, 0);
+ internal static readonly ActivitySource ActivitySourceNew = ActivitySourceFactory.Create(SemanticConventionsVersionNew);
+
+ internal static readonly ActivitySource ActivitySourceBoth = ActivitySourceFactory.Create(null);
+
+ internal static readonly string ActivityName = $"{ActivitySource.Name}.Execute";
+
internal static readonly IEnumerable> OldCreationTags =
[
new(SemanticConventions.AttributeDbSystem, "redis")
diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs
index d9a450a2a6..7f46ab8a01 100644
--- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs
@@ -151,7 +151,7 @@ public static TracerProviderBuilder AddRedisInstrumentation(
}
return builder
- .AddSource(StackExchangeRedisConnectionInstrumentation.ActivitySourceName)
+ .AddSource(StackExchangeRedisConnectionInstrumentation.ActivitySource.Name)
.AddInstrumentation(sp =>
{
var instrumentation = sp.GetRequiredService();
diff --git a/src/OpenTelemetry.OpAmp.Client/CHANGELOG.md b/src/OpenTelemetry.OpAmp.Client/CHANGELOG.md
index 4c3d04515f..1524f62b43 100644
--- a/src/OpenTelemetry.OpAmp.Client/CHANGELOG.md
+++ b/src/OpenTelemetry.OpAmp.Client/CHANGELOG.md
@@ -26,7 +26,8 @@ Released 2026-Apr-21
* Add support for sticky HTTP connections via the `OpAMP-Instance-UID` header.
([#3830](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3830))
-* Apply response size limits for oversized OpAMP responses.
+* Apply response size limits for oversized OpAMP responses to resolve
+ [GHSA-w2jh-77fq-7gp8](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/security/advisories/GHSA-w2jh-77fq-7gp8).
([#4116](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4116))
* Harden WebSocket transport:
diff --git a/src/OpenTelemetry.Resources.AWS/CHANGELOG.md b/src/OpenTelemetry.Resources.AWS/CHANGELOG.md
index 6aec7bda69..da9a6a6094 100644
--- a/src/OpenTelemetry.Resources.AWS/CHANGELOG.md
+++ b/src/OpenTelemetry.Resources.AWS/CHANGELOG.md
@@ -13,7 +13,8 @@ Released 2026-Apr-21
Windows containers running on AWS ECS.
([#4028](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4028))
-* Limit how much of the response body is consumed from metadata service HTTP responses.
+* Limit how much of the response body is consumed from metadata service HTTP responses
+ to resolve [GHSA-28xm-prxc-5866](https://github.com/advisories/GHSA-28xm-prxc-5866).
([#4122](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4122))
* Fix ECS Metadata V4 cluster ARN normalization when the `Cluster` field returns
diff --git a/src/OpenTelemetry.Resources.Azure/CHANGELOG.md b/src/OpenTelemetry.Resources.Azure/CHANGELOG.md
index 7226b8453c..9351ec2b5d 100644
--- a/src/OpenTelemetry.Resources.Azure/CHANGELOG.md
+++ b/src/OpenTelemetry.Resources.Azure/CHANGELOG.md
@@ -9,7 +9,8 @@
Released 2026-Apr-21
-* Limit how much of the response body is consumed from metadata service HTTP responses.
+* Limit how much of the response body is consumed from metadata service HTTP responses
+ to resolve [GHSA-vc24-j8c5-2vw4](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/security/advisories/GHSA-vc24-j8c5-2vw4).
([#4121](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4121))
* Updated OpenTelemetry core component version(s) to `1.15.3`.
diff --git a/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs b/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs
index f5652225f7..3e28f32868 100644
--- a/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs
+++ b/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs
@@ -14,8 +14,13 @@ public sealed class AWSXRayRemoteSampler : Trace.Sampler, IDisposable
{
internal static readonly TimeSpan DefaultTargetInterval = TimeSpan.FromSeconds(10);
+ private const string ClientIdCharacters = "0123456789abcdef";
+
private static readonly Random Random = new();
+ private readonly CancellationTokenSource cancellationTokenSource = new();
+ private readonly SemaphoreSlim pollerLock = new(1, 1);
private bool isFallBackEventToWriteSwitch = true;
+ private int disposed;
[SuppressMessage("Performance", "CA5394: Do not use insecure randomness", Justification = "Secure random is not required for jitters.")]
internal AWSXRayRemoteSampler(Resource resource, TimeSpan pollingInterval, string endpoint, Clock clock)
@@ -73,10 +78,7 @@ internal AWSXRayRemoteSampler(Resource resource, TimeSpan pollingInterval, strin
/// to identify the service attributes for sampling. This resource should
/// be the same as what the OpenTelemetry SDK is configured with.
/// an instance of .
- public static AWSXRayRemoteSamplerBuilder Builder(Resource resource)
- {
- return new AWSXRayRemoteSamplerBuilder(resource);
- }
+ public static AWSXRayRemoteSamplerBuilder Builder(Resource resource) => new(resource);
///
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
@@ -105,53 +107,54 @@ public void Dispose()
GC.SuppressFinalize(this);
}
- [SuppressMessage(
- "Usage",
- "CA5394: Do not use insecure randomness",
- Justification = "using insecure random is fine here since clientId doesn't need to be secure.")]
- private static string GenerateClientId()
+ internal async Task ExecutePollAsync(Func pollAsync)
{
- char[] hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
- var clientIdChars = new char[24];
- for (var i = 0; i < clientIdChars.Length; i++)
+ var lockTaken = false;
+
+ try
{
- clientIdChars[i] = hex[Random.Next(hex.Length)];
- }
+ await this.pollerLock.WaitAsync(this.cancellationTokenSource.Token).ConfigureAwait(false);
+ lockTaken = true;
- return new string(clientIdChars);
- }
+ if (Volatile.Read(ref this.disposed) != 0)
+ {
+ return;
+ }
- private void Dispose(bool disposing)
- {
- if (disposing)
+ await pollAsync(this.cancellationTokenSource.Token).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException) when (this.cancellationTokenSource.IsCancellationRequested)
{
- this.RulePollerTimer?.Dispose();
- this.Client?.Dispose();
- this.RulesCache?.Dispose();
+ // Sampler is shutting down.
+ }
+ catch (ObjectDisposedException) when (Volatile.Read(ref this.disposed) != 0)
+ {
+ // Sampler is shutting down.
+ }
+ catch (Exception ex)
+ {
+ AWSSamplerEventSource.Log.ExceptionFromSampler(ex.Message);
+ }
+ finally
+ {
+ if (lockTaken)
+ {
+ this.pollerLock.Release();
+ }
}
}
- private async void GetAndUpdateRules(object? state)
- {
- var rules = await this.Client.GetSamplingRules().ConfigureAwait(false);
-
- this.RulesCache.UpdateRules(rules);
-
- // schedule the next rule poll.
- this.RulePollerTimer.Change(this.PollingInterval.Add(this.RulePollerJitter), Timeout.InfiniteTimeSpan);
- }
-
- private async void GetAndUpdateTargets(object? state)
+ internal async Task GetAndUpdateTargetsAsync(CancellationToken cancellationToken)
{
- await this.GetAndUpdateTargetsAsync().ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- private async Task GetAndUpdateTargetsAsync()
- {
var statistics = this.RulesCache.Snapshot(this.Clock.Now());
var request = new GetSamplingTargetsRequest(statistics);
- var response = await this.Client.GetSamplingTargets(request).ConfigureAwait(false);
+ var response = await this.Client.GetSamplingTargets(request, cancellationToken).ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
if (response != null)
{
Dictionary targets = [];
@@ -169,7 +172,8 @@ private async Task GetAndUpdateTargetsAsync()
{
var lastRuleModificationTime = this.Clock.ToDateTime(response.LastRuleModification);
- if (lastRuleModificationTime > this.RulesCache.GetUpdatedAt())
+ if (!cancellationToken.IsCancellationRequested &&
+ lastRuleModificationTime > this.RulesCache.GetUpdatedAt())
{
// rules have been updated. fetch the new ones right away.
this.RulePollerTimer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan);
@@ -185,6 +189,100 @@ private async Task GetAndUpdateTargetsAsync()
nextTargetFetchInterval = DefaultTargetInterval;
}
- this.TargetPollerTimer.Change(nextTargetFetchInterval.Add(this.TargetPollerJitter), Timeout.InfiniteTimeSpan);
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ this.TargetPollerTimer.Change(nextTargetFetchInterval.Add(this.TargetPollerJitter), Timeout.InfiniteTimeSpan);
+ }
+ }
+
+ [SuppressMessage(
+ "Usage",
+ "CA5394: Do not use insecure randomness",
+ Justification = "using insecure random is fine here since clientId doesn't need to be secure.")]
+ private static string GenerateClientId()
+ {
+ const int ClientIdLength = 24;
+
+#if NET
+ Span buffer = stackalloc char[ClientIdLength];
+
+ Random.GetItems(ClientIdCharacters, buffer);
+
+ return new(buffer);
+#else
+ var buffer = new char[ClientIdLength];
+ for (var i = 0; i < buffer.Length; i++)
+ {
+ buffer[i] = ClientIdCharacters[Random.Next(ClientIdCharacters.Length)];
+ }
+
+ return new string(buffer);
+#endif
+ }
+
+ private static void DisposeTimer(Timer? timer)
+ {
+ if (timer == null)
+ {
+ return;
+ }
+
+ using var disposedEvent = new ManualResetEvent(false);
+ if (timer.Dispose(disposedEvent))
+ {
+ disposedEvent.WaitOne();
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (Interlocked.Exchange(ref this.disposed, 1) != 0)
+ {
+ return;
+ }
+
+ this.cancellationTokenSource.Cancel();
+ DisposeTimer(this.RulePollerTimer);
+ DisposeTimer(this.TargetPollerTimer);
+
+ this.pollerLock.Wait();
+
+ try
+ {
+ this.Client?.Dispose();
+ this.RulesCache?.Dispose();
+ }
+ finally
+ {
+ this.pollerLock.Release();
+ this.pollerLock.Dispose();
+ this.cancellationTokenSource.Dispose();
+ }
+ }
+ }
+
+ private void GetAndUpdateRules(object? state) =>
+ _ = this.ExecutePollAsync(this.GetAndUpdateRulesAsync);
+
+ private void GetAndUpdateTargets(object? state) =>
+ _ = this.ExecutePollAsync(this.GetAndUpdateTargetsAsync);
+
+ private async Task GetAndUpdateRulesAsync(CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var rules = await this.Client.GetSamplingRules(cancellationToken).ConfigureAwait(false);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ this.RulesCache.UpdateRules(rules);
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ // schedule the next rule poll.
+ this.RulePollerTimer.Change(this.PollingInterval.Add(this.RulePollerJitter), Timeout.InfiniteTimeSpan);
+ }
}
}
diff --git a/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md b/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md
index 15cf482976..9aa1d03158 100644
--- a/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md
+++ b/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md
@@ -23,7 +23,8 @@ Released 2026-Apr-14
* Updated OpenTelemetry core component version(s) to `1.15.2`.
([#4080](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4080))
-* Limit the max size read for response body getting the sampling rules to 1MB.
+* Limit the max size read for response body getting the sampling rules to 1MB to
+ resolve [GHSA-28xm-prxc-5866](https://github.com/advisories/GHSA-28xm-prxc-5866).
([#4100](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4100))
## 0.1.0-alpha.7
diff --git a/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs b/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs
index 01cba4af52..c216072869 100644
--- a/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs
+++ b/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs
@@ -94,19 +94,19 @@ public bool Matches(SamplingParameters samplingParameters, Resource resource)
{
foreach (var tag in samplingParameters.Tags)
{
- if (tag.Key.Equals(SemanticConventions.AttributeUrlPath, StringComparison.Ordinal))
+ if (string.Equals(tag.Key, SemanticConventions.AttributeUrlPath, StringComparison.Ordinal))
{
httpTarget = (string?)tag.Value;
}
- else if (tag.Key.Equals(SemanticConventions.AttributeUrlFull, StringComparison.Ordinal))
+ else if (string.Equals(tag.Key, SemanticConventions.AttributeUrlFull, StringComparison.Ordinal))
{
httpUrl = (string?)tag.Value;
}
- else if (tag.Key.Equals(SemanticConventions.AttributeHttpRequestMethod, StringComparison.Ordinal))
+ else if (string.Equals(tag.Key, SemanticConventions.AttributeHttpRequestMethod, StringComparison.Ordinal))
{
httpMethod = (string?)tag.Value;
}
- else if (tag.Key.Equals(SemanticConventions.AttributeHttpHost, StringComparison.Ordinal))
+ else if (string.Equals(tag.Key, SemanticConventions.AttributeHttpHost, StringComparison.Ordinal))
{
httpHost = (string?)tag.Value;
}
@@ -132,7 +132,7 @@ public bool Matches(SamplingParameters samplingParameters, Resource resource)
}
var serviceName = (string)resource.Attributes.FirstOrDefault(kvp =>
- kvp.Key.Equals("service.name", StringComparison.Ordinal)).Value;
+ string.Equals(kvp.Key, "service.name", StringComparison.Ordinal)).Value;
return Matcher.AttributeMatch(samplingParameters.Tags, this.Rule.Attributes) &&
Matcher.WildcardMatch(httpTarget, this.Rule.UrlPath) &&
@@ -228,7 +228,7 @@ public SamplingRuleApplier WithTarget(SamplingTargetDocument target, DateTimeOff
private static string GetServiceType(Resource resource)
{
var cloudPlatform = (string)resource.Attributes.FirstOrDefault(kvp =>
- kvp.Key.Equals("cloud.platform", StringComparison.Ordinal)).Value;
+ string.Equals(kvp.Key, "cloud.platform", StringComparison.Ordinal)).Value;
return cloudPlatform == null ? string.Empty :
Matcher.XRayCloudPlatform.TryGetValue(cloudPlatform, out var value) ? value : string.Empty;
@@ -238,16 +238,16 @@ private static string GetArn(in SamplingParameters samplingParameters, Resource
{
// currently the aws resource detectors only capture ARNs for ECS and Lambda environments.
var arn = (string?)resource.Attributes.FirstOrDefault(kvp =>
- kvp.Key.Equals("aws.ecs.container.arn", StringComparison.Ordinal)).Value;
+ string.Equals(kvp.Key, "aws.ecs.container.arn", StringComparison.Ordinal)).Value;
if (arn != null)
{
return arn;
}
- if (GetServiceType(resource).Equals("AWS::Lambda::Function", StringComparison.Ordinal))
+ if (string.Equals(GetServiceType(resource), "AWS::Lambda::Function", StringComparison.Ordinal))
{
- arn = (string?)samplingParameters.Tags?.FirstOrDefault(kvp => kvp.Key.Equals("faas.id", StringComparison.Ordinal)).Value;
+ arn = (string?)samplingParameters.Tags?.FirstOrDefault(kvp => string.Equals(kvp.Key, "faas.id", StringComparison.Ordinal)).Value;
if (arn != null)
{
diff --git a/src/Shared/SqlProcessor.cs b/src/Shared/SqlProcessor.cs
index 5bce9757d7..a8ec92f185 100644
--- a/src/Shared/SqlProcessor.cs
+++ b/src/Shared/SqlProcessor.cs
@@ -33,8 +33,13 @@ internal static class SqlProcessor
private static readonly ConcurrentDictionary Cache = new();
private static readonly char[] WhitespaceChars = [SpaceChar, TabChar, CarriageReturnChar, NewLineChar];
+#if !NET
+ private static readonly char[] LineBreakChars = [CarriageReturnChar, NewLineChar];
+#endif
#if NET
+ private static readonly SearchValues AsciiLetterSearchValues = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+ private static readonly SearchValues LineBreakSearchValues = SearchValues.Create("\n\r");
private static readonly SearchValues WhitespaceSearchValues = SearchValues.Create(WhitespaceChars);
#endif
@@ -223,6 +228,27 @@ private static bool IsValidTokenCharacter(ReadOnlySpan sql, int currentPos
return (currentChar != DotChar || indexInToken != 0) && IsUnescapedIdentifierChar(currentChar);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool HasTerminatingEscapedIdentifier(ReadOnlySpan sql, int start)
+ {
+ for (var i = start + 1; i < sql.Length; i++)
+ {
+ if (sql[i] == CloseSquareBracketChar)
+ {
+ if (i + 1 < sql.Length && sql[i + 1] == CloseSquareBracketChar)
+ {
+ i++;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
private static SqlStatementInfo SanitizeSql(string sql)
{
var sqlSpan = sql.AsSpan();
@@ -407,6 +433,15 @@ private static void ParseNextToken(
// Determine the length of the next contiguous ascii-letter run.
// This allows some fast paths in the comparisons below.
+#if NET
+ var asciiLetterLength = sql.Slice(start, remaining)
+ .IndexOfAnyExcept(AsciiLetterSearchValues);
+
+ if (asciiLetterLength < 0)
+ {
+ asciiLetterLength = remaining;
+ }
+#else
var asciiLetterLength = 1;
while (asciiLetterLength < remaining)
{
@@ -422,6 +457,7 @@ private static void ParseNextToken(
asciiLetterLength++;
}
+#endif
// IMPLEMENTATION NOTE: At one stage we tried checking if the length was between 2 and 12 (inclusive)
// the range of shortest and longest keywords. This ended up being slower in practice
@@ -611,7 +647,9 @@ private static void ParseNextToken(
// Brackets may occur when using schema-qualified or delimited identifiers.
state.CaptureNextNonKeywordTokenAsIdentifier = state.InFromClause && (currentChar is CommaChar or OpenSquareBracketChar or DotChar);
- if (state.CaptureNextNonKeywordTokenAsIdentifier && currentChar is OpenSquareBracketChar)
+ if (state.CaptureNextNonKeywordTokenAsIdentifier
+ && currentChar is OpenSquareBracketChar
+ && HasTerminatingEscapedIdentifier(sql, state.ParsePosition))
{
state.InEscapedIdentifier = true;
AppendSummaryChar(OpenSquareBracketChar, ref state);
@@ -646,38 +684,42 @@ static bool IsCaseInsensitiveMatch(ReadOnlySpan sql, int tokenStart, int t
private static bool ParseWhitespace(ReadOnlySpan sql, Span buffer, ref ParseState state)
{
var start = state.ParsePosition;
- var foundWhitespace = false;
+#if NET
+ var remaining = sql.Slice(start);
+ var length = remaining.IndexOfAnyExcept(WhitespaceSearchValues);
+ if (length == 0)
+ {
+ return false;
+ }
- // Find the end of whitespace run first
+ if (length < 0)
+ {
+ length = remaining.Length;
+ }
+#else
var i = start;
while (i < sql.Length)
{
var currentChar = sql[i];
-
-#if NET
- if (WhitespaceSearchValues.Contains(currentChar))
-#else
- if (currentChar is SpaceChar or TabChar or CarriageReturnChar or NewLineChar)
-#endif
+ if (currentChar is not (SpaceChar or TabChar or CarriageReturnChar or NewLineChar))
{
- foundWhitespace = true;
- i++;
- continue;
+ break;
}
- break;
+ i++;
}
- // Bulk copy whitespace if found
- if (foundWhitespace)
+ var length = i - start;
+ if (length == 0)
{
- var length = i - start;
- sql.Slice(start, length).CopyTo(buffer.Slice(state.SanitizedPosition));
- state.SanitizedPosition += length;
- state.ParsePosition = i;
+ return false;
}
+#endif
- return foundWhitespace;
+ sql.Slice(start, length).CopyTo(buffer.Slice(state.SanitizedPosition));
+ state.SanitizedPosition += length;
+ state.ParsePosition = start + length;
+ return true;
}
private static bool SkipComment(ReadOnlySpan sql, ref ParseState state)
@@ -692,20 +734,24 @@ private static bool SkipComment(ReadOnlySpan sql, ref ParseState state)
// Scan past multi-line comment
if (ch == '/' && iPlusOne < length && sql[iPlusOne] == AsteriskChar)
{
- // Use index arithmetic instead of slicing
- var searchPos = iPlusTwo;
- while (searchPos < length)
+ var remainingComment = sql.Slice(iPlusTwo);
+ var searchOffset = 0;
+ while (searchOffset < remainingComment.Length)
{
- if (sql[searchPos] == AsteriskChar)
+ var asteriskIndex = remainingComment.Slice(searchOffset).IndexOf(AsteriskChar);
+ if (asteriskIndex < 0)
{
- if (searchPos + 1 < length && sql[searchPos + 1] == ForwardSlashChar)
- {
- state.ParsePosition = searchPos + 2;
- return true;
- }
+ break;
}
- searchPos++;
+ searchOffset += asteriskIndex;
+ if (searchOffset + 1 < remainingComment.Length && remainingComment[searchOffset + 1] == ForwardSlashChar)
+ {
+ state.ParsePosition = iPlusTwo + searchOffset + 2;
+ return true;
+ }
+
+ searchOffset++;
}
// Unterminated comment, consume to end
@@ -716,19 +762,16 @@ private static bool SkipComment(ReadOnlySpan sql, ref ParseState state)
// Scan past single-line comment
if (ch == DashChar && iPlusOne < length && sql[iPlusOne] == DashChar)
{
- // Find next line break efficiently using index arithmetic
- var searchPosition = iPlusTwo;
- while (searchPosition < length)
+#if NET
+ var lineBreakIndex = sql.Slice(iPlusTwo).IndexOfAny(LineBreakSearchValues);
+#else
+ var lineBreakIndex = sql.Slice(iPlusTwo).IndexOfAny(LineBreakChars);
+#endif
+ if (lineBreakIndex >= 0)
{
- var currentChar = sql[searchPosition];
- if (currentChar is CarriageReturnChar or NewLineChar)
- {
- // Position at the newline so ParseWhitespace can copy it
- state.ParsePosition = searchPosition;
- return true;
- }
-
- searchPosition++;
+ // Position at the newline so ParseWhitespace can copy it
+ state.ParsePosition = iPlusTwo + lineBreakIndex;
+ return true;
}
state.ParsePosition = length;
@@ -752,32 +795,33 @@ private static bool SanitizeStringLiteral(ReadOnlySpan sql, Span buf
// If so, we want to skip the Unicode prefix when sanitizing.
var isUnicode = state.ParsePosition >= 1 && sql[state.ParsePosition - 1] is UnicodePrefixChar;
- // Use index arithmetic instead of slicing
var searchPos = state.ParsePosition + 1;
while (searchPos < sql.Length)
{
- if (sql[searchPos] == SingleQuoteChar)
+ var quoteIndex = sql.Slice(searchPos).IndexOf(SingleQuoteChar);
+ if (quoteIndex < 0)
{
- if (searchPos + 1 < sql.Length && sql[searchPos + 1] == SingleQuoteChar)
- {
- // Skip escaped quote ('')
- searchPos += 2;
- continue;
- }
+ break;
+ }
- // Found terminating quote
- if (isUnicode)
- {
- // Skip the Unicode prefix by overwriting the previous position instead
- state.SanitizedPosition--;
- }
+ searchPos += quoteIndex;
+ if (searchPos + 1 < sql.Length && sql[searchPos + 1] == SingleQuoteChar)
+ {
+ // Skip escaped quote ('')
+ searchPos += 2;
+ continue;
+ }
- state.ParsePosition = searchPos + 1;
- buffer[state.SanitizedPosition++] = SanitizationPlaceholder;
- return true;
+ // Found terminating quote
+ if (isUnicode)
+ {
+ // Skip the Unicode prefix by overwriting the previous position instead
+ state.SanitizedPosition--;
}
- searchPos++;
+ state.ParsePosition = searchPos + 1;
+ buffer[state.SanitizedPosition++] = SanitizationPlaceholder;
+ return true;
}
state.ParsePosition = sql.Length;
@@ -925,18 +969,12 @@ private static bool TrySanitizeLiteralsForInClause(ReadOnlySpan sql, Span<
return false;
}
- // Use index arithmetic instead of slicing
- var searchPosition = parsePosition;
- while (searchPosition < sql.Length)
+ var closeParenIndex = sql.Slice(parsePosition).IndexOf(CloseParenChar);
+ if (closeParenIndex >= 0)
{
- if (sql[searchPosition] == CloseParenChar)
- {
- state.ParsePosition = searchPosition;
- buffer[state.SanitizedPosition++] = SanitizationPlaceholder;
- return true;
- }
-
- searchPosition++;
+ state.ParsePosition = parsePosition + closeParenIndex;
+ buffer[state.SanitizedPosition++] = SanitizationPlaceholder;
+ return true;
}
}
diff --git a/src/Shared/StopwatchExtensions.cs b/src/Shared/StopwatchExtensions.cs
new file mode 100644
index 0000000000..0f88139981
--- /dev/null
+++ b/src/Shared/StopwatchExtensions.cs
@@ -0,0 +1,25 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+#if !NET
+
+namespace System.Diagnostics;
+
+internal static class StopwatchExtensions
+{
+ extension(Stopwatch)
+ {
+ public static TimeSpan GetElapsedTime(long begin)
+ {
+ var end = Stopwatch.GetTimestamp();
+
+ var timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
+ var delta = end - begin;
+ var ticks = (long)(timestampToTicks * delta);
+
+ return new TimeSpan(ticks);
+ }
+ }
+}
+
+#endif
diff --git a/test/OpenTelemetry.Contrib.Shared.FuzzTests/SqlProcessorTests.cs b/test/OpenTelemetry.Contrib.Shared.FuzzTests/SqlProcessorTests.cs
index a7d3f28d6e..268bb83270 100644
--- a/test/OpenTelemetry.Contrib.Shared.FuzzTests/SqlProcessorTests.cs
+++ b/test/OpenTelemetry.Contrib.Shared.FuzzTests/SqlProcessorTests.cs
@@ -1,6 +1,7 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
+using System.Globalization;
using FsCheck;
using FsCheck.Xunit;
using Xunit;
@@ -377,4 +378,38 @@ public static void GetSanitizedSql_Multiple_Values_In_In_Clause(PositiveInt valu
var questionMarkCount = result.SanitizedSql.Count((p) => p == '?');
Assert.Equal(1, questionMarkCount);
}
+
+ [Property(MaxTest = MaxValue)]
+ public static void GetSanitizedSql_Unterminated_Escaped_Identifier_In_From_Clause_Sanitizes_Following_Literals(
+ NonEmptyString input,
+ PositiveInt number,
+ NonNegativeInt variant)
+ {
+ // Arrange
+ var secret = "secret_" + new string([.. input.Get.Where(char.IsLetterOrDigit).Take(32)]);
+ if (secret.Length == "secret_".Length)
+ {
+ secret += "value";
+ }
+
+ var numericLiteral = number.Get + 100_000;
+ var numericLiteralString = numericLiteral.ToString(CultureInfo.InvariantCulture);
+ var sqlPrefix = (variant.Get % 3) switch
+ {
+ 0 => "SELECT * FROM [Orders",
+ 1 => "SELECT * FROM dbo.[Orders",
+ _ => "SELECT * FROM Customers, [Orders",
+ };
+
+ var sql = $"{sqlPrefix} WHERE Name = '{secret}' AND Id = {numericLiteralString} AND Token = 0xDEADBEEF";
+
+ // Act
+ var result = SqlProcessor.GetSanitizedSql(sql);
+
+ // Assert
+ Assert.Contains("?", result.SanitizedSql);
+ Assert.DoesNotContain(secret, result.SanitizedSql);
+ Assert.DoesNotContain(numericLiteralString, result.SanitizedSql);
+ Assert.DoesNotContain("DEADBEEF", result.SanitizedSql);
+ }
}
diff --git a/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorTests.cs b/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorTests.cs
index 9832c15742..90ed5e3c40 100644
--- a/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorTests.cs
+++ b/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorTests.cs
@@ -28,6 +28,27 @@ public void GetSanitizedSql_CreateTableWithTrailingIdentifier_DoesNotThrow()
Assert.Equal(sql, sqlStatementInfo.DbQuerySummary);
}
+ [Fact]
+ public void GetSanitizedSql_SingleLineCommentWithCarriageReturnLineFeed_PreservesLineBreak()
+ {
+ var sql = "SELECT * FROM table -- comment\r\nWHERE id = 42";
+
+ var sqlStatementInfo = SqlProcessor.GetSanitizedSql(sql);
+
+ Assert.Equal("SELECT * FROM table \r\nWHERE id = ?", sqlStatementInfo.SanitizedSql);
+ Assert.Equal("SELECT table", sqlStatementInfo.DbQuerySummary);
+ }
+
+ [Fact]
+ public void GetSanitizedSql_UnterminatedEscapedIdentifierInFromClause_SanitizesLiterals()
+ {
+ var sql = "SELECT * FROM [Orders WHERE CustomerName = 'secret-name' AND Id = 123 AND Token = 0xDEADBEEF";
+
+ var sqlStatementInfo = SqlProcessor.GetSanitizedSql(sql);
+
+ Assert.Equal("SELECT * FROM [Orders WHERE CustomerName = ? AND Id = ? AND Token = ?", sqlStatementInfo.SanitizedSql);
+ }
+
[SkippableTheory]
[MemberData(nameof(TestData))]
public void TestGetSanitizedSql(SqlProcessorTestCases.TestCase testCase)
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
index 77eef04c62..af0e0a26bd 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs
@@ -728,7 +728,6 @@ public async Task ActivitiesStartedInMiddlewareBySettingHostActivityToNullShould
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", aspnetcoreframeworkactivity.OperationName);
}
-#if NET
[Fact]
public async Task UserRegisteredActivitySourceIsUsedForActivityCreationByAspNetCore()
{
@@ -769,7 +768,6 @@ void ConfigureTestServices(IServiceCollection services)
Assert.Equal("UserRegisteredActivitySource", activity.Source.Name);
}
-#endif
[Theory]
[InlineData(1)]
@@ -1329,20 +1327,15 @@ private static void WaitForActivityExport(List exportedItems, int coun
Thread.Sleep(10);
return exportedItems.Count >= count;
},
- TimeSpan.FromSeconds(1)),
+ TimeSpan.FromSeconds(5)),
$"Actual: {exportedItems.Count} Expected: {count}");
private static void ValidateAspNetCoreActivity(Activity activityToValidate, string expectedHttpPath)
{
Assert.Equal(ActivityKind.Server, activityToValidate.Kind);
-#if NET
Assert.Equal(HttpInListener.AspNetCoreActivitySourceName, activityToValidate.Source.Name);
Assert.NotNull(activityToValidate.Source.Version);
Assert.Empty(activityToValidate.Source.Version);
-#else
- Assert.Equal(HttpInListener.ActivitySourceName, activityToValidate.Source.Name);
- Assert.Equal(HttpInListener.Version.ToString(), activityToValidate.Source.Version);
-#endif
Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeUrlPath) as string);
}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/EndToEndTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/EndToEndTests.cs
new file mode 100644
index 0000000000..09fc5df6d5
--- /dev/null
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/EndToEndTests.cs
@@ -0,0 +1,114 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+using System.Diagnostics;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
+using OpenTelemetry.Trace;
+using Xunit;
+
+namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
+
+[Collection("AspNetCore")]
+public sealed class EndToEndTests
+ : IClassFixture>, IDisposable
+{
+ private readonly WebApplicationFactory factory;
+ private TracerProvider? tracerProvider;
+
+ public EndToEndTests(WebApplicationFactory factory)
+ {
+ this.factory = factory;
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task HttpRequestActivityIsCorrectWithFeatureSwitch(bool isEnabled)
+ {
+ bool? originalValue = null;
+
+ if (AppContext.TryGetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", out var existingValue))
+ {
+ originalValue = existingValue;
+ }
+
+ AppContext.SetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", isEnabled);
+
+ try
+ {
+ var exportedItems = new List();
+
+ void ConfigureTestServices(IServiceCollection services)
+ {
+ this.tracerProvider = Sdk.CreateTracerProviderBuilder()
+ .AddAspNetCoreInstrumentation()
+ .AddInMemoryExporter(exportedItems)
+ .Build();
+ }
+
+ // Arrange
+ using var client = this.factory
+ .WithWebHostBuilder(builder =>
+ {
+ builder.ConfigureTestServices(ConfigureTestServices);
+ builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
+ })
+ .CreateClient();
+
+ client.DefaultRequestHeaders.UserAgent.Add(new("OpenTelemetry.Instrumentation.AspNetCore.Tests", "1.0"));
+
+ _ = await client.GetStringAsync(new Uri("/ping", UriKind.Relative));
+
+ WaitForActivityExport(exportedItems, 1);
+
+ var activity = Assert.Single(exportedItems);
+
+ ValidateAspNetCoreActivity(activity, "/ping");
+
+ Assert.Equal("GET /ping", activity.DisplayName);
+ Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
+ Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
+ Assert.Equal("OpenTelemetry.Instrumentation.AspNetCore.Tests/1.0", activity.GetTagValue(SemanticConventions.AttributeUserAgentOriginal));
+ Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
+ Assert.Equal("/ping", activity.GetTagValue(SemanticConventions.AttributeUrlPath));
+ }
+ finally
+ {
+ if (originalValue is { } previousValue)
+ {
+ AppContext.SetSwitch("Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData", previousValue);
+ }
+ }
+ }
+
+ public void Dispose()
+ => this.tracerProvider?.Dispose();
+
+ private static void WaitForActivityExport(List exportedItems, int count)
+ => Assert.True(
+ SpinWait.SpinUntil(
+ () =>
+ {
+ // We need to let End callback execute as it is executed AFTER response was returned.
+ // In unit tests environment there may be a lot of parallel unit tests executed, so
+ // giving some breathing room for the End callback to complete
+ Thread.Sleep(10);
+ return exportedItems.Count >= count;
+ },
+ TimeSpan.FromSeconds(5)),
+ $"Actual: {exportedItems.Count} Expected: {count}");
+
+ private static void ValidateAspNetCoreActivity(Activity activityToValidate, string expectedHttpPath)
+ {
+ Assert.Equal(ActivityKind.Server, activityToValidate.Kind);
+ Assert.Equal(HttpInListener.AspNetCoreActivitySourceName, activityToValidate.Source.Name);
+ Assert.NotNull(activityToValidate.Source.Version);
+ Assert.Empty(activityToValidate.Source.Version);
+ Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeUrlPath) as string);
+ }
+}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs
index 46b2318896..9ae2dec044 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/MetricTests.cs
@@ -1,22 +1,14 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
-#if NET
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Builder;
-#endif
using Microsoft.AspNetCore.Hosting;
-#if NET
using Microsoft.AspNetCore.Http;
-#endif
using Microsoft.AspNetCore.Mvc.Testing;
-#if NET
using Microsoft.AspNetCore.RateLimiting;
-#endif
-#if NET
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-#endif
using Microsoft.Extensions.Logging;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
@@ -38,7 +30,6 @@ public void AddAspNetCoreInstrumentation_BadArgs()
Assert.Throws(builder!.AddAspNetCoreInstrumentation);
}
-#if NET
[Fact]
public async Task ValidateNetMetricsAsync()
{
@@ -178,7 +169,6 @@ static string GetTicks()
await app.DisposeAsync();
}
-#endif
[Theory]
[InlineData("/api/values/2", "api/Values/{id}", null, 200)]
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/RouteInfo.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/RouteInfo.cs
index ad2e92e737..d02ba23b77 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/RouteInfo.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/RouteInfo.cs
@@ -3,9 +3,7 @@
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Http;
-#if NET
using Microsoft.AspNetCore.Http.Metadata;
-#endif
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Routing;
@@ -38,9 +36,7 @@ public void SetValues(HttpContext context)
this.Path = $"{context.Request.Path}{context.Request.QueryString}";
var endpoint = context.GetEndpoint();
this.RawText = (endpoint as RouteEndpoint)?.RoutePattern.RawText;
-#if NET
this.RouteDiagnosticMetadata = endpoint?.Metadata.GetMetadata()?.Route;
-#endif
this.RouteData = new Dictionary();
foreach (var value in context.GetRouteData().Values)
{
@@ -48,8 +44,6 @@ public void SetValues(HttpContext context)
}
}
- public void SetValues(ActionDescriptor actionDescriptor)
- {
+ public void SetValues(ActionDescriptor actionDescriptor) =>
this.ActionDescriptor ??= new ActionDescriptorInfo(actionDescriptor);
- }
}
diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/TestApplicationFactory.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/TestApplicationFactory.cs
index a0e99e5775..478fe00c05 100644
--- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/TestApplicationFactory.cs
+++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/RouteTests/TestApplication/TestApplicationFactory.cs
@@ -129,11 +129,9 @@ private static WebApplication CreateMinimalApiApplication()
app.MapGet("/MinimalApi", () => Results.Ok());
app.MapGet("/MinimalApi/{id}", (int id) => Results.Ok());
-#if NET
var api = app.MapGroup("/MinimalApiUsingMapGroup");
api.MapGet("/", () => Results.Ok());
api.MapGet("/{id}", (int id) => Results.Ok());
-#endif
return app;
}
diff --git a/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/KafkaFixture.cs b/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/KafkaFixture.cs
index 3a00202cd1..22a7ae9d12 100644
--- a/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/KafkaFixture.cs
+++ b/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/KafkaFixture.cs
@@ -10,5 +10,9 @@ public sealed class KafkaFixture : XunitContainerFixture
{
protected override string DockerfileName => "kafka.Dockerfile";
- protected override KafkaContainer CreateContainer() => new KafkaBuilder(this.GetImage()).Build();
+ protected override KafkaContainer CreateContainer() =>
+ new KafkaBuilder(this.GetImage())
+ .WithListener("127.0.0.1:19092")
+ .WithKRaft()
+ .Build();
}
diff --git a/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/kafka.Dockerfile b/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/kafka.Dockerfile
index 4ec0de3bda..a607480c85 100644
--- a/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/kafka.Dockerfile
+++ b/test/OpenTelemetry.Instrumentation.ConfluentKafka.Tests/kafka.Dockerfile
@@ -1 +1 @@
-FROM confluentinc/cp-kafka:7.9.6@sha256:9e178783dba8cf44cd8b1d260f548a587a21235921957edd4ed7aa7d060b0852
+FROM confluentinc/cp-kafka:8.2.0@sha256:acbbf674f2ed40e5d0a8ca51beb0f00692c866fc22b5ce06f8cadbdc54cd4436
diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/mysql.Dockerfile b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/mysql.Dockerfile
index 94e946e721..0903677f7c 100644
--- a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/mysql.Dockerfile
+++ b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/mysql.Dockerfile
@@ -1 +1 @@
-FROM mysql:9.6.0@sha256:c5df04bee1a42b74a5841c6409e669cf62126cd0416f00c1cea8ab933b9361b9
+FROM mysql:9.7.0@sha256:c9e48b0c008f1936d4139d1c0dcd5950a9dbe57d4d40f383013cde432fa6d6aa
diff --git a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/postgres.Dockerfile b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/postgres.Dockerfile
index 75b31bc5a0..561a5acd7e 100644
--- a/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/postgres.Dockerfile
+++ b/test/OpenTelemetry.Instrumentation.EntityFrameworkCore.Tests/postgres.Dockerfile
@@ -1 +1 @@
-FROM postgres:18.3@sha256:059fa0289cc5a184034e05a1f4f6d6fd79f69dc718b8b04ab60b6b469eed411e
+FROM postgres:18.3@sha256:78481659c47e862334611ccdaf7c369c986b3046da9857112f3b309114a65fb4
diff --git a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs
index f10a36d8b7..2c778e9a04 100644
--- a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs
+++ b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs
@@ -21,17 +21,15 @@ public void ProcessMetricsAreCaptured()
meterProviderA.ForceFlush(MaxTimeToAllowForFlush);
- Assert.Equal(5, exportedItemsA.Count);
var physicalMemoryMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.memory.usage");
Assert.NotNull(physicalMemoryMetric);
var virtualMemoryMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.memory.virtual");
Assert.NotNull(virtualMemoryMetric);
var cpuTimeMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.cpu.time");
Assert.NotNull(cpuTimeMetric);
- var processorCountMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.cpu.count");
- Assert.NotNull(processorCountMetric);
var threadMetric = exportedItemsA.FirstOrDefault(i => i.Name == "process.thread.count");
Assert.NotNull(threadMetric);
+ Assert.Equal(4, exportedItemsA.Count);
exportedItemsA.Clear();
@@ -51,8 +49,11 @@ public void ProcessMetricsAreCaptured()
meterProviderB.ForceFlush(MaxTimeToAllowForFlush);
- Assert.Equal(5, exportedItemsA.Count);
- Assert.Equal(5, exportedItemsB.Count);
+ Assert.Equal(4, exportedItemsA.Count);
+ Assert.Equal(4, exportedItemsB.Count);
+
+ AssertMetrics(exportedItemsA);
+ AssertMetrics(exportedItemsB);
}
[Fact]
@@ -129,29 +130,28 @@ public async Task ProcessMetricsAreCapturedWhenTasksOverlap()
await Task.WhenAll(tasks);
- Assert.Equal(5, exportedItemsA.Count);
var physicalMemoryMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.memory.usage");
Assert.NotNull(physicalMemoryMetricA);
var virtualMemoryMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.memory.virtual");
Assert.NotNull(virtualMemoryMetricA);
var cpuTimeMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.cpu.time");
Assert.NotNull(cpuTimeMetricA);
- var processorCountMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.cpu.count");
- Assert.NotNull(processorCountMetricA);
var threadMetricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.thread.count");
Assert.NotNull(threadMetricA);
+ Assert.Equal(4, exportedItemsA.Count);
- Assert.Equal(5, exportedItemsB.Count);
var physicalMemoryMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.memory.usage");
Assert.NotNull(physicalMemoryMetricB);
var virtualMemoryMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.memory.virtual");
Assert.NotNull(virtualMemoryMetricB);
var cpuTimeMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.cpu.time");
Assert.NotNull(cpuTimeMetricB);
- var processorCountMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.cpu.count");
- Assert.NotNull(processorCountMetricB);
var threadMetricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.thread.count");
Assert.NotNull(threadMetricB);
+ Assert.Equal(4, exportedItemsB.Count);
+
+ AssertMetrics(exportedItemsA);
+ AssertMetrics(exportedItemsB);
}
[Fact]
@@ -221,4 +221,14 @@ private static double GetValue(Metric metric)
return sum;
}
+
+ private static void AssertMetrics(IEnumerable metrics)
+ {
+ foreach (var metric in metrics)
+ {
+ Assert.NotNull(metric.MeterVersion);
+ Assert.NotEmpty(metric.MeterVersion);
+ Assert.StartsWith("https://opentelemetry.io/schemas/", metric.MeterSchemaUrl);
+ }
+ }
}
diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs
index 816ededb9c..d33e6d0ff1 100644
--- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs
+++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs
@@ -162,6 +162,21 @@ public void SuccessfulCommandTest(
// TODO VerifySamplingParameters(sampler.LatestSamplingParameters);
}
+
+ string? expectedSchemaUrl = (emitOldAttributes, emitNewAttributes) switch
+ {
+ (false, true) => "https://opentelemetry.io/schemas/1.28.0",
+ (true, false) => "https://opentelemetry.io/schemas/1.23.0",
+ _ => null,
+ };
+
+ foreach (var activity in exportedItems)
+ {
+ Assert.Equal("OpenTelemetry.Instrumentation.StackExchangeRedis", activity.Source.Name);
+ Assert.NotNull(activity.Source.Version);
+ Assert.NotEmpty(activity.Source.Version);
+ Assert.Equal(expectedSchemaUrl, activity.Source.TelemetrySchemaUrl);
+ }
}
[EnabledOnDockerPlatformFact(DockerPlatform.Linux)]
@@ -551,6 +566,10 @@ private static void VerifyNewActivityData(Activity activity, bool isSet, EndPoin
Assert.Equal(dbOperationName, activity.GetTagValue(SemanticConventions.AttributeDbOperationName));
Assert.Equal(dbQueryText, activity.GetTagValue(SemanticConventions.AttributeDbQueryText));
+ Assert.Equal("OpenTelemetry.Instrumentation.StackExchangeRedis", activity.Source.Name);
+ Assert.NotNull(activity.Source.Version);
+ Assert.NotEmpty(activity.Source.Version);
+
VerifyEndPoint(activity, endPoint);
}
diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/redis.Dockerfile b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/redis.Dockerfile
index 22f4de1a66..2549b13632 100644
--- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/redis.Dockerfile
+++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/redis.Dockerfile
@@ -1 +1 @@
-FROM redis:8.6.2@sha256:d372cf7cd5ab47fb6ad1a73c45ea1104d6f3fa11cc833ff0b3ac997890b4ccec
+FROM redis:8.6.2@sha256:832d7785830f3f4b559300e6191fc914b15642c1935252338825cf4332200148
diff --git a/test/OpenTelemetry.Instrumentation.Wcf.Tests/TelemetryEndpointBehaviorTests.cs b/test/OpenTelemetry.Instrumentation.Wcf.Tests/TelemetryEndpointBehaviorTests.cs
index d1a9b0a7d7..6f96b9aa08 100644
--- a/test/OpenTelemetry.Instrumentation.Wcf.Tests/TelemetryEndpointBehaviorTests.cs
+++ b/test/OpenTelemetry.Instrumentation.Wcf.Tests/TelemetryEndpointBehaviorTests.cs
@@ -34,6 +34,31 @@ public void ApplyClientBehaviorToClientRuntime_WithNullActionOperation_DoesNotTh
}
}
+#if NETFRAMEWORK
+ [Fact]
+ public void ApplyDispatchBehaviorToEndpoint_WithNullActionOperation_DoesNotThrow()
+ {
+ // Arrange
+ var endpointDispatcher = new EndpointDispatcher(
+ new EndpointAddress("http://localhost/something"),
+ contractName: "Service",
+ contractNamespace: "http://opentelemetry.io/");
+
+ endpointDispatcher.DispatchRuntime.Operations.Add(
+ new DispatchOperation(
+ endpointDispatcher.DispatchRuntime,
+ name: "NullActionOperation",
+ action: null));
+
+ // Act
+ var exception = Record.Exception(() => TelemetryEndpointBehavior.ApplyDispatchBehaviorToEndpoint(endpointDispatcher));
+
+ // Assert
+ Assert.Null(exception);
+ Assert.Single(endpointDispatcher.DispatchRuntime.MessageInspectors);
+ }
+#endif
+
private sealed class InjectNullActionOperationBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs
index 4c2a7e3a2f..09b6c1c244 100644
--- a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs
+++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs
@@ -22,14 +22,12 @@ public void TestSamplerWithConfiguration()
.SetEndpoint(endpoint)
.Build();
- var rootSamplerFieldInfo = typeof(ParentBasedSampler).GetField("rootSampler", BindingFlags.NonPublic | BindingFlags.Instance);
-
- var xraySampler = (AWSXRayRemoteSampler?)rootSamplerFieldInfo?.GetValue(parentBasedSampler);
+ using var xraySampler = GetRemoteSampler(parentBasedSampler);
- Assert.Equal(pollingInterval, xraySampler?.PollingInterval);
- Assert.Equal(endpoint, xraySampler?.Endpoint);
- Assert.NotNull(xraySampler?.RulePollerTimer);
- Assert.NotNull(xraySampler?.Client);
+ Assert.Equal(pollingInterval, xraySampler.PollingInterval);
+ Assert.Equal(endpoint, xraySampler.Endpoint);
+ Assert.NotNull(xraySampler.RulePollerTimer);
+ Assert.NotNull(xraySampler.Client);
}
[Fact]
@@ -37,14 +35,12 @@ public void TestSamplerWithDefaults()
{
var parentBasedSampler = AWSXRayRemoteSampler.Builder(ResourceBuilder.CreateEmpty().Build()).Build();
- var rootSamplerFieldInfo = typeof(ParentBasedSampler).GetField("rootSampler", BindingFlags.NonPublic | BindingFlags.Instance);
-
- var xraySampler = (AWSXRayRemoteSampler?)rootSamplerFieldInfo?.GetValue(parentBasedSampler);
+ using var xraySampler = GetRemoteSampler(parentBasedSampler);
- Assert.Equal(TimeSpan.FromMinutes(5), xraySampler?.PollingInterval);
- Assert.Equal("http://localhost:2000", xraySampler?.Endpoint);
- Assert.NotNull(xraySampler?.RulePollerTimer);
- Assert.NotNull(xraySampler?.Client);
+ Assert.Equal(TimeSpan.FromMinutes(5), xraySampler.PollingInterval);
+ Assert.Equal("http://localhost:2000", xraySampler.Endpoint);
+ Assert.NotNull(xraySampler.RulePollerTimer);
+ Assert.NotNull(xraySampler.Client);
}
[Fact]
@@ -66,6 +62,8 @@ public async Task TestSamplerUpdateAndSample()
.SetClock(clock)
.Build();
+ using var remoteSampler = GetRemoteSampler(sampler);
+
// the sampler will use fallback sampler until rules are fetched.
Assert.Equal(SamplingDecision.RecordAndSample, this.DoSample(sampler, "cat-service"));
@@ -122,26 +120,48 @@ public async Task TestSamplerUpdateTargetsWithMissingTargetDocumentsDoesNotThrow
.SetClock(clock)
.Build();
- var rootSamplerFieldInfo = typeof(ParentBasedSampler).GetField("rootSampler", BindingFlags.NonPublic | BindingFlags.Instance);
- var sampler = (AWSXRayRemoteSampler?)rootSamplerFieldInfo?.GetValue(parentBasedSampler);
-
- Assert.NotNull(sampler);
+ using var sampler = GetRemoteSampler(parentBasedSampler);
requestHandler.SetResponse("/SamplingTargets", "{\"LastRuleModification\":1530920505.0}");
- var getAndUpdateTargetsAsyncMethod = typeof(AWSXRayRemoteSampler).GetMethod("GetAndUpdateTargetsAsync", BindingFlags.NonPublic | BindingFlags.Instance);
- var getAndUpdateTargetsAsyncTask = (Task?)getAndUpdateTargetsAsyncMethod?.Invoke(sampler, null);
+ await sampler.GetAndUpdateTargetsAsync(CancellationToken.None);
+ }
- Assert.NotNull(getAndUpdateTargetsAsyncTask);
+ [Fact]
+ public async Task ExecutePollAsyncDoesNotBlockCaller()
+ {
+ using var sampler = GetRemoteSampler(AWSXRayRemoteSampler.Builder(ResourceBuilder.CreateEmpty().Build()).Build());
- try
- {
- await getAndUpdateTargetsAsyncTask!;
- }
- finally
+ var pollStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var releasePoll = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var executePollAsyncMethod = typeof(AWSXRayRemoteSampler).GetMethod("ExecutePollAsync", BindingFlags.NonPublic | BindingFlags.Instance);
+
+ Assert.NotNull(executePollAsyncMethod);
+
+ Task PollAsync(CancellationToken cancellationToken)
{
- sampler.Dispose();
+ pollStarted.TrySetResult(true);
+ cancellationToken.Register(() => releasePoll.TrySetCanceled(cancellationToken));
+ return releasePoll.Task;
}
+
+ var stopwatch = Stopwatch.StartNew();
+ var executePollTask = sampler.ExecutePollAsync(PollAsync);
+ stopwatch.Stop();
+
+ await pollStarted.Task;
+ Assert.True(stopwatch.Elapsed < TimeSpan.FromSeconds(1), $"Expected ExecutePollAsync to return without waiting for the poll to finish, but it took {stopwatch.Elapsed}.");
+
+ releasePoll.TrySetResult(true);
+ await executePollTask;
+ }
+
+ private static AWSXRayRemoteSampler GetRemoteSampler(Trace.Sampler sampler)
+ {
+ var rootSamplerFieldInfo = typeof(ParentBasedSampler).GetField("rootSampler", BindingFlags.NonPublic | BindingFlags.Instance);
+ var remoteSampler = (AWSXRayRemoteSampler?)rootSamplerFieldInfo?.GetValue(sampler);
+
+ return remoteSampler ?? throw new InvalidOperationException("Unable to get AWSXRayRemoteSampler from ParentBasedSampler.");
}
private SamplingDecision DoSample(Trace.Sampler sampler, string serviceName)
diff --git a/test/Shared/TestHttpServer.cs b/test/Shared/TestHttpServer.cs
index 3ace5275b0..333ea62520 100644
--- a/test/Shared/TestHttpServer.cs
+++ b/test/Shared/TestHttpServer.cs
@@ -105,7 +105,7 @@ private static bool IsResponseAlreadyClosedException(Exception exception)
return true;
}
- if (ex is HttpListenerException httpEx && (httpEx.ErrorCode is 1 or 6 or 995 or 10057))
+ if (ex is HttpListenerException httpEx && (httpEx.ErrorCode is 1 or 6 or 995 or 1229 or 10057))
{
return true;
}
diff --git a/test/TestApp.AspNetCore/Program.cs b/test/TestApp.AspNetCore/Program.cs
index 110693b6fa..b3c2de565d 100644
--- a/test/TestApp.AspNetCore/Program.cs
+++ b/test/TestApp.AspNetCore/Program.cs
@@ -53,6 +53,8 @@ public static void Main(string[] args)
app.AddTestMiddleware();
+ app.MapGet("/ping", () => "pong");
+
app.Run();
}
}