diff --git a/src/OpenTelemetry.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs b/src/OpenTelemetry.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs index 004b32cf335..86f96d2b344 100644 --- a/src/OpenTelemetry.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs +++ b/src/OpenTelemetry.Exporter.ApplicationInsights/Implementation/TraceExporterHandler.cs @@ -30,23 +30,44 @@ namespace OpenTelemetry.Exporter.ApplicationInsights.Implementation internal class TraceExporterHandler : IHandler { private readonly TelemetryClient telemetryClient; + private readonly string serviceEndpoint; public TraceExporterHandler(TelemetryConfiguration telemetryConfiguration) { this.telemetryClient = new TelemetryClient(telemetryConfiguration); + this.serviceEndpoint = telemetryConfiguration.TelemetryChannel.EndpointAddress; } public Task ExportAsync(IEnumerable spanDataList) { foreach (var span in spanDataList) { + bool shouldExport = true; + string httpUrlAttr = null; + + foreach (var attr in span.Attributes.AttributeMap) + { + if (attr.Key == "http.url") + { + httpUrlAttr = attr.Value.ToString(); + if (httpUrlAttr == this.serviceEndpoint) + { + shouldExport = false; + break; + } + } + } + + if (!shouldExport) + { + continue; + } + this.ExtractGenericProperties( span, - out var resultKind, - out var timestamp, out var name, out var resultCode, - out var props, + out var statusDescription, out var traceId, out var spanId, out var parentId, @@ -54,30 +75,42 @@ public Task ExportAsync(IEnumerable spanDataList) out var success, out var duration); + // BUILDING resulting telemetry + OperationTelemetry result; + if (span.Kind == SpanKind.Client || span.Kind == SpanKind.Internal || span.Kind == SpanKind.Producer) + { + var resultD = new DependencyTelemetry(); + if (span.Kind == SpanKind.Internal) + { + resultD.Type = "InProc"; + } + + result = resultD; + } + else + { + result = new RequestTelemetry(); + } + string data = null; string target = null; string type = null; string userAgent = null; - string spanKindAttr = null; string errorAttr = null; string httpStatusCodeAttr = null; string httpMethodAttr = null; string httpPathAttr = null; string httpHostAttr = null; - string httpUrlAttr = null; + string httpUserAgentAttr = null; string httpRouteAttr = null; string httpPortAttr = null; foreach (var attr in span.Attributes.AttributeMap) { - var key = attr.Key; switch (attr.Key) { - case "span.kind": - spanKindAttr = attr.Value.ToString(); - break; case "error": errorAttr = attr.Value.ToString(); break; @@ -90,9 +123,6 @@ public Task ExportAsync(IEnumerable spanDataList) case "http.host": httpHostAttr = attr.Value.ToString(); break; - case "http.url": - httpUrlAttr = attr.Value.ToString(); - break; case "http.status_code": httpStatusCodeAttr = attr.Value.ToString(); break; @@ -108,21 +138,59 @@ public Task ExportAsync(IEnumerable spanDataList) default: var value = attr.Value.ToString(); - AddPropertyWithAdjustedName(props, attr.Key, value); + AddPropertyWithAdjustedName(result.Properties, attr.Key, value); break; } } + this.OverwriteFieldsForHttpSpans( + httpMethodAttr, + httpUrlAttr, + httpHostAttr, + httpPathAttr, + httpStatusCodeAttr, + httpUserAgentAttr, + httpRouteAttr, + httpPortAttr, + ref name, + ref resultCode, + ref data, + ref target, + ref type, + ref userAgent); + + if (result is DependencyTelemetry dependency) + { + dependency.Data = data; + dependency.Target = target; + dependency.Data = data; + dependency.ResultCode = resultCode; + + if (string.IsNullOrEmpty(dependency.Type)) + { + dependency.Type = type; + } + } + else if (result is RequestTelemetry request) + { + if (Uri.TryCreate(data, UriKind.RelativeOrAbsolute, out var url)) + { + request.Url = url; + } + + request.ResponseCode = resultCode; + } + var linkId = 0; foreach (var link in span.Links.Links) { - AddPropertyWithAdjustedName(props, "link" + linkId + "_traceId", link.Context.TraceId.ToHexString()); - AddPropertyWithAdjustedName(props, "link" + linkId + "_spanId", link.Context.SpanId.ToHexString()); + AddPropertyWithAdjustedName(result.Properties, "link" + linkId + "_traceId", link.Context.TraceId.ToHexString()); + AddPropertyWithAdjustedName(result.Properties, "link" + linkId + "_spanId", link.Context.SpanId.ToHexString()); foreach (var attr in link.Attributes) { - AddPropertyWithAdjustedName(props, "link" + linkId + "_" + attr.Key, attr.Value.ToString()); + AddPropertyWithAdjustedName(result.Properties, "link" + linkId + "_" + attr.Key, attr.Value.ToString()); } ++linkId; @@ -150,65 +218,25 @@ public Task ExportAsync(IEnumerable spanDataList) this.telemetryClient.Track(log); } - this.OverwriteSpanKindFromAttribute(spanKindAttr, ref resultKind); this.OverwriteErrorAttribute(errorAttr, ref success); - this.OverwriteFieldsForHttpSpans( - httpMethodAttr, - httpUrlAttr, - httpHostAttr, - httpPathAttr, - httpStatusCodeAttr, - httpUserAgentAttr, - httpRouteAttr, - httpPortAttr, - ref name, - ref resultCode, - ref data, - ref target, - ref type, - ref userAgent); - // BUILDING resulting telemetry - OperationTelemetry result; - if (resultKind == SpanKind.Client || resultKind == SpanKind.Producer) - { - var resultD = new DependencyTelemetry - { - ResultCode = resultCode, - Data = data, - Target = target, - Type = type, - }; - - result = resultD; - } - else + result.Success = success; + if (statusDescription != null) { - var resultR = new RequestTelemetry(); - resultR.ResponseCode = resultCode; - Uri.TryCreate(data, UriKind.RelativeOrAbsolute, out var url); - resultR.Url = url; - result = resultR; + AddPropertyWithAdjustedName(result.Properties, "statusDescription", statusDescription); } - result.Success = success; - - result.Timestamp = timestamp; + result.Timestamp = span.StartTimestamp; result.Name = name; result.Context.Operation.Id = traceId; result.Context.User.UserAgent = userAgent; - foreach (var prop in props) - { - AddPropertyWithAdjustedName(result.Properties, prop.Key, prop.Value); - } - if (parentId != null) { result.Context.Operation.ParentId = string.Concat("|", traceId, ".", parentId, "."); } - // TODO: I don't understant why this concatanation is required + // TODO: this concatenation is required for Application Insights backward compatibility reasons result.Id = string.Concat("|", traceId, ".", spanId, "."); foreach (var ts in tracestate.Entries) @@ -220,7 +248,6 @@ public Task ExportAsync(IEnumerable spanDataList) // TODO: deal with those: // span.ChildSpanCount - // span.Context.IsValid; // span.Context.TraceOptions; this.telemetryClient.Track(result); @@ -242,22 +269,11 @@ private static void AddPropertyWithAdjustedName(IDictionary prop props.Add(n, value); } - private void ExtractGenericProperties(SpanData span, out SpanKind resultKind, out DateTimeOffset timestamp, out string name, out string resultCode, out IDictionary props, out string traceId, out string spanId, out string parentId, out Tracestate tracestate, out bool? success, out TimeSpan duration) + private void ExtractGenericProperties(SpanData span, out string name, out string resultCode, out string statusDescription, out string traceId, out string spanId, out string parentId, out Tracestate tracestate, out bool? success, out TimeSpan duration) { - resultKind = span.Kind; - - // TODO: Should this be a part of generic logic? - if (resultKind == SpanKind.Internal) - { - resultKind = SpanKind.Client; - } - - // 1 tick is 100 ns - timestamp = new DateTimeOffset(span.StartTimestamp); - name = span.Name; - props = new Dictionary(); + statusDescription = null; traceId = span.Context.TraceId.ToHexString(); spanId = span.Context.SpanId.ToHexString(); @@ -275,7 +291,7 @@ private void ExtractGenericProperties(SpanData span, out SpanKind resultKind, ou success = span.Status.IsOk; if (!string.IsNullOrEmpty(span.Status.Description)) { - props["statusDescription"] = span.Status.Description; + statusDescription = span.Status.Description; } } @@ -283,34 +299,6 @@ private void ExtractGenericProperties(SpanData span, out SpanKind resultKind, ou duration = span.EndTimestamp - span.StartTimestamp; } - private void OverwriteSpanKindFromAttribute(string spanKindAttr, ref SpanKind resultKind) - { - // override span kind with attribute named span.kind - if (spanKindAttr != null) - { - var kind = spanKindAttr; - - switch (kind.ToLower(CultureInfo.InvariantCulture)) - { - case "server": - resultKind = SpanKind.Server; - break; - case "client": - resultKind = SpanKind.Client; - break; - case "producer": - resultKind = SpanKind.Producer; - break; - case "consumer": - resultKind = SpanKind.Consumer; - break; - default: - resultKind = SpanKind.Internal; - break; - } - } - } - private void OverwriteErrorAttribute(string errorAttr, ref bool? success) { if (errorAttr != null) @@ -347,7 +335,7 @@ private void OverwriteFieldsForHttpSpans( { var urlString = httpUrlAttr; Uri.TryCreate(urlString, UriKind.RelativeOrAbsolute, out url); - } + } string httpMethod = null; string httpPath = null; diff --git a/test/OpenTelemetry.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs b/test/OpenTelemetry.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs index c845b6b8758..707a9354050 100644 --- a/test/OpenTelemetry.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs +++ b/test/OpenTelemetry.Exporter.ApplicationInsights.Tests/Implementation/TraceExporterHandlerTests.cs @@ -36,6 +36,7 @@ public class OpenTelemetryTelemetryConverterTests private const string TestTraceId = "d79bdda7eb9c4a9fa9bda52fe7b48b95"; private const string TestSpanId = "d7ddeb4aa9a5e78b"; private const string TestParentSpanId = "9ba79c9fbd2fb495"; + private const string TestChannelEndpoint = "https://applicationinsights.com"; private readonly byte[] testTraceIdBytes = { 0xd7, 0x9b, 0xdd, 0xa7, 0xeb, 0x9c, 0x4a, 0x9f, 0xa9, 0xbd, 0xa5, 0x2f, 0xe7, 0xb4, 0x8b, 0x95 }; private readonly byte[] testSpanIdBytes = { 0xd7, 0xdd, 0xeb, 0x4a, 0xa9, 0xa5, 0xe7, 0x8b }; @@ -55,6 +56,7 @@ private ConcurrentQueue ConvertSpan(SpanData data) ITelemetryChannel channel = new StubTelemetryChannel { OnSend = t => sentItems.Enqueue(t), + EndpointAddress = TestChannelEndpoint, }; configuration.TelemetryChannel = channel; @@ -278,6 +280,62 @@ public void OpenTelemetryTelemetryConverterTests_TracksClientDependency() Assert.True(string.IsNullOrEmpty(dependency.Type)); } + [Fact] + public void OpenTelemetryTelemetryConverterTests_TracksInternalSpanAsDependency() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var resource, out var name, out var startTimestamp, out var attributes, out var events, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + kind = SpanKind.Internal; + + var span = SpanData.Create(context, parentSpanId, resource, name, startTimestamp, attributes, events, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Single(sentItems); + Assert.True(sentItems.Single() is DependencyTelemetry); + + var dependency = sentItems.OfType().Single(); + Assert.Equal("spanName", dependency.Name); + Assert.Equal("InProc", dependency.Type); + Assert.Equal(now.AddSeconds(-1), dependency.Timestamp); + Assert.Equal(1, dependency.Duration.TotalSeconds); + + Assert.Equal(TestTraceId, dependency.Context.Operation.Id); + Assert.Null(dependency.Context.Operation.ParentId); + Assert.Equal($"|{TestTraceId}.{TestSpanId}.", dependency.Id); + + Assert.True(string.IsNullOrEmpty(dependency.ResultCode)); + Assert.False(dependency.Success.HasValue); + + // Assert.Equal("lf_unspecified-oc:0.0.0", dependency.Context.GetInternalContext().SdkVersion); + } + + [Fact] + public void OpenTelemetryTelemetryConverterTests_DoesNotTrackCallToAppInsights() + { + // ARRANGE + this.GetDefaults(out var context, out var parentSpanId, out var resource, out var name, out var startTimestamp, out var attributes, out var events, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); + + kind = SpanKind.Client; + attributes = Attributes.Create(new Dictionary + { + { "http.url", TestChannelEndpoint }, + { "http.method", "POST" }, + { "http.status_code", 200 }, + }, 0); + + var span = SpanData.Create(context, parentSpanId, resource, name, startTimestamp, attributes, events, links, childSpanCount, status, kind, endTimestamp); + + // ACT + var sentItems = this.ConvertSpan(span); + + // ASSERT + Assert.Empty(sentItems); + } + [Fact] public void OpenTelemetryTelemetryConverterTests_TracksProducerDependency() { @@ -517,20 +575,6 @@ public void OpenTelemetryTelemetryConverterTests_TracksDependencyErrorAttribute( Assert.False(dependency.Success.Value); } - [Fact] - public void OpenTelemetryTelemetryConverterTests_TracksRequestBasedOnServerSpanKindAttribute() - { - this.GetDefaults(out var context, out var parentSpanId, out var resource, out var name, out var startTimestamp, out var attributes, out var events, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); - kind = SpanKind.Client; - attributes = Attributes.Create(new Dictionary() { { "span.kind", "server" } }, 0); - - var span = SpanData.Create(context, parentSpanId, resource, name, startTimestamp, attributes, events, links, childSpanCount, status, kind, endTimestamp); - - var sentItems = this.ConvertSpan(span); - - Assert.True(sentItems.Single() is RequestTelemetry); - } - [Fact] public void OpenTelemetryTelemetryConverterTests_TracksRequestBasedOnClientSpanKindAttribute() { @@ -563,22 +607,7 @@ public void OpenTelemetryTelemetryConverterTests_TracksRequestBasedOnProducerSpa public void OpenTelemetryTelemetryConverterTests_TracksRequestBasedOnConsumerSpanKindAttribute() { this.GetDefaults(out var context, out var parentSpanId, out var resource, out var name, out var startTimestamp, out var attributes, out var events, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); - kind = SpanKind.Client; - attributes = Attributes.Create(new Dictionary() { { "span.kind", "consumer" } }, 0); - - var span = SpanData.Create(context, parentSpanId, resource, name, startTimestamp, attributes, events, links, childSpanCount, status, kind, endTimestamp); - - var sentItems = this.ConvertSpan(span); - - Assert.True(sentItems.Single() is RequestTelemetry); - } - - [Fact] - public void OpenTelemetryTelemetryConverterTests_TracksRequestBasedOnOtherSpanKindAttribute() - { - this.GetDefaults(out var context, out var parentSpanId, out var resource, out var name, out var startTimestamp, out var attributes, out var events, out var links, out var childSpanCount, out var status, out var kind, out var endTimestamp); - kind = SpanKind.Client; - attributes = Attributes.Create(new Dictionary() { { "span.kind", "other" } }, 0); + kind = SpanKind.Consumer; var span = SpanData.Create(context, parentSpanId, resource, name, startTimestamp, attributes, events, links, childSpanCount, status, kind, endTimestamp);