diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs index b19a2c3493de..5a2b3324da2c 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs @@ -2,25 +2,11 @@ // Licensed under the MIT License. using System.Diagnostics; -using OpenTelemetry.Trace; namespace OpenTelemetry.Exporter.AzureMonitor { internal static class ActivityExtensions { - private const string StatusCode_0 = "0"; - private const string StatusCode_200 = "200"; - private const string StatusCode_400 = "400"; - private const string StatusCode_401 = "401"; - private const string StatusCode_403 = "403"; - private const string StatusCode_404 = "404"; - private const string StatusCode_409 = "409"; - private const string StatusCode_412 = "412"; - private const string StatusCode_500 = "500"; - private const string StatusCode_501 = "501"; - private const string StatusCode_503 = "503"; - private const string StatusCode_504 = "504"; - internal static TelemetryType GetTelemetryType(this Activity activity) { var kind = activity.Kind switch @@ -34,30 +20,5 @@ internal static TelemetryType GetTelemetryType(this Activity activity) return kind; } - - // TODO: Change the return type to integer once .NET support it. - internal static string GetStatusCode(this Activity activity) - { - var status = activity.GetStatus().CanonicalCode switch - { - StatusCanonicalCode.Cancelled => StatusCode_400, - StatusCanonicalCode.InvalidArgument => StatusCode_400, - StatusCanonicalCode.DeadlineExceeded => StatusCode_504, - StatusCanonicalCode.NotFound => StatusCode_404, - StatusCanonicalCode.AlreadyExists => StatusCode_409, - StatusCanonicalCode.PermissionDenied => StatusCode_403, - StatusCanonicalCode.ResourceExhausted => StatusCode_409, - StatusCanonicalCode.FailedPrecondition => StatusCode_412, - StatusCanonicalCode.OutOfRange => StatusCode_400, - StatusCanonicalCode.Unimplemented => StatusCode_501, - StatusCanonicalCode.Internal => StatusCode_500, - StatusCanonicalCode.Unavailable => StatusCode_503, - StatusCanonicalCode.Unauthenticated => StatusCode_401, - StatusCanonicalCode.Ok => StatusCode_200, - _ => StatusCode_0 - }; - - return status; - } } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index 4cfef1a6f9d8..f44968aa97c5 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -6,13 +6,13 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Azure.Core.Pipeline; using OpenTelemetry.Exporter.AzureMonitor.ConnectionString; using OpenTelemetry.Exporter.AzureMonitor.Models; -using OpenTelemetry.Trace; namespace OpenTelemetry.Exporter.AzureMonitor { @@ -101,23 +101,27 @@ private static TelemetryEnvelope GeneratePartAEnvelope(Activity activity) private MonitorBase GenerateTelemetryData(Activity activity) { - MonitorBase telemetry = new MonitorBase(); - var telemetryType = activity.GetTelemetryType(); - telemetry.BaseType = Telemetry_Base_Type_Mapping[telemetryType]; - string url = GetHttpUrl(activity.Tags); + var tags = activity.Tags.ToAzureMonitorTags(out var activityType); + MonitorBase telemetry = new MonitorBase + { + BaseType = Telemetry_Base_Type_Mapping[telemetryType] + }; if (telemetryType == TelemetryType.Request) { - var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), activity.GetStatus().IsOk, activity.GetStatusCode()) + var url = activity.Kind == ActivityKind.Server ? UrlHelper.GetUrl(tags) : GetMessagingUrl(tags); + var statusCode = GetHttpStatusCode(tags); + var success = GetSuccessFromHttpStatusCode(statusCode); + var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) { Name = activity.DisplayName, Url = url, // TODO: Handle request.source. }; - // TODO: Handle activity.TagObjects - ExtractPropertiesFromTags(request.Properties, activity.Tags); + // TODO: Handle activity.TagObjects, extract well-known tags + // ExtractPropertiesFromTags(request.Properties, activity.Tags); telemetry.BaseData = request; } @@ -125,18 +129,19 @@ private MonitorBase GenerateTelemetryData(Activity activity) { var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration) { - Id = activity.Context.SpanId.ToHexString(), - Success = activity.GetStatus().IsOk + Id = activity.Context.SpanId.ToHexString() }; // TODO: Handle activity.TagObjects - ExtractPropertiesFromTags(dependency.Properties, activity.Tags); + // ExtractPropertiesFromTags(dependency.Properties, activity.Tags); - if (url != null) + if (activityType == PartBType.Http) { - dependency.Data = url; + dependency.Data = UrlHelper.GetUrl(tags); dependency.Type = "HTTP"; // TODO: Parse for storage / SB. - dependency.ResultCode = activity.GetStatusCode(); + var statusCode = GetHttpStatusCode(tags); + dependency.ResultCode = statusCode; + dependency.Success = GetSuccessFromHttpStatusCode(statusCode); } // TODO: Handle dependency.target. @@ -146,30 +151,27 @@ private MonitorBase GenerateTelemetryData(Activity activity) return telemetry; } - private static string GetHttpUrl(IEnumerable> tags) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetHttpStatusCode(Dictionary tags) { - var httpTags = tags.Where(item => item.Key.StartsWith("http.", StringComparison.InvariantCulture)) - .ToDictionary(item => item.Key, item => item.Value); - - - httpTags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url); - if (url != null) + if (tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status)) { - return url; + return status; } - httpTags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost); - - if (httpHost != null) - { - httpTags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme); - httpTags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); - url = httpScheme + httpHost + httpTarget; - return url; - } + return "0"; + } - // TODO: Follow spec - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetSuccessFromHttpStatusCode(string statusCode) + { + return statusCode == "200" || statusCode == "Ok"; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetMessagingUrl(Dictionary tags) + { + tags.TryGetValue(SemanticConventions.AttributeMessagingUrl, out var url); return url; } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs new file mode 100644 index 000000000000..b2f94a4b9f91 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal enum PartBType + { + Unknown, + Http, + Db, + Messaging, + Rpc, + FaaS, + Net + }; + +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs index ec27ec52c3c8..6ceb60002b4a 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs @@ -114,6 +114,7 @@ internal static class SemanticConventions public const string AttributeFaasTrigger = "faas.trigger"; public const string AttributeFaasExecution = "faas.execution"; + public const string AttributeFaasColdStart = "faas.coldstart"; public const string AttributeFaasDocumentCollection = "faas.document.collection"; public const string AttributeFaasDocumentOperation = "faas.document.operation"; public const string AttributeFaasDocumentTime = "faas.document.time"; diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs new file mode 100644 index 000000000000..b6c84326a9aa --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal static class TagsExtension + { + private static readonly IReadOnlyDictionary Part_B_Mapping = new Dictionary() + { + [SemanticConventions.AttributeDbSystem] = PartBType.Db, + [SemanticConventions.AttributeDbConnectionString] = PartBType.Db, + [SemanticConventions.AttributeDbUser] = PartBType.Db, + + [SemanticConventions.AttributeHttpMethod] = PartBType.Http, + [SemanticConventions.AttributeHttpUrl] = PartBType.Http, + [SemanticConventions.AttributeHttpStatusCode] = PartBType.Http, + [SemanticConventions.AttributeHttpScheme] = PartBType.Http, + [SemanticConventions.AttributeHttpHost] = PartBType.Http, + [SemanticConventions.AttributeHttpTarget] = PartBType.Http, + + [SemanticConventions.AttributeNetPeerName] = PartBType.Net, + [SemanticConventions.AttributeNetPeerIp] = PartBType.Net, + [SemanticConventions.AttributeNetPeerPort] = PartBType.Net, + [SemanticConventions.AttributeNetTransport] = PartBType.Net, + [SemanticConventions.AttributeNetHostIp] = PartBType.Net, + [SemanticConventions.AttributeNetHostPort] = PartBType.Net, + [SemanticConventions.AttributeNetHostName] = PartBType.Net, + + [SemanticConventions.AttributeRpcSystem] = PartBType.Rpc, + [SemanticConventions.AttributeRpcService] = PartBType.Rpc, + [SemanticConventions.AttributeRpcMethod] = PartBType.Rpc, + + [SemanticConventions.AttributeFaasTrigger] = PartBType.FaaS, + [SemanticConventions.AttributeFaasExecution] = PartBType.FaaS, + [SemanticConventions.AttributeFaasColdStart] = PartBType.FaaS, + [SemanticConventions.AttributeFaasDocumentCollection] = PartBType.FaaS, + [SemanticConventions.AttributeFaasDocumentOperation] = PartBType.FaaS, + [SemanticConventions.AttributeFaasDocumentTime] = PartBType.FaaS, + [SemanticConventions.AttributeFaasDocumentName] = PartBType.FaaS, + [SemanticConventions.AttributeFaasCron] = PartBType.FaaS, + [SemanticConventions.AttributeFaasTime] = PartBType.FaaS, + + [SemanticConventions.AttributeMessagingSystem] = PartBType.Messaging, + [SemanticConventions.AttributeMessagingDestination] = PartBType.Messaging, + [SemanticConventions.AttributeMessagingDestinationKind] = PartBType.Messaging, + [SemanticConventions.AttributeMessagingTempDestination] = PartBType.Messaging, + [SemanticConventions.AttributeMessagingUrl] = PartBType.Messaging + }; + + internal static Dictionary ToAzureMonitorTags(this IEnumerable> tags, out PartBType activityType) + { + Dictionary partBTags = new Dictionary(); + activityType = PartBType.Unknown; + + foreach (var entry in tags) + { + // TODO: May need to store unknown to write to properties as Part C + if ((activityType == PartBType.Unknown || activityType == PartBType.Net) && !Part_B_Mapping.TryGetValue(entry.Key, out activityType)) + { + if (activityType == PartBType.Net) + { + partBTags.Add(entry.Key, entry.Value); + activityType = PartBType.Unknown; + } + + continue; + } + + if (Part_B_Mapping.TryGetValue(entry.Key, out var tempActivityType) && (tempActivityType == activityType || tempActivityType == PartBType.Net)) + { + partBTags.Add(entry.Key, entry.Value); + } + } + + return partBTags; + } + + } +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs new file mode 100644 index 000000000000..38cb6cf5f25a --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal class UrlHelper + { + private const string SchemePostfix = "://"; + private const string Colon = ":"; + + /// + /// This method follows OpenTelemetry specification to retrieve http URL. + /// Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client. + /// + /// Activity Tags + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string GetUrl(Dictionary tags) + { + if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url)) + { + if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri) + { + return url; + } + } + + if (tags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme)) + { + tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); + if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost) && !string.IsNullOrWhiteSpace(httpHost)) + { + tags.TryGetValue(SemanticConventions.AttributeHttpHostPort, out var httpPort); + if (httpPort != null && httpPort != "80" && httpPort != "443") + { + url = $"{httpScheme}{SchemePostfix}{httpHost}{Colon}{httpPort}{httpTarget}"; + } + else + { + url = $"{httpScheme}{SchemePostfix}{httpHost}{httpTarget}"; + } + + return url; + } + else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerName, out var netPeerName) + && tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out var netPeerPort)) + { + return string.IsNullOrWhiteSpace(netPeerName) ? null : $"{httpScheme}{SchemePostfix}{netPeerName}{(string.IsNullOrWhiteSpace(netPeerPort) ? null : Colon)}{netPeerPort}{httpTarget}"; + } + else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerIp, out var netPeerIP) + && tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out netPeerPort)) + { + return string.IsNullOrWhiteSpace(netPeerIP) ? null : $"{httpScheme}{SchemePostfix}{netPeerIP}{(string.IsNullOrWhiteSpace(netPeerPort) ? null : Colon)}{netPeerPort}{httpTarget}"; + } + } + + if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host) && !string.IsNullOrWhiteSpace(host)) + { + tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); + tags.TryGetValue(SemanticConventions.AttributeHttpHostPort, out var httpPort); + url = $"{host}{(string.IsNullOrWhiteSpace(httpPort) ? null : Colon)}{httpPort}{httpTarget}"; + return url; + } + + return string.IsNullOrWhiteSpace(url) ? null : url; + } + } +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Demo.Tracing/InstrumentationWithActivitySource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Demo.Tracing/InstrumentationWithActivitySource.cs index b0f6d2c21702..28a31c480d97 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Demo.Tracing/InstrumentationWithActivitySource.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Demo.Tracing/InstrumentationWithActivitySource.cs @@ -64,6 +64,7 @@ public void Start(string url) } activity?.SetTag("http.url", context.Request.Url.AbsolutePath); + activity?.SetTag("http.host", context.Request.Url.Host); string requestContent; using (var childSpan = source.StartActivity("ReadStream", ActivityKind.Consumer)) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs index 40b2808eec83..b04c781ca93e 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs @@ -1,16 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Moq; +using OpenTelemetry.Trace; using System; +using System.Diagnostics; using System.Reflection; -using NUnit.Framework; +using Xunit; namespace OpenTelemetry.Exporter.AzureMonitor { public class AzureMonitorTraceExporterTests { - [Test] + [Fact] public void VerifyConnectionString_CorrectlySetsEndpoint() { var testIkey = "test_ikey"; @@ -19,11 +22,11 @@ public void VerifyConnectionString_CorrectlySetsEndpoint() var exporter = new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = $"InstrumentationKey={testIkey};IngestionEndpoint={testEndpoint}" }); GetInternalFields(exporter, out string ikey, out string endpoint); - Assert.AreEqual(testIkey, ikey); - Assert.AreEqual(testEndpoint, endpoint); + Assert.Equal(testIkey, ikey); + Assert.Equal(testEndpoint, endpoint); } - [Test] + [Fact] public void VerifyConnectionString_CorrectlySetsDefaultEndpoint() { var testIkey = "test_ikey"; @@ -31,17 +34,17 @@ public void VerifyConnectionString_CorrectlySetsDefaultEndpoint() var exporter = new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = $"InstrumentationKey={testIkey};" }); GetInternalFields(exporter, out string ikey, out string endpoint); - Assert.AreEqual(testIkey, ikey); - Assert.AreEqual(ConnectionString.Constants.DefaultIngestionEndpoint, endpoint); + Assert.Equal(testIkey, ikey); + Assert.Equal(ConnectionString.Constants.DefaultIngestionEndpoint, endpoint); } - [Test] + [Fact] public void VerifyConnectionString_ThrowsExceptionWhenInvalid() { Assert.Throws(() => new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = null })); } - [Test] + [Fact] public void VerifyConnectionString_ThrowsExceptionWhenMissingInstrumentationKey() { var testEndpoint = "https://www.bing.com/"; diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParserTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParserTests.cs index 2f5fb107ad2d..e24d72417282 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParserTests.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParserTests.cs @@ -2,14 +2,13 @@ // Licensed under the MIT License. using System; - -using NUnit.Framework; +using Xunit; namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString { public class ConnectionStringParserTests { - [Test] + [Fact] public void TestInstrumentationKey_IsRequired() { Assert.Throws(() => RunTest( @@ -18,7 +17,7 @@ public void TestInstrumentationKey_IsRequired() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestInstrumentationKey_CannotBeEmpty() { Assert.Throws(() => RunTest( @@ -27,7 +26,7 @@ public void TestInstrumentationKey_CannotBeEmpty() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestDefaultEndpoints() { RunTest( @@ -36,7 +35,7 @@ public void TestDefaultEndpoints() expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } - [Test] + [Fact] public void TestEndpointSuffix() { RunTest( @@ -45,7 +44,7 @@ public void TestEndpointSuffix() expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } - [Test] + [Fact] public void TestEndpointSuffix_WithExplicitOverride() { RunTest( @@ -54,7 +53,7 @@ public void TestEndpointSuffix_WithExplicitOverride() expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } - [Test] + [Fact] public void TestEndpointSuffix_WithLocation() { RunTest( @@ -63,7 +62,7 @@ public void TestEndpointSuffix_WithLocation() expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } - [Test] + [Fact] public void TestEndpointSuffix_WithLocation_WithExplicitOverride() { RunTest( @@ -72,7 +71,7 @@ public void TestEndpointSuffix_WithLocation_WithExplicitOverride() expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } - [Test] + [Fact] public void TestExpliticOverride_PreservesSchema() { RunTest( @@ -81,7 +80,7 @@ public void TestExpliticOverride_PreservesSchema() expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } - [Test] + [Fact] public void TestExpliticOverride_InvalidValue() { Assert.Throws(() => RunTest( @@ -90,7 +89,7 @@ public void TestExpliticOverride_InvalidValue() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestExpliticOverride_InvalidValue2() { Assert.Throws(() => RunTest( @@ -99,7 +98,7 @@ public void TestExpliticOverride_InvalidValue2() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestExpliticOverride_InvalidValue3() { Assert.Throws(() => RunTest( @@ -108,7 +107,7 @@ public void TestExpliticOverride_InvalidValue3() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestExpliticOverride_InvalidLocation() { Assert.Throws(() => RunTest( @@ -117,7 +116,7 @@ public void TestExpliticOverride_InvalidLocation() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestMaliciousConnectionString() { Assert.Throws(() => RunTest( @@ -126,7 +125,7 @@ public void TestMaliciousConnectionString() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestParseConnectionString_Null() { Assert.Throws(() => RunTest( @@ -135,7 +134,7 @@ public void TestParseConnectionString_Null() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestParseConnectionString_Empty() { Assert.Throws(() => RunTest( @@ -144,7 +143,7 @@ public void TestParseConnectionString_Empty() expectedInstrumentationKey: null)); } - [Test] + [Fact] public void TestEndpointProvider_NoInstrumentationKey() { Assert.Throws(() => RunTest( @@ -157,8 +156,8 @@ private void RunTest(string connectionString, string expectedIngestionEndpoint, { ConnectionStringParser.GetValues(connectionString, out string ikey, out string endpoint); - Assert.AreEqual(expectedIngestionEndpoint, endpoint); - Assert.AreEqual(expectedInstrumentationKey, ikey); + Assert.Equal(expectedIngestionEndpoint, endpoint); + Assert.Equal(expectedInstrumentationKey, ikey); } } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParser_BuildUriTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParser_BuildUriTests.cs index b4bede281b90..cb3532279851 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParser_BuildUriTests.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParser_BuildUriTests.cs @@ -2,8 +2,7 @@ // Licensed under the MIT License. using System; - -using NUnit.Framework; +using Xunit; namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString { @@ -13,7 +12,7 @@ namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString /// public class ConnectionStringParser_BuildUriTests { - [Test] + [Fact] public void VerifyCanHandleExtraPeriods() { var result = ConnectionStringParser.TryBuildUri( @@ -22,11 +21,11 @@ public void VerifyCanHandleExtraPeriods() suffix: ".applicationinsights.azure.com", uri: out Uri uri); - Assert.IsTrue(result); - Assert.AreEqual("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); + Assert.True(result); + Assert.Equal("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); } - [Test] + [Fact] public void VerifyGoodAddress_WithLocation() { var result = ConnectionStringParser.TryBuildUri( @@ -35,11 +34,11 @@ public void VerifyGoodAddress_WithLocation() suffix: "applicationinsights.azure.com", uri: out Uri uri); - Assert.IsTrue(result); - Assert.AreEqual("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); + Assert.True(result); + Assert.Equal("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); } - [Test] + [Fact] public void VerifyGoodAddress_WithoutLocation() { var result = ConnectionStringParser.TryBuildUri( @@ -48,11 +47,11 @@ public void VerifyGoodAddress_WithoutLocation() suffix: "applicationinsights.azure.com", uri: out Uri uri); - Assert.IsTrue(result); - Assert.AreEqual("https://dc.applicationinsights.azure.com/", uri.AbsoluteUri); + Assert.True(result); + Assert.Equal("https://dc.applicationinsights.azure.com/", uri.AbsoluteUri); } - [Test] + [Fact] public void VerifyGoodAddress_InvalidCharInLocation() { Assert.Throws(() => @@ -63,7 +62,7 @@ public void VerifyGoodAddress_InvalidCharInLocation() uri: out Uri uri)); } - [Test] + [Fact] public void VerifyGoodAddress_CanHandleExtraSpaces() { var result = ConnectionStringParser.TryBuildUri( @@ -72,8 +71,8 @@ public void VerifyGoodAddress_CanHandleExtraSpaces() suffix: " applicationinsights.azure.com ", uri: out Uri uri); - Assert.IsTrue(result); - Assert.AreEqual("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); + Assert.True(result); + Assert.Equal("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); } } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringTests.cs index 40db17ff8633..e0d6bb28e01b 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringTests.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringTests.cs @@ -2,8 +2,7 @@ // Licensed under the MIT License. using System; - -using NUnit.Framework; +using Xunit; namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString { @@ -12,78 +11,78 @@ namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString /// public class ConnectionStringTests { - [Test] + [Fact] public void TestParse() { var test = Azure.Core.ConnectionString.Parse("key1=value1;key2=value2;key3=value3"); - Assert.AreEqual("value1", test.GetRequired("key1")); - Assert.AreEqual("value2", test.GetRequired("key2")); - Assert.AreEqual("value3", test.GetRequired("key3")); + Assert.Equal("value1", test.GetRequired("key1")); + Assert.Equal("value2", test.GetRequired("key2")); + Assert.Equal("value3", test.GetRequired("key3")); } - [Test] + [Fact] public void TestParse_WithTrailingSemicolon() { var test = Azure.Core.ConnectionString.Parse("key1=value1;key2=value2;key3=value3;"); - Assert.AreEqual("value1", test.GetRequired("key1")); - Assert.AreEqual("value2", test.GetRequired("key2")); - Assert.AreEqual("value3", test.GetRequired("key3")); + Assert.Equal("value1", test.GetRequired("key1")); + Assert.Equal("value2", test.GetRequired("key2")); + Assert.Equal("value3", test.GetRequired("key3")); } - [Test] + [Fact] public void TestParse_WithExtraSpaces() { var test = Azure.Core.ConnectionString.Parse(" key1 = value1 ; key2 = value2 ; key3 =value3 "); - Assert.AreEqual("value1", test.GetRequired("key1")); - Assert.AreEqual("value2", test.GetRequired("key2")); - Assert.AreEqual("value3", test.GetRequired("key3")); + Assert.Equal("value1", test.GetRequired("key1")); + Assert.Equal("value2", test.GetRequired("key2")); + Assert.Equal("value3", test.GetRequired("key3")); } /// /// Users can input unexpected casing in their connection strings. /// Verify that we can fetch any value from the dictionary regardless of the casing. /// - [Test] + [Fact] public void TestParse_IsCaseInsensitive() { var test = Azure.Core.ConnectionString.Parse("UPPERCASE=value1;lowercase=value2;MixedCase=value3"); - Assert.AreEqual("value1", test.GetRequired("UPPERCASE")); - Assert.AreEqual("value1", test.GetRequired("uppercase")); - Assert.AreEqual("value2", test.GetRequired("LOWERCASE")); - Assert.AreEqual("value2", test.GetRequired("lowercase")); - Assert.AreEqual("value3", test.GetRequired("MIXEDCASE")); - Assert.AreEqual("value3", test.GetRequired("mixedcase")); + Assert.Equal("value1", test.GetRequired("UPPERCASE")); + Assert.Equal("value1", test.GetRequired("uppercase")); + Assert.Equal("value2", test.GetRequired("LOWERCASE")); + Assert.Equal("value2", test.GetRequired("lowercase")); + Assert.Equal("value3", test.GetRequired("MIXEDCASE")); + Assert.Equal("value3", test.GetRequired("mixedcase")); } - [Test] + [Fact] public void TestParse_WithNull() { Assert.Throws(() => Azure.Core.ConnectionString.Parse(null)); } - [Test] + [Fact] public void TestParse_WithEmptyString() { Assert.Throws(() => Azure.Core.ConnectionString.Parse(string.Empty)); } - [Test] + [Fact] public void TestParse_WithDuplaceKeys() { Assert.Throws(() => Azure.Core.ConnectionString.Parse("key1=value1;key1=value2")); } - [Test] + [Fact] public void TestParse_WithDuplaceKeysWithSpaces() { Assert.Throws(() => Azure.Core.ConnectionString.Parse("key1=value1;key1 =value2")); } - [Test] + [Fact] public void TestParse_WithInvalidDelimiters() { Assert.Throws(() => Azure.Core.ConnectionString.Parse("key1;key2=value2")); diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/NDJsonWriterTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/NDJsonWriterTests.cs index 374e53afa804..78de2694f158 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/NDJsonWriterTests.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/NDJsonWriterTests.cs @@ -2,13 +2,13 @@ // Licensed under the MIT License. using System.Text; -using NUnit.Framework; +using Xunit; namespace OpenTelemetry.Exporter.AzureMonitor.Tests { public class NDJsonWriterTests { - [Test] + [Fact] public void CanWriteMultilineJson() { var writer = new NDJsonWriter(); @@ -20,7 +20,7 @@ public void CanWriteMultilineJson() writer.JsonWriter.WriteNumber("anotherProperty", 2); writer.JsonWriter.WriteEndObject(); - Assert.AreEqual("{\"property\":\"value\"}\n{\"anotherProperty\":2}", Encoding.UTF8.GetString(writer.ToBytes().Span.ToArray())); + Assert.Equal("{\"property\":\"value\"}\n{\"anotherProperty\":2}", Encoding.UTF8.GetString(writer.ToBytes().Span.ToArray())); } } } \ No newline at end of file diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/OpenTelemetry.Exporter.AzureMonitor.Tests.csproj b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/OpenTelemetry.Exporter.AzureMonitor.Tests.csproj index 02e08fd54897..51321e66f5a5 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/OpenTelemetry.Exporter.AzureMonitor.Tests.csproj +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/OpenTelemetry.Exporter.AzureMonitor.Tests.csproj @@ -6,11 +6,10 @@ - - + + - diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/UrlHelperTests.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/UrlHelperTests.cs new file mode 100644 index 000000000000..2e3e60bb6b82 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/UrlHelperTests.cs @@ -0,0 +1,292 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Xunit; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + public class UrlHelperTests + { + [Fact] + public void GetUrl_Null() + { + var url = UrlHelper.GetUrl(new Dictionary()); + Assert.Null(url); + } + + [Fact] + public void GetUrl_HttpUrl_NullOrEmpty() + { + var url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpUrl] = null }); + Assert.Null(url); + url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpUrl] = string.Empty }); + Assert.Null(url); + } + + [Fact] + public void GetUrl_HttpScheme_NullOrEmpty() + { + var url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpScheme] = null }); + Assert.Null(url); + url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpScheme] = string.Empty }); + Assert.Null(url); + } + + [Fact] + public void GetUrl_HttpHost_NullOrEmpty() + { + var url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpHost] = null }); + Assert.Null(url); + url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpHost] = string.Empty }); + Assert.Null(url); + } + + [Fact] + public void GetUrl_With_HttpScheme_And_Null_HttpHost() + { + var url = UrlHelper.GetUrl(new Dictionary + { [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeHttpHost] = null}); + + Assert.Null(url); + } + + [Fact] + public void GetUrl_NetPeerName_NullOrEmpty() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerName] = null, + [SemanticConventions.AttributeNetPeerPort] = null + }); + + Assert.Null(url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerName] = string.Empty, + [SemanticConventions.AttributeNetPeerPort] = null + }); + + Assert.Null(url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerName] = "netpeername", + [SemanticConventions.AttributeNetPeerPort] = null + }); + + Assert.Equal("https://netpeername", url); + } + + [Fact] + public void GetUrl_NetPeerIP_NullOrEmpty() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerIp] = null, + [SemanticConventions.AttributeNetPeerPort] = null + }); + + Assert.Null(url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerIp] = string.Empty, + [SemanticConventions.AttributeNetPeerPort] = null + }); + + Assert.Null(url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerIp] = "127.0.0.1", + [SemanticConventions.AttributeNetPeerPort] = null + }); + + Assert.Equal("https://127.0.0.1", url); + } + + [Fact] + public void GetUrl_HttpPort_NullEmptyOrDefault() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = null + }); + + Assert.Equal("https://localhost", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "http", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "80" + }); + + Assert.Equal("http://localhost", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "443" + }); + + Assert.Equal("https://localhost", url); + } + + [Fact] + public void GetUrl_HttpPort_RandomPort_With_HttpTarget() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "8888" + }); + + Assert.Equal("https://localhost:8888", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "http", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "80", + [SemanticConventions.AttributeHttpTarget] = "/test" + }); + + Assert.Equal("http://localhost/test", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "443", + [SemanticConventions.AttributeHttpTarget] = "/test" + }); + + Assert.Equal("https://localhost/test", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "8888", + [SemanticConventions.AttributeHttpTarget] = "/test" + }); + + Assert.Equal("https://localhost:8888/test", url); + } + + [Fact] + public void GetUrl_NetPeerIP_Success() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerIp] = "10.0.0.1", + [SemanticConventions.AttributeNetPeerPort] = "443" + }); + + Assert.Equal("https://10.0.0.1:443", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerIp] = "10.0.0.1", + [SemanticConventions.AttributeNetPeerPort] = "443", + [SemanticConventions.AttributeHttpTarget] = "/test" + }); + + Assert.Equal("https://10.0.0.1:443/test", url); + } + + [Fact] + public void GetUrl_NetPeerName_Success() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerName] = "localhost", + [SemanticConventions.AttributeNetPeerPort] = "443" + }); + + Assert.Equal("https://localhost:443", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpScheme] = "https", + [SemanticConventions.AttributeNetPeerName] = "localhost", + [SemanticConventions.AttributeNetPeerPort] = "443", + [SemanticConventions.AttributeHttpTarget] = "/test" + }); + + Assert.Equal("https://localhost:443/test", url); + } + + [Fact] + public void GetUrl_HttpHost_Success() + { + var url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpHost] = "localhost", + }); + + Assert.Equal("localhost", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "8888", + }); + + Assert.Equal("localhost:8888", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = "8080", + [SemanticConventions.AttributeHttpTarget] = "/test" + }); + + Assert.Equal("localhost:8080/test", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = null, + [SemanticConventions.AttributeHttpTarget] = null + }); + + Assert.Equal("localhost", url); + + url = UrlHelper.GetUrl(new Dictionary + { + [SemanticConventions.AttributeHttpHost] = "localhost", + [SemanticConventions.AttributeHttpHostPort] = string.Empty, + [SemanticConventions.AttributeHttpTarget] = string.Empty + }); + + Assert.Equal("localhost", url); + } + + [Fact] + public void GetUrl_HttpUrl_Success() + { + var url = UrlHelper.GetUrl(new Dictionary { [SemanticConventions.AttributeHttpUrl] = "https://www.wiki.com" }); + Assert.Equal("https://www.wiki.com", url); + } + + // TODO: Order of precedence. + } +}