From f36e198a64e13e164c35f897c846c1fd6cdae2a9 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 20 Aug 2020 11:07:12 -0700 Subject: [PATCH 01/17] Add EventSource --- .../AzureMonitorTraceExporterEventSource.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs new file mode 100644 index 000000000000..d6c89607b3eb --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.Tracing; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + [EventSource(Name = EventSourceName)] + internal sealed class AzureMonitorTraceExporterEventSource : EventSource + { + private const string EventSourceName = "OpenTelemetry-TraceExporter-AzureMonitor"; + public static AzureMonitorTraceExporterEventSource Log = new AzureMonitorTraceExporterEventSource(); + + [NonEvent] + public void ConfigurationStringParseWarning(string message) + { + if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + { + this.WarnToParseConfigurationString(message); + } + } + + [Event(1, Message = "{0}", Level = EventLevel.Warning)] + public void WarnToParseConfigurationString(string message) => this.WriteEvent(1, message); + } +} From f3b79767bd6e3f4aa57fbecaee6f0ecbbbaca3b2 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 20 Aug 2020 11:46:33 -0700 Subject: [PATCH 02/17] Add Changelog,readme --- sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/CHANGELOG.md | 3 +++ sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md | 3 +++ .../src/OpenTelemetry.Exporter.AzureMonitor.csproj | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/CHANGELOG.md create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/CHANGELOG.md b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/CHANGELOG.md new file mode 100644 index 000000000000..658feded06b4 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/CHANGELOG.md @@ -0,0 +1,3 @@ +# Release History + +## 0.1.0-preview.1 (Unreleased) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md new file mode 100644 index 000000000000..ddd791dab497 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md @@ -0,0 +1,3 @@ +# OpenTelemetry Azure Monitor Exporter + +TODO diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/OpenTelemetry.Exporter.AzureMonitor.csproj b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/OpenTelemetry.Exporter.AzureMonitor.csproj index 2bd0675c4b26..9883869d4f1b 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/OpenTelemetry.Exporter.AzureMonitor.csproj +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/OpenTelemetry.Exporter.AzureMonitor.csproj @@ -2,7 +2,7 @@ An OpenTelemetry .NET exporter that exports to Azure Monitor AzureMonitor OpenTelemetry Exporter - 0.1.0 + 0.1.0-preview.1 Azure Monitor OpenTelemetry Exporter $(RequiredTargetFrameworks) From 76304ccacc37061e8b2f13510df556a3bba05c38 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 20 Aug 2020 12:14:24 -0700 Subject: [PATCH 03/17] Header to readme --- .../README.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md index ddd791dab497..0d0130f45934 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md @@ -1,3 +1,27 @@ # OpenTelemetry Azure Monitor Exporter TODO + +## Getting started + +TODO + +## Key concepts + +TODO + +## Examples + +TODO + +## Troubleshooting + +TODO + +## Next steps + +TODO + +## Contributing + +TODO \ No newline at end of file From ddc78a7a2391e12bd047933a507c872bfabb3834 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 20 Aug 2020 12:23:20 -0700 Subject: [PATCH 04/17] Readme header change. --- sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md index 0d0130f45934..c7be46380436 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md @@ -1,4 +1,4 @@ -# OpenTelemetry Azure Monitor Exporter +# Azure Monitor Exporter for OpenTelemetry .NET TODO From e87111a23f13ccb26483690509abdc2c0ae51ab6 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 20 Aug 2020 12:42:22 -0700 Subject: [PATCH 05/17] update readme --- .../OpenTelemetry.Exporter.AzureMonitor/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md index c7be46380436..241443a3d810 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/README.md @@ -1,10 +1,17 @@ -# Azure Monitor Exporter for OpenTelemetry .NET +# Azure Monitor Exporter client library for .NET TODO ## Getting started -TODO +### Install the package + + +### Prerequisites + + +### Authenticate the client + ## Key concepts From 9cc4fdf67560f8bc73fcf4a5d9ae7a658805e2de Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 21 Aug 2020 10:57:54 -0700 Subject: [PATCH 06/17] Add Sdkversion --- .../src/ActivityExtensions.cs | 43 ++++--------------- .../src/AzureMonitorTransmitter.cs | 3 +- .../src/SdkVersionUtils.cs | 35 +++++++++++++++ 3 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs index 1c80e590df40..b19a2c3493de 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; using System.Diagnostics; using OpenTelemetry.Trace; @@ -9,9 +8,6 @@ namespace OpenTelemetry.Exporter.AzureMonitor { internal static class ActivityExtensions { - private static readonly string INVALID_SPAN_ID = default(ActivitySpanId).ToHexString(); - private static readonly string INVALID_TRACE_ID = default(ActivityTraceId).ToHexString(); - private const string StatusCode_0 = "0"; private const string StatusCode_200 = "200"; private const string StatusCode_400 = "400"; @@ -25,39 +21,18 @@ internal static class ActivityExtensions private const string StatusCode_503 = "503"; private const string StatusCode_504 = "504"; - internal static string GetSpanId(this Activity activity) - { - var spanId = activity.SpanId.ToHexString(); - if (!string.Equals(spanId, INVALID_SPAN_ID, StringComparison.Ordinal)) - { - return spanId; - } - - return string.Empty; - } - - internal static string GetTraceId(this Activity activity) - { - var traceId = activity.TraceId.ToHexString(); - if (!string.Equals(traceId, INVALID_TRACE_ID, StringComparison.Ordinal)) - { - return traceId; - } - - return string.Empty; - } - internal static TelemetryType GetTelemetryType(this Activity activity) { - if (activity.Kind == ActivityKind.Server || activity.Kind == ActivityKind.Consumer) + var kind = activity.Kind switch { - return TelemetryType.Request; - } - else - { - // TODO: If there a need, extend for other telemetry types. - return TelemetryType.Dependency; - } + ActivityKind.Server => TelemetryType.Request, + ActivityKind.Client => TelemetryType.Dependency, + ActivityKind.Producer => TelemetryType.Dependency, + ActivityKind.Consumer => TelemetryType.Request, + _ => TelemetryType.Dependency + }; + + return kind; } // TODO: Change the return type to integer once .NET support it. diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index f430cb978bd2..2f6a31c50e77 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -18,6 +18,7 @@ internal class AzureMonitorTransmitter { private readonly ServiceRestClient serviceRestClient; private readonly AzureMonitorExporterOptions options; + private static readonly string SdkVersion = SdkVersionUtils.GetSdkVersion(); private static readonly IReadOnlyDictionary Telemetry_Base_Type_Mapping = new Dictionary { @@ -80,7 +81,7 @@ private static TelemetryEnvelope GeneratePartAEnvelope(Activity activity) // TODO: "ai.location.ip" - envelope.Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = "dotnet5:ot0.4.0-beta:ext1.0.0-alpha.1"; // {language}{sdkVersion}:ot{OpenTelemetryVersion}:ext{ExporterVersion} + envelope.Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = SdkVersion; return envelope; } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs new file mode 100644 index 000000000000..586dd9ebc3ee --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal class SdkVersionUtils + { + internal static string GetSdkVersion() + { + Version dotnetSdkVersion = GetVersion(typeof(object)); + Version otSdkVersion = GetVersion(typeof(OpenTelemetry.Sdk)); + Version extensionVersion = GetVersion(typeof(OpenTelemetry.Exporter.AzureMonitor.AzureMonitorTraceExporter)); + + return string.Format(CultureInfo.InvariantCulture, $"dotnet{dotnetSdkVersion.ToString(2)}:ot{otSdkVersion.ToString(3)}:ext{extensionVersion.ToString(3)}"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Version GetVersion(Type type) + { + var versionString = type.Assembly.GetCustomAttributes(false) + .OfType() + .First() + .Version; + + // Return zeros rather then failing if the version string fails to parse + return Version.TryParse(versionString, out var version) ? version : new Version(); + } + } +} From 64afaa5b5a446f8c4bbd03dc50d6c3ec3a5df171 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 21 Aug 2020 12:52:52 -0700 Subject: [PATCH 07/17] Addressing PR feedback --- .../src/AzureMonitorTransmitter.cs | 5 ++--- .../src/SdkVersionUtils.cs | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index 2f6a31c50e77..78b4a57b733d 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -18,7 +18,6 @@ internal class AzureMonitorTransmitter { private readonly ServiceRestClient serviceRestClient; private readonly AzureMonitorExporterOptions options; - private static readonly string SdkVersion = SdkVersionUtils.GetSdkVersion(); private static readonly IReadOnlyDictionary Telemetry_Base_Type_Mapping = new Dictionary { @@ -80,8 +79,8 @@ private static TelemetryEnvelope GeneratePartAEnvelope(Activity activity) } // TODO: "ai.location.ip" - - envelope.Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = SdkVersion; + // TODO: Handle exception + envelope.Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = SdkVersionUtils.SdkVersion; return envelope; } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs index 586dd9ebc3ee..d108fa89af29 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs @@ -5,24 +5,32 @@ using System.Globalization; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; namespace OpenTelemetry.Exporter.AzureMonitor { internal class SdkVersionUtils { - internal static string GetSdkVersion() + private static string sdkVersion; + + internal static string SdkVersion { + get + { + return sdkVersion ??= GetSdkVersion(); + } + } + + private static string GetSdkVersion() { Version dotnetSdkVersion = GetVersion(typeof(object)); - Version otSdkVersion = GetVersion(typeof(OpenTelemetry.Sdk)); + Version otelSdkVersion = GetVersion(typeof(OpenTelemetry.Sdk)); Version extensionVersion = GetVersion(typeof(OpenTelemetry.Exporter.AzureMonitor.AzureMonitorTraceExporter)); - return string.Format(CultureInfo.InvariantCulture, $"dotnet{dotnetSdkVersion.ToString(2)}:ot{otSdkVersion.ToString(3)}:ext{extensionVersion.ToString(3)}"); + return string.Format(CultureInfo.InvariantCulture, $"dotnet{dotnetSdkVersion.ToString(2)}:otel{otelSdkVersion.ToString(3)}:ext{extensionVersion.ToString(3)}"); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Version GetVersion(Type type) { + // TODO: Distinguish preview/stable release and minor versions. e.g: 5.0.0-preview.8.20365.13 var versionString = type.Assembly.GetCustomAttributes(false) .OfType() .First() From 4524d60eafc2da2df1f601b27746509031e67cd6 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 21 Aug 2020 13:34:17 -0700 Subject: [PATCH 08/17] Handled exception in SdkVersionUtils --- .../AzureMonitorTraceExporterEventSource.cs | 13 ++++++++ .../src/ExceptionExtensions.cs | 27 +++++++++++++++++ .../src/SdkVersionUtils.cs | 30 +++++++++++++++---- 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs index d6c89607b3eb..fcc6b26816a1 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Diagnostics.Tracing; namespace OpenTelemetry.Exporter.AzureMonitor @@ -20,7 +21,19 @@ public void ConfigurationStringParseWarning(string message) } } + [NonEvent] + public void SdkVersionCreateFailed(Exception ex) + { + if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + { + this.WarnSdkVersionCreateException(ex.ToInvariantString()); + } + } + [Event(1, Message = "{0}", Level = EventLevel.Warning)] public void WarnToParseConfigurationString(string message) => this.WriteEvent(1, message); + + [Event(2, Message = "Error creating SdkVersion : '{0}'", Level = EventLevel.Warning)] + public void WarnSdkVersionCreateException(string message) => this.WriteEvent(2, message); } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs new file mode 100644 index 000000000000..3737ba92f13c --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Globalization; +using System.Threading; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal static class ExceptionExtensions + { + internal static string ToInvariantString(this Exception exception) + { + var originalUICulture = Thread.CurrentThread.CurrentUICulture; + + try + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; + return exception.ToString(); + } + finally + { + Thread.CurrentThread.CurrentUICulture = originalUICulture; + } + } + } +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs index d108fa89af29..01b1f3fc8d7f 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs @@ -21,20 +21,40 @@ internal static string SdkVersion { private static string GetSdkVersion() { - Version dotnetSdkVersion = GetVersion(typeof(object)); - Version otelSdkVersion = GetVersion(typeof(OpenTelemetry.Sdk)); - Version extensionVersion = GetVersion(typeof(OpenTelemetry.Exporter.AzureMonitor.AzureMonitorTraceExporter)); + string sdkVer = null; - return string.Format(CultureInfo.InvariantCulture, $"dotnet{dotnetSdkVersion.ToString(2)}:otel{otelSdkVersion.ToString(3)}:ext{extensionVersion.ToString(3)}"); + try + { + Version dotnetSdkVersion = GetVersion(typeof(object)); + Version otelSdkVersion = GetVersion(typeof(OpenTelemetry.Sdk)); + Version extensionVersion = GetVersion(typeof(OpenTelemetry.Exporter.AzureMonitor.AzureMonitorTraceExporter)); + + sdkVer = string.Format(CultureInfo.InvariantCulture, $"dotnet{dotnetSdkVersion.ToString(2)}:otel{otelSdkVersion.ToString(3)}:ext{extensionVersion.ToString(3)}"); + } + catch (Exception ex) + { + AzureMonitorTraceExporterEventSource.Log.SdkVersionCreateFailed(ex); + } + + return sdkVer; } private static Version GetVersion(Type type) { + string versionString = null; + + try + { // TODO: Distinguish preview/stable release and minor versions. e.g: 5.0.0-preview.8.20365.13 - var versionString = type.Assembly.GetCustomAttributes(false) + versionString = type.Assembly.GetCustomAttributes(false) .OfType() .First() .Version; + } + catch (Exception ex) + { + AzureMonitorTraceExporterEventSource.Log.SdkVersionCreateFailed(ex); + } // Return zeros rather then failing if the version string fails to parse return Version.TryParse(versionString, out var version) ? version : new Version(); From d23978c5a0b1358ef4ece42ddb1af1732986453d Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 21 Aug 2020 14:48:15 -0700 Subject: [PATCH 09/17] Fixed indent --- .../src/AzureMonitorTraceExporterEventSource.cs | 4 ++-- .../src/SdkVersionUtils.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs index fcc6b26816a1..dfb0fe0abe4c 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs @@ -15,7 +15,7 @@ internal sealed class AzureMonitorTraceExporterEventSource : EventSource [NonEvent] public void ConfigurationStringParseWarning(string message) { - if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) { this.WarnToParseConfigurationString(message); } @@ -24,7 +24,7 @@ public void ConfigurationStringParseWarning(string message) [NonEvent] public void SdkVersionCreateFailed(Exception ex) { - if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + if (this.IsEnabled(EventLevel.Warning, EventKeywords.All)) { this.WarnSdkVersionCreateException(ex.ToInvariantString()); } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs index 01b1f3fc8d7f..1a85dbc041b3 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SdkVersionUtils.cs @@ -45,11 +45,11 @@ private static Version GetVersion(Type type) try { - // TODO: Distinguish preview/stable release and minor versions. e.g: 5.0.0-preview.8.20365.13 - versionString = type.Assembly.GetCustomAttributes(false) - .OfType() - .First() - .Version; + // TODO: Distinguish preview/stable release and minor versions. e.g: 5.0.0-preview.8.20365.13 + versionString = type.Assembly.GetCustomAttributes(false) + .OfType() + .First() + .Version; } catch (Exception ex) { From a3077acb54dada3ebc6790fe7bdaa68b8de3e3e3 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Mon, 24 Aug 2020 21:11:59 -0700 Subject: [PATCH 10/17] Tags parsing, duration fix for dependency. --- .../src/ActivityExtensions.cs | 63 -------------- .../AzureMonitorTraceExporterEventSource.cs | 1 + .../src/AzureMonitorTransmitter.cs | 85 ++++++++++++++----- .../src/Extensions/ActivityExtensions.cs | 24 ++++++ .../{ => Extensions}/ExceptionExtensions.cs | 2 +- .../src/Extensions/TagsExtension.cs | 81 ++++++++++++++++++ .../RemoteDependencyData.Serialization.cs | 5 +- .../Generated/Models/RemoteDependencyData.cs | 6 +- .../src/PartBType.cs | 17 ++++ .../src/RemoteDependencyData.cs | 19 +++++ .../src/SemanticConventions.cs | 1 + .../InstrumentationWithActivitySource.cs | 1 + 12 files changed, 213 insertions(+), 92 deletions(-) delete mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ActivityExtensions.cs rename sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/{ => Extensions}/ExceptionExtensions.cs (92%) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/TagsExtension.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs deleted file mode 100644 index b19a2c3493de..000000000000 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// 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 - { - ActivityKind.Server => TelemetryType.Request, - ActivityKind.Client => TelemetryType.Dependency, - ActivityKind.Producer => TelemetryType.Dependency, - ActivityKind.Consumer => TelemetryType.Request, - _ => TelemetryType.Dependency - }; - - 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/AzureMonitorTraceExporterEventSource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs index dfb0fe0abe4c..f8521a637f2c 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.Tracing; +using OpenTelemetry.Exporter.AzureMonitor.Extensions; namespace OpenTelemetry.Exporter.AzureMonitor { diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index 78b4a57b733d..68050af4e546 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -6,9 +6,11 @@ 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.Extensions; using OpenTelemetry.Exporter.AzureMonitor.Models; using OpenTelemetry.Trace; @@ -16,6 +18,11 @@ namespace OpenTelemetry.Exporter.AzureMonitor { internal class AzureMonitorTransmitter { + private const string StatusCode_200 = "200"; + private const string StatusCode_0 = "0"; + private const string StatusCode_Ok = "Ok"; + private const string HttpUrlPrefix = "http://"; + private readonly ServiceRestClient serviceRestClient; private readonly AzureMonitorExporterOptions options; @@ -89,13 +96,15 @@ private MonitorBase GenerateTelemetryData(Activity activity) { MonitorBase telemetry = new MonitorBase(); + var tags = activity.Tags.ToAzureMonitorTags(out var activityType); var telemetryType = activity.GetTelemetryType(); telemetry.BaseType = Telemetry_Base_Type_Mapping[telemetryType]; - string url = GetHttpUrl(activity.Tags); 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 ? GetHttpUrl(tags) : GetMessagingUrl(tags); + var statusCode = GetStatus(tags, out bool success) ?? StatusCode_0 ; + var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) { Name = activity.DisplayName, Url = url, @@ -109,20 +118,21 @@ private MonitorBase GenerateTelemetryData(Activity activity) } else if (telemetryType == TelemetryType.Dependency) { - var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration) + var statusCode = GetStatus(tags, out bool success) ?? StatusCode_0; + var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) { Id = activity.Context.SpanId.ToHexString(), - Success = activity.GetStatus().IsOk + Success = success }; // 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 = GetHttpUrl(tags); dependency.Type = "HTTP"; // TODO: Parse for storage / SB. - dependency.ResultCode = activity.GetStatusCode(); + dependency.ResultCode = statusCode; } // TODO: Handle dependency.target. @@ -132,30 +142,61 @@ private MonitorBase GenerateTelemetryData(Activity activity) return telemetry; } - private static string GetHttpUrl(IEnumerable> tags) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetHttpUrl(Dictionary tags) { - var httpTags = tags.Where(item => item.Key.StartsWith("http.", StringComparison.InvariantCulture)) - .ToDictionary(item => item.Key, item => item.Value); + if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url)) + { + Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri); + if (uri.IsAbsoluteUri) + { + return url; + } + } - httpTags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url); - if (url != null) + // TODO: Consider StringBuilder + if (tags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme)) { - return url; + tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); + if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost)) + { + url = httpScheme + httpHost + httpTarget; + } + else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerName, out var netPeerName) + && tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out var netPeerPort)) + { + url = httpScheme + netPeerName + netPeerPort + httpTarget; + } + else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerIp, out var netPeerIP) + && tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out netPeerPort)) + { + url = httpScheme + netPeerIP + netPeerPort + httpTarget; + } } - httpTags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost); - - if (httpHost != null) + if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host)) { - httpTags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme); - httpTags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); - url = httpScheme + httpHost + httpTarget; - return url; + tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); + url = HttpUrlPrefix + host + (httpTarget ?? url); } - // TODO: Follow spec - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client + return url; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetStatus(Dictionary tags, out bool success) + { + tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status); + success = (status == StatusCode_200 || status == StatusCode_Ok) ? true : false; + + return status; + } + + [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/Extensions/ActivityExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ActivityExtensions.cs new file mode 100644 index 000000000000..02be7571ad5b --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ActivityExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace OpenTelemetry.Exporter.AzureMonitor.Extensions +{ + internal static class ActivityExtensions + { + internal static TelemetryType GetTelemetryType(this Activity activity) + { + var kind = activity.Kind switch + { + ActivityKind.Server => TelemetryType.Request, + ActivityKind.Client => TelemetryType.Dependency, + ActivityKind.Producer => TelemetryType.Dependency, + ActivityKind.Consumer => TelemetryType.Request, + _ => TelemetryType.Dependency + }; + + return kind; + } + } +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs similarity index 92% rename from sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs rename to sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs index 3737ba92f13c..7b28e7e15082 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ExceptionExtensions.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.Threading; -namespace OpenTelemetry.Exporter.AzureMonitor +namespace OpenTelemetry.Exporter.AzureMonitor.Extensions { internal static class ExceptionExtensions { diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/TagsExtension.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/TagsExtension.cs new file mode 100644 index 000000000000..d536a5788ac2 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/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.Extensions +{ + 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/Generated/Models/RemoteDependencyData.Serialization.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs index f8d9e068d0ff..5d9e4579a6a1 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text.Json; using Azure.Core; @@ -47,7 +48,7 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) writer.WriteStringValue(Target); } writer.WritePropertyName("duration"); - writer.WriteStringValue(Duration, "P"); + writer.WriteStringValue(Duration); if (Optional.IsDefined(Success)) { writer.WritePropertyName("success"); @@ -170,7 +171,7 @@ internal static RemoteDependencyData DeserializeRemoteDependencyData(JsonElement continue; } } - return new RemoteDependencyData(test.Value, ver, id.Value, name, resultCode.Value, data.Value, type.Value, target.Value, duration, Optional.ToNullable(success), Optional.ToDictionary(properties), Optional.ToDictionary(measurements)); + return new RemoteDependencyData(test.Value, ver, id.Value, name, resultCode.Value, data.Value, type.Value, target.Value, duration.ToString("c", CultureInfo.InvariantCulture), Optional.ToNullable(success), Optional.ToDictionary(properties), Optional.ToDictionary(measurements)); } } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs index 4f717d17b425..d1519997854b 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs @@ -19,7 +19,7 @@ public partial class RemoteDependencyData : MonitorDomain /// Name of the command initiated with this dependency call. Low cardinality value. Examples are stored procedure name and URL path template. /// Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. /// is null. - public RemoteDependencyData(int ver, string name, TimeSpan duration) + public RemoteDependencyData(int ver, string name, string duration) { if (name == null) { @@ -46,7 +46,7 @@ public RemoteDependencyData(int ver, string name, TimeSpan duration) /// Indication of successfull or unsuccessfull call. /// Collection of custom properties. TODO: max key length validate. /// Collection of custom measurements. TODO: max key length validate. - internal RemoteDependencyData(string test, int ver, string id, string name, string resultCode, string data, string type, string target, TimeSpan duration, bool? success, IDictionary properties, IDictionary measurements) : base(test) + internal RemoteDependencyData(string test, int ver, string id, string name, string resultCode, string data, string type, string target, string duration, bool? success, IDictionary properties, IDictionary measurements) : base(test) { Ver = ver; Id = id; @@ -75,8 +75,6 @@ internal RemoteDependencyData(string test, int ver, string id, string name, stri public string Type { get; set; } /// Target site of a dependency call. Examples are server name, host address. public string Target { get; set; } - /// Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. - public TimeSpan Duration { get; set; } /// Indication of successfull or unsuccessfull call. public bool? Success { get; set; } /// Collection of custom properties. TODO: max key length validate. 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/RemoteDependencyData.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs new file mode 100644 index 000000000000..519c509f1970 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using System.Collections.Generic; +using Azure.Core; + +namespace OpenTelemetry.Exporter.AzureMonitor.Models +{ + public partial class RemoteDependencyData + { + /// Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. + public string Duration { get; set; } + } +} 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/tests/OpenTelemetry.Exporter.AzureMonitor.Demo.Tracing/InstrumentationWithActivitySource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.Demo.Tracing/InstrumentationWithActivitySource.cs index 8c3b00c2bc3d..4e181db635a4 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?.AddTag("http.url", context.Request.Url.AbsolutePath); + activity?.AddTag("http.host", context.Request.Url.Host); string requestContent; using (var childSpan = source.StartActivity("ReadStream", ActivityKind.Consumer)) From c2a1208f2c033352652099a9d9790e2fd7229c64 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Wed, 26 Aug 2020 17:48:07 -0700 Subject: [PATCH 11/17] Renamed GetHttpUrl --- .../src/AzureMonitorTransmitter.cs | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index 68050af4e546..f0a7fd981a2f 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -12,16 +12,19 @@ using Azure.Core.Pipeline; using OpenTelemetry.Exporter.AzureMonitor.Extensions; using OpenTelemetry.Exporter.AzureMonitor.Models; -using OpenTelemetry.Trace; namespace OpenTelemetry.Exporter.AzureMonitor { internal class AzureMonitorTransmitter { - private const string StatusCode_200 = "200"; - private const string StatusCode_0 = "0"; - private const string StatusCode_Ok = "Ok"; - private const string HttpUrlPrefix = "http://"; + private const string StatusCode200 = "200"; + private const string StatusCode0 = "0"; + private const string StatusCodeOk = "Ok"; + private const string HttpScheme = "http://"; + private const string SchemePostfix = "://"; + private const char Colon = '/'; + private const string HttpPort80 = "80"; + private const string HttpPort443 = "443"; private readonly ServiceRestClient serviceRestClient; private readonly AzureMonitorExporterOptions options; @@ -94,16 +97,17 @@ private static TelemetryEnvelope GeneratePartAEnvelope(Activity activity) private MonitorBase GenerateTelemetryData(Activity activity) { - MonitorBase telemetry = new MonitorBase(); - - var tags = activity.Tags.ToAzureMonitorTags(out var activityType); var telemetryType = activity.GetTelemetryType(); - telemetry.BaseType = Telemetry_Base_Type_Mapping[telemetryType]; + var tags = activity.Tags.ToAzureMonitorTags(out var activityType); + MonitorBase telemetry = new MonitorBase + { + BaseType = Telemetry_Base_Type_Mapping[telemetryType] + }; if (telemetryType == TelemetryType.Request) { - var url = activity.Kind == ActivityKind.Server ? GetHttpUrl(tags) : GetMessagingUrl(tags); - var statusCode = GetStatus(tags, out bool success) ?? StatusCode_0 ; + var url = activity.Kind == ActivityKind.Server ? GetUrl(tags) : GetMessagingUrl(tags); + var statusCode = GetStatus(tags, out bool success) ?? StatusCode0 ; var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) { Name = activity.DisplayName, @@ -111,14 +115,14 @@ private MonitorBase GenerateTelemetryData(Activity activity) // 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; } else if (telemetryType == TelemetryType.Dependency) { - var statusCode = GetStatus(tags, out bool success) ?? StatusCode_0; + var statusCode = GetStatus(tags, out bool success) ?? StatusCode0; var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) { Id = activity.Context.SpanId.ToHexString(), @@ -130,7 +134,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) if (activityType != PartBType.Http) { - dependency.Data = GetHttpUrl(tags); + dependency.Data = GetUrl(tags); dependency.Type = "HTTP"; // TODO: Parse for storage / SB. dependency.ResultCode = statusCode; } @@ -143,7 +147,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetHttpUrl(Dictionary tags) + private static string GetUrl(Dictionary tags) { if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url)) { @@ -155,30 +159,39 @@ private static string GetHttpUrl(Dictionary tags) } } - // TODO: Consider StringBuilder if (tags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme)) { tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost)) { - url = httpScheme + httpHost + httpTarget; + tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpPort); + if (httpPort != null && httpPort != HttpPort80 && httpPort != HttpPort443) + { + 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)) { - url = httpScheme + netPeerName + netPeerPort + httpTarget; + return $"{httpScheme}{SchemePostfix}{netPeerName}{Colon}{netPeerPort}{httpTarget}"; } else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerIp, out var netPeerIP) && tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out netPeerPort)) { - url = httpScheme + netPeerIP + netPeerPort + httpTarget; + return $"{httpScheme}{SchemePostfix}{netPeerIP}{Colon}{netPeerPort}{httpTarget}"; } } if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host)) { tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); - url = HttpUrlPrefix + host + (httpTarget ?? url); + url = $"{HttpScheme}{host}{Colon}{(httpTarget ?? url)}"; } return url; @@ -188,7 +201,7 @@ private static string GetHttpUrl(Dictionary tags) private static string GetStatus(Dictionary tags, out bool success) { tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status); - success = (status == StatusCode_200 || status == StatusCode_Ok) ? true : false; + success = status == StatusCode200 || status == StatusCodeOk; return status; } From 830efa740d638b55d565bdfa30b9554e87007fcc Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 28 Aug 2020 14:16:07 -0700 Subject: [PATCH 12/17] status change --- .../src/AzureMonitorTransmitter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index f0a7fd981a2f..45144a851428 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -107,7 +107,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) if (telemetryType == TelemetryType.Request) { var url = activity.Kind == ActivityKind.Server ? GetUrl(tags) : GetMessagingUrl(tags); - var statusCode = GetStatus(tags, out bool success) ?? StatusCode0 ; + var statusCode = GetStatus(tags, out bool success) ; var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) { Name = activity.DisplayName, @@ -122,7 +122,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) } else if (telemetryType == TelemetryType.Dependency) { - var statusCode = GetStatus(tags, out bool success) ?? StatusCode0; + var statusCode = GetStatus(tags, out bool success); var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) { Id = activity.Context.SpanId.ToHexString(), @@ -132,7 +132,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) // TODO: Handle activity.TagObjects // ExtractPropertiesFromTags(dependency.Properties, activity.Tags); - if (activityType != PartBType.Http) + if (activityType == PartBType.Http) { dependency.Data = GetUrl(tags); dependency.Type = "HTTP"; // TODO: Parse for storage / SB. @@ -203,7 +203,7 @@ private static string GetStatus(Dictionary tags, out bool succes tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status); success = status == StatusCode200 || status == StatusCodeOk; - return status; + return status ?? StatusCode0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 271cde7d4b078e5ff981efe858ac24f7d43a840f Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Mon, 31 Aug 2020 13:56:38 -0700 Subject: [PATCH 13/17] Modify to xunit --- .../AzureMonitorTraceExporterTests.cs | 83 +++++++++ .../ConnectionStringParserTests.cs | 163 ++++++++++++++++++ .../ConnectionStringParser_BuildUriTests.cs | 78 +++++++++ .../ConnectionString/ConnectionStringTests.cs | 91 ++++++++++ .../NDJsonWriterTests.cs | 6 +- ...lemetry.Exporter.AzureMonitor.Tests.csproj | 5 +- 6 files changed, 420 insertions(+), 6 deletions(-) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParserTests.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParser_BuildUriTests.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringTests.cs 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 new file mode 100644 index 000000000000..50fc3c9a07e7 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/AzureMonitorTraceExporterTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Reflection; + +using Xunit; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + public class AzureMonitorTraceExporterTests + { + [Fact] + public void VerifyConnectionString_CorrectlySetsEndpoint() + { + var testIkey = "test_ikey"; + var testEndpoint = "https://www.bing.com/"; + + var exporter = new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = $"InstrumentationKey={testIkey};IngestionEndpoint={testEndpoint}" }); + + GetInternalFields(exporter, out string ikey, out string endpoint); + Assert.Equal(testIkey, ikey); + Assert.Equal(testEndpoint, endpoint); + } + + [Fact] + public void VerifyConnectionString_CorrectlySetsDefaultEndpoint() + { + var testIkey = "test_ikey"; + + var exporter = new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = $"InstrumentationKey={testIkey};" }); + + GetInternalFields(exporter, out string ikey, out string endpoint); + Assert.Equal(testIkey, ikey); + Assert.Equal(ConnectionString.Constants.DefaultIngestionEndpoint, endpoint); + } + + [Fact] + public void VerifyConnectionString_ThrowsExceptionWhenInvalid() + { + Assert.Throws(() => new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = null })); + } + + [Fact] + public void VerifyConnectionString_ThrowsExceptionWhenMissingInstrumentationKey() + { + var testEndpoint = "https://www.bing.com/"; + + Assert.Throws(() => new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = $"IngestionEndpoint={testEndpoint}" })); + } + + [Fact] + public void ReadUrlFromActivityTags() + { + + } + + private void GetInternalFields(AzureMonitorTraceExporter exporter, out string ikey, out string endpoint) + { + // TODO: NEED A BETTER APPROACH FOR TESTING. WE DECIDED AGAINST MAKING FIELDS "internal". + // instrumentationKey: AzureMonitorTraceExporter.AzureMonitorTransmitter.instrumentationKey + // endpoint: AzureMonitorTraceExporter.AzureMonitorTransmitter.ServiceRestClient.endpoint + + var transmitter = typeof(AzureMonitorTraceExporter) + .GetField("AzureMonitorTransmitter", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(exporter); + + ikey = typeof(AzureMonitorTransmitter) + .GetField("instrumentationKey", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(transmitter) + .ToString(); + + var serviceRestClient = typeof(AzureMonitorTransmitter) + .GetField("serviceRestClient", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(transmitter); + + endpoint = typeof(ServiceRestClient) + .GetField("endpoint", BindingFlags.Instance | BindingFlags.NonPublic) + .GetValue(serviceRestClient) + .ToString(); + } + } +} 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 new file mode 100644 index 000000000000..e24d72417282 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParserTests.cs @@ -0,0 +1,163 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Xunit; + +namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString +{ + public class ConnectionStringParserTests + { + [Fact] + public void TestInstrumentationKey_IsRequired() + { + Assert.Throws(() => RunTest( + connectionString: "EndpointSuffix=ingestion.azuremonitor.com", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestInstrumentationKey_CannotBeEmpty() + { + Assert.Throws(() => RunTest( + connectionString: "InstrumentationKey=;EndpointSuffix=ingestion.azuremonitor.com", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestDefaultEndpoints() + { + RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000", + expectedIngestionEndpoint: Constants.DefaultIngestionEndpoint, + expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); + } + + [Fact] + public void TestEndpointSuffix() + { + RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com", + expectedIngestionEndpoint: "https://dc.ingestion.azuremonitor.com/", + expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); + } + + [Fact] + public void TestEndpointSuffix_WithExplicitOverride() + { + RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;IngestionEndpoint=https://custom.contoso.com:444/", + expectedIngestionEndpoint: "https://custom.contoso.com:444/", + expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); + } + + [Fact] + public void TestEndpointSuffix_WithLocation() + { + RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=westus2", + expectedIngestionEndpoint: "https://westus2.dc.ingestion.azuremonitor.com/", + expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); + } + + [Fact] + public void TestEndpointSuffix_WithLocation_WithExplicitOverride() + { + RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=westus2;IngestionEndpoint=https://custom.contoso.com:444/", + expectedIngestionEndpoint: "https://custom.contoso.com:444/", + expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); + } + + [Fact] + public void TestExpliticOverride_PreservesSchema() + { + RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=http://custom.contoso.com:444/", + expectedIngestionEndpoint: "http://custom.contoso.com:444/", + expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); + } + + [Fact] + public void TestExpliticOverride_InvalidValue() + { + Assert.Throws(() => RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https:////custom.contoso.com", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestExpliticOverride_InvalidValue2() + { + Assert.Throws(() => RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://www.~!@#$%&^*()_{}{}>:L\":\"_+_+_", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestExpliticOverride_InvalidValue3() + { + Assert.Throws(() => RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=~!@#$%&^*()_{}{}>:L\":\"_+_+_", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestExpliticOverride_InvalidLocation() + { + Assert.Throws(() => RunTest( + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=~!@#$%&^*()_{}{}>:L\":\"_+_+_", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestMaliciousConnectionString() + { + Assert.Throws(() => RunTest( + connectionString: new string('*', Constants.ConnectionStringMaxLength + 1), + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestParseConnectionString_Null() + { + Assert.Throws(() => RunTest( + connectionString: null, + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestParseConnectionString_Empty() + { + Assert.Throws(() => RunTest( + connectionString: "", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + [Fact] + public void TestEndpointProvider_NoInstrumentationKey() + { + Assert.Throws(() => RunTest( + connectionString: "key1=value1;key2=value2;key3=value3", + expectedIngestionEndpoint: null, + expectedInstrumentationKey: null)); + } + + private void RunTest(string connectionString, string expectedIngestionEndpoint, string expectedInstrumentationKey) + { + ConnectionStringParser.GetValues(connectionString, out string ikey, out string endpoint); + + 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 new file mode 100644 index 000000000000..cb3532279851 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringParser_BuildUriTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Xunit; + +namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString +{ + /// + /// The method takes user input to construct an endpoint. + /// These tests verify that user input is correctly sanitized and that valid endpoints are constructed. + /// + public class ConnectionStringParser_BuildUriTests + { + [Fact] + public void VerifyCanHandleExtraPeriods() + { + var result = ConnectionStringParser.TryBuildUri( + location: "westus2.", + prefix: "dc", + suffix: ".applicationinsights.azure.com", + uri: out Uri uri); + + Assert.True(result); + Assert.Equal("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); + } + + [Fact] + public void VerifyGoodAddress_WithLocation() + { + var result = ConnectionStringParser.TryBuildUri( + location: "westus2", + prefix: "dc", + suffix: "applicationinsights.azure.com", + uri: out Uri uri); + + Assert.True(result); + Assert.Equal("https://westus2.dc.applicationinsights.azure.com/", uri.AbsoluteUri); + } + + [Fact] + public void VerifyGoodAddress_WithoutLocation() + { + var result = ConnectionStringParser.TryBuildUri( + location: null, + prefix: "dc", + suffix: "applicationinsights.azure.com", + uri: out Uri uri); + + Assert.True(result); + Assert.Equal("https://dc.applicationinsights.azure.com/", uri.AbsoluteUri); + } + + [Fact] + public void VerifyGoodAddress_InvalidCharInLocation() + { + Assert.Throws(() => + ConnectionStringParser.TryBuildUri( + location: "westus2/", + prefix: "dc", + suffix: "applicationinsights.azure.com", + uri: out Uri uri)); + } + + [Fact] + public void VerifyGoodAddress_CanHandleExtraSpaces() + { + var result = ConnectionStringParser.TryBuildUri( + location: " westus2 ", + prefix: "dc", + suffix: " applicationinsights.azure.com ", + uri: out Uri uri); + + 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 new file mode 100644 index 000000000000..e0d6bb28e01b --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/ConnectionString/ConnectionStringTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Xunit; + +namespace OpenTelemetry.Exporter.AzureMonitor.ConnectionString +{ + /// + /// Because we don't own the code for , these tests are to verify expected behavior. + /// + public class ConnectionStringTests + { + [Fact] + public void TestParse() + { + var test = Azure.Core.ConnectionString.Parse("key1=value1;key2=value2;key3=value3"); + + Assert.Equal("value1", test.GetRequired("key1")); + Assert.Equal("value2", test.GetRequired("key2")); + Assert.Equal("value3", test.GetRequired("key3")); + } + + [Fact] + public void TestParse_WithTrailingSemicolon() + { + var test = Azure.Core.ConnectionString.Parse("key1=value1;key2=value2;key3=value3;"); + + Assert.Equal("value1", test.GetRequired("key1")); + Assert.Equal("value2", test.GetRequired("key2")); + Assert.Equal("value3", test.GetRequired("key3")); + } + + [Fact] + public void TestParse_WithExtraSpaces() + { + var test = Azure.Core.ConnectionString.Parse(" key1 = value1 ; key2 = value2 ; key3 =value3 "); + + 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. + /// + [Fact] + public void TestParse_IsCaseInsensitive() + { + var test = Azure.Core.ConnectionString.Parse("UPPERCASE=value1;lowercase=value2;MixedCase=value3"); + + 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")); + } + + [Fact] + public void TestParse_WithNull() + { + Assert.Throws(() => Azure.Core.ConnectionString.Parse(null)); + } + + [Fact] + public void TestParse_WithEmptyString() + { + Assert.Throws(() => Azure.Core.ConnectionString.Parse(string.Empty)); + } + + [Fact] + public void TestParse_WithDuplaceKeys() + { + Assert.Throws(() => Azure.Core.ConnectionString.Parse("key1=value1;key1=value2")); + } + + [Fact] + public void TestParse_WithDuplaceKeysWithSpaces() + { + Assert.Throws(() => Azure.Core.ConnectionString.Parse("key1=value1;key1 =value2")); + } + + [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 602c87f41d0a..cf2c41e1fd4c 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 @@ -5,11 +5,10 @@ - - + + - From 3101044566547bccc1ff7f8cade56c03d08de5e6 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Wed, 2 Sep 2020 17:00:39 -0700 Subject: [PATCH 14/17] Added test for GetUrl --- .../src/AzureMonitorTransmitter.cs | 60 +--- .../src/UrlHelper.cs | 70 +++++ .../InstrumentationWithActivitySource.cs | 4 +- .../AzureMonitorTraceExporterTests.cs | 9 +- .../UrlHelperTests.cs | 292 ++++++++++++++++++ 5 files changed, 369 insertions(+), 66 deletions(-) create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs create mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/tests/OpenTelemetry.Exporter.AzureMonitor.UnitTest/UrlHelperTests.cs diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index 76a964e33287..afab132a472c 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -22,11 +22,6 @@ internal class AzureMonitorTransmitter private const string StatusCode200 = "200"; private const string StatusCode0 = "0"; private const string StatusCodeOk = "Ok"; - private const string HttpScheme = "http://"; - private const string SchemePostfix = "://"; - private const char Colon = '/'; - private const string HttpPort80 = "80"; - private const string HttpPort443 = "443"; private readonly ServiceRestClient serviceRestClient; private readonly AzureMonitorExporterOptions options; @@ -120,7 +115,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) if (telemetryType == TelemetryType.Request) { - var url = activity.Kind == ActivityKind.Server ? GetUrl(tags) : GetMessagingUrl(tags); + var url = activity.Kind == ActivityKind.Server ? UrlHelper.GetUrl(tags) : GetMessagingUrl(tags); var statusCode = GetStatus(tags, out bool success) ; var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) { @@ -148,7 +143,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) if (activityType == PartBType.Http) { - dependency.Data = GetUrl(tags); + dependency.Data = UrlHelper.GetUrl(tags); dependency.Type = "HTTP"; // TODO: Parse for storage / SB. dependency.ResultCode = statusCode; } @@ -160,57 +155,6 @@ private MonitorBase GenerateTelemetryData(Activity activity) return telemetry; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetUrl(Dictionary tags) - { - if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url)) - { - Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri); - - if (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)) - { - tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpPort); - if (httpPort != null && httpPort != HttpPort80 && httpPort != HttpPort443) - { - 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 $"{httpScheme}{SchemePostfix}{netPeerName}{Colon}{netPeerPort}{httpTarget}"; - } - else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerIp, out var netPeerIP) - && tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out netPeerPort)) - { - return $"{httpScheme}{SchemePostfix}{netPeerIP}{Colon}{netPeerPort}{httpTarget}"; - } - } - - if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host)) - { - tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); - url = $"{HttpScheme}{host}{Colon}{(httpTarget ?? url)}"; - } - - return url; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string GetStatus(Dictionary tags, out bool success) { 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..f3cb8a39e7f3 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs @@ -0,0 +1,70 @@ +// 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 = ":"; + private const string HttpPort80 = "80"; + private const string HttpPort443 = "443"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string GetUrl(Dictionary tags) + { + if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url)) + { + Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri); + + if (uri?.IsAbsoluteUri == true) + { + 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 != HttpPort80 && httpPort != HttpPort443) + { + 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 79f5f00ec7eb..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 @@ -63,8 +63,8 @@ public void Start(string url) activity?.SetTag($"http.header.{headerKey}", headerValue); } - activity?.AddTag("http.url", context.Request.Url.AbsolutePath); - activity?.AddTag("http.host", context.Request.Url.Host); + 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 50fc3c9a07e7..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,7 +1,10 @@ // 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 Xunit; @@ -49,12 +52,6 @@ public void VerifyConnectionString_ThrowsExceptionWhenMissingInstrumentationKey( Assert.Throws(() => new AzureMonitorTraceExporter(new AzureMonitorExporterOptions { ConnectionString = $"IngestionEndpoint={testEndpoint}" })); } - [Fact] - public void ReadUrlFromActivityTags() - { - - } - private void GetInternalFields(AzureMonitorTraceExporter exporter, out string ikey, out string endpoint) { // TODO: NEED A BETTER APPROACH FOR TESTING. WE DECIDED AGAINST MAKING FIELDS "internal". 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. + } +} From 21f0910e4e43d447e3cbdf0ccc89b66933995137 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 3 Sep 2020 12:51:13 -0700 Subject: [PATCH 15/17] Incorporating PR comments --- .../AzureMonitorTraceExporterEventSource.cs | 1 - .../src/AzureMonitorTransmitter.cs | 29 +++++++++++-------- .../src/UrlHelper.cs | 8 ++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs index f7243a4506f5..ad9158886810 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTraceExporterEventSource.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.Tracing; -using OpenTelemetry.Exporter.AzureMonitor.Extensions; namespace OpenTelemetry.Exporter.AzureMonitor { diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index afab132a472c..5a87269f8ad8 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -19,10 +19,6 @@ namespace OpenTelemetry.Exporter.AzureMonitor { internal class AzureMonitorTransmitter { - private const string StatusCode200 = "200"; - private const string StatusCode0 = "0"; - private const string StatusCodeOk = "Ok"; - private readonly ServiceRestClient serviceRestClient; private readonly AzureMonitorExporterOptions options; private readonly string instrumentationKey; @@ -116,7 +112,8 @@ private MonitorBase GenerateTelemetryData(Activity activity) if (telemetryType == TelemetryType.Request) { var url = activity.Kind == ActivityKind.Server ? UrlHelper.GetUrl(tags) : GetMessagingUrl(tags); - var statusCode = GetStatus(tags, out bool success) ; + 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, @@ -131,11 +128,9 @@ private MonitorBase GenerateTelemetryData(Activity activity) } else if (telemetryType == TelemetryType.Dependency) { - var statusCode = GetStatus(tags, out bool success); var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) { - Id = activity.Context.SpanId.ToHexString(), - Success = success + Id = activity.Context.SpanId.ToHexString() }; // TODO: Handle activity.TagObjects @@ -145,7 +140,9 @@ private MonitorBase GenerateTelemetryData(Activity activity) { dependency.Data = UrlHelper.GetUrl(tags); dependency.Type = "HTTP"; // TODO: Parse for storage / SB. + var statusCode = GetHttpStatusCode(tags); dependency.ResultCode = statusCode; + dependency.Success = GetSuccessFromHttpStatusCode(statusCode); } // TODO: Handle dependency.target. @@ -156,12 +153,20 @@ private MonitorBase GenerateTelemetryData(Activity activity) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetStatus(Dictionary tags, out bool success) + private static string GetHttpStatusCode(Dictionary tags) { - tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status); - success = status == StatusCode200 || status == StatusCodeOk; + if (tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status)) + { + return status; + } - return status ?? StatusCode0; + return "0"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetSuccessFromHttpStatusCode(string statusCode) + { + return statusCode == "200" || statusCode == "Ok"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs index f3cb8a39e7f3..8c47e3ce9983 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs @@ -11,17 +11,13 @@ internal class UrlHelper { private const string SchemePostfix = "://"; private const string Colon = ":"; - private const string HttpPort80 = "80"; - private const string HttpPort443 = "443"; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static string GetUrl(Dictionary tags) { if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url)) { - Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri); - - if (uri?.IsAbsoluteUri == true) + if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri) { return url; } @@ -33,7 +29,7 @@ internal static string GetUrl(Dictionary tags) if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost) && !string.IsNullOrWhiteSpace(httpHost)) { tags.TryGetValue(SemanticConventions.AttributeHttpHostPort, out var httpPort); - if (httpPort != null && httpPort != HttpPort80 && httpPort != HttpPort443) + if (httpPort != null && httpPort != "80" && httpPort != "443") { url = $"{httpScheme}{SchemePostfix}{httpHost}{Colon}{httpPort}{httpTarget}"; } From 1fe5c3d1999b685962a4e65691bf9dbc694781ab Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 3 Sep 2020 13:58:27 -0700 Subject: [PATCH 16/17] Revert extension/RDD change --- .../{Extensions => }/ActivityExtensions.cs | 2 +- .../src/AzureMonitorTransmitter.cs | 3 +-- .../src/Extensions/ExceptionExtensions.cs | 27 ------------------- .../RemoteDependencyData.Serialization.cs | 5 ++-- .../Generated/Models/RemoteDependencyData.cs | 6 +++-- .../src/RemoteDependencyData.cs | 19 ------------- .../src/{Extensions => }/TagsExtension.cs | 2 +- 7 files changed, 9 insertions(+), 55 deletions(-) rename sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/{Extensions => }/ActivityExtensions.cs (92%) delete mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs delete mode 100644 sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs rename sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/{Extensions => }/TagsExtension.cs (98%) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ActivityExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs similarity index 92% rename from sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ActivityExtensions.cs rename to sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs index 02be7571ad5b..5a2b3324da2c 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ActivityExtensions.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ActivityExtensions.cs @@ -3,7 +3,7 @@ using System.Diagnostics; -namespace OpenTelemetry.Exporter.AzureMonitor.Extensions +namespace OpenTelemetry.Exporter.AzureMonitor { internal static class ActivityExtensions { diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs index 5a87269f8ad8..f44968aa97c5 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorTransmitter.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Azure.Core.Pipeline; -using OpenTelemetry.Exporter.AzureMonitor.Extensions; using OpenTelemetry.Exporter.AzureMonitor.ConnectionString; using OpenTelemetry.Exporter.AzureMonitor.Models; @@ -128,7 +127,7 @@ private MonitorBase GenerateTelemetryData(Activity activity) } else if (telemetryType == TelemetryType.Dependency) { - var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) + var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration) { Id = activity.Context.SpanId.ToHexString() }; diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs deleted file mode 100644 index 7b28e7e15082..000000000000 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/ExceptionExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Globalization; -using System.Threading; - -namespace OpenTelemetry.Exporter.AzureMonitor.Extensions -{ - internal static class ExceptionExtensions - { - internal static string ToInvariantString(this Exception exception) - { - var originalUICulture = Thread.CurrentThread.CurrentUICulture; - - try - { - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; - return exception.ToString(); - } - finally - { - Thread.CurrentThread.CurrentUICulture = originalUICulture; - } - } - } -} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs index 5d9e4579a6a1..f8d9e068d0ff 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.Serialization.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Text.Json; using Azure.Core; @@ -48,7 +47,7 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) writer.WriteStringValue(Target); } writer.WritePropertyName("duration"); - writer.WriteStringValue(Duration); + writer.WriteStringValue(Duration, "P"); if (Optional.IsDefined(Success)) { writer.WritePropertyName("success"); @@ -171,7 +170,7 @@ internal static RemoteDependencyData DeserializeRemoteDependencyData(JsonElement continue; } } - return new RemoteDependencyData(test.Value, ver, id.Value, name, resultCode.Value, data.Value, type.Value, target.Value, duration.ToString("c", CultureInfo.InvariantCulture), Optional.ToNullable(success), Optional.ToDictionary(properties), Optional.ToDictionary(measurements)); + return new RemoteDependencyData(test.Value, ver, id.Value, name, resultCode.Value, data.Value, type.Value, target.Value, duration, Optional.ToNullable(success), Optional.ToDictionary(properties), Optional.ToDictionary(measurements)); } } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs index d1519997854b..4f717d17b425 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Generated/Models/RemoteDependencyData.cs @@ -19,7 +19,7 @@ public partial class RemoteDependencyData : MonitorDomain /// Name of the command initiated with this dependency call. Low cardinality value. Examples are stored procedure name and URL path template. /// Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. /// is null. - public RemoteDependencyData(int ver, string name, string duration) + public RemoteDependencyData(int ver, string name, TimeSpan duration) { if (name == null) { @@ -46,7 +46,7 @@ public RemoteDependencyData(int ver, string name, string duration) /// Indication of successfull or unsuccessfull call. /// Collection of custom properties. TODO: max key length validate. /// Collection of custom measurements. TODO: max key length validate. - internal RemoteDependencyData(string test, int ver, string id, string name, string resultCode, string data, string type, string target, string duration, bool? success, IDictionary properties, IDictionary measurements) : base(test) + internal RemoteDependencyData(string test, int ver, string id, string name, string resultCode, string data, string type, string target, TimeSpan duration, bool? success, IDictionary properties, IDictionary measurements) : base(test) { Ver = ver; Id = id; @@ -75,6 +75,8 @@ internal RemoteDependencyData(string test, int ver, string id, string name, stri public string Type { get; set; } /// Target site of a dependency call. Examples are server name, host address. public string Target { get; set; } + /// Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. + public TimeSpan Duration { get; set; } /// Indication of successfull or unsuccessfull call. public bool? Success { get; set; } /// Collection of custom properties. TODO: max key length validate. diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs deleted file mode 100644 index 519c509f1970..000000000000 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyData.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// - -#nullable disable - -using System; -using System.Collections.Generic; -using Azure.Core; - -namespace OpenTelemetry.Exporter.AzureMonitor.Models -{ - public partial class RemoteDependencyData - { - /// Request duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days. - public string Duration { get; set; } - } -} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/TagsExtension.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs similarity index 98% rename from sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/TagsExtension.cs rename to sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs index d536a5788ac2..b6c84326a9aa 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/Extensions/TagsExtension.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace OpenTelemetry.Exporter.AzureMonitor.Extensions +namespace OpenTelemetry.Exporter.AzureMonitor { internal static class TagsExtension { From 3a33480eebe8139658c4150e8702ca8cb10e47c8 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 3 Sep 2020 14:16:26 -0700 Subject: [PATCH 17/17] Add method summary. --- .../OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs index 8c47e3ce9983..38cb6cf5f25a 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs @@ -12,6 +12,12 @@ 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) {