HttpWebRequest ActivitySource (#694)
* Updated HttpWebRequestDiagnosticSource to use the new .NET 5 ActivitySource API. * Code review part 1 and string allocation reduction. * Code review part 2 added ExceptionInitializingInstrumentation event. * Code review feedback 3. * Code review feedback 4. * Updated unit tests and some code review feedback. * Updated to JaegerActivityExporter since it was just merged. * Refactored http tag value caching so that it can be shared in dependencies project. * Code review. * Noticed a couple comments needed to be updated. * Added Activity.IsAllDataRequested logic. * Code review. * Reverted change to OpenTelemetrySdk. Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
parent
dcedf02ec5
commit
96e1eb9f30
|
|
@ -1,7 +1,9 @@
|
|||
using System.Web;
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.Web.Http;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using OpenTelemetry.Trace;
|
||||
using OpenTelemetry.Trace.Configuration;
|
||||
|
||||
namespace OpenTelemetry.Exporter.AspNet
|
||||
|
|
@ -9,6 +11,7 @@ namespace OpenTelemetry.Exporter.AspNet
|
|||
public class WebApiApplication : HttpApplication
|
||||
{
|
||||
private TracerFactory tracerFactory;
|
||||
private IDisposable openTelemetry;
|
||||
|
||||
protected void Application_Start()
|
||||
{
|
||||
|
|
@ -24,6 +27,16 @@ namespace OpenTelemetry.Exporter.AspNet
|
|||
.AddDependencyInstrumentation();
|
||||
});
|
||||
|
||||
TracerFactoryBase.SetDefault(this.tracerFactory);
|
||||
|
||||
this.openTelemetry = OpenTelemetrySdk.EnableOpenTelemetry(
|
||||
(builder) => builder.AddDependencyInstrumentation()
|
||||
.UseJaegerActivityExporter(c =>
|
||||
{
|
||||
c.AgentHost = "localhost";
|
||||
c.AgentPort = 6831;
|
||||
}));
|
||||
|
||||
GlobalConfiguration.Configure(WebApiConfig.Register);
|
||||
|
||||
AreaRegistration.RegisterAllAreas();
|
||||
|
|
@ -33,6 +46,7 @@ namespace OpenTelemetry.Exporter.AspNet
|
|||
protected void Application_End()
|
||||
{
|
||||
this.tracerFactory?.Dispose();
|
||||
this.openTelemetry?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
<assemblyIdentity name="System.Memory" publicKeyToken="CC7B13FFCD2DDD51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1"/>
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="CC7B13FFCD2DDD51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
|
|
|||
|
|
@ -25,8 +25,14 @@ namespace OpenTelemetry.Trace
|
|||
public const string ComponentKey = "component";
|
||||
public const string PeerServiceKey = "peer.service";
|
||||
|
||||
public const string StatusCodeKey = "ot.status_code";
|
||||
public const string StatusDescriptionKey = "ot.status_description";
|
||||
|
||||
public const string HttpMethodKey = "http.method";
|
||||
public const string HttpSchemeKey = "http.scheme";
|
||||
public const string HttpTargetKey = "http.target";
|
||||
public const string HttpStatusCodeKey = "http.status_code";
|
||||
public const string HttpStatusTextKey = "http.status_text";
|
||||
public const string HttpUserAgentKey = "http.user_agent";
|
||||
public const string HttpPathKey = "http.path";
|
||||
public const string HttpHostKey = "http.host";
|
||||
|
|
|
|||
|
|
@ -112,6 +112,23 @@ namespace OpenTelemetry.Trace
|
|||
return span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that populates span properties from host and port
|
||||
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/2316771e7e0ca3bfe9b2286d13e3a41ded6b8858/specification/data-http.md.
|
||||
/// </summary>
|
||||
/// <param name="span">Span to fill out.</param>
|
||||
/// <param name="hostAndPort">Host and port value.</param>
|
||||
/// <returns>Span with populated host properties.</returns>
|
||||
public static TelemetrySpan PutHttpHostAttribute(this TelemetrySpan span, string hostAndPort)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(hostAndPort))
|
||||
{
|
||||
span.SetAttribute(SpanAttributeConstants.HttpHostKey, hostAndPort);
|
||||
}
|
||||
|
||||
return span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that populates span properties from route
|
||||
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/2316771e7e0ca3bfe9b2286d13e3a41ded6b8858/specification/data-http.md.
|
||||
|
|
@ -171,46 +188,7 @@ namespace OpenTelemetry.Trace
|
|||
{
|
||||
span.PutHttpStatusCodeAttribute(statusCode);
|
||||
|
||||
var newStatus = Status.Unknown;
|
||||
|
||||
if (statusCode >= 200 && statusCode <= 399)
|
||||
{
|
||||
newStatus = Status.Ok;
|
||||
}
|
||||
else if (statusCode == 400)
|
||||
{
|
||||
newStatus = Status.InvalidArgument;
|
||||
}
|
||||
else if (statusCode == 401)
|
||||
{
|
||||
newStatus = Status.Unauthenticated;
|
||||
}
|
||||
else if (statusCode == 403)
|
||||
{
|
||||
newStatus = Status.PermissionDenied;
|
||||
}
|
||||
else if (statusCode == 404)
|
||||
{
|
||||
newStatus = Status.NotFound;
|
||||
}
|
||||
else if (statusCode == 429)
|
||||
{
|
||||
newStatus = Status.ResourceExhausted;
|
||||
}
|
||||
else if (statusCode == 501)
|
||||
{
|
||||
newStatus = Status.Unimplemented;
|
||||
}
|
||||
else if (statusCode == 503)
|
||||
{
|
||||
newStatus = Status.Unavailable;
|
||||
}
|
||||
else if (statusCode == 504)
|
||||
{
|
||||
newStatus = Status.DeadlineExceeded;
|
||||
}
|
||||
|
||||
span.Status = newStatus.WithDescription(reasonPhrase);
|
||||
span.Status = SpanHelper.ResolveSpanStatusForHttpStatusCode(statusCode).WithDescription(reasonPhrase);
|
||||
|
||||
return span;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
// <copyright file="SpanHelper.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.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Trace
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of helper methods to be used when building spans.
|
||||
/// </summary>
|
||||
public static class SpanHelper
|
||||
{
|
||||
private static readonly Dictionary<StatusCanonicalCode, string> StatusCanonicalCodeToStringCache = new Dictionary<StatusCanonicalCode, string>()
|
||||
{
|
||||
[StatusCanonicalCode.Ok] = StatusCanonicalCode.Ok.ToString(),
|
||||
[StatusCanonicalCode.Cancelled] = StatusCanonicalCode.Cancelled.ToString(),
|
||||
[StatusCanonicalCode.Unknown] = StatusCanonicalCode.Unknown.ToString(),
|
||||
[StatusCanonicalCode.InvalidArgument] = StatusCanonicalCode.InvalidArgument.ToString(),
|
||||
[StatusCanonicalCode.DeadlineExceeded] = StatusCanonicalCode.DeadlineExceeded.ToString(),
|
||||
[StatusCanonicalCode.NotFound] = StatusCanonicalCode.NotFound.ToString(),
|
||||
[StatusCanonicalCode.AlreadyExists] = StatusCanonicalCode.AlreadyExists.ToString(),
|
||||
[StatusCanonicalCode.PermissionDenied] = StatusCanonicalCode.PermissionDenied.ToString(),
|
||||
[StatusCanonicalCode.ResourceExhausted] = StatusCanonicalCode.ResourceExhausted.ToString(),
|
||||
[StatusCanonicalCode.FailedPrecondition] = StatusCanonicalCode.FailedPrecondition.ToString(),
|
||||
[StatusCanonicalCode.Aborted] = StatusCanonicalCode.Aborted.ToString(),
|
||||
[StatusCanonicalCode.OutOfRange] = StatusCanonicalCode.OutOfRange.ToString(),
|
||||
[StatusCanonicalCode.Unimplemented] = StatusCanonicalCode.Unimplemented.ToString(),
|
||||
[StatusCanonicalCode.Internal] = StatusCanonicalCode.Internal.ToString(),
|
||||
[StatusCanonicalCode.Unavailable] = StatusCanonicalCode.Unavailable.ToString(),
|
||||
[StatusCanonicalCode.DataLoss] = StatusCanonicalCode.DataLoss.ToString(),
|
||||
[StatusCanonicalCode.Unauthenticated] = StatusCanonicalCode.Unauthenticated.ToString(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that returns the string version of a <see cref="StatusCanonicalCode"/> using a cache to save on allocations.
|
||||
/// </summary>
|
||||
/// <param name="statusCanonicalCode"><see cref="StatusCanonicalCode"/>.</param>
|
||||
/// <returns>String version of the supplied <see cref="StatusCanonicalCode"/>.</returns>
|
||||
public static string GetCachedCanonicalCodeString(StatusCanonicalCode statusCanonicalCode)
|
||||
{
|
||||
if (!StatusCanonicalCodeToStringCache.TryGetValue(statusCanonicalCode, out string canonicalCode))
|
||||
{
|
||||
return statusCanonicalCode.ToString();
|
||||
}
|
||||
|
||||
return canonicalCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that populates span properties from http status code according
|
||||
/// to https://github.com/open-telemetry/opentelemetry-specification/blob/2316771e7e0ca3bfe9b2286d13e3a41ded6b8858/specification/data-http.md.
|
||||
/// </summary>
|
||||
/// <param name="httpStatusCode">Http status code.</param>
|
||||
/// <returns>Resolved span <see cref="Status"/> for the Http status code.</returns>
|
||||
public static Status ResolveSpanStatusForHttpStatusCode(int httpStatusCode)
|
||||
{
|
||||
var newStatus = Status.Unknown;
|
||||
|
||||
if (httpStatusCode >= 200 && httpStatusCode <= 399)
|
||||
{
|
||||
newStatus = Status.Ok;
|
||||
}
|
||||
else if (httpStatusCode == 400)
|
||||
{
|
||||
newStatus = Status.InvalidArgument;
|
||||
}
|
||||
else if (httpStatusCode == 401)
|
||||
{
|
||||
newStatus = Status.Unauthenticated;
|
||||
}
|
||||
else if (httpStatusCode == 403)
|
||||
{
|
||||
newStatus = Status.PermissionDenied;
|
||||
}
|
||||
else if (httpStatusCode == 404)
|
||||
{
|
||||
newStatus = Status.NotFound;
|
||||
}
|
||||
else if (httpStatusCode == 429)
|
||||
{
|
||||
newStatus = Status.ResourceExhausted;
|
||||
}
|
||||
else if (httpStatusCode == 501)
|
||||
{
|
||||
newStatus = Status.Unimplemented;
|
||||
}
|
||||
else if (httpStatusCode == 503)
|
||||
{
|
||||
newStatus = Status.Unavailable;
|
||||
}
|
||||
else if (httpStatusCode == 504)
|
||||
{
|
||||
newStatus = Status.DeadlineExceeded;
|
||||
}
|
||||
|
||||
return newStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,6 @@ namespace OpenTelemetry.Exporter.Jaeger.Implementation
|
|||
{
|
||||
internal static class JaegerConversionExtensions
|
||||
{
|
||||
private const string StatusCode = "ot.status_code";
|
||||
private const string StatusDescription = "ot.status_description";
|
||||
|
||||
private const int DaysPerYear = 365;
|
||||
|
||||
// Number of days in 4 years
|
||||
|
|
@ -56,8 +53,6 @@ namespace OpenTelemetry.Exporter.Jaeger.Implementation
|
|||
["db.instance"] = 4, // peer.service for Redis.
|
||||
};
|
||||
|
||||
private static readonly Dictionary<StatusCanonicalCode, string> CanonicalCodeDictionary = new Dictionary<StatusCanonicalCode, string>();
|
||||
|
||||
private static readonly DictionaryEnumerator<string, object, TagState>.ForEachDelegate ProcessAttributeRef = ProcessAttribute;
|
||||
private static readonly DictionaryEnumerator<string, object, TagState>.ForEachDelegate ProcessLibraryAttributeRef = ProcessLibraryAttribute;
|
||||
private static readonly ListEnumerator<Link, PooledListState<JaegerSpanRef>>.ForEachDelegate ProcessLinkRef = ProcessLink;
|
||||
|
|
@ -127,17 +122,11 @@ namespace OpenTelemetry.Exporter.Jaeger.Implementation
|
|||
|
||||
if (status.IsValid)
|
||||
{
|
||||
if (!CanonicalCodeDictionary.TryGetValue(status.CanonicalCode, out string statusCode))
|
||||
{
|
||||
statusCode = status.CanonicalCode.ToString();
|
||||
CanonicalCodeDictionary.Add(status.CanonicalCode, statusCode);
|
||||
}
|
||||
|
||||
PooledList<JaegerTag>.Add(ref jaegerTags.Tags, new JaegerTag(StatusCode, JaegerTagType.STRING, vStr: statusCode));
|
||||
PooledList<JaegerTag>.Add(ref jaegerTags.Tags, new JaegerTag(SpanAttributeConstants.StatusCodeKey, JaegerTagType.STRING, vStr: SpanHelper.GetCachedCanonicalCodeString(status.CanonicalCode)));
|
||||
|
||||
if (status.Description != null)
|
||||
{
|
||||
PooledList<JaegerTag>.Add(ref jaegerTags.Tags, new JaegerTag(StatusDescription, JaegerTagType.STRING, vStr: status.Description));
|
||||
PooledList<JaegerTag>.Add(ref jaegerTags.Tags, new JaegerTag(SpanAttributeConstants.StatusDescriptionKey, JaegerTagType.STRING, vStr: status.Description));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,6 @@ namespace OpenTelemetry.Exporter.Zipkin.Implementation
|
|||
{
|
||||
internal static class ZipkinConversionExtensions
|
||||
{
|
||||
private const string StatusCode = "ot.status_code";
|
||||
private const string StatusDescription = "ot.status_description";
|
||||
|
||||
private static readonly Dictionary<string, int> RemoteEndpointServiceNameKeyResolutionDictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[SpanAttributeConstants.PeerServiceKey] = 0, // RemoteEndpoint.ServiceName primary.
|
||||
|
|
@ -41,7 +38,6 @@ namespace OpenTelemetry.Exporter.Zipkin.Implementation
|
|||
|
||||
private static readonly ConcurrentDictionary<string, ZipkinEndpoint> LocalEndpointCache = new ConcurrentDictionary<string, ZipkinEndpoint>();
|
||||
private static readonly ConcurrentDictionary<string, ZipkinEndpoint> RemoteEndpointCache = new ConcurrentDictionary<string, ZipkinEndpoint>();
|
||||
private static readonly ConcurrentDictionary<StatusCanonicalCode, string> CanonicalCodeCache = new ConcurrentDictionary<StatusCanonicalCode, string>();
|
||||
|
||||
private static readonly DictionaryEnumerator<string, object, AttributeEnumerationState>.ForEachDelegate ProcessAttributesRef = ProcessAttributes;
|
||||
private static readonly DictionaryEnumerator<string, object, AttributeEnumerationState>.ForEachDelegate ProcessLibraryResourcesRef = ProcessLibraryResources;
|
||||
|
|
@ -96,17 +92,11 @@ namespace OpenTelemetry.Exporter.Zipkin.Implementation
|
|||
|
||||
if (status.IsValid)
|
||||
{
|
||||
if (!CanonicalCodeCache.TryGetValue(status.CanonicalCode, out string canonicalCode))
|
||||
{
|
||||
canonicalCode = status.CanonicalCode.ToString();
|
||||
CanonicalCodeCache.TryAdd(status.CanonicalCode, canonicalCode);
|
||||
}
|
||||
|
||||
PooledList<KeyValuePair<string, string>>.Add(ref attributeEnumerationState.Tags, new KeyValuePair<string, string>(StatusCode, canonicalCode));
|
||||
PooledList<KeyValuePair<string, string>>.Add(ref attributeEnumerationState.Tags, new KeyValuePair<string, string>(SpanAttributeConstants.StatusCodeKey, SpanHelper.GetCachedCanonicalCodeString(status.CanonicalCode)));
|
||||
|
||||
if (status.Description != null)
|
||||
{
|
||||
PooledList<KeyValuePair<string, string>>.Add(ref attributeEnumerationState.Tags, new KeyValuePair<string, string>(StatusDescription, status.Description));
|
||||
PooledList<KeyValuePair<string, string>>.Add(ref attributeEnumerationState.Tags, new KeyValuePair<string, string>(SpanAttributeConstants.StatusDescriptionKey, status.Description));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
// <copyright file="Constants.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>
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.Dependencies
|
||||
{
|
||||
internal static class Constants
|
||||
{
|
||||
public const string HttpSpanPrefix = "HTTP ";
|
||||
}
|
||||
}
|
||||
|
|
@ -42,13 +42,11 @@ namespace OpenTelemetry.Instrumentation.Dependencies
|
|||
var assemblyVersion = typeof(DependenciesInstrumentation).Assembly.GetName().Version;
|
||||
|
||||
var httpClientListener = new HttpClientInstrumentation(tracerFactory.GetTracer(nameof(HttpClientInstrumentation), "semver:" + assemblyVersion), httpOptions ?? new HttpClientInstrumentationOptions());
|
||||
var httpWebRequestInstrumentation = new HttpWebRequestInstrumentation(tracerFactory.GetTracer(nameof(HttpWebRequestInstrumentation), "semver:" + assemblyVersion), httpOptions ?? new HttpClientInstrumentationOptions());
|
||||
var azureClientsListener = new AzureClientsInstrumentation(tracerFactory.GetTracer(nameof(AzureClientsInstrumentation), "semver:" + assemblyVersion));
|
||||
var azurePipelineListener = new AzurePipelineInstrumentation(tracerFactory.GetTracer(nameof(AzurePipelineInstrumentation), "semver:" + assemblyVersion));
|
||||
var sqlClientListener = new SqlClientInstrumentation(tracerFactory.GetTracer(nameof(SqlClientInstrumentation), "semver:" + assemblyVersion), sqlOptions ?? new SqlClientInstrumentationOptions());
|
||||
|
||||
this.instrumentations.Add(httpClientListener);
|
||||
this.instrumentations.Add(httpWebRequestInstrumentation);
|
||||
this.instrumentations.Add(azureClientsListener);
|
||||
this.instrumentations.Add(azurePipelineListener);
|
||||
this.instrumentations.Add(sqlClientListener);
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@
|
|||
// limitations under the License.
|
||||
// </copyright>
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using OpenTelemetry.Context.Propagation;
|
||||
using OpenTelemetry.Instrumentation.Dependencies.Implementation;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.Dependencies
|
||||
{
|
||||
|
|
@ -101,18 +99,6 @@ namespace OpenTelemetry.Instrumentation.Dependencies
|
|||
return true;
|
||||
}
|
||||
}
|
||||
#if NET461
|
||||
else if (activityName == HttpWebRequestDiagnosticSource.ActivityName)
|
||||
{
|
||||
if (arg1 is HttpWebRequest request &&
|
||||
request.RequestUri != null &&
|
||||
request.Method == "POST")
|
||||
{
|
||||
requestUri = request.RequestUri;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
requestUri = null;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
// <copyright file="HttpWebRequestInstrumentation.net461.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;
|
||||
using OpenTelemetry.Instrumentation.Dependencies.Implementation;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.Dependencies
|
||||
{
|
||||
/// <summary>
|
||||
/// Dependencies instrumentation.
|
||||
/// </summary>
|
||||
public class HttpWebRequestInstrumentation : IDisposable
|
||||
{
|
||||
#if NET461
|
||||
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpWebRequestInstrumentation"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tracer">Tracer to record traced with.</param>
|
||||
public HttpWebRequestInstrumentation(Tracer tracer)
|
||||
: this(tracer, new HttpClientInstrumentationOptions())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpWebRequestInstrumentation"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tracer">Tracer to record traced with.</param>
|
||||
/// <param name="options">Configuration options for HttpWebRequest instrumentation.</param>
|
||||
public HttpWebRequestInstrumentation(Tracer tracer, HttpClientInstrumentationOptions options)
|
||||
{
|
||||
#if NET461
|
||||
GC.KeepAlive(HttpWebRequestDiagnosticSource.Instance);
|
||||
|
||||
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(new HttpWebRequestDiagnosticListener(tracer, options), options.EventFilter);
|
||||
this.diagnosticSourceSubscriber.Subscribe();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
#if NET461
|
||||
this.diagnosticSourceSubscriber?.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -73,24 +73,24 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
return;
|
||||
}
|
||||
|
||||
this.Tracer.StartActiveSpanFromActivity(Constants.HttpSpanPrefix + request.Method, activity, SpanKind.Client, out var span);
|
||||
this.Tracer.StartActiveSpanFromActivity(HttpTagHelper.GetOperationNameForHttpMethod(request.Method), activity, SpanKind.Client, out var span);
|
||||
|
||||
if (span.IsRecording)
|
||||
{
|
||||
span.PutComponentAttribute("http");
|
||||
span.PutHttpMethodAttribute(request.Method.ToString());
|
||||
span.PutHttpHostAttribute(request.RequestUri.Host, request.RequestUri.Port);
|
||||
span.PutHttpMethodAttribute(HttpTagHelper.GetNameForHttpMethod(request.Method));
|
||||
span.PutHttpHostAttribute(HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri));
|
||||
span.PutHttpRawUrlAttribute(request.RequestUri.OriginalString);
|
||||
|
||||
if (this.options.SetHttpFlavor)
|
||||
{
|
||||
span.PutHttpFlavorAttribute(request.Version.ToString());
|
||||
span.PutHttpFlavorAttribute(HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version));
|
||||
}
|
||||
}
|
||||
|
||||
if (!(this.httpClientSupportsW3C && this.options.TextFormat is TraceContextFormat))
|
||||
{
|
||||
this.options.TextFormat.Inject<HttpRequestMessage>(span.Context, request, (r, k, v) => r.Headers.Add(k, v));
|
||||
this.options.TextFormat.Inject(span.Context, request, (r, k, v) => r.Headers.Add(k, v));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
// <copyright file="HttpTagHelper.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;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of helper methods to be used when building Http spans.
|
||||
/// </summary>
|
||||
public static class HttpTagHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, string> MethodOperationNameCache = new ConcurrentDictionary<string, string>();
|
||||
private static readonly ConcurrentDictionary<HttpMethod, string> HttpMethodOperationNameCache = new ConcurrentDictionary<HttpMethod, string>();
|
||||
private static readonly ConcurrentDictionary<HttpMethod, string> HttpMethodNameCache = new ConcurrentDictionary<HttpMethod, string>();
|
||||
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<int, string>> HostAndPortToStringCache = new ConcurrentDictionary<string, ConcurrentDictionary<int, string>>();
|
||||
private static readonly ConcurrentDictionary<Version, string> ProtocolVersionToStringCache = new ConcurrentDictionary<Version, string>();
|
||||
private static readonly ConcurrentDictionary<HttpStatusCode, string> StatusCodeToStringCache = new ConcurrentDictionary<HttpStatusCode, string>();
|
||||
|
||||
private static readonly Func<string, string> ConvertMethodToOperationNameRef = ConvertMethodToOperationName;
|
||||
private static readonly Func<HttpMethod, string> ConvertHttpMethodToOperationNameRef = ConvertHttpMethodToOperationName;
|
||||
private static readonly Func<HttpMethod, string> ConvertHttpMethodToNameRef = ConvertHttpMethodToName;
|
||||
private static readonly Func<Version, string> ConvertConvertProtcolVersionToStringRef = ConvertProtcolVersionToString;
|
||||
private static readonly Func<HttpStatusCode, string> ConvertHttpStatusCodeToStringRef = ConvertHttpStatusCodeToString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpenTelemetry standard operation name for a span based on its Http method.
|
||||
/// </summary>
|
||||
/// <param name="method">Http method.</param>
|
||||
/// <returns>Span operation name.</returns>
|
||||
public static string GetOperationNameForHttpMethod(string method) => MethodOperationNameCache.GetOrAdd(method, ConvertMethodToOperationNameRef);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpenTelemetry standard operation name for a span based on its <see cref="HttpMethod"/>.
|
||||
/// </summary>
|
||||
/// <param name="method"><see cref="HttpMethod"/>.</param>
|
||||
/// <returns>Span operation name.</returns>
|
||||
public static string GetOperationNameForHttpMethod(HttpMethod method) => HttpMethodOperationNameCache.GetOrAdd(method, ConvertHttpMethodToOperationNameRef);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpenTelemetry standard method name for a span based on its <see cref="HttpMethod"/>.
|
||||
/// </summary>
|
||||
/// <param name="method"><see cref="HttpMethod"/>.</param>
|
||||
/// <returns>Span method name.</returns>
|
||||
public static string GetNameForHttpMethod(HttpMethod method) => HttpMethodNameCache.GetOrAdd(method, ConvertHttpMethodToNameRef);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpenTelemetry standard version tag value for a span based on its protocol <see cref="Version"/>.
|
||||
/// </summary>
|
||||
/// <param name="protocolVersion"><see cref="Version"/>.</param>
|
||||
/// <returns>Span flavor value.</returns>
|
||||
public static string GetFlavorTagValueFromProtocolVersion(Version protocolVersion) => ProtocolVersionToStringCache.GetOrAdd(protocolVersion, ConvertConvertProtcolVersionToStringRef);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpenTelemetry standard status code tag value for a span based on its protocol <see cref="HttpStatusCode"/>.
|
||||
/// </summary>
|
||||
/// <param name="statusCode"><see cref="HttpStatusCode"/>.</param>
|
||||
/// <returns>Span status code value.</returns>
|
||||
public static string GetStatusCodeTagValueFromHttpStatusCode(HttpStatusCode statusCode) => StatusCodeToStringCache.GetOrAdd(statusCode, ConvertHttpStatusCodeToStringRef);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OpenTelemetry standard host tag value for a span based on its request <see cref="Uri"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestUri"><see cref="Uri"/>.</param>
|
||||
/// <returns>Span host value.</returns>
|
||||
public static string GetHostTagValueFromRequestUri(Uri requestUri)
|
||||
{
|
||||
string host = requestUri.Host;
|
||||
|
||||
if (requestUri.IsDefaultPort)
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
int port = requestUri.Port;
|
||||
|
||||
if (!HostAndPortToStringCache.TryGetValue(host, out ConcurrentDictionary<int, string> portCache))
|
||||
{
|
||||
portCache = new ConcurrentDictionary<int, string>();
|
||||
HostAndPortToStringCache.TryAdd(host, portCache);
|
||||
}
|
||||
|
||||
if (!portCache.TryGetValue(port, out string hostTagValue))
|
||||
{
|
||||
hostTagValue = $"{requestUri.Host}:{requestUri.Port}";
|
||||
portCache.TryAdd(port, hostTagValue);
|
||||
}
|
||||
|
||||
return hostTagValue;
|
||||
}
|
||||
|
||||
private static string ConvertMethodToOperationName(string method) => $"HTTP {method}";
|
||||
|
||||
private static string ConvertHttpMethodToOperationName(HttpMethod method) => $"HTTP {method}";
|
||||
|
||||
private static string ConvertHttpMethodToName(HttpMethod method) => method.ToString();
|
||||
|
||||
private static string ConvertHttpStatusCodeToString(HttpStatusCode statusCode) => ((int)statusCode).ToString();
|
||||
|
||||
private static string ConvertProtcolVersionToString(Version protocolVersion) => protocolVersion.ToString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// <copyright file="HttpWebRequestDiagnosticSource.net461.cs" company="OpenTelemetry Authors">
|
||||
// <copyright file="HttpWebRequestActivitySource.net461.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
@ -21,7 +21,9 @@ using System.Diagnostics;
|
|||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
||||
{
|
||||
|
|
@ -29,24 +31,23 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
/// Hooks into the <see cref="HttpWebRequest"/> class reflectively and writes diagnostic events as requests are processed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Created from the System.Diagnostics.DiagnosticSource.HttpHandlerDiagnosticListener class which has some bugs and feature gaps.
|
||||
/// Inspired from the System.Diagnostics.DiagnosticSource.HttpHandlerDiagnosticListener class which has some bugs and feature gaps.
|
||||
/// See https://github.com/dotnet/runtime/pull/33732 for details.
|
||||
/// </remarks>
|
||||
internal sealed class HttpWebRequestDiagnosticSource : DiagnosticListener
|
||||
internal sealed class HttpWebRequestActivitySource
|
||||
{
|
||||
internal const string DiagnosticListenerName = "HttpWebRequestDiagnosticListener";
|
||||
internal const string ActivityName = DiagnosticListenerName + ".HttpRequestOut";
|
||||
internal const string RequestStartName = ActivityName + ".Start";
|
||||
internal const string RequestStopName = ActivityName + ".Stop";
|
||||
internal const string RequestExceptionName = ActivityName + ".Exception";
|
||||
internal const string ActivitySourceName = "HttpWebRequest";
|
||||
internal const string ActivityName = ActivitySourceName + ".HttpRequestOut";
|
||||
|
||||
internal static readonly HttpWebRequestDiagnosticSource Instance = new HttpWebRequestDiagnosticSource();
|
||||
internal static readonly HttpWebRequestActivitySource Instance = new HttpWebRequestActivitySource();
|
||||
|
||||
private const string InitializationFailed = DiagnosticListenerName + ".InitializationFailed";
|
||||
private const string CorrelationContextHeaderName = "Correlation-Context";
|
||||
private const string TraceParentHeaderName = "traceparent";
|
||||
private const string TraceStateHeaderName = "tracestate";
|
||||
|
||||
private static readonly Version Version = typeof(HttpWebRequestActivitySource).Assembly.GetName().Version;
|
||||
private static readonly ActivitySource WebRequestActivitySource = new ActivitySource(ActivitySourceName, Version.ToString());
|
||||
|
||||
// Fields for reflection
|
||||
private static FieldInfo connectionGroupListField;
|
||||
private static Type connectionGroupType;
|
||||
|
|
@ -75,35 +76,115 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
private static Func<HttpWebResponse, bool> isWebSocketResponseAccessor;
|
||||
private static Func<HttpWebResponse, string> connectionGroupNameAccessor;
|
||||
|
||||
// Fields for controlling initialization of the HttpWebRequestDiagnosticSource singleton
|
||||
private bool initialized = false;
|
||||
|
||||
private HttpWebRequestDiagnosticSource()
|
||||
: base(DiagnosticListenerName)
|
||||
internal HttpWebRequestActivitySource()
|
||||
{
|
||||
try
|
||||
{
|
||||
PrepareReflectionObjects();
|
||||
PerformInjection();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If anything went wrong, just no-op. Write an event so at least we can find out.
|
||||
InstrumentationEventSource.Log.ExceptionInitializingInstrumentation(typeof(HttpWebRequestActivitySource).FullName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer, Predicate<string> isEnabled)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AddRequestTagsAndInstrumentRequest(HttpWebRequest request, Activity activity)
|
||||
{
|
||||
IDisposable result = base.Subscribe(observer, isEnabled);
|
||||
this.Initialize();
|
||||
return result;
|
||||
activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method);
|
||||
|
||||
InstrumentRequest(request, activity);
|
||||
|
||||
activity.SetCustomProperty("HttpWebRequest.Request", request);
|
||||
|
||||
if (activity.IsAllDataRequested)
|
||||
{
|
||||
activity.AddTag(SpanAttributeConstants.ComponentKey, "http");
|
||||
activity.AddTag(SpanAttributeConstants.HttpMethodKey, request.Method);
|
||||
activity.AddTag(SpanAttributeConstants.HttpHostKey, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri));
|
||||
activity.AddTag(SpanAttributeConstants.HttpUrlKey, request.RequestUri.OriginalString);
|
||||
activity.AddTag(SpanAttributeConstants.HttpFlavorKey, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.ProtocolVersion));
|
||||
}
|
||||
}
|
||||
|
||||
public override IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer, Func<string, object, object, bool> isEnabled)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AddResponseTags(HttpWebResponse response, Activity activity)
|
||||
{
|
||||
IDisposable result = base.Subscribe(observer, isEnabled);
|
||||
this.Initialize();
|
||||
return result;
|
||||
activity.SetCustomProperty("HttpWebRequest.Response", response);
|
||||
|
||||
if (activity.IsAllDataRequested)
|
||||
{
|
||||
activity.AddTag(SpanAttributeConstants.HttpStatusCodeKey, HttpTagHelper.GetStatusCodeTagValueFromHttpStatusCode(response.StatusCode));
|
||||
|
||||
Status status = SpanHelper.ResolveSpanStatusForHttpStatusCode((int)response.StatusCode);
|
||||
|
||||
activity.AddTag(SpanAttributeConstants.StatusCodeKey, SpanHelper.GetCachedCanonicalCodeString(status.CanonicalCode));
|
||||
activity.AddTag(SpanAttributeConstants.StatusDescriptionKey, response.StatusDescription);
|
||||
}
|
||||
}
|
||||
|
||||
public override IDisposable Subscribe(IObserver<KeyValuePair<string, object>> observer)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void AddExceptionTags(Exception exception, Activity activity)
|
||||
{
|
||||
IDisposable result = base.Subscribe(observer);
|
||||
this.Initialize();
|
||||
return result;
|
||||
activity.SetCustomProperty("HttpWebRequest.Exception", exception);
|
||||
|
||||
if (!activity.IsAllDataRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (exception is WebException wexc)
|
||||
{
|
||||
if (wexc.Response is HttpWebResponse response)
|
||||
{
|
||||
activity.AddTag(SpanAttributeConstants.HttpStatusCodeKey, HttpTagHelper.GetStatusCodeTagValueFromHttpStatusCode(response.StatusCode));
|
||||
|
||||
status = SpanHelper.ResolveSpanStatusForHttpStatusCode((int)response.StatusCode).WithDescription(response.StatusDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (wexc.Status)
|
||||
{
|
||||
case WebExceptionStatus.Timeout:
|
||||
status = Status.DeadlineExceeded;
|
||||
break;
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
status = Status.InvalidArgument.WithDescription(exception.Message);
|
||||
break;
|
||||
case WebExceptionStatus.SendFailure:
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
case WebExceptionStatus.SecureChannelFailure:
|
||||
case WebExceptionStatus.TrustFailure:
|
||||
status = Status.FailedPrecondition.WithDescription(exception.Message);
|
||||
break;
|
||||
case WebExceptionStatus.ServerProtocolViolation:
|
||||
status = Status.Unimplemented.WithDescription(exception.Message);
|
||||
break;
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
status = Status.Cancelled;
|
||||
break;
|
||||
case WebExceptionStatus.MessageLengthLimitExceeded:
|
||||
status = Status.ResourceExhausted.WithDescription(exception.Message);
|
||||
break;
|
||||
default:
|
||||
status = Status.Unknown.WithDescription(exception.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = Status.Unknown.WithDescription(exception.Message);
|
||||
}
|
||||
|
||||
activity.AddTag(SpanAttributeConstants.StatusCodeKey, SpanHelper.GetCachedCanonicalCodeString(status.CanonicalCode));
|
||||
activity.AddTag(SpanAttributeConstants.StatusDescriptionKey, status.Description);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InstrumentRequest(HttpWebRequest request, Activity activity)
|
||||
{
|
||||
// do not inject header if it was injected already
|
||||
|
|
@ -139,12 +220,53 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsRequestInstrumented(HttpWebRequest request)
|
||||
=> request.Headers.Get(TraceParentHeaderName) != null;
|
||||
|
||||
private static void ProcessRequest(HttpWebRequest request)
|
||||
{
|
||||
if (!WebRequestActivitySource.HasListeners() || IsRequestInstrumented(request))
|
||||
{
|
||||
// No subscribers to the ActivitySource or this request was instrumented by previous
|
||||
// ProcessRequest, such is the case with redirect responses where the same request is sent again.
|
||||
return;
|
||||
}
|
||||
|
||||
var activity = WebRequestActivitySource.StartActivity(ActivityName, ActivityKind.Client);
|
||||
|
||||
if (activity == null)
|
||||
{
|
||||
// There is a listener but it decided not to sample the current request.
|
||||
return;
|
||||
}
|
||||
|
||||
IAsyncResult asyncContext = writeAResultAccessor(request);
|
||||
if (asyncContext != null)
|
||||
{
|
||||
// Flow here is for [Begin]GetRequestStream[Async].
|
||||
|
||||
AsyncCallbackWrapper callback = new AsyncCallbackWrapper(request, activity, asyncCallbackAccessor(asyncContext));
|
||||
asyncCallbackModifier(asyncContext, callback.AsyncCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flow here is for [Begin]GetResponse[Async] without a prior call to [Begin]GetRequestStream[Async].
|
||||
|
||||
asyncContext = readAResultAccessor(request);
|
||||
AsyncCallbackWrapper callback = new AsyncCallbackWrapper(request, activity, asyncCallbackAccessor(asyncContext));
|
||||
asyncCallbackModifier(asyncContext, callback.AsyncCallback);
|
||||
}
|
||||
|
||||
AddRequestTagsAndInstrumentRequest(request, activity);
|
||||
}
|
||||
|
||||
private static void HookOrProcessResult(HttpWebRequest request)
|
||||
{
|
||||
IAsyncResult writeAsyncContext = writeAResultAccessor(request);
|
||||
if (writeAsyncContext == null || !(asyncCallbackAccessor(writeAsyncContext)?.Target is AsyncCallbackWrapper writeAsyncContextCallback))
|
||||
{
|
||||
// If we already hooked into the read result during RaiseRequestEvent or we hooked up after the fact already we don't need to do anything here.
|
||||
// If we already hooked into the read result during ProcessRequest or we hooked up after the fact already we don't need to do anything here.
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +285,7 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
if (endCalledAccessor.Invoke(readAsyncContext) || readAsyncContext.CompletedSynchronously)
|
||||
{
|
||||
// We need to process the result directly because the read callback has already fired. Force a copy because response has likely already been disposed.
|
||||
ProcessResult(readAsyncContext, null, writeAsyncContextCallback.Request, writeAsyncContextCallback.Activity, resultAccessor(readAsyncContext), true);
|
||||
ProcessResult(readAsyncContext, null, writeAsyncContextCallback.Activity, resultAccessor(readAsyncContext), true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -172,16 +294,20 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
asyncCallbackModifier(readAsyncContext, callback.AsyncCallback);
|
||||
}
|
||||
|
||||
private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncCallback, HttpWebRequest request, Activity activity, object result, bool forceResponseCopy)
|
||||
private static void ProcessResult(IAsyncResult asyncResult, AsyncCallback asyncCallback, Activity activity, object result, bool forceResponseCopy)
|
||||
{
|
||||
// We could be executing on a different thread now so set the activity.
|
||||
Activity.Current = activity;
|
||||
Debug.Assert(Activity.Current == null || Activity.Current == activity, "There was an unexpected active Activity on the result thread.");
|
||||
if (Activity.Current == null)
|
||||
{
|
||||
Activity.Current = activity;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (result is Exception ex)
|
||||
{
|
||||
Instance.RaiseExceptionEvent(request, ex);
|
||||
AddExceptionTags(ex, activity);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -202,11 +328,11 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
isWebSocketResponseAccessor(response), connectionGroupNameAccessor(response),
|
||||
});
|
||||
|
||||
Instance.RaiseResponseEvent(request, responseCopy);
|
||||
AddResponseTags(responseCopy, activity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance.RaiseResponseEvent(request, response);
|
||||
AddResponseTags(response, activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -456,97 +582,6 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
return (Func<object[], T>)setterMethod.CreateDelegate(typeof(Func<object[], T>));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes all the reflection objects it will ever need. Reflection is costly, but it's better to take
|
||||
/// this one time performance hit than to get it multiple times later, or do it lazily and have to worry about
|
||||
/// threading issues. If Initialize has been called before, it will not doing anything.
|
||||
/// </summary>
|
||||
private void Initialize()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (!this.initialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This flag makes sure we only do this once. Even if we failed to initialize in an
|
||||
// earlier time, we should not retry because this initialization is not cheap and
|
||||
// the likelihood it will succeed the second time is very small.
|
||||
this.initialized = true;
|
||||
|
||||
PrepareReflectionObjects();
|
||||
PerformInjection();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If anything went wrong, just no-op. Write an event so at least we can find out.
|
||||
this.Write(InitializationFailed, new { Exception = ex });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseRequestEvent(HttpWebRequest request)
|
||||
{
|
||||
if (this.IsRequestInstrumented(request))
|
||||
{
|
||||
// This request was instrumented by previous RaiseRequestEvent, such is the case with redirect responses where the same request is sent again.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.IsEnabled(ActivityName, request))
|
||||
{
|
||||
// We don't call StartActivity here because it will fire into user code before the headers are added.
|
||||
|
||||
var activity = new Activity(ActivityName);
|
||||
activity.Start();
|
||||
|
||||
IAsyncResult asyncContext = readAResultAccessor(request);
|
||||
if (asyncContext != null)
|
||||
{
|
||||
// Flow here is for [Begin]GetResponse[Async] without a prior call to [Begin]GetRequestStream[Async].
|
||||
|
||||
AsyncCallbackWrapper callback = new AsyncCallbackWrapper(request, activity, asyncCallbackAccessor(asyncContext));
|
||||
asyncCallbackModifier(asyncContext, callback.AsyncCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flow here is for [Begin]GetRequestStream[Async].
|
||||
|
||||
asyncContext = writeAResultAccessor(request);
|
||||
AsyncCallbackWrapper callback = new AsyncCallbackWrapper(request, activity, asyncCallbackAccessor(asyncContext));
|
||||
asyncCallbackModifier(asyncContext, callback.AsyncCallback);
|
||||
}
|
||||
|
||||
InstrumentRequest(request, activity);
|
||||
|
||||
// Only send start event to users who subscribed for it, but start activity anyway
|
||||
if (this.IsEnabled(RequestStartName))
|
||||
{
|
||||
this.Write(activity.OperationName + ".Start", new { Request = request });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseResponseEvent(HttpWebRequest request, HttpWebResponse response)
|
||||
{
|
||||
if (this.IsEnabled(RequestStopName))
|
||||
{
|
||||
this.Write(RequestStopName, new { Request = request, Response = response });
|
||||
}
|
||||
}
|
||||
|
||||
private void RaiseExceptionEvent(HttpWebRequest request, Exception exception)
|
||||
{
|
||||
if (this.IsEnabled(RequestExceptionName))
|
||||
{
|
||||
this.Write(RequestExceptionName, new { Request = request, Exception = exception });
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRequestInstrumented(HttpWebRequest request)
|
||||
=> request.Headers.Get(TraceParentHeaderName) != null;
|
||||
|
||||
private class HashtableWrapper : Hashtable, IEnumerable
|
||||
{
|
||||
private readonly Hashtable table;
|
||||
|
|
@ -957,7 +992,7 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
|
||||
if (value is HttpWebRequest request)
|
||||
{
|
||||
Instance.RaiseRequestEvent(request);
|
||||
ProcessRequest(request);
|
||||
}
|
||||
|
||||
return index;
|
||||
|
|
@ -1011,7 +1046,7 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
|||
object result = resultAccessor(asyncResult);
|
||||
if (result is Exception || result is HttpWebResponse)
|
||||
{
|
||||
ProcessResult(asyncResult, this.OriginalCallback, this.Request, this.Activity, result, false);
|
||||
ProcessResult(asyncResult, this.OriginalCallback, this.Activity, result, false);
|
||||
}
|
||||
|
||||
this.OriginalCallback?.Invoke(asyncResult);
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
// <copyright file="HttpWebRequestDiagnosticListener.net461.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>
|
||||
#if NET461
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using OpenTelemetry.Context.Propagation;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
|
||||
{
|
||||
internal class HttpWebRequestDiagnosticListener : ListenerHandler
|
||||
{
|
||||
private readonly PropertyFetcher startRequestFetcher = new PropertyFetcher("Request");
|
||||
private readonly PropertyFetcher stopResponseFetcher = new PropertyFetcher("Response");
|
||||
private readonly PropertyFetcher stopExceptionFetcher = new PropertyFetcher("Exception");
|
||||
private readonly HttpClientInstrumentationOptions options;
|
||||
|
||||
public HttpWebRequestDiagnosticListener(Tracer tracer, HttpClientInstrumentationOptions options)
|
||||
: base(HttpWebRequestDiagnosticSource.DiagnosticListenerName, tracer)
|
||||
{
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public override void OnStartActivity(Activity activity, object payload)
|
||||
{
|
||||
const string EventNameSuffix = ".OnStartActivity";
|
||||
if (!(this.startRequestFetcher.Fetch(payload) is HttpWebRequest request))
|
||||
{
|
||||
InstrumentationEventSource.Log.NullPayload(nameof(HttpWebRequestDiagnosticListener) + EventNameSuffix);
|
||||
return;
|
||||
}
|
||||
|
||||
this.Tracer.StartActiveSpanFromActivity(Constants.HttpSpanPrefix + request.Method, activity, SpanKind.Client, out var span);
|
||||
|
||||
if (span.IsRecording)
|
||||
{
|
||||
span.PutComponentAttribute("http");
|
||||
span.PutHttpMethodAttribute(request.Method);
|
||||
span.PutHttpHostAttribute(request.RequestUri.Host, request.RequestUri.Port);
|
||||
span.PutHttpRawUrlAttribute(request.RequestUri.OriginalString);
|
||||
|
||||
if (this.options.SetHttpFlavor)
|
||||
{
|
||||
span.PutHttpFlavorAttribute(request.ProtocolVersion.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!(this.options.TextFormat is TraceContextFormat))
|
||||
{
|
||||
this.options.TextFormat.Inject<HttpWebRequest>(span.Context, request, (r, k, v) => r.Headers.Add(k, v));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStopActivity(Activity activity, object payload)
|
||||
{
|
||||
const string EventNameSuffix = ".OnStopActivity";
|
||||
var span = this.Tracer.CurrentSpan;
|
||||
try
|
||||
{
|
||||
if (span == null || !span.Context.IsValid)
|
||||
{
|
||||
InstrumentationEventSource.Log.NullOrBlankSpan(nameof(HttpWebRequestDiagnosticListener) + EventNameSuffix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (span.IsRecording)
|
||||
{
|
||||
if (this.stopResponseFetcher.Fetch(payload) is HttpWebResponse response)
|
||||
{
|
||||
span.PutHttpStatusCode((int)response.StatusCode, response.StatusDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
span?.End();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnException(Activity activity, object payload)
|
||||
{
|
||||
const string EventNameSuffix = ".OnException";
|
||||
var span = this.Tracer.CurrentSpan;
|
||||
try
|
||||
{
|
||||
if (span == null || !span.Context.IsValid)
|
||||
{
|
||||
InstrumentationEventSource.Log.NullOrBlankSpan(nameof(HttpWebRequestDiagnosticListener) + EventNameSuffix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (span.IsRecording)
|
||||
{
|
||||
if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc))
|
||||
{
|
||||
InstrumentationEventSource.Log.NullPayload(nameof(HttpWebRequestDiagnosticListener) + EventNameSuffix);
|
||||
return;
|
||||
}
|
||||
|
||||
ProcessException(span, exc);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
span?.End();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessException(TelemetrySpan span, Exception exception)
|
||||
{
|
||||
if (exception is WebException wexc)
|
||||
{
|
||||
if (wexc.Response is HttpWebResponse response)
|
||||
{
|
||||
span.PutHttpStatusCode((int)response.StatusCode, response.StatusDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (wexc.Status)
|
||||
{
|
||||
case WebExceptionStatus.Timeout:
|
||||
span.Status = Status.DeadlineExceeded;
|
||||
return;
|
||||
case WebExceptionStatus.NameResolutionFailure:
|
||||
span.Status = Status.InvalidArgument.WithDescription(exception.Message);
|
||||
return;
|
||||
case WebExceptionStatus.SendFailure:
|
||||
case WebExceptionStatus.ConnectFailure:
|
||||
case WebExceptionStatus.SecureChannelFailure:
|
||||
case WebExceptionStatus.TrustFailure:
|
||||
span.Status = Status.FailedPrecondition.WithDescription(exception.Message);
|
||||
return;
|
||||
case WebExceptionStatus.ServerProtocolViolation:
|
||||
span.Status = Status.Unimplemented.WithDescription(exception.Message);
|
||||
return;
|
||||
case WebExceptionStatus.RequestCanceled:
|
||||
span.Status = Status.Cancelled;
|
||||
return;
|
||||
case WebExceptionStatus.MessageLengthLimitExceeded:
|
||||
span.Status = Status.ResourceExhausted.WithDescription(exception.Message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
span.Status = Status.Unknown.WithDescription(exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// <copyright file="OpenTelemetryBuilderExtensions.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;
|
||||
using OpenTelemetry.Instrumentation.Dependencies.Implementation;
|
||||
|
||||
namespace OpenTelemetry.Trace.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods to simplify registering of dependency instrumentation.
|
||||
/// </summary>
|
||||
public static class OpenTelemetryBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables the outgoing requests automatic data collection for all supported activity sources.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="OpenTelemetryBuilder"/> being configured.</param>
|
||||
/// <returns>The instance of <see cref="OpenTelemetryBuilder"/> to chain the calls.</returns>
|
||||
public static OpenTelemetryBuilder AddDependencyInstrumentation(this OpenTelemetryBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
#if NET461
|
||||
builder.AddHttpWebRequestDependencyInstrumentation();
|
||||
#endif
|
||||
return builder;
|
||||
}
|
||||
|
||||
#if NET461
|
||||
/// <summary>
|
||||
/// Enables the outgoing requests automatic data collection for .NET Framework HttpWebRequest activity source.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="OpenTelemetryBuilder"/> being configured.</param>
|
||||
/// <returns>The instance of <see cref="OpenTelemetryBuilder"/> to chain the calls.</returns>
|
||||
public static OpenTelemetryBuilder AddHttpWebRequestDependencyInstrumentation(this OpenTelemetryBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
GC.KeepAlive(HttpWebRequestActivitySource.Instance);
|
||||
|
||||
builder.AddActivitySource(HttpWebRequestActivitySource.ActivitySourceName);
|
||||
|
||||
return builder;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,6 @@ namespace OpenTelemetry.Trace.Configuration
|
|||
.AddInstrumentation((t) => new AzureClientsInstrumentation(t))
|
||||
.AddInstrumentation((t) => new AzurePipelineInstrumentation(t))
|
||||
.AddInstrumentation((t) => new HttpClientInstrumentation(t))
|
||||
.AddInstrumentation((t) => new HttpWebRequestInstrumentation(t))
|
||||
.AddInstrumentation((t) => new SqlClientInstrumentation(t));
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +70,6 @@ namespace OpenTelemetry.Trace.Configuration
|
|||
.AddInstrumentation((t) => new AzureClientsInstrumentation(t))
|
||||
.AddInstrumentation((t) => new AzurePipelineInstrumentation(t))
|
||||
.AddInstrumentation((t) => new HttpClientInstrumentation(t, httpOptions))
|
||||
.AddInstrumentation((t) => new HttpWebRequestInstrumentation(t, httpOptions))
|
||||
.AddInstrumentation((t) => new SqlClientInstrumentation(t, sqlOptions));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,21 @@ namespace OpenTelemetry.Instrumentation
|
|||
this.WriteEvent(6, eventName);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void ExceptionInitializingInstrumentation(string instrumentationType, Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
|
||||
{
|
||||
this.ExceptionInitializingInstrumentation(instrumentationType, ToInvariantString(ex));
|
||||
}
|
||||
}
|
||||
|
||||
[Event(7, Message = "Error initializing instrumentation type {0}. Exception : {1}", Level = EventLevel.Error)]
|
||||
public void ExceptionInitializingInstrumentation(string instrumentationType, string ex)
|
||||
{
|
||||
this.WriteEvent(7, instrumentationType, ex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a culture-independent string representation of the given <paramref name="exception"/> object,
|
||||
/// appropriate for diagnostics tracing.
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ namespace OpenTelemetry.Trace.Configuration
|
|||
/// Enables OpenTelemetry.
|
||||
/// </summary>
|
||||
/// <param name="configureOpenTelemetryBuilder">Function that configures OpenTelemetryBuilder.</param>
|
||||
/// <returns><see cref="IDisposable"/> to be disposed on application shutdown.</returns>
|
||||
/// <remarks>
|
||||
/// Basic implementation only. Most logic from TracerBuilder will be ported here.
|
||||
/// </remarks>
|
||||
public static void EnableOpenTelemetry(Action<OpenTelemetryBuilder> configureOpenTelemetryBuilder)
|
||||
public static IDisposable EnableOpenTelemetry(Action<OpenTelemetryBuilder> configureOpenTelemetryBuilder)
|
||||
{
|
||||
var openTelemetryBuilder = new OpenTelemetryBuilder();
|
||||
configureOpenTelemetryBuilder(openTelemetryBuilder);
|
||||
|
|
@ -66,7 +67,7 @@ namespace OpenTelemetry.Trace.Configuration
|
|||
|
||||
// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
|
||||
// or not
|
||||
ShouldListenTo = (activitySource) => openTelemetryBuilder.ActivitySourceNames.Contains(activitySource.Name.ToUpperInvariant()),
|
||||
ShouldListenTo = (activitySource) => openTelemetryBuilder.ActivitySourceNames?.Contains(activitySource.Name.ToUpperInvariant()) ?? false,
|
||||
|
||||
// The following parameter is not used now.
|
||||
GetRequestedDataUsingParentId = (ref ActivityCreationOptions<string> options) => ActivityDataRequest.AllData,
|
||||
|
|
@ -86,7 +87,7 @@ namespace OpenTelemetry.Trace.Configuration
|
|||
var shouldSample = sampler.ShouldSample(
|
||||
options.Parent,
|
||||
options.Parent.TraceId,
|
||||
default(ActivitySpanId), // Passing default SpanId here. The actual SpanId is not known before actual Activity creation
|
||||
spanId: default, // Passing default SpanId here. The actual SpanId is not known before actual Activity creation
|
||||
options.Name,
|
||||
options.Kind,
|
||||
options.Tags,
|
||||
|
|
@ -105,6 +106,8 @@ namespace OpenTelemetry.Trace.Configuration
|
|||
};
|
||||
|
||||
ActivitySource.AddActivityListener(listener);
|
||||
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -62,9 +62,12 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
[Fact]
|
||||
public async Task HttpDependenciesInstrumentationInjectsHeadersAsync()
|
||||
{
|
||||
var spanProcessor = new Mock<SpanProcessor>();
|
||||
var tracer = TracerFactory.Create(b => b.AddProcessorPipeline(p => p.AddProcessor(_ => spanProcessor.Object)))
|
||||
.GetTracer(null);
|
||||
var activityProcessor = new Mock<ActivityProcessor>();
|
||||
using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
|
||||
{
|
||||
b.SetProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
|
||||
b.AddHttpWebRequestDependencyInstrumentation();
|
||||
});
|
||||
|
||||
var request = (HttpWebRequest)WebRequest.Create(this.url);
|
||||
|
||||
|
|
@ -76,28 +79,26 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
parent.TraceStateString = "k1=v1,k2=v2";
|
||||
parent.ActivityTraceFlags = ActivityTraceFlags.Recorded;
|
||||
|
||||
using (new HttpWebRequestInstrumentation(tracer, new HttpClientInstrumentationOptions()))
|
||||
{
|
||||
using var response = await request.GetResponseAsync();
|
||||
}
|
||||
using var response = await request.GetResponseAsync();
|
||||
|
||||
Assert.Equal(2, spanProcessor.Invocations.Count); // begin and end was called
|
||||
var span = (SpanData)spanProcessor.Invocations[1].Arguments[0];
|
||||
Assert.Equal(2, activityProcessor.Invocations.Count); // begin and end was called
|
||||
var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];
|
||||
|
||||
Assert.Equal(parent.TraceId, span.Context.TraceId);
|
||||
Assert.Equal(parent.SpanId, span.ParentSpanId);
|
||||
Assert.NotEqual(parent.SpanId, span.Context.SpanId);
|
||||
Assert.NotEqual(default, span.Context.SpanId);
|
||||
Assert.Equal(parent.TraceId, activity.Context.TraceId);
|
||||
Assert.Equal(parent.SpanId, activity.ParentSpanId);
|
||||
Assert.NotEqual(parent.SpanId, activity.Context.SpanId);
|
||||
Assert.NotEqual(default, activity.Context.SpanId);
|
||||
|
||||
string traceparent = request.Headers.Get("traceparent");
|
||||
string tracestate = request.Headers.Get("tracestate");
|
||||
|
||||
Assert.Equal($"00-{span.Context.TraceId}-{span.Context.SpanId}-01", traceparent);
|
||||
Assert.Equal($"00-{activity.Context.TraceId}-{activity.Context.SpanId}-01", traceparent);
|
||||
Assert.Equal("k1=v1,k2=v2", tracestate);
|
||||
|
||||
parent.Stop();
|
||||
}
|
||||
|
||||
/* TBD: ActivitySource doesn't support custom format TraceIds.
|
||||
[Fact]
|
||||
public async Task HttpDependenciesInstrumentationInjectsHeadersAsync_CustomFormat()
|
||||
{
|
||||
|
|
@ -109,10 +110,12 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
action(message, "custom_tracestate", Activity.Current.TraceStateString);
|
||||
});
|
||||
|
||||
var spanProcessor = new Mock<SpanProcessor>();
|
||||
var tracer = TracerFactory.Create(b => b
|
||||
.AddProcessorPipeline(p => p.AddProcessor(_ => spanProcessor.Object)))
|
||||
.GetTracer(null);
|
||||
var activityProcessor = new Mock<ActivityProcessor>();
|
||||
using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
|
||||
{
|
||||
b.SetProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
|
||||
b.AddHttpWebRequestDependencyInstrumentation();
|
||||
});
|
||||
|
||||
var request = (HttpWebRequest)WebRequest.Create(this.url);
|
||||
|
||||
|
|
@ -124,72 +127,35 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
parent.TraceStateString = "k1=v1,k2=v2";
|
||||
parent.ActivityTraceFlags = ActivityTraceFlags.Recorded;
|
||||
|
||||
using (new HttpWebRequestInstrumentation(tracer, new HttpClientInstrumentationOptions { TextFormat = textFormat.Object }))
|
||||
{
|
||||
using var response = await request.GetResponseAsync();
|
||||
}
|
||||
using var response = await request.GetResponseAsync();
|
||||
|
||||
Assert.Equal(2, spanProcessor.Invocations.Count); // begin and end was called
|
||||
Assert.Equal(2, activityProcessor.Invocations.Count); // begin and end was called
|
||||
|
||||
var span = (SpanData)spanProcessor.Invocations[1].Arguments[0];
|
||||
var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];
|
||||
|
||||
Assert.Equal(parent.TraceId, span.Context.TraceId);
|
||||
Assert.Equal(parent.SpanId, span.ParentSpanId);
|
||||
Assert.NotEqual(parent.SpanId, span.Context.SpanId);
|
||||
Assert.NotEqual(default, span.Context.SpanId);
|
||||
Assert.Equal(parent.TraceId, activity.Context.TraceId);
|
||||
Assert.Equal(parent.SpanId, activity.ParentSpanId);
|
||||
Assert.NotEqual(parent.SpanId, activity.Context.SpanId);
|
||||
Assert.NotEqual(default, activity.Context.SpanId);
|
||||
|
||||
string traceparent = request.Headers.Get("custom_traceparent");
|
||||
string tracestate = request.Headers.Get("custom_tracestate");
|
||||
|
||||
Assert.Equal($"00/{span.Context.TraceId}/{span.Context.SpanId}/01", traceparent);
|
||||
Assert.Equal($"00/{activity.Context.TraceId}/{activity.Context.SpanId}/01", traceparent);
|
||||
Assert.Equal("k1=v1,k2=v2", tracestate);
|
||||
|
||||
parent.Stop();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HttpDependenciesInstrumentation_AddViaFactory_HttpInstrumentation_CollectsSpans()
|
||||
{
|
||||
var spanProcessor = new Mock<SpanProcessor>();
|
||||
|
||||
using (TracerFactory.Create(b => b
|
||||
.AddProcessorPipeline(p => p.AddProcessor(_ => spanProcessor.Object))
|
||||
.AddInstrumentation(t => new HttpWebRequestInstrumentation(t))))
|
||||
{
|
||||
using var c = new HttpClient();
|
||||
await c.GetAsync(this.url);
|
||||
}
|
||||
|
||||
Assert.Single(spanProcessor.Invocations.Where(i => i.Method.Name == "OnStart"));
|
||||
Assert.Single(spanProcessor.Invocations.Where(i => i.Method.Name == "OnEnd"));
|
||||
Assert.IsType<SpanData>(spanProcessor.Invocations[1].Arguments[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HttpDependenciesInstrumentation_AddViaFactory_DependencyInstrumentation_CollectsSpans()
|
||||
{
|
||||
var spanProcessor = new Mock<SpanProcessor>();
|
||||
|
||||
using (TracerFactory.Create(b => b
|
||||
.AddProcessorPipeline(p => p.AddProcessor(_ => spanProcessor.Object))
|
||||
.AddDependencyInstrumentation()))
|
||||
{
|
||||
using var c = new HttpClient();
|
||||
await c.GetAsync(this.url);
|
||||
}
|
||||
|
||||
Assert.Single(spanProcessor.Invocations.Where(i => i.Method.Name == "OnStart"));
|
||||
Assert.Single(spanProcessor.Invocations.Where(i => i.Method.Name == "OnEnd"));
|
||||
Assert.IsType<SpanData>(spanProcessor.Invocations[1].Arguments[0]);
|
||||
}
|
||||
}*/
|
||||
|
||||
[Fact]
|
||||
public async Task HttpDependenciesInstrumentationBacksOffIfAlreadyInstrumented()
|
||||
{
|
||||
var spanProcessor = new Mock<SpanProcessor>();
|
||||
var tracer = TracerFactory.Create(b => b
|
||||
.AddProcessorPipeline(p => p.AddProcessor(_ => spanProcessor.Object)))
|
||||
.GetTracer(null);
|
||||
var activityProcessor = new Mock<ActivityProcessor>();
|
||||
using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
|
||||
{
|
||||
b.SetProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
|
||||
b.AddHttpWebRequestDependencyInstrumentation();
|
||||
});
|
||||
|
||||
var request = new HttpRequestMessage
|
||||
{
|
||||
|
|
@ -199,13 +165,18 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
|
||||
request.Headers.Add("traceparent", "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01");
|
||||
|
||||
using (new HttpWebRequestInstrumentation(tracer, new HttpClientInstrumentationOptions()))
|
||||
using (var activityListener = new ActivityListener
|
||||
{
|
||||
ShouldListenTo = (activitySource) => activitySource.Name == HttpWebRequestActivitySource.ActivitySourceName,
|
||||
})
|
||||
{
|
||||
ActivitySource.AddActivityListener(activityListener);
|
||||
|
||||
using var c = new HttpClient();
|
||||
await c.SendAsync(request);
|
||||
}
|
||||
|
||||
Assert.Equal(0, spanProcessor.Invocations.Count);
|
||||
Assert.Equal(0, activityProcessor.Invocations.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -218,12 +189,17 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
.GetTracer(null);
|
||||
|
||||
var options = new HttpClientInstrumentationOptions((activityName, arg1, _)
|
||||
=> !(activityName == HttpWebRequestDiagnosticSource.ActivityName &&
|
||||
=> !(activityName == HttpWebRequestActivitySource.ActivityName &&
|
||||
arg1 is HttpWebRequest request &&
|
||||
request.RequestUri.OriginalString.Contains(this.url)));
|
||||
|
||||
using (new HttpWebRequestInstrumentation(tracer, options))
|
||||
using (var activityListener = new ActivityListener
|
||||
{
|
||||
ShouldListenTo = (activitySource) => activitySource.Name == HttpWebRequestActivitySource.ActivitySourceName,
|
||||
})
|
||||
{
|
||||
ActivitySource.AddActivityListener(activityListener);
|
||||
|
||||
using var c = new HttpClient();
|
||||
await c.GetAsync(this.url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#if NET461
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
|
@ -46,74 +47,67 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
out var host,
|
||||
out var port);
|
||||
|
||||
var spanProcessor = new Mock<SpanProcessor>();
|
||||
var tracer = TracerFactory.Create(b => b
|
||||
.AddProcessorPipeline(p => p.AddProcessor(_ => spanProcessor.Object)))
|
||||
.GetTracer(null);
|
||||
var activityProcessor = new Mock<ActivityProcessor>();
|
||||
using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
|
||||
{
|
||||
b.SetProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
|
||||
b.AddHttpWebRequestDependencyInstrumentation();
|
||||
});
|
||||
|
||||
tc.Url = HttpTestData.NormalizeValues(tc.Url, host, port);
|
||||
|
||||
using (new HttpWebRequestInstrumentation(tracer, new HttpClientInstrumentationOptions() { SetHttpFlavor = tc.SetHttpFlavor }))
|
||||
try
|
||||
{
|
||||
try
|
||||
var request = (HttpWebRequest)WebRequest.Create(tc.Url);
|
||||
|
||||
request.Method = tc.Method;
|
||||
|
||||
if (tc.Headers != null)
|
||||
{
|
||||
var request = (HttpWebRequest)WebRequest.Create(tc.Url);
|
||||
|
||||
request.Method = tc.Method;
|
||||
|
||||
if (tc.Headers != null)
|
||||
foreach (var header in tc.Headers)
|
||||
{
|
||||
foreach (var header in tc.Headers)
|
||||
{
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
request.ContentLength = 0;
|
||||
|
||||
using var response = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
new StreamReader(response.GetResponseStream()).ReadToEnd();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//test case can intentionally send request that will result in exception
|
||||
}
|
||||
|
||||
request.ContentLength = 0;
|
||||
|
||||
using var response = (HttpWebResponse)request.GetResponse();
|
||||
|
||||
new StreamReader(response.GetResponseStream()).ReadToEnd();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//test case can intentionally send request that will result in exception
|
||||
}
|
||||
|
||||
Assert.Equal(2, spanProcessor.Invocations.Count); // begin and end was called
|
||||
var span = (SpanData)spanProcessor.Invocations[1].Arguments[0];
|
||||
Assert.Equal(2, activityProcessor.Invocations.Count); // begin and end was called
|
||||
var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];
|
||||
|
||||
Assert.Equal(tc.SpanName, span.Name);
|
||||
Assert.Equal(tc.SpanKind, span.Kind.ToString());
|
||||
Assert.Equal(tc.SpanName, activity.DisplayName);
|
||||
Assert.Equal(tc.SpanKind, activity.Kind.ToString());
|
||||
|
||||
var d = new Dictionary<StatusCanonicalCode, string>()
|
||||
var d = new Dictionary<string, string>()
|
||||
{
|
||||
{ StatusCanonicalCode.Ok, "OK"},
|
||||
{ StatusCanonicalCode.Cancelled, "CANCELLED"},
|
||||
{ StatusCanonicalCode.Unknown, "UNKNOWN"},
|
||||
{ StatusCanonicalCode.InvalidArgument, "INVALID_ARGUMENT"},
|
||||
{ StatusCanonicalCode.DeadlineExceeded, "DEADLINE_EXCEEDED"},
|
||||
{ StatusCanonicalCode.NotFound, "NOT_FOUND"},
|
||||
{ StatusCanonicalCode.AlreadyExists, "ALREADY_EXISTS"},
|
||||
{ StatusCanonicalCode.PermissionDenied, "PERMISSION_DENIED"},
|
||||
{ StatusCanonicalCode.ResourceExhausted, "RESOURCE_EXHAUSTED"},
|
||||
{ StatusCanonicalCode.FailedPrecondition, "FAILED_PRECONDITION"},
|
||||
{ StatusCanonicalCode.Aborted, "ABORTED"},
|
||||
{ StatusCanonicalCode.OutOfRange, "OUT_OF_RANGE"},
|
||||
{ StatusCanonicalCode.Unimplemented, "UNIMPLEMENTED"},
|
||||
{ StatusCanonicalCode.Internal, "INTERNAL"},
|
||||
{ StatusCanonicalCode.Unavailable, "UNAVAILABLE"},
|
||||
{ StatusCanonicalCode.DataLoss, "DATA_LOSS"},
|
||||
{ StatusCanonicalCode.Unauthenticated, "UNAUTHENTICATED"},
|
||||
{ StatusCanonicalCode.Ok.ToString(), "OK"},
|
||||
{ StatusCanonicalCode.Cancelled.ToString(), "CANCELLED"},
|
||||
{ StatusCanonicalCode.Unknown.ToString(), "UNKNOWN"},
|
||||
{ StatusCanonicalCode.InvalidArgument.ToString(), "INVALID_ARGUMENT"},
|
||||
{ StatusCanonicalCode.DeadlineExceeded.ToString(), "DEADLINE_EXCEEDED"},
|
||||
{ StatusCanonicalCode.NotFound.ToString(), "NOT_FOUND"},
|
||||
{ StatusCanonicalCode.AlreadyExists.ToString(), "ALREADY_EXISTS"},
|
||||
{ StatusCanonicalCode.PermissionDenied.ToString(), "PERMISSION_DENIED"},
|
||||
{ StatusCanonicalCode.ResourceExhausted.ToString(), "RESOURCE_EXHAUSTED"},
|
||||
{ StatusCanonicalCode.FailedPrecondition.ToString(), "FAILED_PRECONDITION"},
|
||||
{ StatusCanonicalCode.Aborted.ToString(), "ABORTED"},
|
||||
{ StatusCanonicalCode.OutOfRange.ToString(), "OUT_OF_RANGE"},
|
||||
{ StatusCanonicalCode.Unimplemented.ToString(), "UNIMPLEMENTED"},
|
||||
{ StatusCanonicalCode.Internal.ToString(), "INTERNAL"},
|
||||
{ StatusCanonicalCode.Unavailable.ToString(), "UNAVAILABLE"},
|
||||
{ StatusCanonicalCode.DataLoss.ToString(), "DATA_LOSS"},
|
||||
{ StatusCanonicalCode.Unauthenticated.ToString(), "UNAUTHENTICATED"},
|
||||
};
|
||||
|
||||
Assert.Equal(tc.SpanStatus, d[span.Status.CanonicalCode]);
|
||||
if (tc.SpanStatusHasDescription.HasValue)
|
||||
Assert.Equal(tc.SpanStatusHasDescription.Value, !string.IsNullOrEmpty(span.Status.Description));
|
||||
|
||||
var normalizedAttributes = span.Attributes.ToDictionary(
|
||||
x => x.Key,
|
||||
x => x.Value.ToString());
|
||||
tc.SpanAttributes = tc.SpanAttributes.ToDictionary(
|
||||
x => x.Key,
|
||||
x =>
|
||||
|
|
@ -123,7 +117,35 @@ namespace OpenTelemetry.Instrumentation.Dependencies.Tests
|
|||
return HttpTestData.NormalizeValues(x.Value, host, port);
|
||||
});
|
||||
|
||||
Assert.Equal(tc.SpanAttributes, normalizedAttributes);
|
||||
foreach (KeyValuePair<string, string> tag in activity.Tags)
|
||||
{
|
||||
if (!tc.SpanAttributes.TryGetValue(tag.Key, out string value))
|
||||
{
|
||||
if (tag.Key == "http.flavor")
|
||||
{
|
||||
// http.flavor is optional in .NET Core instrumentation but there is no way to pass that option to the new ActivitySource model so it always shows up here.
|
||||
if (tc.SetHttpFlavor)
|
||||
{
|
||||
Assert.Equal(value, tag.Value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (tag.Key == SpanAttributeConstants.StatusCodeKey)
|
||||
{
|
||||
Assert.Equal(tc.SpanStatus, d[tag.Value]);
|
||||
continue;
|
||||
}
|
||||
if (tag.Key == SpanAttributeConstants.StatusDescriptionKey)
|
||||
{
|
||||
if (tc.SpanStatusHasDescription.HasValue)
|
||||
Assert.Equal(tc.SpanStatusHasDescription.Value, !string.IsNullOrEmpty(tag.Value));
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.True(false, $"Tag {tag.Key} was not found in test data.");
|
||||
}
|
||||
Assert.Equal(value, tag.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in New Issue