|
| 1 | +// <copyright file="OpenTelemetryAspNetMvc5Tests.cs" company="Datadog"> |
| 2 | +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. |
| 3 | +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. |
| 4 | +// </copyright> |
| 5 | + |
| 6 | +#if NETFRAMEWORK |
| 7 | +#pragma warning disable SA1402 // File may only contain a single class |
| 8 | +#pragma warning disable SA1649 // File name must match first type name |
| 9 | + |
| 10 | +using System.Threading.Tasks; |
| 11 | +using Datadog.Trace.ClrProfiler.IntegrationTests.Helpers; |
| 12 | +using Datadog.Trace.TestHelpers; |
| 13 | +using Xunit; |
| 14 | +using Xunit.Abstractions; |
| 15 | + |
| 16 | +namespace Datadog.Trace.ClrProfiler.IntegrationTests |
| 17 | +{ |
| 18 | + [Collection(nameof(TestAgentOtlpCollection))] |
| 19 | + public class OpenTelemetryAspNetMvc5TestsDatadogSemantics : OpenTelemetryAspNetMvc5Tests |
| 20 | + { |
| 21 | + public OpenTelemetryAspNetMvc5TestsDatadogSemantics(IisFixture iisFixture, ITestOutputHelper output) |
| 22 | + : base(iisFixture, output, openTelemetrySemanticsEnabled: false) |
| 23 | + { |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + [Collection(nameof(TestAgentOtlpCollection))] |
| 28 | + public class OpenTelemetryAspNetMvc5TestsOtelSemantics : OpenTelemetryAspNetMvc5Tests |
| 29 | + { |
| 30 | + public OpenTelemetryAspNetMvc5TestsOtelSemantics(IisFixture iisFixture, ITestOutputHelper output) |
| 31 | + : base(iisFixture, output, openTelemetrySemanticsEnabled: true) |
| 32 | + { |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + /// <summary> |
| 37 | + /// Covers the HTTP server coverage for the OpenTelemetry HTTP Semantic Conventions |
| 38 | + /// implementation, using the ASP.NET MVC 5 sample hosted in IIS Express. One test case per |
| 39 | + /// endpoint, each exercising a different requirement from |
| 40 | + /// <see href="https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server">the HTTP |
| 41 | + /// server conventions</see>: standard vs. unknown request methods, route templates as |
| 42 | + /// <c>http.route</c>, status-to-error mapping (which for server spans differs from client spans |
| 43 | + /// in the 4xx range), and <c>url.query</c> obfuscation. |
| 44 | + /// </summary> |
| 45 | + public abstract class OpenTelemetryAspNetMvc5Tests : OpenTelemetryAspNetTestBase |
| 46 | + { |
| 47 | + protected OpenTelemetryAspNetMvc5Tests(IisFixture iisFixture, ITestOutputHelper output, bool openTelemetrySemanticsEnabled) |
| 48 | + : base(iisFixture, output, nameof(OpenTelemetryAspNetMvc5Tests), openTelemetrySemanticsEnabled) |
| 49 | + { |
| 50 | + } |
| 51 | + |
| 52 | + /// <summary> |
| 53 | + /// One endpoint per OpenTelemetry HTTP server requirement, as (method, path, status code, |
| 54 | + /// Datadog-semantics span count, OpenTelemetry-semantics span count). With Datadog semantics |
| 55 | + /// the count is 2 for a request that reaches an MVC action (the <c>aspnet.request</c> span |
| 56 | + /// plus the <c>aspnet-mvc.request</c> span nested inside it) and 1 when no action runs, |
| 57 | + /// because the MVC span is created by the action invoker. With OpenTelemetry semantics the |
| 58 | + /// <c>aspnet-mvc.request</c> span is not created, because the conventions describe a single |
| 59 | + /// HTTP server span per request. |
| 60 | + /// </summary> |
| 61 | + public static TheoryData<string, string, int, int, int> Data => new() |
| 62 | + { |
| 63 | + // Baseline: a conventionally-routed request that exercises every required and recommended |
| 64 | + // server attribute (http.request.method, url.path, url.scheme, http.route, |
| 65 | + // http.response.status_code, server.address, server.port, user_agent.original) and the |
| 66 | + // "{method} {http.route}" span name. |
| 67 | + { "GET", "/home/index", 200, 2, 1 }, |
| 68 | + |
| 69 | + // A route with a parameter: http.route must stay low-cardinality, so it carries the |
| 70 | + // template ("delay/{seconds}") rather than the substituted value. |
| 71 | + { "GET", "/delay/0", 200, 2, 1 }, |
| 72 | + |
| 73 | + // A standard method other than GET, to show http.request.method follows the request. |
| 74 | + { "POST", "/home/index", 200, 2, 1 }, |
| 75 | + |
| 76 | + // Unknown/non-standard method: http.request.method must be _OTHER, the raw verb must move |
| 77 | + // to http.request.method_original, and the span name must fall back to "HTTP" for the |
| 78 | + // method part instead of the raw verb. |
| 79 | + { "FOO", "/home/index", 200, 2, 1 }, |
| 80 | + |
| 81 | + // Sensitive query string: url.query must be captured and obfuscated with the same |
| 82 | + // DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP that http.url uses. Note that the two keys here |
| 83 | + // are treated differently, which the paired snapshots pin: "token" is matched by the |
| 84 | + // default pattern, but the bare key "sig" is not, even though the OpenTelemetry |
| 85 | + // conventions name it as one of the values to redact in a URL. |
| 86 | + { "GET", "/statuscode/201?token=SUPER-SECRET-TOKEN-VALUE&sig=SUPER-SECRET-SIGNATURE", 201, 2, 1 }, |
| 87 | + |
| 88 | + // 2xx and 3xx responses: not an error under either Datadog or OpenTelemetry semantics. |
| 89 | + { "GET", "/statuscode/302", 302, 2, 1 }, |
| 90 | + |
| 91 | + // 4xx response: for *server* spans the status stays unset, which is the opposite of the |
| 92 | + // client-span rule where 4xx is an error. |
| 93 | + { "GET", "/statuscode/404", 404, 2, 1 }, |
| 94 | + |
| 95 | + // 5xx response: an error under both Datadog and OpenTelemetry semantics. |
| 96 | + { "GET", "/statuscode/503", 503, 2, 1 }, |
| 97 | + |
| 98 | + // An unhandled exception rather than a status code: the exception is recorded on the span |
| 99 | + // and error.type carries the status code. |
| 100 | + { "GET", "/badrequest", 500, 2, 1 }, |
| 101 | + |
| 102 | + // No controller for the matched route, so no MVC span is created and the server span is |
| 103 | + // the only record of the request. With OpenTelemetry semantics there is also no |
| 104 | + // http.route, so the span name must be just the method rather than the URI path. |
| 105 | + { "GET", "/not-a-registered-route/1", 404, 1, 1 }, |
| 106 | + }; |
| 107 | + |
| 108 | + [SkippableTheory] |
| 109 | + [Trait("Category", "EndToEnd")] |
| 110 | + [Trait("RunOnWindows", "True")] |
| 111 | + [Trait("LoadFromGAC", "True")] |
| 112 | + [Trait("RequiresDockerDependency", "true")] |
| 113 | + [MemberData(nameof(Data))] |
| 114 | + public Task SubmitsOtlpTraces(string httpMethod, string path, int statusCode, int datadogSpanCount, int otelSpanCount) |
| 115 | + => RunTestCaseAsync(httpMethod, path, statusCode, OpenTelemetrySemanticsEnabled ? otelSpanCount : datadogSpanCount); |
| 116 | + } |
| 117 | +} |
| 118 | +#endif |
0 commit comments