[ASP.NET Core] Remove OTEL_SEMCONV_STABILITY_OPT_IN (#5066)
This commit is contained in:
parent
a55341a50b
commit
0c4f065484
|
|
@ -18,7 +18,6 @@
|
|||
using System.Diagnostics.Metrics;
|
||||
using System.Reflection;
|
||||
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
|
||||
using OpenTelemetry.Internal;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore;
|
||||
|
||||
|
|
@ -46,11 +45,10 @@ internal sealed class AspNetCoreMetrics : IDisposable
|
|||
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
|
||||
private readonly Meter meter;
|
||||
|
||||
internal AspNetCoreMetrics(AspNetCoreMetricsInstrumentationOptions options)
|
||||
internal AspNetCoreMetrics()
|
||||
{
|
||||
Guard.ThrowIfNull(options);
|
||||
this.meter = new Meter(InstrumentationName, InstrumentationVersion);
|
||||
var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore", this.meter, options);
|
||||
var metricsListener = new HttpInMetricsListener("Microsoft.AspNetCore", this.meter);
|
||||
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(metricsListener, this.isEnabled, AspNetCoreInstrumentationEventSource.Log.UnknownErrorProcessingEvent);
|
||||
this.diagnosticSourceSubscriber.Subscribe();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
// <copyright file="AspNetCoreMetricsInstrumentationOptions.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore;
|
||||
|
||||
/// <summary>
|
||||
/// Options for metrics requests instrumentation.
|
||||
/// </summary>
|
||||
internal sealed class AspNetCoreMetricsInstrumentationOptions
|
||||
{
|
||||
internal readonly HttpSemanticConvention HttpSemanticConvention;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AspNetCoreMetricsInstrumentationOptions"/> class.
|
||||
/// </summary>
|
||||
public AspNetCoreMetricsInstrumentationOptions()
|
||||
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
|
||||
{
|
||||
}
|
||||
|
||||
internal AspNetCoreMetricsInstrumentationOptions(IConfiguration configuration)
|
||||
{
|
||||
Debug.Assert(configuration != null, "configuration was null");
|
||||
|
||||
this.HttpSemanticConvention = GetSemanticConventionOptIn(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
* Removed support for `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. The
|
||||
library will now emit only the
|
||||
[stable](https://github.com/open-telemetry/semantic-conventions/tree/v1.23.0/docs/http)
|
||||
semantic conventions.
|
||||
([#5066](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5066))
|
||||
|
||||
## 1.6.0-beta.3
|
||||
|
||||
Released 2023-Nov-17
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ using OpenTelemetry.Instrumentation.GrpcNetClient;
|
|||
#endif
|
||||
using OpenTelemetry.Internal;
|
||||
using OpenTelemetry.Trace;
|
||||
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation;
|
||||
|
||||
|
|
@ -66,8 +65,6 @@ internal class HttpInListener : ListenerHandler
|
|||
private readonly PropertyFetcher<string> beforeActionTemplateFetcher = new("Template");
|
||||
#endif
|
||||
private readonly AspNetCoreInstrumentationOptions options;
|
||||
private readonly bool emitOldAttributes;
|
||||
private readonly bool emitNewAttributes;
|
||||
|
||||
public HttpInListener(AspNetCoreInstrumentationOptions options)
|
||||
: base(DiagnosticSourceName)
|
||||
|
|
@ -75,10 +72,6 @@ internal class HttpInListener : ListenerHandler
|
|||
Guard.ThrowIfNull(options);
|
||||
|
||||
this.options = options;
|
||||
|
||||
this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
|
||||
|
||||
this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
|
||||
}
|
||||
|
||||
public override void OnEventWritten(string name, object payload)
|
||||
|
|
@ -197,67 +190,36 @@ internal class HttpInListener : ListenerHandler
|
|||
var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/";
|
||||
activity.DisplayName = this.GetDisplayName(request.Method);
|
||||
|
||||
// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/semantic_conventions/http.md
|
||||
if (this.emitOldAttributes)
|
||||
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/http/http-spans.md
|
||||
|
||||
if (request.Host.HasValue)
|
||||
{
|
||||
if (request.Host.HasValue)
|
||||
activity.SetTag(SemanticConventions.AttributeServerAddress, request.Host.Host);
|
||||
|
||||
if (request.Host.Port is not null && request.Host.Port != 80 && request.Host.Port != 443)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeNetHostName, request.Host.Host);
|
||||
|
||||
if (request.Host.Port is not null && request.Host.Port != 80 && request.Host.Port != 443)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeNetHostPort, request.Host.Port);
|
||||
}
|
||||
}
|
||||
|
||||
activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
|
||||
activity.SetTag(SemanticConventions.AttributeHttpScheme, request.Scheme);
|
||||
activity.SetTag(SemanticConventions.AttributeHttpTarget, path);
|
||||
activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUri(request));
|
||||
activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(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.AttributeHttpUserAgent, userAgent);
|
||||
}
|
||||
activity.SetTag(SemanticConventions.AttributeServerPort, request.Host.Port);
|
||||
}
|
||||
}
|
||||
|
||||
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/http/http-spans.md
|
||||
if (this.emitNewAttributes)
|
||||
if (request.QueryString.HasValue)
|
||||
{
|
||||
if (request.Host.HasValue)
|
||||
// QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
|
||||
activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
|
||||
}
|
||||
|
||||
RequestMethodHelper.SetHttpMethodTag(activity, request.Method);
|
||||
|
||||
activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
|
||||
activity.SetTag(SemanticConventions.AttributeUrlPath, path);
|
||||
activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(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.AttributeServerAddress, request.Host.Host);
|
||||
|
||||
if (request.Host.Port is not null && request.Host.Port != 80 && request.Host.Port != 443)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeServerPort, request.Host.Port);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.QueryString.HasValue)
|
||||
{
|
||||
// QueryString should be sanitized. see: https://github.com/open-telemetry/opentelemetry-dotnet/issues/4571
|
||||
activity.SetTag(SemanticConventions.AttributeUrlQuery, request.QueryString.Value);
|
||||
}
|
||||
|
||||
RequestMethodHelper.SetHttpMethodTag(activity, request.Method);
|
||||
|
||||
activity.SetTag(SemanticConventions.AttributeUrlScheme, request.Scheme);
|
||||
activity.SetTag(SemanticConventions.AttributeUrlPath, path);
|
||||
activity.SetTag(SemanticConventions.AttributeNetworkProtocolVersion, HttpTagHelper.GetFlavorTagValueFromProtocol(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);
|
||||
}
|
||||
activity.SetTag(SemanticConventions.AttributeUserAgentOriginal, userAgent);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -294,15 +256,7 @@ internal class HttpInListener : ListenerHandler
|
|||
}
|
||||
#endif
|
||||
|
||||
if (this.emitOldAttributes)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
|
||||
}
|
||||
|
||||
if (this.emitNewAttributes)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
|
||||
}
|
||||
activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, TelemetryHelper.GetBoxedStatusCode(response.StatusCode));
|
||||
|
||||
#if !NETSTANDARD2_0
|
||||
if (this.options.EnableGrpcAspNetCoreSupport && TryGetGrpcMethod(activity, out var grpcMethod))
|
||||
|
|
@ -366,10 +320,7 @@ internal class HttpInListener : ListenerHandler
|
|||
return;
|
||||
}
|
||||
|
||||
if (this.emitNewAttributes)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeErrorType, exc.GetType().FullName);
|
||||
}
|
||||
activity.SetTag(SemanticConventions.AttributeErrorType, exc.GetType().FullName);
|
||||
|
||||
if (this.options.RecordException)
|
||||
{
|
||||
|
|
@ -454,9 +405,7 @@ internal class HttpInListener : ListenerHandler
|
|||
|
||||
private string GetDisplayName(string httpMethod, string httpRoute = null)
|
||||
{
|
||||
var normalizedMethod = this.emitNewAttributes
|
||||
? RequestMethodHelper.GetNormalizedHttpMethod(httpMethod)
|
||||
: httpMethod;
|
||||
var normalizedMethod = RequestMethodHelper.GetNormalizedHttpMethod(httpMethod);
|
||||
|
||||
return string.IsNullOrEmpty(httpRoute)
|
||||
? normalizedMethod
|
||||
|
|
@ -474,27 +423,14 @@ internal class HttpInListener : ListenerHandler
|
|||
|
||||
activity.SetTag(SemanticConventions.AttributeRpcSystem, GrpcTagHelper.RpcSystemGrpc);
|
||||
|
||||
if (this.emitOldAttributes)
|
||||
{
|
||||
if (context.Connection.RemoteIpAddress != null)
|
||||
{
|
||||
// TODO: This attribute was changed in v1.13.0 https://github.com/open-telemetry/opentelemetry-specification/pull/2614
|
||||
activity.SetTag(SemanticConventions.AttributeNetPeerIp, context.Connection.RemoteIpAddress.ToString());
|
||||
}
|
||||
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/rpc/rpc-spans.md
|
||||
|
||||
activity.SetTag(SemanticConventions.AttributeNetPeerPort, context.Connection.RemotePort);
|
||||
if (context.Connection.RemoteIpAddress != null)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeClientAddress, context.Connection.RemoteIpAddress.ToString());
|
||||
}
|
||||
|
||||
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/rpc/rpc-spans.md
|
||||
if (this.emitNewAttributes)
|
||||
{
|
||||
if (context.Connection.RemoteIpAddress != null)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeClientAddress, context.Connection.RemoteIpAddress.ToString());
|
||||
}
|
||||
|
||||
activity.SetTag(SemanticConventions.AttributeClientPort, context.Connection.RemotePort);
|
||||
}
|
||||
activity.SetTag(SemanticConventions.AttributeClientPort, context.Connection.RemotePort);
|
||||
|
||||
bool validConversion = GrpcTagHelper.TryGetGrpcStatusCodeFromActivity(activity, out int status);
|
||||
if (validConversion)
|
||||
|
|
|
|||
|
|
@ -24,13 +24,11 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using Microsoft.AspNetCore.Routing;
|
||||
#endif
|
||||
using OpenTelemetry.Trace;
|
||||
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation;
|
||||
|
||||
internal sealed class HttpInMetricsListener : ListenerHandler
|
||||
{
|
||||
internal const string HttpServerDurationMetricName = "http.server.duration";
|
||||
internal const string HttpServerRequestDurationMetricName = "http.server.request.duration";
|
||||
|
||||
internal const string OnUnhandledHostingExceptionEvent = "Microsoft.AspNetCore.Hosting.UnhandledException";
|
||||
|
|
@ -43,31 +41,13 @@ internal sealed class HttpInMetricsListener : ListenerHandler
|
|||
private static readonly object ErrorTypeHttpContextItemsKey = new();
|
||||
|
||||
private readonly Meter meter;
|
||||
private readonly AspNetCoreMetricsInstrumentationOptions options;
|
||||
private readonly Histogram<double> httpServerDuration;
|
||||
private readonly Histogram<double> httpServerRequestDuration;
|
||||
private readonly bool emitOldAttributes;
|
||||
private readonly bool emitNewAttributes;
|
||||
|
||||
internal HttpInMetricsListener(string name, Meter meter, AspNetCoreMetricsInstrumentationOptions options)
|
||||
internal HttpInMetricsListener(string name, Meter meter)
|
||||
: base(name)
|
||||
{
|
||||
this.meter = meter;
|
||||
this.options = options;
|
||||
|
||||
this.emitOldAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.Old);
|
||||
|
||||
this.emitNewAttributes = this.options.HttpSemanticConvention.HasFlag(HttpSemanticConvention.New);
|
||||
|
||||
if (this.emitOldAttributes)
|
||||
{
|
||||
this.httpServerDuration = meter.CreateHistogram<double>(HttpServerDurationMetricName, "ms", "Measures the duration of inbound HTTP requests.");
|
||||
}
|
||||
|
||||
if (this.emitNewAttributes)
|
||||
{
|
||||
this.httpServerRequestDuration = meter.CreateHistogram<double>(HttpServerRequestDurationMetricName, "s", "Duration of HTTP server requests.");
|
||||
}
|
||||
this.httpServerRequestDuration = meter.CreateHistogram<double>(HttpServerRequestDurationMetricName, "s", "Duration of HTTP server requests.");
|
||||
}
|
||||
|
||||
public override void OnEventWritten(string name, object payload)
|
||||
|
|
@ -77,24 +57,13 @@ internal sealed class HttpInMetricsListener : ListenerHandler
|
|||
case OnUnhandledDiagnosticsExceptionEvent:
|
||||
case OnUnhandledHostingExceptionEvent:
|
||||
{
|
||||
if (this.emitNewAttributes)
|
||||
{
|
||||
this.OnExceptionEventWritten(name, payload);
|
||||
}
|
||||
this.OnExceptionEventWritten(name, payload);
|
||||
}
|
||||
|
||||
break;
|
||||
case OnStopEvent:
|
||||
{
|
||||
if (this.emitOldAttributes)
|
||||
{
|
||||
this.OnEventWritten_Old(name, payload);
|
||||
}
|
||||
|
||||
if (this.emitNewAttributes)
|
||||
{
|
||||
this.OnEventWritten_New(name, payload);
|
||||
}
|
||||
this.OnStopEventWritten(name, payload);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -106,7 +75,7 @@ internal sealed class HttpInMetricsListener : ListenerHandler
|
|||
// We need to use reflection here as the payload type is not a defined public type.
|
||||
if (!TryFetchException(payload, out Exception exc) || !TryFetchHttpContext(payload, out HttpContext ctx))
|
||||
{
|
||||
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), nameof(this.OnExceptionEventWritten), HttpServerDurationMetricName);
|
||||
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), nameof(this.OnExceptionEventWritten), HttpServerRequestDurationMetricName);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -127,56 +96,7 @@ internal sealed class HttpInMetricsListener : ListenerHandler
|
|||
=> HttpContextPropertyFetcher.TryFetch(payload, out ctx) && ctx != null;
|
||||
}
|
||||
|
||||
public void OnEventWritten_Old(string name, object payload)
|
||||
{
|
||||
var context = payload as HttpContext;
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
AspNetCoreInstrumentationEventSource.Log.NullPayload(nameof(HttpInMetricsListener), EventName, HttpServerDurationMetricName);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Prometheus pulls metrics by invoking the /metrics endpoint. Decide if it makes sense to suppress this.
|
||||
// Below is just a temporary way of achieving this suppression for metrics (we should consider suppressing traces too).
|
||||
// If we want to suppress activity from Prometheus then we should use SuppressInstrumentationScope.
|
||||
if (context.Request.Path.HasValue && context.Request.Path.Value.Contains("metrics"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TagList tags = default;
|
||||
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocol(context.Request.Protocol)));
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, context.Request.Scheme));
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, context.Request.Method));
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, TelemetryHelper.GetBoxedStatusCode(context.Response.StatusCode)));
|
||||
|
||||
if (context.Request.Host.HasValue)
|
||||
{
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostName, context.Request.Host.Host));
|
||||
|
||||
if (context.Request.Host.Port is not null && context.Request.Host.Port != 80 && context.Request.Host.Port != 443)
|
||||
{
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostPort, context.Request.Host.Port));
|
||||
}
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
var route = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText;
|
||||
if (!string.IsNullOrEmpty(route))
|
||||
{
|
||||
tags.Add(new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, route));
|
||||
}
|
||||
#endif
|
||||
|
||||
// We are relying here on ASP.NET Core to set duration before writing the stop event.
|
||||
// https://github.com/dotnet/aspnetcore/blob/d6fa351048617ae1c8b47493ba1abbe94c3a24cf/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L449
|
||||
// TODO: Follow up with .NET team if we can continue to rely on this behavior.
|
||||
this.httpServerDuration.Record(Activity.Current.Duration.TotalMilliseconds, tags);
|
||||
}
|
||||
|
||||
public void OnEventWritten_New(string name, object payload)
|
||||
public void OnStopEventWritten(string name, object payload)
|
||||
{
|
||||
var context = payload as HttpContext;
|
||||
if (context == null)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@
|
|||
// </copyright>
|
||||
|
||||
#if !NET8_0_OR_GREATER
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenTelemetry.Instrumentation.AspNetCore;
|
||||
using OpenTelemetry.Instrumentation.AspNetCore.Implementation;
|
||||
#endif
|
||||
|
|
@ -46,22 +44,9 @@ public static class MeterProviderBuilderExtensions
|
|||
_ = TelemetryHelper.BoxedStatusCodes;
|
||||
_ = RequestMethodHelper.KnownMethods;
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.RegisterOptionsFactory(configuration => new AspNetCoreMetricsInstrumentationOptions(configuration));
|
||||
});
|
||||
|
||||
builder.AddMeter(AspNetCoreMetrics.InstrumentationName);
|
||||
|
||||
builder.AddInstrumentation(sp =>
|
||||
{
|
||||
var options = sp.GetRequiredService<IOptionsMonitor<AspNetCoreMetricsInstrumentationOptions>>().Get(Options.DefaultName);
|
||||
|
||||
// TODO: Add additional options to AspNetCoreMetricsInstrumentationOptions ?
|
||||
// RecordException - probably doesn't make sense for metric instrumentation
|
||||
// EnableGrpcAspNetCoreSupport - this instrumentation will also need to also handle gRPC requests
|
||||
return new AspNetCoreMetrics(options);
|
||||
});
|
||||
builder.AddInstrumentation(new AspNetCoreMetrics());
|
||||
|
||||
return builder;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public sealed class BasicTests
|
|||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(200, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
|
||||
Assert.Equal(200, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
ValidateAspNetCoreActivity(activity, "/api/values");
|
||||
}
|
||||
|
|
@ -643,7 +643,7 @@ public sealed class BasicTests
|
|||
Assert.Equal(activityName, middlewareActivity.DisplayName);
|
||||
|
||||
// tag http.method should be added on activity started by asp.net core
|
||||
Assert.Equal("GET", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpMethod) as string);
|
||||
Assert.Equal("GET", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod) as string);
|
||||
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", aspnetcoreframeworkactivity.OperationName);
|
||||
}
|
||||
|
||||
|
|
@ -761,7 +761,7 @@ public sealed class BasicTests
|
|||
Assert.Equal(activityName, middlewareActivity.DisplayName);
|
||||
|
||||
// tag http.method should be added on activity started by asp.net core
|
||||
Assert.Equal("GET", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpMethod) as string);
|
||||
Assert.Equal("GET", aspnetcoreframeworkactivity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod) as string);
|
||||
Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", aspnetcoreframeworkactivity.OperationName);
|
||||
}
|
||||
|
||||
|
|
@ -1091,7 +1091,7 @@ public sealed class BasicTests
|
|||
Assert.Equal(HttpInListener.ActivitySourceName, activityToValidate.Source.Name);
|
||||
Assert.Equal(HttpInListener.Version.ToString(), activityToValidate.Source.Version);
|
||||
#endif
|
||||
Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeHttpTarget) as string);
|
||||
Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeUrlPath) as string);
|
||||
}
|
||||
|
||||
private static void AssertException(List<Activity> exportedItems)
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ public class IncomingRequestsCollectionsIsAccordingToTheSpecTests
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/api/values", null, "user-agent", 503, "503")]
|
||||
[InlineData("/api/values", "?query=1", null, 503, null)]
|
||||
[InlineData("/api/values", null, "user-agent", 200, null)]
|
||||
[InlineData("/api/values", "?query=1", null, 200, null)]
|
||||
[InlineData("/api/exception", null, null, 503, null)]
|
||||
[InlineData("/api/exception", null, null, 503, null, true)]
|
||||
public async Task SuccessfulTemplateControllerCallGeneratesASpan_Old(
|
||||
public async Task SuccessfulTemplateControllerCallGeneratesASpan_New(
|
||||
string urlPath,
|
||||
string query,
|
||||
string userAgent,
|
||||
|
|
@ -51,102 +51,94 @@ public class IncomingRequestsCollectionsIsAccordingToTheSpecTests
|
|||
string reasonPhrase,
|
||||
bool recordException = false)
|
||||
{
|
||||
try
|
||||
var exportedItems = new List<Activity>();
|
||||
|
||||
// Arrange
|
||||
using (var client = this.factory
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureTestServices((IServiceCollection services) =>
|
||||
{
|
||||
services.AddSingleton<CallbackMiddleware.CallbackMiddlewareImpl>(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(builder => builder
|
||||
.AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
|
||||
.AddInMemoryExporter(exportedItems));
|
||||
});
|
||||
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
||||
})
|
||||
.CreateClient())
|
||||
{
|
||||
Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", "none");
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
|
||||
// Arrange
|
||||
using (var client = this.factory
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureTestServices((IServiceCollection services) =>
|
||||
{
|
||||
services.AddSingleton<CallbackMiddleware.CallbackMiddlewareImpl>(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(builder => builder
|
||||
.AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
|
||||
.AddInMemoryExporter(exportedItems));
|
||||
});
|
||||
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
||||
})
|
||||
.CreateClient())
|
||||
try
|
||||
{
|
||||
try
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
// Act
|
||||
var path = urlPath;
|
||||
if (query != null)
|
||||
{
|
||||
path += query;
|
||||
}
|
||||
|
||||
using var response = await client.GetAsync(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore errors
|
||||
client.DefaultRequestHeaders.Add("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
// Act
|
||||
var path = urlPath;
|
||||
if (query != null)
|
||||
{
|
||||
if (exportedItems.Count == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We need to let End callback execute as it is executed AFTER response was returned.
|
||||
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
||||
// giving some breezing room for the End callback to complete
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
path += query;
|
||||
}
|
||||
|
||||
using var response = await client.GetAsync(path);
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(ActivityKind.Server, activity.Kind);
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
|
||||
Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
|
||||
Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeHttpFlavor));
|
||||
Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeHttpScheme));
|
||||
Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
|
||||
Assert.Equal($"http://localhost{urlPath}{query}", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
|
||||
Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
|
||||
|
||||
if (statusCode == 503)
|
||||
catch (Exception)
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Error, activity.Status);
|
||||
// ignore errors
|
||||
}
|
||||
else
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
if (exportedItems.Count == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We need to let End callback execute as it is executed AFTER response was returned.
|
||||
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
||||
// giving some breezing room for the End callback to complete
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
// Instrumentation is not expected to set status description
|
||||
// as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
|
||||
Assert.Null(activity.StatusDescription);
|
||||
|
||||
if (recordException)
|
||||
{
|
||||
Assert.Single(activity.Events);
|
||||
Assert.Equal("exception", activity.Events.First().Name);
|
||||
}
|
||||
|
||||
ValidateTagValue(activity, SemanticConventions.AttributeHttpUserAgent, userAgent);
|
||||
|
||||
activity.Dispose();
|
||||
}
|
||||
finally
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(ActivityKind.Server, activity.Kind);
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
|
||||
Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
|
||||
Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
|
||||
Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
|
||||
Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeUrlPath));
|
||||
Assert.Equal(query, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
|
||||
Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
|
||||
|
||||
if (statusCode == 503)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null);
|
||||
Assert.Equal(ActivityStatusCode.Error, activity.Status);
|
||||
Assert.Equal("System.Exception", activity.GetTagValue(SemanticConventions.AttributeErrorType));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
}
|
||||
|
||||
// Instrumentation is not expected to set status description
|
||||
// as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
|
||||
Assert.Null(activity.StatusDescription);
|
||||
|
||||
if (recordException)
|
||||
{
|
||||
Assert.Single(activity.Events);
|
||||
Assert.Equal("exception", activity.Events.First().Name);
|
||||
}
|
||||
|
||||
ValidateTagValue(activity, SemanticConventions.AttributeUserAgentOriginal, userAgent);
|
||||
|
||||
activity.Dispose();
|
||||
}
|
||||
|
||||
private static void ValidateTagValue(Activity activity, string attribute, string expectedValue)
|
||||
|
|
|
|||
|
|
@ -1,196 +0,0 @@
|
|||
// <copyright file="IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Trace;
|
||||
using TestApp.AspNetCore;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
|
||||
|
||||
public class IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe
|
||||
: IClassFixture<WebApplicationFactory<Program>>
|
||||
{
|
||||
private readonly WebApplicationFactory<Program> factory;
|
||||
|
||||
public IncomingRequestsCollectionsIsAccordingToTheSpecTests_Dupe(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/api/values", null, "user-agent", 503, "503")]
|
||||
[InlineData("/api/values", "?query=1", null, 503, null)]
|
||||
[InlineData("/api/exception", null, null, 503, null)]
|
||||
[InlineData("/api/exception", null, null, 503, null, true)]
|
||||
public async Task SuccessfulTemplateControllerCallGeneratesASpan_Dupe(
|
||||
string urlPath,
|
||||
string query,
|
||||
string userAgent,
|
||||
int statusCode,
|
||||
string reasonPhrase,
|
||||
bool recordException = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup");
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
|
||||
// Arrange
|
||||
using (var client = this.factory
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureTestServices((IServiceCollection services) =>
|
||||
{
|
||||
services.AddSingleton<CallbackMiddleware.CallbackMiddlewareImpl>(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(builder => builder
|
||||
.AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
|
||||
.AddInMemoryExporter(exportedItems));
|
||||
});
|
||||
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
||||
})
|
||||
.CreateClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
// Act
|
||||
var path = urlPath;
|
||||
if (query != null)
|
||||
{
|
||||
path += query;
|
||||
}
|
||||
|
||||
using var response = await client.GetAsync(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore errors
|
||||
}
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (exportedItems.Count == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We need to let End callback execute as it is executed AFTER response was returned.
|
||||
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
||||
// giving some breezing room for the End callback to complete
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(ActivityKind.Server, activity.Kind);
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
|
||||
Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
|
||||
Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
|
||||
Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
|
||||
Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeHttpFlavor));
|
||||
Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
|
||||
Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeHttpScheme));
|
||||
Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeUrlPath));
|
||||
Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
|
||||
Assert.Equal($"http://localhost{urlPath}{query}", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
|
||||
Assert.Equal(query, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
|
||||
Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
|
||||
Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpStatusCode));
|
||||
|
||||
if (statusCode == 503)
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Error, activity.Status);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
}
|
||||
|
||||
// Instrumentation is not expected to set status description
|
||||
// as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
|
||||
Assert.Null(activity.StatusDescription);
|
||||
|
||||
if (recordException)
|
||||
{
|
||||
Assert.Single(activity.Events);
|
||||
Assert.Equal("exception", activity.Events.First().Name);
|
||||
}
|
||||
|
||||
ValidateTagValue(activity, SemanticConventions.AttributeUserAgentOriginal, userAgent);
|
||||
|
||||
activity.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateTagValue(Activity activity, string attribute, string expectedValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expectedValue))
|
||||
{
|
||||
Assert.Null(activity.GetTagValue(attribute));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedValue, activity.GetTagValue(attribute));
|
||||
}
|
||||
}
|
||||
|
||||
public class TestCallbackMiddlewareImpl : CallbackMiddleware.CallbackMiddlewareImpl
|
||||
{
|
||||
private readonly int statusCode;
|
||||
private readonly string reasonPhrase;
|
||||
|
||||
public TestCallbackMiddlewareImpl(int statusCode, string reasonPhrase)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
this.reasonPhrase = reasonPhrase;
|
||||
}
|
||||
|
||||
public override async Task<bool> ProcessAsync(HttpContext context)
|
||||
{
|
||||
context.Response.StatusCode = this.statusCode;
|
||||
context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = this.reasonPhrase;
|
||||
await context.Response.WriteAsync("empty");
|
||||
|
||||
if (context.Request.Path.Value.EndsWith("exception"))
|
||||
{
|
||||
throw new Exception("exception description");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
// <copyright file="IncomingRequestsCollectionsIsAccordingToTheSpecTests_New.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Trace;
|
||||
using TestApp.AspNetCore;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
|
||||
|
||||
public class IncomingRequestsCollectionsIsAccordingToTheSpecTests_New
|
||||
: IClassFixture<WebApplicationFactory<Program>>
|
||||
{
|
||||
private readonly WebApplicationFactory<Program> factory;
|
||||
|
||||
public IncomingRequestsCollectionsIsAccordingToTheSpecTests_New(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/api/values", null, "user-agent", 200, null)]
|
||||
[InlineData("/api/values", "?query=1", null, 200, null)]
|
||||
[InlineData("/api/exception", null, null, 503, null)]
|
||||
[InlineData("/api/exception", null, null, 503, null, true)]
|
||||
public async Task SuccessfulTemplateControllerCallGeneratesASpan_New(
|
||||
string urlPath,
|
||||
string query,
|
||||
string userAgent,
|
||||
int statusCode,
|
||||
string reasonPhrase,
|
||||
bool recordException = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", "http");
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
|
||||
// Arrange
|
||||
using (var client = this.factory
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureTestServices((IServiceCollection services) =>
|
||||
{
|
||||
services.AddSingleton<CallbackMiddleware.CallbackMiddlewareImpl>(new TestCallbackMiddlewareImpl(statusCode, reasonPhrase));
|
||||
services.AddOpenTelemetry()
|
||||
.WithTracing(builder => builder
|
||||
.AddAspNetCoreInstrumentation(options => options.RecordException = recordException)
|
||||
.AddInMemoryExporter(exportedItems));
|
||||
});
|
||||
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
||||
})
|
||||
.CreateClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
// Act
|
||||
var path = urlPath;
|
||||
if (query != null)
|
||||
{
|
||||
path += query;
|
||||
}
|
||||
|
||||
using var response = await client.GetAsync(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignore errors
|
||||
}
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
if (exportedItems.Count == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// We need to let End callback execute as it is executed AFTER response was returned.
|
||||
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
||||
// giving some breezing room for the End callback to complete
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(ActivityKind.Server, activity.Kind);
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
|
||||
Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
|
||||
Assert.Equal("1.1", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
|
||||
Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
|
||||
Assert.Equal(urlPath, activity.GetTagValue(SemanticConventions.AttributeUrlPath));
|
||||
Assert.Equal(query, activity.GetTagValue(SemanticConventions.AttributeUrlQuery));
|
||||
Assert.Equal(statusCode, activity.GetTagValue(SemanticConventions.AttributeHttpResponseStatusCode));
|
||||
|
||||
if (statusCode == 503)
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Error, activity.Status);
|
||||
Assert.Equal("System.Exception", activity.GetTagValue(SemanticConventions.AttributeErrorType));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
}
|
||||
|
||||
// Instrumentation is not expected to set status description
|
||||
// as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode
|
||||
Assert.Null(activity.StatusDescription);
|
||||
|
||||
if (recordException)
|
||||
{
|
||||
Assert.Single(activity.Events);
|
||||
Assert.Equal("exception", activity.Events.First().Name);
|
||||
}
|
||||
|
||||
ValidateTagValue(activity, SemanticConventions.AttributeUserAgentOriginal, userAgent);
|
||||
|
||||
activity.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN", null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateTagValue(Activity activity, string attribute, string expectedValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(expectedValue))
|
||||
{
|
||||
Assert.Null(activity.GetTagValue(attribute));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedValue, activity.GetTagValue(attribute));
|
||||
}
|
||||
}
|
||||
|
||||
public class TestCallbackMiddlewareImpl : CallbackMiddleware.CallbackMiddlewareImpl
|
||||
{
|
||||
private readonly int statusCode;
|
||||
private readonly string reasonPhrase;
|
||||
|
||||
public TestCallbackMiddlewareImpl(int statusCode, string reasonPhrase)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
this.reasonPhrase = reasonPhrase;
|
||||
}
|
||||
|
||||
public override async Task<bool> ProcessAsync(HttpContext context)
|
||||
{
|
||||
context.Response.StatusCode = this.statusCode;
|
||||
context.Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = this.reasonPhrase;
|
||||
await context.Response.WriteAsync("empty");
|
||||
|
||||
if (context.Request.Path.Value.EndsWith("exception"))
|
||||
{
|
||||
throw new Exception("exception description");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,8 +26,6 @@ using Microsoft.AspNetCore.Mvc.Testing;
|
|||
#if NET8_0_OR_GREATER
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
#endif
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
|
|
@ -38,8 +36,6 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests;
|
|||
public class MetricTests
|
||||
: IClassFixture<WebApplicationFactory<Program>>, IDisposable
|
||||
{
|
||||
public const string SemanticConventionOptInKeyName = "OTEL_SEMCONV_STABILITY_OPT_IN";
|
||||
|
||||
private const int StandardTagsCount = 6;
|
||||
|
||||
private readonly WebApplicationFactory<Program> factory;
|
||||
|
|
@ -188,16 +184,11 @@ public class MetricTests
|
|||
[Theory]
|
||||
[InlineData("/api/values/2", "api/Values/{id}", null, 200)]
|
||||
[InlineData("/api/Error", "api/Error", "System.Exception", 500)]
|
||||
public async Task RequestMetricIsCaptured_New(string api, string expectedRoute, string expectedErrorType, int expectedStatusCode)
|
||||
public async Task RequestMetricIsCaptured(string api, string expectedRoute, string expectedErrorType, int expectedStatusCode)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http" })
|
||||
.Build();
|
||||
|
||||
var metricItems = new List<Metric>();
|
||||
|
||||
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddInMemoryExporter(metricItems)
|
||||
.Build();
|
||||
|
|
@ -237,7 +228,7 @@ public class MetricTests
|
|||
var metricPoints = GetMetricPoints(metric);
|
||||
Assert.Single(metricPoints);
|
||||
|
||||
AssertMetricPoints_New(
|
||||
AssertMetricPoints(
|
||||
metricPoints: metricPoints,
|
||||
expectedRoutes: new List<string> { expectedRoute },
|
||||
expectedErrorType,
|
||||
|
|
@ -259,14 +250,9 @@ public class MetricTests
|
|||
[InlineData("CUSTOM", "_OTHER")]
|
||||
public async Task HttpRequestMethodIsCapturedAsPerSpec(string originalMethod, string expectedMethod)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http" })
|
||||
.Build();
|
||||
|
||||
var metricItems = new List<Metric>();
|
||||
|
||||
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddInMemoryExporter(metricItems)
|
||||
.Build();
|
||||
|
|
@ -321,129 +307,6 @@ public class MetricTests
|
|||
Assert.DoesNotContain(attributes, t => t.Key == SemanticConventions.AttributeHttpRequestMethodOriginal);
|
||||
}
|
||||
|
||||
#if !NET8_0_OR_GREATER
|
||||
[Fact]
|
||||
public async Task RequestMetricIsCaptured_Old()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = null })
|
||||
.Build();
|
||||
|
||||
var metricItems = new List<Metric>();
|
||||
|
||||
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddInMemoryExporter(metricItems)
|
||||
.Build();
|
||||
|
||||
using (var client = this.factory
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
||||
})
|
||||
.CreateClient())
|
||||
{
|
||||
using var response1 = await client.GetAsync("/api/values");
|
||||
using var response2 = await client.GetAsync("/api/values/2");
|
||||
|
||||
response1.EnsureSuccessStatusCode();
|
||||
response2.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
// We need to let End callback execute as it is executed AFTER response was returned.
|
||||
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
||||
// giving some breezing room for the End callback to complete
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
this.meterProvider.Dispose();
|
||||
|
||||
var requestMetrics = metricItems
|
||||
.Where(item => item.Name == "http.server.duration")
|
||||
.ToArray();
|
||||
|
||||
var metric = Assert.Single(requestMetrics);
|
||||
Assert.Equal("ms", metric.Unit);
|
||||
var metricPoints = GetMetricPoints(metric);
|
||||
Assert.Equal(2, metricPoints.Count);
|
||||
|
||||
AssertMetricPoints_Old(
|
||||
metricPoints: metricPoints,
|
||||
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
|
||||
expectedTagsCount: 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequestMetricIsCaptured_Dup()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http/dup" })
|
||||
.Build();
|
||||
|
||||
var metricItems = new List<Metric>();
|
||||
|
||||
this.meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddInMemoryExporter(metricItems)
|
||||
.Build();
|
||||
|
||||
using (var client = this.factory
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureLogging(loggingBuilder => loggingBuilder.ClearProviders());
|
||||
})
|
||||
.CreateClient())
|
||||
{
|
||||
using var response1 = await client.GetAsync("/api/values");
|
||||
using var response2 = await client.GetAsync("/api/values/2");
|
||||
|
||||
response1.EnsureSuccessStatusCode();
|
||||
response2.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
// We need to let End callback execute as it is executed AFTER response was returned.
|
||||
// In unit tests environment there may be a lot of parallel unit tests executed, so
|
||||
// giving some breezing room for the End callback to complete
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
|
||||
this.meterProvider.Dispose();
|
||||
|
||||
// Validate Old Semantic Convention
|
||||
var requestMetrics = metricItems
|
||||
.Where(item => item.Name == "http.server.duration")
|
||||
.ToArray();
|
||||
|
||||
var metric = Assert.Single(requestMetrics);
|
||||
Assert.Equal("ms", metric.Unit);
|
||||
var metricPoints = GetMetricPoints(metric);
|
||||
Assert.Equal(2, metricPoints.Count);
|
||||
|
||||
AssertMetricPoints_Old(
|
||||
metricPoints: metricPoints,
|
||||
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
|
||||
expectedTagsCount: 6);
|
||||
|
||||
// Validate New Semantic Convention
|
||||
requestMetrics = metricItems
|
||||
.Where(item => item.Name == "http.server.request.duration")
|
||||
.ToArray();
|
||||
|
||||
metric = Assert.Single(requestMetrics);
|
||||
|
||||
Assert.Equal("s", metric.Unit);
|
||||
metricPoints = GetMetricPoints(metric);
|
||||
Assert.Equal(2, metricPoints.Count);
|
||||
|
||||
AssertMetricPoints_New(
|
||||
metricPoints: metricPoints,
|
||||
expectedRoutes: new List<string> { "api/Values", "api/Values/{id}" },
|
||||
null,
|
||||
200,
|
||||
expectedTagsCount: 5);
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.meterProvider?.Dispose();
|
||||
|
|
@ -463,7 +326,7 @@ public class MetricTests
|
|||
return metricPoints;
|
||||
}
|
||||
|
||||
private static void AssertMetricPoints_New(
|
||||
private static void AssertMetricPoints(
|
||||
List<MetricPoint> metricPoints,
|
||||
List<string> expectedRoutes,
|
||||
string expectedErrorType,
|
||||
|
|
@ -488,7 +351,7 @@ public class MetricTests
|
|||
|
||||
if (metricPoint.HasValue)
|
||||
{
|
||||
AssertMetricPoint_New(metricPoint.Value, expectedStatusCode, expectedRoute, expectedErrorType, expectedTagsCount);
|
||||
AssertMetricPoint(metricPoint.Value, expectedStatusCode, expectedRoute, expectedErrorType, expectedTagsCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -497,39 +360,7 @@ public class MetricTests
|
|||
}
|
||||
}
|
||||
|
||||
private static void AssertMetricPoints_Old(
|
||||
List<MetricPoint> metricPoints,
|
||||
List<string> expectedRoutes,
|
||||
int expectedTagsCount)
|
||||
{
|
||||
// Assert that one MetricPoint exists for each ExpectedRoute
|
||||
foreach (var expectedRoute in expectedRoutes)
|
||||
{
|
||||
MetricPoint? metricPoint = null;
|
||||
|
||||
foreach (var mp in metricPoints)
|
||||
{
|
||||
foreach (var tag in mp.Tags)
|
||||
{
|
||||
if (tag.Key == SemanticConventions.AttributeHttpRoute && tag.Value.ToString() == expectedRoute)
|
||||
{
|
||||
metricPoint = mp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metricPoint.HasValue)
|
||||
{
|
||||
AssertMetricPoint_Old(metricPoint.Value, expectedRoute, expectedTagsCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Fail($"A metric for route '{expectedRoute}' was not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyValuePair<string, object>[] AssertMetricPoint_New(
|
||||
private static void AssertMetricPoint(
|
||||
MetricPoint metricPoint,
|
||||
int expectedStatusCode,
|
||||
string expectedRoute,
|
||||
|
|
@ -587,56 +418,5 @@ public class MetricTests
|
|||
Enumerable.SequenceEqual(expectedHistogramBoundsNew, histogramBounds);
|
||||
|
||||
Assert.True(histogramBoundsMatchCorrectly);
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private static KeyValuePair<string, object>[] AssertMetricPoint_Old(
|
||||
MetricPoint metricPoint,
|
||||
string expectedRoute = "api/Values",
|
||||
int expectedTagsCount = StandardTagsCount)
|
||||
{
|
||||
var count = metricPoint.GetHistogramCount();
|
||||
var sum = metricPoint.GetHistogramSum();
|
||||
|
||||
Assert.Equal(1L, count);
|
||||
Assert.True(sum > 0);
|
||||
|
||||
var attributes = new KeyValuePair<string, object>[metricPoint.Tags.Count];
|
||||
int i = 0;
|
||||
foreach (var tag in metricPoint.Tags)
|
||||
{
|
||||
attributes[i++] = tag;
|
||||
}
|
||||
|
||||
// Inspect Attributes
|
||||
Assert.Equal(expectedTagsCount, attributes.Length);
|
||||
|
||||
var method = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpMethod, "GET");
|
||||
var scheme = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpScheme, "http");
|
||||
var statusCode = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpStatusCode, 200);
|
||||
var flavor = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpFlavor, "1.1");
|
||||
var host = new KeyValuePair<string, object>(SemanticConventions.AttributeNetHostName, "localhost");
|
||||
var route = new KeyValuePair<string, object>(SemanticConventions.AttributeHttpRoute, expectedRoute);
|
||||
Assert.Contains(method, attributes);
|
||||
Assert.Contains(scheme, attributes);
|
||||
Assert.Contains(statusCode, attributes);
|
||||
Assert.Contains(flavor, attributes);
|
||||
Assert.Contains(host, attributes);
|
||||
Assert.Contains(route, attributes);
|
||||
|
||||
// Inspect Histogram Bounds
|
||||
var histogramBuckets = metricPoint.GetHistogramBuckets();
|
||||
var histogramBounds = new List<double>();
|
||||
foreach (var t in histogramBuckets)
|
||||
{
|
||||
histogramBounds.Add(t.ExplicitBound);
|
||||
}
|
||||
|
||||
Assert.Equal(
|
||||
expected: new List<double> { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000, double.PositiveInfinity },
|
||||
actual: histogramBounds);
|
||||
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ public static class RoutingTestCases
|
|||
continue;
|
||||
}
|
||||
|
||||
result.Add(new object[] { testCase, true });
|
||||
result.Add(new object[] { testCase, false });
|
||||
result.Add(new object[] { testCase });
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -17,14 +17,11 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
using RouteTests.TestApplication;
|
||||
using Xunit;
|
||||
using static OpenTelemetry.Internal.HttpSemanticConventionHelper;
|
||||
|
||||
namespace RouteTests;
|
||||
|
||||
|
|
@ -49,20 +46,14 @@ public class RoutingTests : IClassFixture<RoutingTestFixture>
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(TestData))]
|
||||
public async Task TestHttpRoute(RoutingTestCases.TestCase testCase, bool useLegacyConventions)
|
||||
public async Task TestHttpRoute(RoutingTestCases.TestCase testCase)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?> { [SemanticConventionOptInKeyName] = useLegacyConventions ? null : "http" })
|
||||
.Build();
|
||||
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddInMemoryExporter(this.exportedActivities)
|
||||
.Build()!;
|
||||
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration))
|
||||
.AddAspNetCoreInstrumentation()
|
||||
.AddInMemoryExporter(this.exportedMetrics)
|
||||
.Build()!;
|
||||
|
|
@ -91,8 +82,8 @@ public class RoutingTests : IClassFixture<RoutingTestFixture>
|
|||
var activity = Assert.Single(this.exportedActivities);
|
||||
var metricPoint = Assert.Single(metricPoints);
|
||||
|
||||
GetTagsFromActivity(useLegacyConventions, activity, out var activityHttpStatusCode, out var activityHttpMethod, out var activityHttpRoute);
|
||||
GetTagsFromMetricPoint(useLegacyConventions && Environment.Version.Major < 8, metricPoint, out var metricHttpStatusCode, out var metricHttpMethod, out var metricHttpRoute);
|
||||
GetTagsFromActivity(activity, out var activityHttpStatusCode, out var activityHttpMethod, out var activityHttpRoute);
|
||||
GetTagsFromMetricPoint(Environment.Version.Major < 8, metricPoint, out var metricHttpStatusCode, out var metricHttpMethod, out var metricHttpRoute);
|
||||
|
||||
Assert.Equal(testCase.ExpectedStatusCode, activityHttpStatusCode);
|
||||
Assert.Equal(testCase.ExpectedStatusCode, metricHttpStatusCode);
|
||||
|
|
@ -113,27 +104,23 @@ public class RoutingTests : IClassFixture<RoutingTestFixture>
|
|||
|
||||
Assert.Equal(expectedActivityDisplayName, activity.DisplayName);
|
||||
|
||||
// Only produce README files based on final semantic conventions
|
||||
if (!useLegacyConventions)
|
||||
var testResult = new RoutingTestResult
|
||||
{
|
||||
var testResult = new RoutingTestResult
|
||||
{
|
||||
IdealHttpRoute = testCase.ExpectedHttpRoute,
|
||||
ActivityDisplayName = activity.DisplayName,
|
||||
ActivityHttpRoute = activityHttpRoute,
|
||||
MetricHttpRoute = metricHttpRoute,
|
||||
TestCase = testCase,
|
||||
RouteInfo = RouteInfo.Current,
|
||||
};
|
||||
IdealHttpRoute = testCase.ExpectedHttpRoute,
|
||||
ActivityDisplayName = activity.DisplayName,
|
||||
ActivityHttpRoute = activityHttpRoute,
|
||||
MetricHttpRoute = metricHttpRoute,
|
||||
TestCase = testCase,
|
||||
RouteInfo = RouteInfo.Current,
|
||||
};
|
||||
|
||||
this.fixture.AddTestResult(testResult);
|
||||
}
|
||||
this.fixture.AddTestResult(testResult);
|
||||
}
|
||||
|
||||
private static void GetTagsFromActivity(bool useLegacyConventions, Activity activity, out int httpStatusCode, out string httpMethod, out string? httpRoute)
|
||||
private static void GetTagsFromActivity(Activity activity, out int httpStatusCode, out string httpMethod, out string? httpRoute)
|
||||
{
|
||||
var expectedStatusCodeKey = useLegacyConventions ? OldHttpStatusCode : HttpStatusCode;
|
||||
var expectedHttpMethodKey = useLegacyConventions ? OldHttpMethod : HttpMethod;
|
||||
var expectedStatusCodeKey = HttpStatusCode;
|
||||
var expectedHttpMethodKey = HttpMethod;
|
||||
httpStatusCode = Convert.ToInt32(activity.GetTagItem(expectedStatusCodeKey));
|
||||
httpMethod = (activity.GetTagItem(expectedHttpMethodKey) as string)!;
|
||||
httpRoute = activity.GetTagItem(HttpRoute) as string ?? string.Empty;
|
||||
|
|
@ -141,8 +128,8 @@ public class RoutingTests : IClassFixture<RoutingTestFixture>
|
|||
|
||||
private static void GetTagsFromMetricPoint(bool useLegacyConventions, MetricPoint metricPoint, out int httpStatusCode, out string httpMethod, out string? httpRoute)
|
||||
{
|
||||
var expectedStatusCodeKey = useLegacyConventions ? OldHttpStatusCode : HttpStatusCode;
|
||||
var expectedHttpMethodKey = useLegacyConventions ? OldHttpMethod : HttpMethod;
|
||||
var expectedStatusCodeKey = HttpStatusCode;
|
||||
var expectedHttpMethodKey = HttpMethod;
|
||||
|
||||
httpStatusCode = 0;
|
||||
httpMethod = string.Empty;
|
||||
|
|
|
|||
|
|
@ -51,77 +51,6 @@ public partial class GrpcTests : IDisposable
|
|||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes(bool? enableGrpcAspNetCoreSupport)
|
||||
{
|
||||
var exportedItems = new List<Activity>();
|
||||
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder();
|
||||
|
||||
if (enableGrpcAspNetCoreSupport.HasValue)
|
||||
{
|
||||
tracerProviderBuilder.AddAspNetCoreInstrumentation(options =>
|
||||
{
|
||||
options.EnableGrpcAspNetCoreSupport = enableGrpcAspNetCoreSupport.Value;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
tracerProviderBuilder.AddAspNetCoreInstrumentation();
|
||||
}
|
||||
|
||||
using var tracerProvider = tracerProviderBuilder
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
var clientLoopbackAddresses = new[] { IPAddress.Loopback.ToString(), IPAddress.IPv6Loopback.ToString() };
|
||||
var uri = new Uri($"http://localhost:{this.server.Port}");
|
||||
|
||||
using var channel = GrpcChannel.ForAddress(uri);
|
||||
var client = new Greeter.GreeterClient(channel);
|
||||
var returnMsg = client.SayHello(new HelloRequest()).Message;
|
||||
Assert.False(string.IsNullOrEmpty(returnMsg));
|
||||
|
||||
WaitForExporterToReceiveItems(exportedItems, 1);
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(ActivityKind.Server, activity.Kind);
|
||||
|
||||
if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
|
||||
{
|
||||
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
|
||||
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
|
||||
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
|
||||
Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp), clientLoopbackAddresses);
|
||||
Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
|
||||
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
||||
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
||||
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
||||
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
||||
}
|
||||
|
||||
Assert.Equal(Status.Unset, activity.GetStatus());
|
||||
|
||||
// The following are http.* attributes that are also included on the span for the gRPC invocation.
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
|
||||
Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeNetHostPort));
|
||||
Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
|
||||
Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
|
||||
Assert.Equal($"http://localhost:{this.server.Port}/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
|
||||
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string);
|
||||
}
|
||||
|
||||
// Tests for v1.21.0 Semantic Conventions for database client calls.
|
||||
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md
|
||||
// This test emits the new attributes.
|
||||
// This test method can replace the other (old) test method when this library is GA.
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes_New(bool? enableGrpcAspNetCoreSupport)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http" })
|
||||
|
|
@ -190,112 +119,6 @@ public partial class GrpcTests : IDisposable
|
|||
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeUserAgentOriginal) as string);
|
||||
}
|
||||
|
||||
// Tests for v1.21.0 Semantic Conventions for database client calls.
|
||||
// see the spec https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-spans.md
|
||||
// This test emits both the new and older attributes.
|
||||
// This test method can be deleted when this library is GA.
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void GrpcAspNetCoreInstrumentationAddsCorrectAttributes_Dupe(bool? enableGrpcAspNetCoreSupport)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string> { [SemanticConventionOptInKeyName] = "http/dup" })
|
||||
.Build();
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
var tracerProviderBuilder = Sdk.CreateTracerProviderBuilder()
|
||||
.ConfigureServices(services => services.AddSingleton<IConfiguration>(configuration));
|
||||
|
||||
if (enableGrpcAspNetCoreSupport.HasValue)
|
||||
{
|
||||
tracerProviderBuilder.AddAspNetCoreInstrumentation(options =>
|
||||
{
|
||||
options.EnableGrpcAspNetCoreSupport = enableGrpcAspNetCoreSupport.Value;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
tracerProviderBuilder.AddAspNetCoreInstrumentation();
|
||||
}
|
||||
|
||||
using var tracerProvider = tracerProviderBuilder
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
var clientLoopbackAddresses = new[] { IPAddress.Loopback.ToString(), IPAddress.IPv6Loopback.ToString() };
|
||||
var uri = new Uri($"http://localhost:{this.server.Port}");
|
||||
|
||||
using var channel = GrpcChannel.ForAddress(uri);
|
||||
var client = new Greeter.GreeterClient(channel);
|
||||
var returnMsg = client.SayHello(new HelloRequest()).Message;
|
||||
Assert.False(string.IsNullOrEmpty(returnMsg));
|
||||
|
||||
WaitForExporterToReceiveItems(exportedItems, 1);
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
Assert.Equal(ActivityKind.Server, activity.Kind);
|
||||
|
||||
// OLD
|
||||
if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
|
||||
{
|
||||
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
|
||||
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
|
||||
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
|
||||
Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeNetPeerIp), clientLoopbackAddresses);
|
||||
Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
|
||||
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
||||
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
||||
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
||||
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
||||
}
|
||||
|
||||
Assert.Equal(Status.Unset, activity.GetStatus());
|
||||
|
||||
// The following are http.* attributes that are also included on the span for the gRPC invocation.
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeNetHostName));
|
||||
Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeNetHostPort));
|
||||
Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpMethod));
|
||||
Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpTarget));
|
||||
Assert.Equal($"http://localhost:{this.server.Port}/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeHttpUrl));
|
||||
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeHttpUserAgent) as string);
|
||||
|
||||
// NEW
|
||||
if (!enableGrpcAspNetCoreSupport.HasValue || enableGrpcAspNetCoreSupport.Value)
|
||||
{
|
||||
Assert.Equal("grpc", activity.GetTagValue(SemanticConventions.AttributeRpcSystem));
|
||||
Assert.Equal("greet.Greeter", activity.GetTagValue(SemanticConventions.AttributeRpcService));
|
||||
Assert.Equal("SayHello", activity.GetTagValue(SemanticConventions.AttributeRpcMethod));
|
||||
Assert.Contains(activity.GetTagValue(SemanticConventions.AttributeClientAddress), clientLoopbackAddresses);
|
||||
Assert.NotEqual(0, activity.GetTagValue(SemanticConventions.AttributeClientPort));
|
||||
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
||||
Assert.Null(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
||||
Assert.Equal(0, activity.GetTagValue(SemanticConventions.AttributeRpcGrpcStatusCode));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcMethodTagName));
|
||||
Assert.NotNull(activity.GetTagValue(GrpcTagHelper.GrpcStatusCodeTagName));
|
||||
}
|
||||
|
||||
Assert.Equal(Status.Unset, activity.GetStatus());
|
||||
|
||||
// The following are http.* attributes that are also included on the span for the gRPC invocation.
|
||||
Assert.Equal("localhost", activity.GetTagValue(SemanticConventions.AttributeServerAddress));
|
||||
Assert.Equal(this.server.Port, activity.GetTagValue(SemanticConventions.AttributeServerPort));
|
||||
Assert.Equal("POST", activity.GetTagValue(SemanticConventions.AttributeHttpRequestMethod));
|
||||
Assert.Equal("http", activity.GetTagValue(SemanticConventions.AttributeUrlScheme));
|
||||
Assert.Equal("/greet.Greeter/SayHello", activity.GetTagValue(SemanticConventions.AttributeUrlPath));
|
||||
Assert.Equal("2", activity.GetTagValue(SemanticConventions.AttributeNetworkProtocolVersion));
|
||||
Assert.StartsWith("grpc-dotnet", activity.GetTagValue(SemanticConventions.AttributeUserAgentOriginal) as string);
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[Theory(Skip = "Skipping for .NET 6 and higher due to bug #3023")]
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue