WCF如何在WCF中传播DiagnosticSource.Activity上下文?

问题描述

我尝试在使用WCF的应用程序中将OpenTelemetry与控制台导出器集成在一起。 问题是我当前的System.Diagnostics.DiagnosticSource.Activity的跟踪ID不会传播到另一侧。 因此,另一方面,父信息丢失了,跟踪也被破坏了。

没有打开 WCF中的ActivityPropagation,它在检查日志时可以正常工作。

问题是,我另一端的活动没有/错误的活动ID。


目前,我一无所知,我也可以通过WCF传播System.Diagnostics.DiagnosticSource.Activity.Id

  • 如何正确设置OpenTelemetry或WCF以传播活动ID /上下文?




代码基本上仅是Microsoft WCF Calculator Tutorial。 我尝试将OpenTelemetry集成到其中。

这些是我用于OpenTelemetry的部分

客户:

public class Program
{
    private static readonly ActivitySource MyActivitySource = new ActivitySource("MyCompany.MyProduct.MyClient");

    public static void Main(string[] args)
    {
        var tracerProvider = Sdk.CreateTracerProviderBuilder()
            .AddSource("MyCompany.MyProduct.MyClient")
            .AddConsoleExporter()
            .Build();

        using (var activity_wcf_session = MyActivitySource.StartActivity("WCF_Session"))
        {
            activity_wcf_session?.SetTag("index",0);

            //Step 1: Create an instance of the WCF proxy.
            CalculatorClient client = new CalculatorClient();

            // Step 2: Call the service operations.
            // Call the Add service operation.
            using (var activity = MyActivitySource.StartActivity("Client_call_add()"))
            {
                activity?.SetTag("msg","foo");
                double result = client.Add(100.00D,15.99D);
            }
            
            // Step 3: Close the client to gracefully close the connection and clean up resources.
            Console.WriteLine("\nPress <Enter> to terminate the wcf client.");
            Console.ReadLine();
            client.Close();
        }
    }
}

服务:

public class CalculatorService : ICalculator
    {
        private static readonly ActivitySource MyActivitySource = new ActivitySource(
            "MyCompany.MyProduct.CalculatorService");

        private TracerProvider tracerProvider;

        public CalculatorService()
        {
            tracerProvider = Sdk.CreateTracerProviderBuilder()
            .AddSource("MyCompany.MyProduct.CalculatorService")
            .AddConsoleExporter()
            .Build();
        }


        public double Add(double n1,double n2)
        {
            Console.WriteLine("Activity.Current is null: " + (Activity.Current == null)); // is always null

            using (var activity = MyActivitySource.StartActivity("CalculatorService_add()",ActivityKind.Server))
            {
                // activity.parent is not set

                double result = n1 + n2;
                return result;
            }
        }
        
        // ...
    }
}

这就是我在项目中激活活动传播的方式(与主机相同)

<system.diagnostics>
    <sources>
      <source name="System.ServiceModel" switchValue="information,ActivityTracing" propagateActivity="true">
        <listeners>
          <add name="xml" />
        </listeners>
      </source>
      <source name="System.ServiceModel.MessageLogging">
        <listeners>
          <add name="xml" />
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add initializeData="C:\logs\GettingStarted_Client.svclog" type="System.Diagnostics.XmlWriterTraceListener" name="xml" />
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>

解决方法

您需要解析 HTTP 标头并自行创建 Activity。您可以这样做:

(要求:System.Diagnostics.DiagnosticSource nuget 包。)

  1. 创建以下助手类:
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;

internal sealed class TracingEndpointBehavior : IEndpointBehavior
{
    private readonly TracingMessageInspector messageInspector = new TracingMessageInspector();

    public void AddBindingParameters(ServiceEndpoint endpoint,BindingParameterCollection bindingParameters)
    {
        // No-op
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint,ClientRuntime clientRuntime)
    {
        // No-op
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(messageInspector);
    }

    public void Validate(ServiceEndpoint endpoint)
    {
        // No-op
    }
}

internal sealed class TracingMessageInspector : IDispatchMessageInspector
{
    private readonly ActivitySource activitySource = new ActivitySource(nameof(TracingMessageInspector));

    public object AfterReceiveRequest(ref Message request,IClientChannel channel,InstanceContext instanceContext)
    {
        return StartActivity(WebOperationContext.Current?.IncomingRequest?.Headers);
    }

    private Activity StartActivity(WebHeaderCollection headers)
    {
        if (headers == null)
        {
            return activitySource.StartActivity("<UnknownAction>",ActivityKind.Server);
        }

        Activity activity = activitySource.StartActivity(
            name: headers[HeaderNames.SOAPAction] ?? "<UnknownAction>",kind: ActivityKind.Server,parentId: headers[HeaderNames.TraceParent]);

        if (activity == null)
        {
            return null;
        }

        activity.TraceStateString = headers[HeaderNames.TraceState];

        string baggageString = headers[HeaderNames.Baggage] ?? headers[HeaderNames.CorrelationContext];
        if (baggageString != null)
        {
            foreach (var item in baggageString.Split(','))
            {
                if (NameValueHeaderValue.TryParse(item,out NameValueHeaderValue baggageItem))
                {
                    activity.AddBaggage(baggageItem.Name,WebUtility.UrlDecode(baggageItem.Value));
                }
            }
        }

        return activity;
    }

    public void BeforeSendReply(ref Message reply,object correlationState)
    {
        if (correlationState is Activity activity)
        {
            activity.Stop();
        }
    }
}

internal static class HeaderNames
{
    public const string SOAPAction = "SOAPAction";
    public const string TraceParent = "traceparent";
    public const string TraceState = "tracestate";
    public const string CorrelationContext = "Correlation-Context";
    public const string Baggage = "baggage";
}
  1. 在启动时,连接行为:
ServiceHost host = ...;
ServiceEndpoint endpoint = host.AddServiceEndpoint(...);
endpoint.EndpointBehaviors.Add(new TracingEndpointBehavior());
  1. 确保有 ActivityListener,否则 Activity 创建的 ActivitySource 将为空。例如:
ActivitySource.AddActivityListener(new ActivityListener
{
    ActivityStarted = a => Console.WriteLine($"[{DateTime.UtcNow:o}] Started: {a.OperationName} ({a.Id})"),ActivityStopped = a => Console.WriteLine($"[{DateTime.UtcNow:o}] Stopped: {a.OperationName} ({a.Id}) took {a.Duration}"),ShouldListenTo = _ => true,Sample = (ref ActivityCreationOptions<ActivityContext> o) => ActivitySamplingResult.AllDataAndRecorded,});

变化

  • 上面的代码使用了 IEndpointBehavior,但 IServiceBehaviorIContractBehavior 也可以使用。
  • 我选择了 headers[HeaderNames.SOAPAction] 作为活动名称,但它可以是任何名称。
  • 我通过代码连接了该行为,但也可以通过 app.configweb.config 来实现。不过,您需要创建一个 BehaviorExtensionElement

注意事项

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...