diff --git a/src/OpenTelemetry.Api/AssemblyInfo.cs b/src/OpenTelemetry.Api/AssemblyInfo.cs index 8f684849616..b6a7065c756 100644 --- a/src/OpenTelemetry.Api/AssemblyInfo.cs +++ b/src/OpenTelemetry.Api/AssemblyInfo.cs @@ -16,6 +16,7 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("OpenTelemetry" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("OpenTelemetry.Shims.OpenTracing.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs index 20b94945d7d..ab2215faf69 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceStateUtilsNew.cs @@ -82,7 +82,7 @@ internal static bool AppendTraceState(string traceStateString, List MaxKeyValuePairsCount) { - LogTooManyItemsInTracestate(); + OpenTelemetryApiEventSource.Log.TooManyItemsInTracestate(); isValid = false; break; } @@ -106,11 +106,7 @@ internal static bool AppendTraceState(string traceStateString, List> trace var pairsCount = traceState.Count(); if (pairsCount > MaxKeyValuePairsCount) { - LogTooManyItemsInTracestate(); + OpenTelemetryApiEventSource.Log.TooManyItemsInTracestate(); } var sb = new StringBuilder(); @@ -169,14 +165,14 @@ private static bool TryParseKeyValue(ReadOnlySpan pair, out ReadOnlySpan value) return true; } - - private static void LogTooManyItemsInTracestate() - { -#if API - OpenTelemetryApiEventSource.Log.TooManyItemsInTracestate(); -#else - OpenTelemetrySdkEventSource.Log.TooManyItemsInTracestate(); -#endif - } - - private static void LogKeyIsInvalid(ReadOnlySpan key) - { -#if API - OpenTelemetryApiEventSource.Log.TracestateKeyIsInvalid(key); -#else - OpenTelemetrySdkEventSource.Log.TracestateKeyIsInvalid(key); -#endif - } - - private static void LogValueIsInvalid(ReadOnlySpan value) - { -#if API - OpenTelemetryApiEventSource.Log.TracestateValueIsInvalid(value); -#else - OpenTelemetrySdkEventSource.Log.TracestateValueIsInvalid(value); -#endif - } } } diff --git a/src/OpenTelemetry/Internal/EnumerationHelper.cs b/src/OpenTelemetry.Api/Internal/EnumerationHelper.cs similarity index 100% rename from src/OpenTelemetry/Internal/EnumerationHelper.cs rename to src/OpenTelemetry.Api/Internal/EnumerationHelper.cs diff --git a/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj b/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj index 96d1e58e4fd..dfb0a8b517e 100644 --- a/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj +++ b/src/OpenTelemetry.Api/OpenTelemetry.Api.csproj @@ -3,13 +3,8 @@ net452;netstandard2.0 OpenTelemetry .NET API OpenTelemetry - $(DefineConstants);API - - - - diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs index 9fef47c22ba..020c9b1a1be 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; using System.Collections.Generic; using System.Diagnostics; @@ -42,15 +43,6 @@ internal static class JaegerActivityExtensions private const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; private const long UnixEpochMicroseconds = UnixEpochTicks / TicksPerMicrosecond; // 62,135,596,800,000,000 - private static readonly Dictionary PeerServiceKeyResolutionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - [SemanticConventions.AttributePeerService] = 0, // priority 0 (highest). - ["peer.hostname"] = 1, - ["peer.address"] = 1, - [SemanticConventions.AttributeHttpHost] = 2, // peer.service for Http. - [SemanticConventions.AttributeDbInstance] = 2, // peer.service for Redis. - }; - public static JaegerSpan ToJaegerSpan(this Activity activity) { var jaegerTags = new TagEnumerationState @@ -63,29 +55,9 @@ public static JaegerSpan ToJaegerSpan(this Activity activity) string peerServiceName = null; if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { - // If priority = 0 that means peer.service may have already been included in tags - var addPeerServiceTag = jaegerTags.PeerServicePriority > 0; - - var hostNameOrIpAddress = jaegerTags.HostName ?? jaegerTags.IpAddress; - - // peer.service has not already been included, but net.peer.name/ip and optionally net.peer.port are present - if ((jaegerTags.PeerService == null || addPeerServiceTag) - && hostNameOrIpAddress != null) - { - peerServiceName = jaegerTags.Port == default - ? hostNameOrIpAddress - : $"{hostNameOrIpAddress}:{jaegerTags.Port}"; - - // Add the peer.service tag - addPeerServiceTag = true; - } - - if (peerServiceName == null && jaegerTags.PeerService != null) - { - peerServiceName = jaegerTags.PeerService; - } + PeerServiceResolver.Resolve(ref jaegerTags, out peerServiceName, out bool addAsTag); - if (peerServiceName != null && addPeerServiceTag) + if (peerServiceName != null && addAsTag) { PooledList.Add(ref jaegerTags.Tags, new JaegerTag(SemanticConventions.AttributePeerService, JaegerTagType.STRING, vStr: peerServiceName)); } @@ -274,50 +246,34 @@ private static void ProcessJaegerTagArray(ref PooledList tags, KeyVal } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ProcessJaegerTag(ref TagEnumerationState state, string key, JaegerTag jaegerTag) { if (jaegerTag.VStr != null) { - if (PeerServiceKeyResolutionDictionary.TryGetValue(key, out int priority) - && (state.PeerService == null || priority < state.PeerServicePriority)) - { - state.PeerService = jaegerTag.VStr; - state.PeerServicePriority = priority; - } - else if (key == SemanticConventions.AttributeNetPeerName) - { - state.HostName = jaegerTag.VStr; - } - else if (key == SemanticConventions.AttributeNetPeerIp) - { - state.IpAddress = jaegerTag.VStr; - } - else if (key == SemanticConventions.AttributeNetPeerPort && long.TryParse(jaegerTag.VStr, out var port)) - { - state.Port = port; - } + PeerServiceResolver.InspectTag(ref state, key, jaegerTag.VStr); } - else if (jaegerTag.VLong.HasValue && key == SemanticConventions.AttributeNetPeerPort) + else if (jaegerTag.VLong.HasValue) { - state.Port = jaegerTag.VLong.Value; + PeerServiceResolver.InspectTag(ref state, key, jaegerTag.VLong.Value); } PooledList.Add(ref state.Tags, jaegerTag); } - private struct TagEnumerationState : IActivityEnumerator> + private struct TagEnumerationState : IActivityEnumerator>, PeerServiceResolver.IPeerServiceState { public PooledList Tags; - public string PeerService; + public string PeerService { get; set; } - public int PeerServicePriority; + public int? PeerServicePriority { get; set; } - public string HostName; + public string HostName { get; set; } - public string IpAddress; + public string IpAddress { get; set; } - public long Port; + public long Port { get; set; } public bool ForEach(KeyValuePair activityTag) { diff --git a/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj b/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj index bfecb8dc65f..6f28004efb5 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj +++ b/src/OpenTelemetry.Exporter.Jaeger/OpenTelemetry.Exporter.Jaeger.csproj @@ -11,9 +11,10 @@ - - - + + + + diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index fbbe32eef80..435c0233ba3 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +* `peer.service` tag is now added to outgoing spans (went not already specified) + following the [Zipkin remote endpoint + rules](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk_exporters/zipkin.md#remote-endpoint) + ([#1392](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1392)) + ## 0.7.0-beta.1 Released 2020-Oct-16 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index 67a9b7d1883..1eb8082d35a 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -181,6 +181,23 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity) TagEnumerationState otlpTags = default; activity.EnumerateTags(ref otlpTags); + + if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) + { + PeerServiceResolver.Resolve(ref otlpTags, out string peerServiceName, out bool addAsTag); + + if (peerServiceName != null && addAsTag) + { + PooledList.Add( + ref otlpTags.Tags, + new OtlpCommon.KeyValue + { + Key = SemanticConventions.AttributePeerService, + Value = new OtlpCommon.AnyValue { StringValue = peerServiceName }, + }); + } + } + if (otlpTags.Created) { otlpSpan.Attributes.AddRange(otlpTags.Tags); @@ -435,7 +452,7 @@ private static OtlpCommon.KeyValue CreateOtlpKeyValue(string key, OtlpCommon.Any return new OtlpCommon.KeyValue { Key = key, Value = value }; } - private struct TagEnumerationState : IActivityEnumerator> + private struct TagEnumerationState : IActivityEnumerator>, PeerServiceResolver.IPeerServiceState { public bool Created; @@ -445,6 +462,16 @@ private struct TagEnumerationState : IActivityEnumerator activityTag) { if (activityTag.Value == null) @@ -452,7 +479,9 @@ public bool ForEach(KeyValuePair activityTag) return true; } - switch (activityTag.Key) + var key = activityTag.Key; + + switch (key) { case SpanAttributeConstants.StatusCodeKey: this.StatusCode = activityTag.Value as int?; @@ -471,50 +500,52 @@ public bool ForEach(KeyValuePair activityTag) switch (activityTag.Value) { case string s: - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { StringValue = s })); + PeerServiceResolver.InspectTag(ref this, key, s); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { StringValue = s })); break; case bool b: - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { BoolValue = b })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { BoolValue = b })); break; case int i: - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { IntValue = i })); + PeerServiceResolver.InspectTag(ref this, key, i); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { IntValue = i })); break; case long l: - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { IntValue = l })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { IntValue = l })); break; case double d: - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { DoubleValue = d })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { DoubleValue = d })); break; case int[] intArray: foreach (var item in intArray) { - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { IntValue = item })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { IntValue = item })); } break; case double[] doubleArray: foreach (var item in doubleArray) { - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { DoubleValue = item })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { DoubleValue = item })); } break; case bool[] boolArray: foreach (var item in boolArray) { - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { BoolValue = item })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { BoolValue = item })); } break; case string[] stringArray: foreach (var item in stringArray) { - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { StringValue = item })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { StringValue = item })); } break; default: - PooledList.Add(ref this.Tags, CreateOtlpKeyValue(activityTag.Key, new OtlpCommon.AnyValue { StringValue = activityTag.Value.ToString() })); + PooledList.Add(ref this.Tags, CreateOtlpKeyValue(key, new OtlpCommon.AnyValue { StringValue = activityTag.Value.ToString() })); break; } diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj index 12fa6959c27..28625b22e86 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj @@ -11,8 +11,10 @@ - - + + + + diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index 4318b1d053c..6a8d3ce8cda 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -29,15 +29,6 @@ internal static class ZipkinActivityConversionExtensions private const long UnixEpochTicks = 621355968000000000L; // = DateTimeOffset.FromUnixTimeMilliseconds(0).Ticks private const long UnixEpochMicroseconds = UnixEpochTicks / TicksPerMicrosecond; - private static readonly Dictionary RemoteEndpointServiceNameKeyResolutionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - [SemanticConventions.AttributePeerService] = 0, // priority 0 (highest). - ["peer.hostname"] = 1, - ["peer.address"] = 1, - [SemanticConventions.AttributeHttpHost] = 2, // RemoteEndpoint.ServiceName for Http. - [SemanticConventions.AttributeDbInstance] = 2, // RemoteEndpoint.ServiceName for Redis. - }; - private static readonly string InvalidSpanId = default(ActivitySpanId).ToHexString(); #if !NET452 @@ -76,29 +67,19 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint l ZipkinEndpoint remoteEndpoint = null; if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { - var hostNameOrIpAddress = tagState.HostName ?? tagState.IpAddress; + PeerServiceResolver.Resolve(ref tagState, out string peerServiceName, out bool addAsTag); - if ((tagState.RemoteEndpointServiceName == null || tagState.RemoteEndpointServiceNamePriority > 0) - && hostNameOrIpAddress != null) + if (peerServiceName != null) { #if !NET452 - remoteEndpoint = RemoteEndpointCache.GetOrAdd((hostNameOrIpAddress, tagState.Port), ZipkinEndpoint.Create); + remoteEndpoint = RemoteEndpointCache.GetOrAdd((peerServiceName, default), ZipkinEndpoint.Create); #else - var remoteEndpointStr = tagState.Port != default - ? $"{hostNameOrIpAddress}:{tagState.Port}" - : hostNameOrIpAddress; - - remoteEndpoint = RemoteEndpointCache.GetOrAdd(remoteEndpointStr, ZipkinEndpoint.Create); -#endif - } - - if (remoteEndpoint == null && tagState.RemoteEndpointServiceName != null) - { -#if !NET452 - remoteEndpoint = RemoteEndpointCache.GetOrAdd((tagState.RemoteEndpointServiceName, default), ZipkinEndpoint.Create); -#else - remoteEndpoint = RemoteEndpointCache.GetOrAdd(tagState.RemoteEndpointServiceName, ZipkinEndpoint.Create); + remoteEndpoint = RemoteEndpointCache.GetOrAdd(peerServiceName, ZipkinEndpoint.Create); #endif + if (addAsTag) + { + PooledList>.Add(ref tagState.Tags, new KeyValuePair(SemanticConventions.AttributePeerService, peerServiceName)); + } } } @@ -171,19 +152,19 @@ private static string ToActivityKind(Activity activity) }; } - internal struct TagEnumerationState : IActivityEnumerator> + internal struct TagEnumerationState : IActivityEnumerator>, PeerServiceResolver.IPeerServiceState { public PooledList> Tags; - public string RemoteEndpointServiceName; + public string PeerService { get; set; } - public int RemoteEndpointServiceNamePriority; + public int? PeerServicePriority { get; set; } - public string HostName; + public string HostName { get; set; } - public string IpAddress; + public string IpAddress { get; set; } - public int Port; + public long Port { get; set; } public bool ForEach(KeyValuePair activityTag) { @@ -192,40 +173,19 @@ public bool ForEach(KeyValuePair activityTag) return true; } + string key = activityTag.Key; + if (activityTag.Value is string strVal) { - string key = activityTag.Key; - if (RemoteEndpointServiceNameKeyResolutionDictionary.TryGetValue(key, out int priority) - && (this.RemoteEndpointServiceName == null || priority < this.RemoteEndpointServiceNamePriority)) - { - this.RemoteEndpointServiceName = strVal; - this.RemoteEndpointServiceNamePriority = priority; - } - else if (key == SemanticConventions.AttributeNetPeerName) - { - this.HostName = strVal; - } - else if (key == SemanticConventions.AttributeNetPeerIp) - { - this.IpAddress = strVal; - } - else if (key == SemanticConventions.AttributeNetPeerPort && int.TryParse(strVal, out var port)) - { - this.Port = port; - } - - PooledList>.Add(ref this.Tags, new KeyValuePair(key, strVal)); + PeerServiceResolver.InspectTag(ref this, key, strVal); } - else + else if (activityTag.Value is int intVal && activityTag.Key == SemanticConventions.AttributeNetPeerPort) { - if (activityTag.Value is int intVal && activityTag.Key == SemanticConventions.AttributeNetPeerPort) - { - this.Port = intVal; - } - - PooledList>.Add(ref this.Tags, activityTag); + PeerServiceResolver.InspectTag(ref this, key, intVal); } + PooledList>.Add(ref this.Tags, activityTag); + return true; } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj index b3c7a8f4501..3e24ebc67ac 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj +++ b/src/OpenTelemetry.Exporter.Zipkin/OpenTelemetry.Exporter.Zipkin.csproj @@ -6,9 +6,10 @@ - - - + + + + diff --git a/src/OpenTelemetry/AssemblyInfo.cs b/src/OpenTelemetry/AssemblyInfo.cs index 7e4ed4d2284..6260094761b 100644 --- a/src/OpenTelemetry/AssemblyInfo.cs +++ b/src/OpenTelemetry/AssemblyInfo.cs @@ -17,20 +17,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("OpenTelemetry.Tests" + AssemblyInfo.PublicKey)] -[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)] [assembly: InternalsVisibleTo("Benchmarks" + AssemblyInfo.PublicKey)] - -#if SIGNED -internal static class AssemblyInfo -{ - public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898"; - public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7"; -} -#else -internal static class AssemblyInfo -{ - public const string PublicKey = ""; - public const string MoqPublicKey = ""; -} -#endif diff --git a/src/OpenTelemetry/Exporter/PeerServiceResolver.cs b/src/OpenTelemetry/Exporter/PeerServiceResolver.cs new file mode 100644 index 00000000000..64c9faed302 --- /dev/null +++ b/src/OpenTelemetry/Exporter/PeerServiceResolver.cs @@ -0,0 +1,109 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Exporter +{ + internal static class PeerServiceResolver + { + private static readonly Dictionary PeerServiceKeyResolutionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [SemanticConventions.AttributePeerService] = 0, // priority 0 (highest). + ["peer.hostname"] = 1, + ["peer.address"] = 1, + [SemanticConventions.AttributeHttpHost] = 2, // peer.service for Http. + [SemanticConventions.AttributeDbInstance] = 2, // peer.service for Redis. + }; + + public interface IPeerServiceState + { + string PeerService { get; set; } + + int? PeerServicePriority { get; set; } + + string HostName { get; set; } + + string IpAddress { get; set; } + + long Port { get; set; } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InspectTag(ref T state, string key, string value) + where T : struct, IPeerServiceState + { + if (PeerServiceKeyResolutionDictionary.TryGetValue(key, out int priority) + && (state.PeerService == null || priority < state.PeerServicePriority)) + { + state.PeerService = value; + state.PeerServicePriority = priority; + } + else if (key == SemanticConventions.AttributeNetPeerName) + { + state.HostName = value; + } + else if (key == SemanticConventions.AttributeNetPeerIp) + { + state.IpAddress = value; + } + else if (key == SemanticConventions.AttributeNetPeerPort && long.TryParse(value, out var port)) + { + state.Port = port; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InspectTag(ref T state, string key, long value) + where T : struct, IPeerServiceState + { + if (key == SemanticConventions.AttributeNetPeerPort) + { + state.Port = value; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Resolve(ref T state, out string peerServiceName, out bool addAsTag) + where T : struct, IPeerServiceState + { + peerServiceName = state.PeerService; + + // If priority = 0 that means peer.service was included in tags + addAsTag = state.PeerServicePriority != 0; + + if (addAsTag) + { + var hostNameOrIpAddress = state.HostName ?? state.IpAddress; + + // peer.service has not already been included, but net.peer.name/ip and optionally net.peer.port are present + if (hostNameOrIpAddress != null) + { + peerServiceName = state.Port == default + ? hostNameOrIpAddress + : $"{hostNameOrIpAddress}:{state.Port}"; + } + else if (state.PeerService != null) + { + peerServiceName = state.PeerService; + } + } + } + } +} diff --git a/src/OpenTelemetry/OpenTelemetry.csproj b/src/OpenTelemetry/OpenTelemetry.csproj index e48ec91d132..36ceb4b2b5a 100644 --- a/src/OpenTelemetry/OpenTelemetry.csproj +++ b/src/OpenTelemetry/OpenTelemetry.csproj @@ -8,10 +8,6 @@ $(NoWarn),1591 - - - - diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj index aaf9e3bd357..6ad67abed21 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -18,9 +18,14 @@ - - - + + + + + + + + diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs index cff9bf8dbde..719eebd2815 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterTest.cs @@ -77,7 +77,7 @@ public void ToOtlpResourceSpansTest() .SetResource(resource) .Build(); - var activities = new CircularBuffer(512); + var processor = new BatchExportProcessor(new TestExporter(RunTest)); const int numOfSpans = 10; bool isEven; for (var i = 0; i < numOfSpans; i++) @@ -88,44 +88,49 @@ public void ToOtlpResourceSpansTest() var activityTags = isEven ? evenTags : oddTags; using Activity activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags); - activities.Add(activity); + processor.OnEnd(activity); } - var request = new OtlpCollector.ExportTraceServiceRequest(); + processor.Shutdown(); - request.AddBatch(new Batch(activities, numOfSpans)); + void RunTest(Batch batch) + { + var request = new OtlpCollector.ExportTraceServiceRequest(); - Assert.Single(request.ResourceSpans); - var oltpResource = request.ResourceSpans.First().Resource; - Assert.Equal(resource.Attributes.First().Key, oltpResource.Attributes.First().Key); - Assert.Equal(resource.Attributes.First().Value, oltpResource.Attributes.First().Value.StringValue); + request.AddBatch(batch); - foreach (var instrumentationLibrarySpans in request.ResourceSpans.First().InstrumentationLibrarySpans) - { - Assert.Equal(numOfSpans / 2, instrumentationLibrarySpans.Spans.Count); - Assert.NotNull(instrumentationLibrarySpans.InstrumentationLibrary); + Assert.Single(request.ResourceSpans); + var oltpResource = request.ResourceSpans.First().Resource; + Assert.Equal(resource.Attributes.First().Key, oltpResource.Attributes.First().Key); + Assert.Equal(resource.Attributes.First().Value, oltpResource.Attributes.First().Value.StringValue); - var expectedSpanNames = new List(); - var start = instrumentationLibrarySpans.InstrumentationLibrary.Name == "even" ? 0 : 1; - for (var i = start; i < numOfSpans; i += 2) + foreach (var instrumentationLibrarySpans in request.ResourceSpans.First().InstrumentationLibrarySpans) { - expectedSpanNames.Add($"span-{i}"); - } + Assert.Equal(numOfSpans / 2, instrumentationLibrarySpans.Spans.Count); + Assert.NotNull(instrumentationLibrarySpans.InstrumentationLibrary); - var otlpSpans = instrumentationLibrarySpans.Spans; - Assert.Equal(expectedSpanNames.Count, otlpSpans.Count); + var expectedSpanNames = new List(); + var start = instrumentationLibrarySpans.InstrumentationLibrary.Name == "even" ? 0 : 1; + for (var i = start; i < numOfSpans; i += 2) + { + expectedSpanNames.Add($"span-{i}"); + } - var kv0 = new OtlpCommon.KeyValue { Key = "k0", Value = new OtlpCommon.AnyValue { StringValue = "v0" } }; - var kv1 = new OtlpCommon.KeyValue { Key = "k1", Value = new OtlpCommon.AnyValue { StringValue = "v1" } }; + var otlpSpans = instrumentationLibrarySpans.Spans; + Assert.Equal(expectedSpanNames.Count, otlpSpans.Count); - var expectedTag = instrumentationLibrarySpans.InstrumentationLibrary.Name == "even" - ? kv0 - : kv1; + var kv0 = new OtlpCommon.KeyValue { Key = "k0", Value = new OtlpCommon.AnyValue { StringValue = "v0" } }; + var kv1 = new OtlpCommon.KeyValue { Key = "k1", Value = new OtlpCommon.AnyValue { StringValue = "v1" } }; - foreach (var otlpSpan in otlpSpans) - { - Assert.Contains(otlpSpan.Name, expectedSpanNames); - Assert.Contains(expectedTag, otlpSpan.Attributes); + var expectedTag = instrumentationLibrarySpans.InstrumentationLibrary.Name == "even" + ? kv0 + : kv1; + + foreach (var otlpSpan in otlpSpans) + { + Assert.Contains(otlpSpan.Name, expectedSpanNames); + Assert.Contains(expectedTag, otlpSpan.Attributes); + } } } } @@ -133,7 +138,7 @@ public void ToOtlpResourceSpansTest() [Fact] public void ToOtlpSpanTest() { - var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); + using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); using var rootActivity = activitySource.StartActivity("root", ActivityKind.Producer); @@ -237,6 +242,25 @@ public void ToOtlpSpanTest() } } + [Fact] + public void ToOtlpSpanPeerServiceTest() + { + using var activitySource = new ActivitySource(nameof(this.ToOtlpSpanTest)); + + using var rootActivity = activitySource.StartActivity("root", ActivityKind.Client); + + rootActivity.SetTag(SemanticConventions.AttributeHttpHost, "opentelemetry.io"); + + var otlpSpan = rootActivity.ToOtlpSpan(); + + Assert.NotNull(otlpSpan); + + var peerService = otlpSpan.Attributes.FirstOrDefault(kvp => kvp.Key == SemanticConventions.AttributePeerService); + + Assert.NotNull(peerService); + Assert.Equal("opentelemetry.io", peerService.Value.StringValue); + } + [Fact] public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor() { @@ -264,7 +288,7 @@ public void UseOpenTelemetryProtocolActivityExporterWithCustomActivityProcessor( .AddOtlpExporter() .Build(); - var source = new ActivitySource(ActivitySourceName); + using var source = new ActivitySource(ActivitySourceName); var activity = source.StartActivity("Test Otlp Activity"); activity?.Stop(); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs index 178a55d3ad0..4921e8e651e 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinExporterTests.cs @@ -186,7 +186,7 @@ public void IntegrationTest(bool useShortTraceIds, bool useTestResource) var traceId = useShortTraceIds ? TraceId.Substring(TraceId.Length - 16, 16) : TraceId; Assert.Equal( - $@"[{{""traceId"":""{traceId}"",""name"":""Name"",""parentId"":""{ZipkinActivityConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""{serviceName}""{ipInformation}}},""remoteEndpoint"":{{""serviceName"":""http://localhost:44312/""}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{{resoureTags}""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""longArrayKey"":""1,2"",""boolKey"":""True"",""http.host"":""http://localhost:44312/"",""library.name"":""CreateTestActivity""}}}}]", + $@"[{{""traceId"":""{traceId}"",""name"":""Name"",""parentId"":""{ZipkinActivityConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""{serviceName}""{ipInformation}}},""remoteEndpoint"":{{""serviceName"":""http://localhost:44312/""}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{{resoureTags}""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""longArrayKey"":""1,2"",""boolKey"":""True"",""http.host"":""http://localhost:44312/"",""library.name"":""CreateTestActivity"",""peer.service"":""http://localhost:44312/""}}}}]", Responses[requestId]); } diff --git a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj index 9ac7a8357c6..040b41854a4 100644 --- a/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj +++ b/test/OpenTelemetry.Tests/OpenTelemetry.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/OpenTelemetry.Tests/Shared/TestExporter.cs b/test/OpenTelemetry.Tests/Shared/TestExporter.cs new file mode 100644 index 00000000000..a4f4d942729 --- /dev/null +++ b/test/OpenTelemetry.Tests/Shared/TestExporter.cs @@ -0,0 +1,38 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace OpenTelemetry.Tests +{ + internal class TestExporter : BaseExporter + where T : class + { + private readonly Action> processBatchAction; + + public TestExporter(Action> processBatchAction) + { + this.processBatchAction = processBatchAction ?? throw new ArgumentNullException(nameof(processBatchAction)); + } + + public override ExportResult Export(in Batch batch) + { + this.processBatchAction(batch); + + return ExportResult.Success; + } + } +}