diff --git a/CHANGELOG.md b/CHANGELOG.md index b20a9ebc60..8607e256f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [The `{OriginalFormat}` field in ILogger Scope will be emitted as `OriginalFormat` with the braces removed](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2362) - [Enable SDK to create package for auto-instrumentation](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2365) - [Suppress long running SignalR connections](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2372) +- [Support ActivitySource in Azure SDK client libraries](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2185) ## Version 2.18.0 - [Change Self-Diagnostics to include datetimestamp in filename](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2325) diff --git a/WEB/Src/DependencyCollector/DependencyCollector.Tests/Implementation/AzureSdkDiagnosticListenerTest.cs b/WEB/Src/DependencyCollector/DependencyCollector.Tests/Implementation/AzureSdkDiagnosticListenerTest.cs index ce963823f1..c307ebdce4 100644 --- a/WEB/Src/DependencyCollector/DependencyCollector.Tests/Implementation/AzureSdkDiagnosticListenerTest.cs +++ b/WEB/Src/DependencyCollector/DependencyCollector.Tests/Implementation/AzureSdkDiagnosticListenerTest.cs @@ -634,6 +634,65 @@ public void AzureClientSpansAreCollectedLinks() } } + [TestMethod] + public void AzureClientSpansAreCollectedLinksFromActivitySource() + { + // Create the activity listener to make sure that activitySource.StartActivity returns a value + using (var activityListener = new ActivityListener() + { + ShouldListenTo = source => source.Name == "Azure.SomeClient", + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded + }) + using (var listener = new DiagnosticListener("Azure.SomeClient")) + using (var activitySource = new ActivitySource("Azure.SomeClient")) + using (var module = new DependencyTrackingTelemetryModule()) + { + ActivitySource.AddActivityListener(activityListener); + + module.Initialize(this.configuration); + + var link0TraceId = "70545f717a9aa6a490d820438b9d2bf6"; + var link1TraceId = "c5aa06717eef0c4592af26323ade92f7"; + var link0SpanId = "8b0b2fb40c84e64a"; + var link1SpanId = "3a69ce690411bb4f"; + + var sendActivity = activitySource.StartActivity("Azure.SomeClient.Send", ActivityKind.Consumer, default(ActivityContext), links: new [] + { + new ActivityLink(new ActivityContext(ActivityTraceId.CreateFromString(link0TraceId.AsSpan()), ActivitySpanId.CreateFromString(link0SpanId.AsSpan()), ActivityTraceFlags.None)), + new ActivityLink(new ActivityContext(ActivityTraceId.CreateFromString(link1TraceId.AsSpan()), ActivitySpanId.CreateFromString(link1SpanId.AsSpan()), ActivityTraceFlags.None)) + }); + + listener.Write(sendActivity.OperationName + ".Start", sendActivity); + listener.Write(sendActivity.OperationName + ".Stop", null); + + var telemetry = this.sentItems.Last() as DependencyTelemetry; + + Assert.IsNotNull(telemetry); + Assert.AreEqual("SomeClient.Send", telemetry.Name); + Assert.IsTrue(telemetry.Success.Value); + + Assert.IsNull(telemetry.Context.Operation.ParentId); + Assert.AreEqual(sendActivity.TraceId.ToHexString(), telemetry.Context.Operation.Id); + Assert.AreEqual(sendActivity.SpanId.ToHexString(), telemetry.Id); + + // does not throw + Assert.IsTrue(telemetry.Properties.TryGetValue("_MS.links", out var linksStr)); + var actualLinks = JsonConvert.DeserializeObject(linksStr, JsonSettingThrowOnError); + + Assert.IsNotNull(actualLinks); + Assert.AreEqual(2, actualLinks.Length); + + Assert.AreEqual(link0TraceId, actualLinks[0].OperationId); + Assert.AreEqual(link1TraceId, actualLinks[1].OperationId); + + Assert.AreEqual(link0SpanId, actualLinks[0].Id); + Assert.AreEqual(link1SpanId, actualLinks[1].Id); + + Assert.AreEqual($"[{{\"operation_Id\":\"{link0TraceId}\",\"id\":\"{link0SpanId}\"}},{{\"operation_Id\":\"{link1TraceId}\",\"id\":\"{link1SpanId}\"}}]", linksStr); + Assert.IsFalse(telemetry.Metrics.Any()); + } + } + [TestMethod] public void AzureServerSpansAreCollectedLinks() { diff --git a/WEB/Src/DependencyCollector/DependencyCollector/Implementation/AzureSdk/AzureSdkDiagnosticsEventHandler.cs b/WEB/Src/DependencyCollector/DependencyCollector/Implementation/AzureSdk/AzureSdkDiagnosticsEventHandler.cs index c115f1838e..b24690b448 100644 --- a/WEB/Src/DependencyCollector/DependencyCollector/Implementation/AzureSdk/AzureSdkDiagnosticsEventHandler.cs +++ b/WEB/Src/DependencyCollector/DependencyCollector/Implementation/AzureSdk/AzureSdkDiagnosticsEventHandler.cs @@ -1,6 +1,7 @@ namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation { using System; + using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -70,15 +71,42 @@ public override void OnEvent(KeyValuePair evnt, DiagnosticListen SetEventHubsProperties(currentActivity, telemetry); } - if (this.linksPropertyFetcher.Fetch(evnt.Value) is IEnumerable activityLinks) - { - PopulateLinks(activityLinks, telemetry); + IEnumerable activityLinks = ArrayExtensions.Empty(); - if (telemetry is RequestTelemetry request && - TryGetAverageTimeInQueueForBatch(activityLinks, currentActivity.StartTimeUtc, out long enqueuedTime)) + if (currentActivity.Links.Any()) + { + activityLinks = currentActivity.Links; + } + else if (this.linksPropertyFetcher.Fetch(evnt.Value) is IEnumerable links && links.Any()) + { + // Pre ActivitySource Azure SDKs are using custom activity subtype with + // public IEnumerable Links { get; } + // property to represent links + List convertedLinks = new List(); + foreach (var linksAsActivity in links) { - request.Metrics["timeSinceEnqueued"] = enqueuedTime; + if (linksAsActivity.ParentId != null && + ActivityContext.TryParse(linksAsActivity.ParentId, null, out var context)) + { + ActivityTagsCollection tags = null; + if (linksAsActivity.TagObjects.Any()) + { + tags = new ActivityTagsCollection(linksAsActivity.TagObjects); + } + + convertedLinks.Add(new ActivityLink(context, tags)); + } } + + activityLinks = convertedLinks; + } + + PopulateLinks(activityLinks, telemetry); + + if (telemetry is RequestTelemetry request && + TryGetAverageTimeInQueueForBatch(activityLinks, currentActivity.StartTimeUtc, out long enqueuedTime)) + { + request.Metrics["timeSinceEnqueued"] = enqueuedTime; } this.operationHolder.Store(currentActivity, Tuple.Create(telemetry, /* isCustomCreated: */ false)); @@ -148,7 +176,7 @@ protected override bool IsOperationSuccessful(string eventName, object eventPayl return true; } - private static bool TryGetAverageTimeInQueueForBatch(IEnumerable links, DateTimeOffset requestStartTime, out long avgTimeInQueue) + private static bool TryGetAverageTimeInQueueForBatch(IEnumerable links, DateTimeOffset requestStartTime, out long avgTimeInQueue) { avgTimeInQueue = 0; int linksCount = 0; @@ -179,16 +207,19 @@ private static bool TryGetAverageTimeInQueueForBatch(IEnumerable links return true; } - private static bool TryGetEnqueuedTime(Activity link, out long enqueuedTime) + private static bool TryGetEnqueuedTime(ActivityLink link, out long enqueuedTime) { enqueuedTime = 0; - foreach (var attribute in link.Tags) + if (link.Tags != null) { - if (attribute.Key == "enqueuedTime") + foreach (var attribute in link.Tags) { - if (attribute.Value is string strValue) + if (attribute.Key == "enqueuedTime") { - return long.TryParse(strValue, out enqueuedTime); + if (attribute.Value is string strValue) + { + return long.TryParse(strValue, out enqueuedTime); + } } } } @@ -352,15 +383,15 @@ private static void SetEventHubsProperties(Activity activity, OperationTelemetry } } - private static void PopulateLinks(IEnumerable links, OperationTelemetry telemetry) + private static void PopulateLinks(IEnumerable links, OperationTelemetry telemetry) { - if (links.Any()) + if (links != null && links.Any()) { var linksJson = new StringBuilder(); linksJson.Append('['); foreach (var link in links) { - var linkTraceId = link.TraceId.ToHexString(); + var linkTraceId = link.Context.TraceId.ToHexString(); // avoiding json serializers for now because of extra dependency. // serialization is trivial and looks like `links` property with json blob @@ -376,7 +407,7 @@ private static void PopulateLinks(IEnumerable links, OperationTelemetr linksJson .Append("\"id\":") .Append('\"') - .Append(link.ParentSpanId.ToHexString()) + .Append(link.Context.SpanId.ToHexString()) .Append('\"'); // we explicitly ignore sampling flag, tracestate and attributes at this point.