// // 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 OpenTelemetry.Exporter.Zipkin.Tests; using OpenTelemetry.Internal; using OpenTelemetry.Trace; using Xunit; namespace OpenTelemetry.Exporter.Zipkin.Implementation.Tests { public class ZipkinActivityConversionTest { private const string ZipkinSpanName = "Name"; private static readonly ZipkinEndpoint DefaultZipkinEndpoint = new("TestService"); [Fact] public void ToZipkinSpan_AllPropertiesSet() { // Arrange using var activity = ZipkinExporterTests.CreateTestActivity(); // Act & Assert var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); Assert.Equal(ZipkinSpanName, 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 tagsArray = zipkinSpan.Tags.ToArray(); foreach (var tags in activity.TagObjects) { Assert.Equal(tagsArray[counter].Key, tags.Key); Assert.Equal(tagsArray[counter++].Value, tags.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 ToZipkinSpan_NoEvents() { // Arrange using var activity = ZipkinExporterTests.CreateTestActivity(addEvents: false); // Act & Assert var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); Assert.Equal(ZipkinSpanName, zipkinSpan.Name); Assert.Empty(zipkinSpan.Annotations); Assert.Equal(activity.TraceId.ToHexString(), zipkinSpan.TraceId); Assert.Equal(activity.SpanId.ToHexString(), zipkinSpan.Id); int counter = 0; var tagsArray = zipkinSpan.Tags.ToArray(); foreach (var tags in activity.TagObjects) { Assert.Equal(tagsArray[counter].Key, tags.Key); Assert.Equal(tagsArray[counter++].Value, tags.Value); } Assert.Equal(activity.StartTimeUtc.ToEpochMicroseconds(), zipkinSpan.Timestamp); Assert.Equal((long)activity.Duration.TotalMilliseconds * 1000, zipkinSpan.Duration); } [Theory] [InlineData(StatusCode.Unset, "unset")] [InlineData(StatusCode.Ok, "Ok")] [InlineData(StatusCode.Error, "ERROR")] [InlineData(StatusCode.Unset, "iNvAlId")] public void ToZipkinSpan_Status_ErrorFlagTest(StatusCode expectedStatusCode, string statusCodeTagValue) { // Arrange using var activity = ZipkinExporterTests.CreateTestActivity(); activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue); // Act var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); // Assert Assert.Equal(expectedStatusCode, activity.GetStatus().StatusCode); if (expectedStatusCode == StatusCode.Unset) { Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == SpanAttributeConstants.StatusCodeKey); } else { Assert.Equal( StatusHelper.GetTagValueForStatusCode(expectedStatusCode), zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); } if (expectedStatusCode == StatusCode.Error) { Assert.Contains(zipkinSpan.Tags, t => t.Key == "error" && (string)t.Value == string.Empty); } else { Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "error"); } } [Theory] [InlineData(ActivityStatusCode.Unset)] [InlineData(ActivityStatusCode.Ok)] [InlineData(ActivityStatusCode.Error)] public void ToZipkinSpan_Activity_Status_And_StatusDescription_is_Set(ActivityStatusCode expectedStatusCode) { // Arrange. const string description = "Description when ActivityStatusCode is Error."; using var activity = ZipkinExporterTests.CreateTestActivity(); activity.SetStatus(expectedStatusCode, description); // Act. var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); // Assert. if (expectedStatusCode == ActivityStatusCode.Unset) { Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == SpanAttributeConstants.StatusCodeKey); } else if (expectedStatusCode == ActivityStatusCode.Ok) { Assert.Equal("OK", zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); } // expectedStatusCode is Error else { Assert.Equal("ERROR", zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); } if (expectedStatusCode == ActivityStatusCode.Error) { Assert.Contains( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && (string)t.Value == description); } else { Assert.DoesNotContain( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName); } } [Fact] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsOk() { // Arrange. using var activity = ZipkinExporterTests.CreateTestActivity(); activity.SetStatus(ActivityStatusCode.Ok); activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); // Enrich activity with additional tags. activity.SetTag("myCustomTag", "myCustomTagValue"); // Act. var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); // Assert. Assert.Equal("OK", zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); Assert.Contains(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string)t.Value == "OK"); Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == "otel.status_code" && (string)t.Value == "ERROR"); // Ensure additional Activity tags were being converted. Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue"); Assert.DoesNotContain(zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName); } [Fact] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError() { // Arrange. using var activity = ZipkinExporterTests.CreateTestActivity(); const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; const string TagDescriptionOnError = "Description when TagStatusCode is Error."; activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError); activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError); // Enrich activity with additional tags. activity.SetTag("myCustomTag", "myCustomTagValue"); // Act. var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); // Assert. Assert.Equal("ERROR", zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); // ActivityStatusDescription takes higher precedence. Assert.Contains( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && (string)t.Value == StatusDescriptionOnError); Assert.DoesNotContain( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && (string)t.Value == TagDescriptionOnError); // Ensure additional Activity tags were being converted. Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue"); } [Fact] public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError_SettingTagFirst() { // Arrange. using var activity = ZipkinExporterTests.CreateTestActivity(); const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; const string TagDescriptionOnError = "Description when TagStatusCode is Error."; activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError); activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError); // Enrich activity with additional tags. activity.SetTag("myCustomTag", "myCustomTagValue"); // Act. var zipkinSpan = activity.ToZipkinSpan(DefaultZipkinEndpoint); // Assert. Assert.Equal("ERROR", zipkinSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value); // ActivityStatusDescription takes higher precedence. Assert.Contains( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && (string)t.Value == StatusDescriptionOnError); Assert.DoesNotContain( zipkinSpan.Tags, t => t.Key == ZipkinActivityConversionExtensions.ZipkinErrorFlagTagName && (string)t.Value == TagDescriptionOnError); // Ensure additional Activity tags were being converted. Assert.Contains(zipkinSpan.Tags, t => t.Key == "myCustomTag" && (string)t.Value == "myCustomTagValue"); } } }