From f0fe0117e61795203705814f4ab12368da14df11 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 16 Jun 2020 18:05:24 -0700 Subject: [PATCH 1/9] Adding Zipkin activity exporter --- .../ZipkinActivityConversionExtensions.cs | 203 ++++++++++++++++ .../TracerBuilderExtensions.cs | 36 ++- .../ZipkinActivityExporter.cs | 220 ++++++++++++++++++ ...terOptions.cs => ZipkinExporterOptions.cs} | 7 +- .../ZipkinTraceExporter.cs | 5 +- .../ZipkinActivityConversionTest.cs | 90 +++++++ ...pkinActivityExporterRemoteEndpointTests.cs | 75 ++++++ .../ZipkinActivityExporterTests.cs | 213 +++++++++++++++++ .../ZipkinTraceExporterTests.cs | 2 +- 9 files changed, 841 insertions(+), 10 deletions(-) create mode 100644 src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs create mode 100644 src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs rename src/OpenTelemetry.Exporter.Zipkin/{ZipkinTraceExporterOptions.cs => ZipkinExporterOptions.cs} (89%) create mode 100644 test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs create mode 100644 test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs create mode 100644 test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs new file mode 100644 index 00000000000..4103bb97f5e --- /dev/null +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -0,0 +1,203 @@ +// +// 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.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using OpenTelemetry.Internal; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Exporter.Zipkin.Implementation +{ + internal static class ZipkinActivityConversionExtensions + { + private static readonly Dictionary RemoteEndpointServiceNameKeyResolutionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [SpanAttributeConstants.PeerServiceKey] = 0, // RemoteEndpoint.ServiceName primary. + ["net.peer.name"] = 1, // RemoteEndpoint.ServiceName first alternative. + ["peer.hostname"] = 2, // RemoteEndpoint.ServiceName second alternative. + ["peer.address"] = 2, // RemoteEndpoint.ServiceName second alternative. + ["http.host"] = 3, // RemoteEndpoint.ServiceName for Http. + ["db.instance"] = 4, // RemoteEndpoint.ServiceName for Redis. + }; + + private static readonly ConcurrentDictionary LocalEndpointCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary RemoteEndpointCache = new ConcurrentDictionary(); + + private static readonly DictionaryEnumerator.ForEachDelegate ProcessTagsRef = ProcessTags; + private static readonly ListEnumerator>.ForEachDelegate ProcessActivityEventsRef = ProcessActivityEvents; + + internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint defaultLocalEndpoint, bool useShortTraceIds = false) + { + var context = activity.Context; + var startTimestamp = activity.StartTimeUtc.ToEpochMicroseconds(); + + string parentId = null; + if (activity.ParentSpanId != default) + { + parentId = EncodeSpanId(activity.ParentSpanId); + } + + var attributeEnumerationState = new AttributeEnumerationState + { + Tags = PooledList>.Create(), + }; + + DictionaryEnumerator.AllocationFreeForEach(activity.Tags, ref attributeEnumerationState, ProcessTagsRef); + + var localEndpoint = defaultLocalEndpoint; + + var serviceName = attributeEnumerationState.ServiceName; + + // override default service name + if (!string.IsNullOrWhiteSpace(serviceName)) + { + if (!string.IsNullOrWhiteSpace(attributeEnumerationState.ServiceNamespace)) + { + serviceName = attributeEnumerationState.ServiceNamespace + "." + serviceName; + } + + if (!LocalEndpointCache.TryGetValue(serviceName, out localEndpoint)) + { + localEndpoint = defaultLocalEndpoint.Clone(serviceName); + LocalEndpointCache.TryAdd(serviceName, localEndpoint); + } + } + + ZipkinEndpoint remoteEndpoint = null; + if ((activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) && attributeEnumerationState.RemoteEndpointServiceName != null) + { + remoteEndpoint = RemoteEndpointCache.GetOrAdd(attributeEnumerationState.RemoteEndpointServiceName, ZipkinEndpoint.Create); + } + + var annotations = PooledList.Create(); + ListEnumerator>.AllocationFreeForEach(activity.Events, ref annotations, ProcessActivityEventsRef); + + return new ZipkinSpan( + EncodeTraceId(context.TraceId, useShortTraceIds), + parentId, + EncodeSpanId(context.SpanId), + ToActivityKind(activity), + activity.OperationName, + activity.StartTimeUtc.ToEpochMicroseconds(), + duration: (long)activity.Duration.TotalMilliseconds * 1000, // duration in Microseconds + localEndpoint, + remoteEndpoint, + annotations, + attributeEnumerationState.Tags, + null, + null); + } + + internal static string EncodeSpanId(ActivitySpanId spanId) + { + return spanId.ToHexString(); + } + + internal static long ToEpochMicroseconds(this DateTimeOffset timestamp) + { + return timestamp.ToUnixTimeMilliseconds() * 1000; + } + + internal static long ToEpochMicroseconds(this DateTime utcDateTime) + { + const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; + const long UnixEpochTicks = 621355968000000000; // = DateTimeOffset.FromUnixTimeMilliseconds(0).Ticks + const long UnixEpochMicroseconds = UnixEpochTicks / TicksPerMicrosecond; + + // Truncate sub-microsecond precision before offsetting by the Unix Epoch to avoid + // the last digit being off by one for dates that result in negative Unix times + long microseconds = utcDateTime.Ticks / TicksPerMicrosecond; + return microseconds - UnixEpochMicroseconds; + } + + private static string EncodeTraceId(ActivityTraceId traceId, bool useShortTraceIds) + { + var id = traceId.ToHexString(); + + if (id.Length > 16 && useShortTraceIds) + { + id = id.Substring(id.Length - 16, 16); + } + + return id; + } + + private static string ToActivityKind(Activity activity) + { + switch (activity.Kind) + { + case ActivityKind.Server: + return "SERVER"; + case ActivityKind.Producer: + return "PRODUCER"; + case ActivityKind.Consumer: + return "CONSUMER"; + default: + return "CLIENT"; + } + } + + private static bool ProcessActivityEvents(ref PooledList annotations, ActivityEvent @event) + { + PooledList.Add(ref annotations, new ZipkinAnnotation(@event.Timestamp.ToEpochMicroseconds(), @event.Name)); + return true; + } + + private static bool ProcessTags(ref AttributeEnumerationState state, KeyValuePair attribute) + { + string key = attribute.Key; + string strVal = attribute.Value?.ToString(); + + if (strVal != null) + { + if (RemoteEndpointServiceNameKeyResolutionDictionary.TryGetValue(key, out int priority) + && (state.RemoteEndpointServiceName == null || priority < state.RemoteEndpointServiceNamePriority)) + { + state.RemoteEndpointServiceName = strVal; + state.RemoteEndpointServiceNamePriority = priority; + } + else if (key == Resource.ServiceNameKey) + { + state.ServiceName = strVal; + } + else if (key == Resource.ServiceNamespaceKey) + { + state.ServiceNamespace = strVal; + } + } + + PooledList>.Add(ref state.Tags, new KeyValuePair(key, strVal)); + + return true; + } + + private struct AttributeEnumerationState + { + public PooledList> Tags; + + public string RemoteEndpointServiceName; + + public int RemoteEndpointServiceNamePriority; + + public string ServiceName; + + public string ServiceNamespace; + } + } +} diff --git a/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs index 4a32e0bdd9e..8b4c9d187c9 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs @@ -31,7 +31,7 @@ public static class TracerBuilderExtensions /// Trace builder to use. /// Configuration options. /// The instance of to chain the calls. - public static TracerBuilder UseZipkin(this TracerBuilder builder, Action configure) + public static TracerBuilder UseZipkin(this TracerBuilder builder, Action configure) { if (builder == null) { @@ -43,7 +43,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, Action b .SetExporter(new ZipkinTraceExporter(options)) @@ -57,7 +57,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, ActionConfiguration options. /// Span processor configuration. /// The instance of to chain the calls. - public static TracerBuilder UseZipkin(this TracerBuilder builder, Action zipkinConfigure, Action< + public static TracerBuilder UseZipkin(this TracerBuilder builder, Action zipkinConfigure, Action< SpanProcessorPipelineBuilder> processorConfigure) { if (builder == null) @@ -75,7 +75,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, Action { @@ -83,5 +83,33 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, Action + /// Registers a Zipkin exporter that will receive instances. + /// + /// builder to use. + /// Exporter configuration options. + /// The instance of to chain the calls. + public static OpenTelemetryBuilder UseZipkinActivityExporter(this OpenTelemetryBuilder builder, Action configure) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + return builder.SetProcessorPipeline(pipeline => + { + var options = new ZipkinExporterOptions(); + configure(options); + + var activityExporter = new ZipkinActivityExporter(options); + pipeline.SetExporter(activityExporter); + }); + } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs new file mode 100644 index 00000000000..d819120f6b8 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs @@ -0,0 +1,220 @@ +// +// 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.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Sockets; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Exporter.Zipkin.Implementation; +using OpenTelemetry.Trace.Export; + +namespace OpenTelemetry.Exporter.Zipkin +{ + /// + /// Zipkin exporter. + /// + public class ZipkinActivityExporter : ActivityExporter + { + private readonly ZipkinExporterOptions options; + private readonly HttpClient httpClient; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration options. + /// Http client to use to upload telemetry. + public ZipkinActivityExporter(ZipkinExporterOptions options, HttpClient client = null) + { + this.options = options; + this.LocalEndpoint = this.GetLocalZipkinEndpoint(); + this.httpClient = client ?? new HttpClient(); + } + + internal ZipkinEndpoint LocalEndpoint { get; } + + /// + public override async Task ExportAsync(IEnumerable batchActivity, CancellationToken cancellationToken) + { + try + { + await this.SendBatchActivityAsync(batchActivity).ConfigureAwait(false); + return ExportResult.Success; + } + catch (Exception) + { + // TODO distinguish retryable exceptions + return ExportResult.FailedNotRetryable; + } + } + + /// + public override Task ShutdownAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + private Task SendBatchActivityAsync(IEnumerable batchActivity) + { + var requestUri = this.options.Endpoint; + + var request = new HttpRequestMessage(HttpMethod.Post, requestUri) + { + Content = new JsonContent(this, batchActivity), + }; + + // avoid cancelling here: this is no return point: if we reached this point + // and cancellation is requested, it's better if we try to finish sending spans rather than drop it + return this.httpClient.SendAsync(request); + } + + private ZipkinEndpoint GetLocalZipkinEndpoint() + { + var hostName = this.ResolveHostName(); + + string ipv4 = null; + string ipv6 = null; + if (!string.IsNullOrEmpty(hostName)) + { + ipv4 = this.ResolveHostAddress(hostName, AddressFamily.InterNetwork); + ipv6 = this.ResolveHostAddress(hostName, AddressFamily.InterNetworkV6); + } + + return new ZipkinEndpoint( + this.options.ServiceName, + ipv4, + ipv6, + null); + } + + private string ResolveHostAddress(string hostName, AddressFamily family) + { + string result = null; + + try + { + var results = Dns.GetHostAddresses(hostName); + + if (results != null && results.Length > 0) + { + foreach (var addr in results) + { + if (addr.AddressFamily.Equals(family)) + { + var sanitizedAddress = new IPAddress(addr.GetAddressBytes()); // Construct address sans ScopeID + result = sanitizedAddress.ToString(); + + break; + } + } + } + } + catch (Exception) + { + // Ignore + } + + return result; + } + + private string ResolveHostName() + { + string result = null; + + try + { + result = Dns.GetHostName(); + + if (!string.IsNullOrEmpty(result)) + { + var response = Dns.GetHostEntry(result); + + if (response != null) + { + return response.HostName; + } + } + } + catch (Exception) + { + // Ignore + } + + return result; + } + + private class JsonContent : HttpContent + { + private static readonly MediaTypeHeaderValue JsonHeader = new MediaTypeHeaderValue("application/json") + { + CharSet = "utf-8", + }; + + private readonly ZipkinActivityExporter exporter; + private readonly IEnumerable batchActivity; + + private Utf8JsonWriter writer; + + public JsonContent(ZipkinActivityExporter exporter, IEnumerable batchActivity) + { + this.exporter = exporter; + this.batchActivity = batchActivity; + + this.Headers.ContentType = JsonHeader; + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + if (this.writer == null) + { + this.writer = new Utf8JsonWriter(stream); + } + else + { + this.writer.Reset(stream); + } + + this.writer.WriteStartArray(); + + foreach (var activity in this.batchActivity) + { + var zipkinSpan = activity.ToZipkinSpan(this.exporter.LocalEndpoint, this.exporter.options.UseShortTraceIds); + + zipkinSpan.Write(this.writer); + + zipkinSpan.Return(); + } + + this.writer.WriteEndArray(); + + return this.writer.FlushAsync(); + } + + protected override bool TryComputeLength(out long length) + { + // We can't know the length of the content being pushed to the output stream. + length = -1; + return false; + } + } + } +} diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs similarity index 89% rename from src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs rename to src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs index 25962a9830c..3de148d6e6e 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,14 +13,15 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; namespace OpenTelemetry.Exporter.Zipkin { /// - /// Zipkin trace exporter options. + /// Zipkin exporter options. /// - public sealed class ZipkinTraceExporterOptions + public sealed class ZipkinExporterOptions { /// /// Gets or sets Zipkin endpoint address. See https://zipkin.io/zipkin-api/#/default/post_spans. diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs index 6b8386fa2b0..861ad16db81 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.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.IO; @@ -33,7 +34,7 @@ namespace OpenTelemetry.Exporter.Zipkin /// public class ZipkinTraceExporter : SpanExporter { - private readonly ZipkinTraceExporterOptions options; + private readonly ZipkinExporterOptions options; private readonly HttpClient httpClient; /// @@ -41,7 +42,7 @@ public class ZipkinTraceExporter : SpanExporter /// /// Configuration options. /// Http client to use to upload telemetry. - public ZipkinTraceExporter(ZipkinTraceExporterOptions options, HttpClient client = null) + public ZipkinTraceExporter(ZipkinExporterOptions options, HttpClient client = null) { this.options = options; this.LocalEndpoint = this.GetLocalZipkinEndpoint(); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs new file mode 100644 index 00000000000..bf623e33275 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs @@ -0,0 +1,90 @@ +// +// 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.Diagnostics; +using System.Linq; +using OpenTelemetry.Exporter.Zipkin.Implementation; +using Xunit; + +namespace OpenTelemetry.Exporter.Zipkin.Tests.Implementation +{ + public class ZipkinActivityConversionTest + { + private static readonly ZipkinEndpoint DefaultZipkinEndpoint = new ZipkinEndpoint("TestService"); + + [Fact] + public void ZipkinActivityConversion_ToZipkinSpan_AllPropertiesSet() + { + // Arrange + var activity = ZipkinActivityExporterTests.CreateTestActivity(); + + // Act & Assert + var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); + + Assert.Equal("Name", zipkinSpan.Name); + + Assert.Equal(activity.TraceId.ToHexString(), zipkinSpan.TraceId); + Assert.Equal(activity.SpanId.ToHexString(), zipkinSpan.Id); + + Assert.Equal(activity.StartTimeUtc.ToEpochMicroseconds(), zipkinSpan.Timestamp); + Assert.Equal((long)(activity.Duration.TotalMilliseconds * 1000), zipkinSpan.Duration); + + int counter = 0; + var activityTag = activity.Tags.ToArray(); + + foreach (var tags in zipkinSpan.Tags.Value) + { + Assert.Equal(activityTag[counter].Key, tags.Key); + Assert.Equal(activityTag[counter++].Value, tags.Value); + } + + var events = zipkinSpan.Annotations.Value; + + foreach (var annotation in zipkinSpan.Annotations) + { + // Timestamp is same in both events + Assert.Equal(activity.Events.First().Timestamp.ToEpochMicroseconds(), annotation.Timestamp); + } + } + + [Fact] + public void ZipkinActivityConversion_ToZipkinSpan_NoEvents() + { + // Arrange + var activity = ZipkinActivityExporterTests.CreateTestActivity(addEvents: false); + + // Act & Assert + var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); + + Assert.Equal("Name", zipkinSpan.Name); + Assert.Empty(zipkinSpan.Annotations.Value); + Assert.Equal(activity.TraceId.ToHexString(), zipkinSpan.TraceId); + Assert.Equal(activity.SpanId.ToHexString(), zipkinSpan.Id); + + int counter = 0; + var activityTag = activity.Tags.ToArray(); + + foreach (var tags in zipkinSpan.Tags.Value) + { + Assert.Equal(activityTag[counter].Key, tags.Key); + Assert.Equal(activityTag[counter++].Value, tags.Value); + } + + Assert.Equal(activity.StartTimeUtc.ToEpochMicroseconds(), zipkinSpan.Timestamp); + Assert.Equal((long)activity.Duration.TotalMilliseconds * 1000, zipkinSpan.Duration); + } + } +} diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs new file mode 100644 index 00000000000..8d48e18ccf8 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityExporterRemoteEndpointTests.cs @@ -0,0 +1,75 @@ +// +// 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.Collections.Generic; +using OpenTelemetry.Exporter.Zipkin.Implementation; +using Xunit; + +namespace OpenTelemetry.Exporter.Zipkin.Tests.Implementation +{ + public class ZipkinActivityExporterRemoteEndpointTests + { + private static readonly ZipkinEndpoint DefaultZipkinEndpoint = new ZipkinEndpoint("TestService"); + + [Fact] + public void ZipkinSpanConverterTest_GenerateActivity_RemoteEndpointOmittedByDefault() + { + // Arrange + var activity = ZipkinActivityExporterTests.CreateTestActivity(); + + // Act & Assert + var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); + + Assert.Null(zipkinSpan.RemoteEndpoint); + } + + [Fact] + public void ZipkinSpanConverterTest_GenerateActivity_RemoteEndpointResolution() + { + // Arrange + var activity = ZipkinActivityExporterTests.CreateTestActivity( + additionalAttributes: new Dictionary + { + ["net.peer.name"] = "RemoteServiceName", + }); + + // Act & Assert + var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); + + Assert.NotNull(zipkinSpan.RemoteEndpoint); + Assert.Equal("RemoteServiceName", zipkinSpan.RemoteEndpoint.ServiceName); + } + + [Fact] + public void ZipkinSpanConverterTest_GenerateActivity_RemoteEndpointResolutionPriority() + { + // Arrange + var activity = ZipkinActivityExporterTests.CreateTestActivity( + additionalAttributes: new Dictionary + { + ["http.host"] = "DiscardedRemoteServiceName", + ["net.peer.name"] = "RemoteServiceName", + ["peer.hostname"] = "DiscardedRemoteServiceName", + }); + + // Act & Assert + var zipkinSpan = ZipkinActivityConversionExtensions.ToZipkinSpan(activity, DefaultZipkinEndpoint); + + Assert.NotNull(zipkinSpan.RemoteEndpoint); + Assert.Equal("RemoteServiceName", zipkinSpan.RemoteEndpoint.ServiceName); + } + } +} diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs new file mode 100644 index 00000000000..12417880256 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs @@ -0,0 +1,213 @@ +// +// 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.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenTelemetry.Exporter.Zipkin.Implementation; +using OpenTelemetry.Internal.Test; +using OpenTelemetry.Resources; +using Xunit; + +namespace OpenTelemetry.Exporter.Zipkin.Tests +{ + public class ZipkinActivityExporterTests : IDisposable + { + private static readonly ConcurrentDictionary Responses = new ConcurrentDictionary(); + + private readonly IDisposable testServer; + private readonly string testServerHost; + private readonly int testServerPort; + + static ZipkinActivityExporterTests() + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + + var listener = new ActivityListener + { + ShouldListenTo = _ => true, + GetRequestedDataUsingParentId = (ref ActivityCreationOptions options) => ActivityDataRequest.AllData, + GetRequestedDataUsingContext = (ref ActivityCreationOptions options) => ActivityDataRequest.AllData, + }; + + ActivitySource.AddActivityListener(listener); + } + + public ZipkinActivityExporterTests() + { + this.testServer = TestHttpServer.RunServer( + ctx => ProcessServerRequest(ctx), + out this.testServerHost, + out this.testServerPort); + + static void ProcessServerRequest(HttpListenerContext context) + { + context.Response.StatusCode = 200; + + using StreamReader readStream = new StreamReader(context.Request.InputStream); + + string requestContent = readStream.ReadToEnd(); + + Responses.TryAdd( + Guid.Parse(context.Request.QueryString["requestId"]), + requestContent); + + context.Response.OutputStream.Close(); + } + } + + public void Dispose() + { + this.testServer.Dispose(); + } + + [Fact] + public async Task ZipkinActivityExporterIntegrationTest() + { + var batchActivity = new List { CreateTestActivity() }; + + Guid requestId = Guid.NewGuid(); + + ZipkinActivityExporter exporter = new ZipkinActivityExporter( + new ZipkinExporterOptions + { + Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), + }); + + await exporter.ExportAsync(batchActivity, CancellationToken.None).ConfigureAwait(false); + + await exporter.ShutdownAsync(CancellationToken.None).ConfigureAwait(false); + + var activity = batchActivity[0]; + var context = activity.Context; + + var timestamp = activity.StartTimeUtc.ToEpochMicroseconds(); + var eventTimestamp = activity.Events.First().Timestamp.ToEpochMicroseconds(); + + StringBuilder ipInformation = new StringBuilder(); + if (!string.IsNullOrEmpty(exporter.LocalEndpoint.Ipv4)) + { + ipInformation.Append($@",""ipv4"":""{exporter.LocalEndpoint.Ipv4}"""); + } + + if (!string.IsNullOrEmpty(exporter.LocalEndpoint.Ipv6)) + { + ipInformation.Append($@",""ipv6"":""{exporter.LocalEndpoint.Ipv6}"""); + } + + Assert.Equal( + $@"[{{""traceId"":""e8ea7e9ac72de94e91fabc613f9686b2"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True""}}}}]", + Responses[requestId]); + } + + internal static Activity CreateTestActivity( + bool setAttributes = true, + Dictionary additionalAttributes = null, + bool addEvents = true, + bool addLinks = true, + Resource resource = null, + ActivityKind kind = ActivityKind.Client) + { + var startTimestamp = DateTime.UtcNow; + var endTimestamp = startTimestamp.AddSeconds(60); + var eventTimestamp = DateTime.UtcNow; + var traceId = ActivityTraceId.CreateFromString("e8ea7e9ac72de94e91fabc613f9686b2".AsSpan()); + + var parentSpanId = ActivitySpanId.CreateFromBytes(new byte[] { 12, 23, 34, 45, 56, 67, 78, 89 }); + + var attributes = new Dictionary + { + { "stringKey", "value" }, + { "longKey", 1L }, + { "longKey2", 1 }, + { "doubleKey", 1D }, + { "doubleKey2", 1F }, + { "boolKey", true }, + }; + if (additionalAttributes != null) + { + foreach (var attribute in additionalAttributes) + { + attributes.Add(attribute.Key, attribute.Value); + } + } + + var events = new List + { + new ActivityEvent( + "Event1", + eventTimestamp, + new Dictionary + { + { "key", "value" }, + }), + new ActivityEvent( + "Event2", + eventTimestamp, + new Dictionary + { + { "key", "value" }, + }), + }; + + var linkedSpanId = ActivitySpanId.CreateFromString("888915b6286b9c41".AsSpan()); + + var activitySource = new ActivitySource(nameof(CreateTestActivity)); + + var tags = setAttributes ? + attributes.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.ToString())) + : null; + var links = addLinks ? + new[] + { + new ActivityLink(new ActivityContext( + traceId, + linkedSpanId, + ActivityTraceFlags.Recorded)), + } + : null; + + var activity = activitySource.StartActivity( + "Name", + kind, + parentContext: new ActivityContext(traceId, parentSpanId, ActivityTraceFlags.Recorded), + tags, + links, + startTime: startTimestamp); + + if (addEvents) + { + foreach (var evnt in events) + { + activity.AddEvent(evnt); + } + } + + activity.SetEndTime(endTimestamp); + activity.Stop(); + + return activity; + } + } +} diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs index abeea418eba..b57c8673545 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs @@ -73,7 +73,7 @@ public async Task ZipkinExporterIntegrationTest() Guid requestId = Guid.NewGuid(); ZipkinTraceExporter exporter = new ZipkinTraceExporter( - new ZipkinTraceExporterOptions + new ZipkinExporterOptions { Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), }); From 6003a1b8721433944f6c01c140990c381bea2880 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 16 Jun 2020 23:17:01 -0700 Subject: [PATCH 2/9] Added activitysource to tags --- .../ZipkinActivityConversionExtensions.cs | 44 ++++++++++++------- .../ZipkinActivityConversionTest.cs | 16 +++---- .../ZipkinActivityExporterTests.cs | 2 +- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index 4103bb97f5e..1751c92ce50 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -60,6 +60,16 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint d DictionaryEnumerator.AllocationFreeForEach(activity.Tags, ref attributeEnumerationState, ProcessTagsRef); + var activitySource = activity.Source; + if (!string.IsNullOrEmpty(activitySource.Name)) + { + PooledList>.Add(ref attributeEnumerationState.Tags, new KeyValuePair("library.name", activitySource.Name)); + if (!string.IsNullOrEmpty(activitySource.Version)) + { + PooledList>.Add(ref attributeEnumerationState.Tags, new KeyValuePair("library.version", activitySource.Version)); + } + } + var localEndpoint = defaultLocalEndpoint; var serviceName = attributeEnumerationState.ServiceName; @@ -164,25 +174,25 @@ private static bool ProcessTags(ref AttributeEnumerationState state, KeyValuePai string key = attribute.Key; string strVal = attribute.Value?.ToString(); - if (strVal != null) + if (strVal != null + && RemoteEndpointServiceNameKeyResolutionDictionary.TryGetValue(key, out int priority) + && (state.RemoteEndpointServiceName == null || priority < state.RemoteEndpointServiceNamePriority)) { - if (RemoteEndpointServiceNameKeyResolutionDictionary.TryGetValue(key, out int priority) - && (state.RemoteEndpointServiceName == null || priority < state.RemoteEndpointServiceNamePriority)) - { - state.RemoteEndpointServiceName = strVal; - state.RemoteEndpointServiceNamePriority = priority; - } - else if (key == Resource.ServiceNameKey) - { - state.ServiceName = strVal; - } - else if (key == Resource.ServiceNamespaceKey) - { - state.ServiceNamespace = strVal; - } + state.RemoteEndpointServiceName = strVal; + state.RemoteEndpointServiceNamePriority = priority; + } + else if (key == Resource.ServiceNameKey && strVal != null) + { + state.ServiceName = strVal; + } + else if (key == Resource.ServiceNamespaceKey && strVal != null) + { + state.ServiceNamespace = strVal; + } + else + { + PooledList>.Add(ref state.Tags, new KeyValuePair(key, strVal)); } - - PooledList>.Add(ref state.Tags, new KeyValuePair(key, strVal)); return true; } diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs index bf623e33275..4cb3d5d384b 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs @@ -43,12 +43,12 @@ public void ZipkinActivityConversion_ToZipkinSpan_AllPropertiesSet() Assert.Equal((long)(activity.Duration.TotalMilliseconds * 1000), zipkinSpan.Duration); int counter = 0; - var activityTag = activity.Tags.ToArray(); + var tagsArray = zipkinSpan.Tags.Value.ToArray(); - foreach (var tags in zipkinSpan.Tags.Value) + foreach (var tags in activity.Tags) { - Assert.Equal(activityTag[counter].Key, tags.Key); - Assert.Equal(activityTag[counter++].Value, tags.Value); + Assert.Equal(tagsArray[counter].Key, tags.Key); + Assert.Equal(tagsArray[counter++].Value, tags.Value); } var events = zipkinSpan.Annotations.Value; @@ -75,12 +75,12 @@ public void ZipkinActivityConversion_ToZipkinSpan_NoEvents() Assert.Equal(activity.SpanId.ToHexString(), zipkinSpan.Id); int counter = 0; - var activityTag = activity.Tags.ToArray(); + var tagsArray = zipkinSpan.Tags.Value.ToArray(); - foreach (var tags in zipkinSpan.Tags.Value) + foreach (var tags in activity.Tags) { - Assert.Equal(activityTag[counter].Key, tags.Key); - Assert.Equal(activityTag[counter++].Value, tags.Value); + Assert.Equal(tagsArray[counter].Key, tags.Key); + Assert.Equal(tagsArray[counter++].Value, tags.Value); } Assert.Equal(activity.StartTimeUtc.ToEpochMicroseconds(), zipkinSpan.Timestamp); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs index 12417880256..84c50d4c865 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs @@ -117,7 +117,7 @@ public async Task ZipkinActivityExporterIntegrationTest() } Assert.Equal( - $@"[{{""traceId"":""e8ea7e9ac72de94e91fabc613f9686b2"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True""}}}}]", + $@"[{{""traceId"":""e8ea7e9ac72de94e91fabc613f9686b2"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True"",""library.name"":""CreateTestActivity""}}}}]", Responses[requestId]); } From 895492a0646820731da9d3ac889feafc060eda25 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Wed, 17 Jun 2020 12:43:46 -0700 Subject: [PATCH 3/9] Revert ZipkinTraceExporterOptions name change --- .../TracerBuilderExtensions.cs | 12 ++++++------ .../ZipkinActivityExporter.cs | 4 ++-- .../ZipkinTraceExporter.cs | 4 ++-- ...orterOptions.cs => ZipkinTraceExporterOptions.cs} | 4 ++-- .../ZipkinActivityExporterTests.cs | 2 +- .../ZipkinTraceExporterTests.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) rename src/OpenTelemetry.Exporter.Zipkin/{ZipkinExporterOptions.cs => ZipkinTraceExporterOptions.cs} (92%) diff --git a/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs index 8b4c9d187c9..a11a389e6f4 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs @@ -31,7 +31,7 @@ public static class TracerBuilderExtensions /// Trace builder to use. /// Configuration options. /// The instance of to chain the calls. - public static TracerBuilder UseZipkin(this TracerBuilder builder, Action configure) + public static TracerBuilder UseZipkin(this TracerBuilder builder, Action configure) { if (builder == null) { @@ -43,7 +43,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, Action b .SetExporter(new ZipkinTraceExporter(options)) @@ -57,7 +57,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, ActionConfiguration options. /// Span processor configuration. /// The instance of to chain the calls. - public static TracerBuilder UseZipkin(this TracerBuilder builder, Action zipkinConfigure, Action< + public static TracerBuilder UseZipkin(this TracerBuilder builder, Action zipkinConfigure, Action< SpanProcessorPipelineBuilder> processorConfigure) { if (builder == null) @@ -75,7 +75,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, Action { @@ -90,7 +90,7 @@ public static TracerBuilder UseZipkin(this TracerBuilder builder, Action builder to use. /// Exporter configuration options. /// The instance of to chain the calls. - public static OpenTelemetryBuilder UseZipkinActivityExporter(this OpenTelemetryBuilder builder, Action configure) + public static OpenTelemetryBuilder UseZipkinActivityExporter(this OpenTelemetryBuilder builder, Action configure) { if (builder == null) { @@ -104,7 +104,7 @@ public static OpenTelemetryBuilder UseZipkinActivityExporter(this OpenTelemetryB return builder.SetProcessorPipeline(pipeline => { - var options = new ZipkinExporterOptions(); + var options = new ZipkinTraceExporterOptions(); configure(options); var activityExporter = new ZipkinActivityExporter(options); diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs index d819120f6b8..8b0f8435021 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinActivityExporter.cs @@ -35,7 +35,7 @@ namespace OpenTelemetry.Exporter.Zipkin /// public class ZipkinActivityExporter : ActivityExporter { - private readonly ZipkinExporterOptions options; + private readonly ZipkinTraceExporterOptions options; private readonly HttpClient httpClient; /// @@ -43,7 +43,7 @@ public class ZipkinActivityExporter : ActivityExporter /// /// Configuration options. /// Http client to use to upload telemetry. - public ZipkinActivityExporter(ZipkinExporterOptions options, HttpClient client = null) + public ZipkinActivityExporter(ZipkinTraceExporterOptions options, HttpClient client = null) { this.options = options; this.LocalEndpoint = this.GetLocalZipkinEndpoint(); diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs index 861ad16db81..d47b48515ad 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs @@ -34,7 +34,7 @@ namespace OpenTelemetry.Exporter.Zipkin /// public class ZipkinTraceExporter : SpanExporter { - private readonly ZipkinExporterOptions options; + private readonly ZipkinTraceExporterOptions options; private readonly HttpClient httpClient; /// @@ -42,7 +42,7 @@ public class ZipkinTraceExporter : SpanExporter /// /// Configuration options. /// Http client to use to upload telemetry. - public ZipkinTraceExporter(ZipkinExporterOptions options, HttpClient client = null) + public ZipkinTraceExporter(ZipkinTraceExporterOptions options, HttpClient client = null) { this.options = options; this.LocalEndpoint = this.GetLocalZipkinEndpoint(); diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs similarity index 92% rename from src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs rename to src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs index 3de148d6e6e..43314a27b8a 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ namespace OpenTelemetry.Exporter.Zipkin /// /// Zipkin exporter options. /// - public sealed class ZipkinExporterOptions + public sealed class ZipkinTraceExporterOptions { /// /// Gets or sets Zipkin endpoint address. See https://zipkin.io/zipkin-api/#/default/post_spans. diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs index 84c50d4c865..17cfac81760 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs @@ -90,7 +90,7 @@ public async Task ZipkinActivityExporterIntegrationTest() Guid requestId = Guid.NewGuid(); ZipkinActivityExporter exporter = new ZipkinActivityExporter( - new ZipkinExporterOptions + new ZipkinTraceExporterOptions { Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), }); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs index b57c8673545..abeea418eba 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinTraceExporterTests.cs @@ -73,7 +73,7 @@ public async Task ZipkinExporterIntegrationTest() Guid requestId = Guid.NewGuid(); ZipkinTraceExporter exporter = new ZipkinTraceExporter( - new ZipkinExporterOptions + new ZipkinTraceExporterOptions { Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), }); From 475bdea6aceb1a83c8aeeaa92f6130cbb62ebe8e Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Wed, 17 Jun 2020 13:22:20 -0700 Subject: [PATCH 4/9] Refactored ProcessTags --- samples/Exporters/Console/Program.cs | 5 +- samples/Exporters/Console/TestZipkin.cs | 77 +++++++++++++++---- .../ZipkinActivityConversionExtensions.cs | 32 ++++---- .../ZipkinTraceExporter.cs | 1 - .../ZipkinTraceExporterOptions.cs | 3 +- .../ZipkinActivityConversionTest.cs | 2 - 6 files changed, 87 insertions(+), 33 deletions(-) diff --git a/samples/Exporters/Console/Program.cs b/samples/Exporters/Console/Program.cs index 647409e0f72..448412597ed 100644 --- a/samples/Exporters/Console/Program.cs +++ b/samples/Exporters/Console/Program.cs @@ -40,7 +40,7 @@ public static void Main(string[] args) Parser.Default.ParseArguments(args) .MapResult( (JaegerOptions options) => TestJaeger.Run(options.Host, options.Port, options.UseActivitySource), - (ZipkinOptions options) => TestZipkin.Run(options.Uri), + (ZipkinOptions options) => TestZipkin.Run(options.Uri, true), (PrometheusOptions options) => TestPrometheus.RunAsync(options.Port, options.PushIntervalInSecs, options.DurationInMins), (HttpClientOptions options) => TestHttpClient.Run(), (RedisOptions options) => TestRedis.Run(options.Uri), @@ -74,6 +74,9 @@ internal class ZipkinOptions { [Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)] public string Uri { get; set; } + + [Option('a', "activity", HelpText = "Set it to true to export ActivitySource data", Default = false)] + public bool UseActivitySource { get; set; } } [Verb("prometheus", HelpText = "Specify the options required to test Prometheus")] diff --git a/samples/Exporters/Console/TestZipkin.cs b/samples/Exporters/Console/TestZipkin.cs index 188e8328cfc..f80995d18c1 100644 --- a/samples/Exporters/Console/TestZipkin.cs +++ b/samples/Exporters/Console/TestZipkin.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using OpenTelemetry.Trace; using OpenTelemetry.Trace.Configuration; @@ -24,25 +25,73 @@ namespace Samples { internal class TestZipkin { - internal static object Run(string zipkinUri) + internal static object Run(string zipkinUri, bool useActivitySource) { - // Configure exporter to export traces to Zipkin - using (var tracerFactory = TracerFactory.Create(builder => builder - .UseZipkin(o => - { - o.ServiceName = "test-zipkin"; - o.Endpoint = new Uri(zipkinUri); - }))) + if (useActivitySource) { - var tracer = tracerFactory.GetTracer("zipkin-test"); + // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" + // and use the Jaeger exporter. + using var openTelemetry = OpenTelemetrySdk.EnableOpenTelemetry( + builder => builder + // .AddActivitySource("MyCompany.MyProduct.MyWebServer") + .AddActivitySource("Samples.SampleServer") + .AddActivitySource("Samples.SampleClient") + .UseZipkinActivityExporter(o => + { + o.ServiceName = "test-zipkin"; + o.Endpoint = new Uri(zipkinUri); + })); + + // The above lines are required only in Applications + // which decide to use OT. - // Create a scoped span. It will end automatically when using statement ends - using (tracer.WithSpan(tracer.StartSpan("Main"))) + using (var sample = new InstrumentationWithActivitySource()) { - Console.WriteLine("About to do a busy work"); - for (var i = 0; i < 10; i++) + sample.Start(); + + Console.WriteLine("Sample is running on the background, press ENTER to stop"); + Console.ReadLine(); + } + + /* + var source = new ActivitySource("MyCompany.MyProduct.MyWebServer"); + using (var parent = source.StartActivity("HttpIn", ActivityKind.Server)) + { + parent?.AddTag("http.method", "GET"); + parent?.AddTag("http.url", "https://localhost/wiki/Rabbit"); + using (var child = source.StartActivity("HttpOut", ActivityKind.Client)) + { + child?.AddTag("http.method", "GET"); + child?.AddTag("http.url", "https://www.wikipedia.org/wiki/Rabbit"); + // TODO: change status code to int after .NET added support for int + child?.AddTag("http.status_code", "200"); + } + + // TODO: change status code to int after .NET added support for int + parent?.AddTag("http.status_code", "200"); + parent?.AddTag("http.host", "localhost"); + }*/ + } + else + { + // Configure exporter to export traces to Zipkin + using (var tracerFactory = TracerFactory.Create(builder => builder + .UseZipkin(o => + { + o.ServiceName = "test-zipkin"; + o.Endpoint = new Uri(zipkinUri); + }))) + { + var tracer = tracerFactory.GetTracer("zipkin-test"); + + // Create a scoped span. It will end automatically when using statement ends + using (tracer.WithSpan(tracer.StartSpan("Main"))) { - DoWork(i, tracer); + Console.WriteLine("About to do a busy work"); + for (var i = 0; i < 10; i++) + { + DoWork(i, tracer); + } } } } diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index 1751c92ce50..5fb80c492af 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -174,20 +174,26 @@ private static bool ProcessTags(ref AttributeEnumerationState state, KeyValuePai string key = attribute.Key; string strVal = attribute.Value?.ToString(); - if (strVal != null - && RemoteEndpointServiceNameKeyResolutionDictionary.TryGetValue(key, out int priority) - && (state.RemoteEndpointServiceName == null || priority < state.RemoteEndpointServiceNamePriority)) + if (strVal != null) { - state.RemoteEndpointServiceName = strVal; - state.RemoteEndpointServiceNamePriority = priority; - } - else if (key == Resource.ServiceNameKey && strVal != null) - { - state.ServiceName = strVal; - } - else if (key == Resource.ServiceNamespaceKey && strVal != null) - { - state.ServiceNamespace = strVal; + if (RemoteEndpointServiceNameKeyResolutionDictionary.TryGetValue(key, out int priority) + && (state.RemoteEndpointServiceName == null || priority < state.RemoteEndpointServiceNamePriority)) + { + state.RemoteEndpointServiceName = strVal; + state.RemoteEndpointServiceNamePriority = priority; + } + else if (key == Resource.ServiceNameKey) + { + state.ServiceName = strVal; + } + else if (key == Resource.ServiceNamespaceKey) + { + state.ServiceNamespace = strVal; + } + else + { + PooledList>.Add(ref state.Tags, new KeyValuePair(key, strVal)); + } } else { diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs index d47b48515ad..6b8386fa2b0 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporter.cs @@ -13,7 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. // - using System; using System.Collections.Generic; using System.IO; diff --git a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs index 43314a27b8a..25962a9830c 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/ZipkinTraceExporterOptions.cs @@ -13,13 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // - using System; namespace OpenTelemetry.Exporter.Zipkin { /// - /// Zipkin exporter options. + /// Zipkin trace exporter options. /// public sealed class ZipkinTraceExporterOptions { diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs index 4cb3d5d384b..f085f846a0d 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs @@ -51,8 +51,6 @@ public void ZipkinActivityConversion_ToZipkinSpan_AllPropertiesSet() Assert.Equal(tagsArray[counter++].Value, tags.Value); } - var events = zipkinSpan.Annotations.Value; - foreach (var annotation in zipkinSpan.Annotations) { // Timestamp is same in both events From 07cdafa008b75284bb4822527d5760887817d8f3 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Wed, 17 Jun 2020 13:28:51 -0700 Subject: [PATCH 5/9] Removing sample files --- samples/Exporters/Console/Program.cs | 5 +- samples/Exporters/Console/TestZipkin.cs | 77 +++++-------------------- 2 files changed, 15 insertions(+), 67 deletions(-) diff --git a/samples/Exporters/Console/Program.cs b/samples/Exporters/Console/Program.cs index 448412597ed..647409e0f72 100644 --- a/samples/Exporters/Console/Program.cs +++ b/samples/Exporters/Console/Program.cs @@ -40,7 +40,7 @@ public static void Main(string[] args) Parser.Default.ParseArguments(args) .MapResult( (JaegerOptions options) => TestJaeger.Run(options.Host, options.Port, options.UseActivitySource), - (ZipkinOptions options) => TestZipkin.Run(options.Uri, true), + (ZipkinOptions options) => TestZipkin.Run(options.Uri), (PrometheusOptions options) => TestPrometheus.RunAsync(options.Port, options.PushIntervalInSecs, options.DurationInMins), (HttpClientOptions options) => TestHttpClient.Run(), (RedisOptions options) => TestRedis.Run(options.Uri), @@ -74,9 +74,6 @@ internal class ZipkinOptions { [Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)] public string Uri { get; set; } - - [Option('a', "activity", HelpText = "Set it to true to export ActivitySource data", Default = false)] - public bool UseActivitySource { get; set; } } [Verb("prometheus", HelpText = "Specify the options required to test Prometheus")] diff --git a/samples/Exporters/Console/TestZipkin.cs b/samples/Exporters/Console/TestZipkin.cs index f80995d18c1..188e8328cfc 100644 --- a/samples/Exporters/Console/TestZipkin.cs +++ b/samples/Exporters/Console/TestZipkin.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using OpenTelemetry.Trace; using OpenTelemetry.Trace.Configuration; @@ -25,73 +24,25 @@ namespace Samples { internal class TestZipkin { - internal static object Run(string zipkinUri, bool useActivitySource) + internal static object Run(string zipkinUri) { - if (useActivitySource) - { - // Enable OpenTelemetry for the sources "Samples.SampleServer" and "Samples.SampleClient" - // and use the Jaeger exporter. - using var openTelemetry = OpenTelemetrySdk.EnableOpenTelemetry( - builder => builder - // .AddActivitySource("MyCompany.MyProduct.MyWebServer") - .AddActivitySource("Samples.SampleServer") - .AddActivitySource("Samples.SampleClient") - .UseZipkinActivityExporter(o => - { - o.ServiceName = "test-zipkin"; - o.Endpoint = new Uri(zipkinUri); - })); - - // The above lines are required only in Applications - // which decide to use OT. - - using (var sample = new InstrumentationWithActivitySource()) - { - sample.Start(); - - Console.WriteLine("Sample is running on the background, press ENTER to stop"); - Console.ReadLine(); - } - - /* - var source = new ActivitySource("MyCompany.MyProduct.MyWebServer"); - using (var parent = source.StartActivity("HttpIn", ActivityKind.Server)) + // Configure exporter to export traces to Zipkin + using (var tracerFactory = TracerFactory.Create(builder => builder + .UseZipkin(o => { - parent?.AddTag("http.method", "GET"); - parent?.AddTag("http.url", "https://localhost/wiki/Rabbit"); - using (var child = source.StartActivity("HttpOut", ActivityKind.Client)) - { - child?.AddTag("http.method", "GET"); - child?.AddTag("http.url", "https://www.wikipedia.org/wiki/Rabbit"); - // TODO: change status code to int after .NET added support for int - child?.AddTag("http.status_code", "200"); - } - - // TODO: change status code to int after .NET added support for int - parent?.AddTag("http.status_code", "200"); - parent?.AddTag("http.host", "localhost"); - }*/ - } - else + o.ServiceName = "test-zipkin"; + o.Endpoint = new Uri(zipkinUri); + }))) { - // Configure exporter to export traces to Zipkin - using (var tracerFactory = TracerFactory.Create(builder => builder - .UseZipkin(o => - { - o.ServiceName = "test-zipkin"; - o.Endpoint = new Uri(zipkinUri); - }))) - { - var tracer = tracerFactory.GetTracer("zipkin-test"); + var tracer = tracerFactory.GetTracer("zipkin-test"); - // Create a scoped span. It will end automatically when using statement ends - using (tracer.WithSpan(tracer.StartSpan("Main"))) + // Create a scoped span. It will end automatically when using statement ends + using (tracer.WithSpan(tracer.StartSpan("Main"))) + { + Console.WriteLine("About to do a busy work"); + for (var i = 0; i < 10; i++) { - Console.WriteLine("About to do a busy work"); - for (var i = 0; i < 10; i++) - { - DoWork(i, tracer); - } + DoWork(i, tracer); } } } From 2afb1e043ccd1ebdee03b4cd48c36df21964459e Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Wed, 17 Jun 2020 17:59:32 -0700 Subject: [PATCH 6/9] Modified UseZipkinActivityExporter to use AddProcessorPipeline --- src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs index a11a389e6f4..3551b9f2540 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/TracerBuilderExtensions.cs @@ -102,7 +102,7 @@ public static OpenTelemetryBuilder UseZipkinActivityExporter(this OpenTelemetryB throw new ArgumentNullException(nameof(configure)); } - return builder.SetProcessorPipeline(pipeline => + return builder.AddProcessorPipeline(pipeline => { var options = new ZipkinTraceExporterOptions(); configure(options); From 37e6084e43976b51f9c27b1b7033c6e298bbca0e Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Thu, 18 Jun 2020 14:14:42 -0700 Subject: [PATCH 7/9] Refactored based on Reiley's comments --- .../ZipkinActivityConversionExtensions.cs | 28 +++++++++++++------ .../Implementation/ZipkinSpan.cs | 5 +++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index 5fb80c492af..f44e0aa9138 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -26,6 +26,8 @@ namespace OpenTelemetry.Exporter.Zipkin.Implementation { internal static class ZipkinActivityConversionExtensions { + private const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; + private static readonly Dictionary RemoteEndpointServiceNameKeyResolutionDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) { [SpanAttributeConstants.PeerServiceKey] = 0, // RemoteEndpoint.ServiceName primary. @@ -36,6 +38,8 @@ internal static class ZipkinActivityConversionExtensions ["db.instance"] = 4, // RemoteEndpoint.ServiceName for Redis. }; + private static readonly string InvalidSpanId = default(ActivitySpanId).ToHexString(); + private static readonly ConcurrentDictionary LocalEndpointCache = new ConcurrentDictionary(); private static readonly ConcurrentDictionary RemoteEndpointCache = new ConcurrentDictionary(); @@ -47,10 +51,10 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint d var context = activity.Context; var startTimestamp = activity.StartTimeUtc.ToEpochMicroseconds(); - string parentId = null; - if (activity.ParentSpanId != default) + string parentId = EncodeSpanId(activity.ParentSpanId); + if (string.Equals(parentId, InvalidSpanId, StringComparison.Ordinal)) { - parentId = EncodeSpanId(activity.ParentSpanId); + parentId = null; } var attributeEnumerationState = new AttributeEnumerationState @@ -105,7 +109,7 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint d ToActivityKind(activity), activity.OperationName, activity.StartTimeUtc.ToEpochMicroseconds(), - duration: (long)activity.Duration.TotalMilliseconds * 1000, // duration in Microseconds + duration: (long)activity.Duration.ToEpochMicroseconds(), localEndpoint, remoteEndpoint, annotations, @@ -119,15 +123,19 @@ internal static string EncodeSpanId(ActivitySpanId spanId) return spanId.ToHexString(); } - internal static long ToEpochMicroseconds(this DateTimeOffset timestamp) + internal static long ToEpochMicroseconds(this DateTimeOffset dateTimeOffset) + { + return dateTimeOffset.Ticks / TicksPerMicrosecond; + } + + internal static long ToEpochMicroseconds(this TimeSpan timeSpan) { - return timestamp.ToUnixTimeMilliseconds() * 1000; + return timeSpan.Ticks / TicksPerMicrosecond; } internal static long ToEpochMicroseconds(this DateTime utcDateTime) { - const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; - const long UnixEpochTicks = 621355968000000000; // = DateTimeOffset.FromUnixTimeMilliseconds(0).Ticks + const long UnixEpochTicks = 621355968000000000L; // = DateTimeOffset.FromUnixTimeMilliseconds(0).Ticks const long UnixEpochMicroseconds = UnixEpochTicks / TicksPerMicrosecond; // Truncate sub-microsecond precision before offsetting by the Unix Epoch to avoid @@ -158,9 +166,11 @@ private static string ToActivityKind(Activity activity) return "PRODUCER"; case ActivityKind.Consumer: return "CONSUMER"; - default: + case ActivityKind.Client: return "CLIENT"; } + + return null; } private static bool ProcessActivityEvents(ref PooledList annotations, ActivityEvent @event) diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs index 7a8bc1222e2..f00153c490c 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinSpan.cs @@ -112,7 +112,10 @@ public void Write(Utf8JsonWriter writer) writer.WriteString("id", this.Id); - writer.WriteString("kind", this.Kind); + if (this.Kind != null) + { + writer.WriteString("kind", this.Kind); + } if (this.Timestamp.HasValue) { From 2bfb0f53c85c19aa63ce0065716b2205958e8749 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 19 Jun 2020 15:46:32 -0700 Subject: [PATCH 8/9] Added UseShortTraceIds to test --- .../Implementation/ZipkinActivityConversionExtensions.cs | 2 +- .../Implementation/ZipkinActivityConversionTest.cs | 5 +++-- .../ZipkinActivityExporterTests.cs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs index f44e0aa9138..806a26bcfc0 100644 --- a/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs +++ b/src/OpenTelemetry.Exporter.Zipkin/Implementation/ZipkinActivityConversionExtensions.cs @@ -182,7 +182,7 @@ private static bool ProcessActivityEvents(ref PooledList annot private static bool ProcessTags(ref AttributeEnumerationState state, KeyValuePair attribute) { string key = attribute.Key; - string strVal = attribute.Value?.ToString(); + string strVal = attribute.Value; if (strVal != null) { diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs index f085f846a0d..27a6d086b47 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/Implementation/ZipkinActivityConversionTest.cs @@ -23,6 +23,7 @@ namespace OpenTelemetry.Exporter.Zipkin.Tests.Implementation { public class ZipkinActivityConversionTest { + private const string ZipkinSpanName = "Name"; private static readonly ZipkinEndpoint DefaultZipkinEndpoint = new ZipkinEndpoint("TestService"); [Fact] @@ -34,7 +35,7 @@ public void ZipkinActivityConversion_ToZipkinSpan_AllPropertiesSet() // Act & Assert var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); - Assert.Equal("Name", zipkinSpan.Name); + Assert.Equal(ZipkinSpanName, zipkinSpan.Name); Assert.Equal(activity.TraceId.ToHexString(), zipkinSpan.TraceId); Assert.Equal(activity.SpanId.ToHexString(), zipkinSpan.Id); @@ -67,7 +68,7 @@ public void ZipkinActivityConversion_ToZipkinSpan_NoEvents() // Act & Assert var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); - Assert.Equal("Name", zipkinSpan.Name); + Assert.Equal(ZipkinSpanName, zipkinSpan.Name); Assert.Empty(zipkinSpan.Annotations.Value); Assert.Equal(activity.TraceId.ToHexString(), zipkinSpan.TraceId); Assert.Equal(activity.SpanId.ToHexString(), zipkinSpan.Id); diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs index 17cfac81760..3cdc9a3f614 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs @@ -93,6 +93,7 @@ public async Task ZipkinActivityExporterIntegrationTest() new ZipkinTraceExporterOptions { Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), + UseShortTraceIds = true, }); await exporter.ExportAsync(batchActivity, CancellationToken.None).ConfigureAwait(false); @@ -117,7 +118,7 @@ public async Task ZipkinActivityExporterIntegrationTest() } Assert.Equal( - $@"[{{""traceId"":""e8ea7e9ac72de94e91fabc613f9686b2"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True"",""library.name"":""CreateTestActivity""}}}}]", + $@"[{{""traceId"":""91fabc613f9686b2"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True"",""library.name"":""CreateTestActivity""}}}}]", Responses[requestId]); } From 2b5144361ab3746c1cccbd8988d3a4d13555e98f Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Fri, 19 Jun 2020 16:14:36 -0700 Subject: [PATCH 9/9] Added useShortTraceIds to test --- .../ZipkinActivityExporterTests.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs index 3cdc9a3f614..462bf328a6c 100644 --- a/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Zipkin.Tests/ZipkinActivityExporterTests.cs @@ -33,6 +33,7 @@ namespace OpenTelemetry.Exporter.Zipkin.Tests { public class ZipkinActivityExporterTests : IDisposable { + private const string TraceId = "e8ea7e9ac72de94e91fabc613f9686b2"; private static readonly ConcurrentDictionary Responses = new ConcurrentDictionary(); private readonly IDisposable testServer; @@ -82,8 +83,10 @@ public void Dispose() this.testServer.Dispose(); } - [Fact] - public async Task ZipkinActivityExporterIntegrationTest() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ZipkinActivityExporterIntegrationTest(bool useShortTraceIds) { var batchActivity = new List { CreateTestActivity() }; @@ -93,7 +96,7 @@ public async Task ZipkinActivityExporterIntegrationTest() new ZipkinTraceExporterOptions { Endpoint = new Uri($"http://{this.testServerHost}:{this.testServerPort}/api/v2/spans?requestId={requestId}"), - UseShortTraceIds = true, + UseShortTraceIds = useShortTraceIds, }); await exporter.ExportAsync(batchActivity, CancellationToken.None).ConfigureAwait(false); @@ -117,8 +120,10 @@ public async Task ZipkinActivityExporterIntegrationTest() ipInformation.Append($@",""ipv6"":""{exporter.LocalEndpoint.Ipv6}"""); } + var traceId = useShortTraceIds ? TraceId.Substring(TraceId.Length - 16, 16) : TraceId; + Assert.Equal( - $@"[{{""traceId"":""91fabc613f9686b2"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True"",""library.name"":""CreateTestActivity""}}}}]", + $@"[{{""traceId"":""{traceId}"",""name"":""Name"",""parentId"":""{ZipkinConversionExtensions.EncodeSpanId(activity.ParentSpanId)}"",""id"":""{ZipkinActivityConversionExtensions.EncodeSpanId(context.SpanId)}"",""kind"":""CLIENT"",""timestamp"":{timestamp},""duration"":60000000,""localEndpoint"":{{""serviceName"":""Open Telemetry Exporter""{ipInformation}}},""annotations"":[{{""timestamp"":{eventTimestamp},""value"":""Event1""}},{{""timestamp"":{eventTimestamp},""value"":""Event2""}}],""tags"":{{""stringKey"":""value"",""longKey"":""1"",""longKey2"":""1"",""doubleKey"":""1"",""doubleKey2"":""1"",""boolKey"":""True"",""library.name"":""CreateTestActivity""}}}}]", Responses[requestId]); }