src/WebJobs.Extensions.DurableTask/Correlation/DurableTaskCorrelationTelemetryInitializer.cs (234 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using DurableTask.Core; using DurableTask.Core.Settings; using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Correlation { /// <summary> /// Telemetry Initializer that sets correlation ids for W3C. /// This source is based on W3COperationCorrelationTelemetryInitializer.cs /// 1. Modified with CorrelationTraceContext.Current /// 2. Avoid to be overriden when it is RequestTelemetry /// Original Source is here https://github.com/microsoft/ApplicationInsights-dotnet-server/blob/2.8.0/Src/Common/W3C/W3COperationCorrelationTelemetryInitializer.cs. /// </summary> internal class DurableTaskCorrelationTelemetryInitializer : ITelemetryInitializer { private const string RddDiagnosticSourcePrefix = "rdddsc"; private const string SqlRemoteDependencyType = "SQL"; /// These internal property is copied from W3CConstants /// <summary>Trace-Id tag name.</summary> internal const string TraceIdTag = "w3c_traceId"; /// <summary>Span-Id tag name.</summary> internal const string SpanIdTag = "w3c_spanId"; /// <summary>Parent span-Id tag name.</summary> internal const string ParentSpanIdTag = "w3c_parentSpanId"; /// <summary>Version tag name.</summary> internal const string VersionTag = "w3c_version"; /// <summary>Sampled tag name.</summary> internal const string SampledTag = "w3c_sampled"; /// <summary>Tracestate tag name.</summary> internal const string TracestateTag = "w3c_tracestate"; /// <summary>Default version value.</summary> internal const string DefaultVersion = "00"; /// <summary> /// Default sampled flag value: may be recorded, not requested. /// </summary> internal const string TraceFlagRecordedAndNotRequested = "02"; /// <summary>Recorded and requested sampled flag value.</summary> internal const string TraceFlagRecordedAndRequested = "03"; /// <summary>Requested trace flag.</summary> internal const byte RequestedTraceFlag = 1; /// <summary>Legacy root Id tag name.</summary> internal const string LegacyRootIdProperty = "ai_legacyRootId"; /// <summary>Legacy root Id tag name.</summary> internal const string LegacyRequestIdProperty = "ai_legacyRequestId"; /// <summary> /// Constructor. /// </summary> public DurableTaskCorrelationTelemetryInitializer() { this.ExcludeComponentCorrelationHttpHeadersOnDomains = new HashSet<string>(); } /// <summary> /// Set of suppress telemetry tracking if you add Host name on this. /// </summary> public HashSet<string> ExcludeComponentCorrelationHttpHeadersOnDomains { get; set; } /// <summary> /// Initializes telemetry item. /// </summary> /// <param name="telemetry">Telemetry item.</param> public void Initialize(ITelemetry telemetry) { if (this.IsSuppressedTelemetry(telemetry)) { this.SuppressTelemetry(telemetry); return; } if (!(telemetry is RequestTelemetry)) { Activity currentActivity = Activity.Current; if (telemetry is ExceptionTelemetry) { Console.WriteLine("exception!"); } if (currentActivity == null) { if (CorrelationTraceContext.Current != null) { UpdateTelemetry(telemetry, CorrelationTraceContext.Current); } } else { if (CorrelationTraceContext.Current != null) { UpdateTelemetry(telemetry, CorrelationTraceContext.Current); } else if (CorrelationSettings.Current.Protocol == Protocol.W3CTraceContext) { UpdateTelemetry(telemetry, currentActivity, false); } else if (CorrelationSettings.Current.Protocol == Protocol.HttpCorrelationProtocol && telemetry is ExceptionTelemetry) { UpdateTelemetryExceptionForHTTPCorrelationProtocol((ExceptionTelemetry)telemetry, currentActivity); } } } } internal static void UpdateTelemetry(ITelemetry telemetry, TraceContextBase contextBase) { switch (contextBase) { case NullObjectTraceContext nullObjectContext: return; case W3CTraceContext w3cContext: UpdateTelemetryW3C(telemetry, w3cContext); break; case HttpCorrelationProtocolTraceContext httpCorrelationProtocolTraceContext: UpdateTelemetryHttpCorrelationProtocol(telemetry, httpCorrelationProtocolTraceContext); break; default: return; } } internal static void UpdateTelemetryHttpCorrelationProtocol(ITelemetry telemetry, HttpCorrelationProtocolTraceContext context) { OperationTelemetry opTelemetry = telemetry as OperationTelemetry; bool initializeFromCurrent = opTelemetry != null; if (initializeFromCurrent) { initializeFromCurrent &= !(opTelemetry is DependencyTelemetry dependency && dependency.Type == SqlRemoteDependencyType && dependency.Context.GetInternalContext().SdkVersion .StartsWith(RddDiagnosticSourcePrefix, StringComparison.Ordinal)); } if (initializeFromCurrent) { opTelemetry.Id = !string.IsNullOrEmpty(opTelemetry.Id) ? opTelemetry.Id : context.TelemetryId; telemetry.Context.Operation.ParentId = !string.IsNullOrEmpty(telemetry.Context.Operation.ParentId) ? telemetry.Context.Operation.ParentId : context.TelemetryContextOperationParentId; } else { telemetry.Context.Operation.Id = !string.IsNullOrEmpty(telemetry.Context.Operation.Id) ? telemetry.Context.Operation.Id : context.TelemetryContextOperationId; if (telemetry is ExceptionTelemetry) { telemetry.Context.Operation.ParentId = context.TelemetryId; } else { telemetry.Context.Operation.ParentId = !string.IsNullOrEmpty(telemetry.Context.Operation.ParentId) ? telemetry.Context.Operation.ParentId : context.TelemetryContextOperationParentId; } } } internal static void UpdateTelemetryW3C(ITelemetry telemetry, W3CTraceContext context) { OperationTelemetry opTelemetry = telemetry as OperationTelemetry; bool initializeFromCurrent = opTelemetry != null; if (initializeFromCurrent) { initializeFromCurrent &= !(opTelemetry is DependencyTelemetry dependency && dependency.Type == SqlRemoteDependencyType && dependency.Context.GetInternalContext().SdkVersion .StartsWith(RddDiagnosticSourcePrefix, StringComparison.Ordinal)); } if (!string.IsNullOrEmpty(context.TraceState)) { opTelemetry.Properties["w3c_tracestate"] = context.TraceState; } TraceParent traceParent = TraceParent.FromString(context.TraceParent); if (initializeFromCurrent) { if (string.IsNullOrEmpty(opTelemetry.Id)) { opTelemetry.Id = traceParent.SpanId; } if (string.IsNullOrEmpty(context.ParentSpanId)) { telemetry.Context.Operation.ParentId = telemetry.Context.Operation.Id; } } else { if (telemetry.Context.Operation.Id == null) { telemetry.Context.Operation.Id = traceParent.TraceId; } if (telemetry.Context.Operation.ParentId == null) { telemetry.Context.Operation.ParentId = traceParent.SpanId; } } } internal void SuppressTelemetry(ITelemetry telemetry) { // TODO For suppressing Dependency, I make the Id as suppressed. This strategy increases the number of telemetery. // However, new implementation is already supressed. Once we've fully tested this logic, remove the suppression logic on this class. telemetry.Context.Operation.Id = "suppressed"; telemetry.Context.Operation.ParentId = "suppressed"; #pragma warning disable 618 // Context. Properties. ai_legacyRequestId , ai_legacyRequestId foreach (var key in telemetry.Context.Properties.Keys) { if (key == "ai_legacyRootId" || key == "ai_legacyRequestId") { telemetry.Context.Properties[key] = "suppressed"; } } #pragma warning restore 618 ((OperationTelemetry)telemetry).Id = "suppressed"; } internal bool IsSuppressedTelemetry(ITelemetry telemetry) { OperationTelemetry opTelemetry = telemetry as OperationTelemetry; if (telemetry is DependencyTelemetry) { DependencyTelemetry dTelemetry = telemetry as DependencyTelemetry; #pragma warning disable 618 if (!string.IsNullOrEmpty(dTelemetry.CommandName)) { var host = new Uri(dTelemetry.CommandName).Host; #pragma warning restore 618 if (this.ExcludeComponentCorrelationHttpHeadersOnDomains.Contains(host)) { return true; } } } return false; } internal static void UpdateTelemetryExceptionForHTTPCorrelationProtocol(ExceptionTelemetry telemetry, Activity activity) { telemetry.Context.Operation.ParentId = activity.Id; telemetry.Context.Operation.Id = activity.RootId; } [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This method has different code for Net45/NetCore")] internal static void UpdateTelemetry(ITelemetry telemetry, Activity activity, bool forceUpdate) { if (activity == null) { return; } // Requests and dependencies are initialized from the Activity.Current. // (i.e. telemetry.Id = current.Id). Activity is created for such requests specifically // Traces, exceptions, events on the other side are children of current activity // There is one exception - SQL DiagnosticSource where current Activity is a parent // for dependency calls. OperationTelemetry opTelemetry = telemetry as OperationTelemetry; bool initializeFromCurrent = opTelemetry != null; if (initializeFromCurrent) { initializeFromCurrent &= !(opTelemetry is DependencyTelemetry dependency && dependency.Type == SqlRemoteDependencyType && dependency.Context.GetInternalContext().SdkVersion .StartsWith(RddDiagnosticSourcePrefix, StringComparison.Ordinal)); } if (telemetry is OperationTelemetry operation) { operation.Properties[TracestateTag] = activity.TraceStateString; } if (initializeFromCurrent) { opTelemetry.Id = activity.SpanId.ToHexString(); if (activity.ParentSpanId != default) { opTelemetry.Context.Operation.ParentId = activity.ParentSpanId.ToHexString(); } } else { telemetry.Context.Operation.ParentId = activity.SpanId.ToHexString(); } } } }