.NET Framework Tracing SDK

虽然使用 Instana 进行跟踪是自动进行的,但如果您希望对自定义代码、特定应用程序区域或某些内部组件获得更深入的洞察,可以使用 Instana 的.NET 跟踪SDK,具体方法如下。

安装 SDK

我们在 nuget.org 的官方订阅源中提供 SDK for .NET 作为 nuget 包。 搜索 Instana.ManagedTracing.Sdk 以查找该项目并将其添加到项目中。 安装后,您的项目将具有两个额外的引用 (Instana.ManagedTracing.SdkInstana.ManagedTracing.Api)。

跟踪自己的代码

以下示例将演示如何将分布式跟踪功能添加到代码中。

注意: 该 SDK 目前正在积极开发中,因此每次更新时请务必重新访问此页面,以确保使用的是最新的 API。

创建一个简单的 span 标签

对方法调用进行跟踪的最简单方法是使用 CustomSpan.Create API。

public void MyTracedMethod()
{
    using(var span = CustomSpan.Create())
    {
        // your original code here
    }
}

这段代码会创建一个中间 span 标签,用于表示您的方法及其内部所花费的时间。

如果您不希望使用中间项,而是希望创建入口或出口范围,那么可以使用方便的 API(分别称为 CustomSpan.CreateEntryCustomSpan.CreateExit)。

创建一个简单的 span 并捕获异常

如果您想用处理方法的主体期间发生的任何错误来注释已创建的范围,那么您当然可以手动执行此操作,并使用类似如下的 CustomSpan.SetError API。

public void MyTracedMethod()
{
    using(var span = CustomSpan.Create())
    {
        try
        {
        // your original code here
        }
        catch(Exception e)
        {
            span.SetError(e);
        }
    }
}

一种更简单且首选的方法是改为使用 CustomSpan.WrapActionCustomSpan.Wrap<T> API。

public void MyTracedMethod()
{
    using(var span = CustomSpan.Create())
    {
        // setting the second argument to "false" will prevent exceptions from being thrown. Instead they will be
        // captured in the span and swallowed. Setting it to true will let you handle exceptions yourself.
        span.WrapAction(
            ()=>{
                // your original code here
            }, true);
    }
}

如果要合并的代码块返回需要进行进一步处理的内容,请改为使用 CustomSpan.Wrap<T> API。

public void MyTracedMethod()
{
    using(var span = CustomSpan.Create())
    {
        // setting the second argument to "false" will prevent exceptions from being thrown. Instead they will be
        // captured in the span and swallowed. Setting it to true will let you handle exceptions yourself.
        bool result = span.Wrap<bool>(
            ()=>{
                // your original code here
                return myBooleanValue;
            }, true);
    }
}

现在,我们知道如何创建条目、出口和中间产品。 我们还了解了如何使用 SetErrorWrapAction / Wrap<T> API 来捕获异常。

向跨度中添加数据

范围本身仅由一个计时、一个调用堆栈和一个名称组成。 这很好,但是在大多数情况下不是很有用。 那么,如何将一些数据添加到您的范围中? 范围可以包含 DataTagsCustomSpan 类为其提供简单的 API。

public void MyTracedMethod(string userName, string someSuperRelevantData)
{
    using(var span = CustomSpan.Create())
    {
        span.SetData("username", userName);
        span.SetData("relevant", someSuperRelevantData);
        span.WrapAction(
            ()=>{
                // your original code here
            }, true);
    }
}

正在将数据传输到后端,可以通过下载跟踪从 UI 下载数据。 不过,此处输入的数据并未显示在 Instana 的用户界面中。

为 span 标签添加标签

您还可以使用 SetTag,以字符串数组作为键(可用于将传递到范围的数据作为层次结构进行构造),而不是使用 SetData

public void MyTracedMethod(string userName, string someSuperRelevantData)
{
    using(var span = CustomSpan.Create())
    {
        span.SetTag("username", userName);
        span.SetTag("relevant", someSuperRelevantData);
        span.WrapAction(
            ()=>{
                // your original code here
            }, true);
    }
}

标记将直接显示在“调用详细信息”视图中,并且也可以在无界分析中搜索。

将自定义跨度映射到服务

您通常希望将定制范围与 Instana 的“应用程序透视图”中的逻辑服务关联。 这就像调用 SetServicename API 一样简单,它只需要一个字符串。 要区分使用 SDK 实现的端点,您还可以提供端点以使用 SetEndpointName API 进行更详细的映射。

public void MyTracedMethod(string userName, string someSuperRelevantData)
{
    using(var span = CustomSpan.Create())
    {
        span.SetServiceName("AwesomeSDKService");
        span.SetEndpointName("TracingEndpoint");
        .
        .
        .
    }
}

虽然您可以在每个范围上设置服务和端点,但请务必注意,对于 INTERMEDIATE 范围,这些设置将被废弃(这些设置将从 ENTRY 之前的最后一个范围继承)

获取 span 的结果

假设您检测到的方法返回值。 假设在您的跟踪中具有此值将有助于故障诊断。 输入 SetResult API。

public bool MyTracedMethod(string userName, string someSuperRelevantData)
{
    using(var span = CustomSpan.Create())
    {
        bool result = span.Wrap<bool>(
            ()=>{
                // your original code here
                return resultingBoolean;
            }, true);

        span.SetResult(result.ToString());
        return result;
    }
}

嵌套跨度

嵌套范围与从另一个方法调用方法一样简单。 为了完整起见,以下是一个示例。 我们假定一个方法充当一个条目,而它的子范围是一个中间项。

public bool MyTracedEntryMethod(string userName, string someSuperRelevantData)
{
    using(var span = CustomSpan.CreateEntry())
    {
        bool result = span.Wrap<bool>(
            ()=>{
                List<string> data = this.GetSomeDataFromSomewhere();
                // do some heavy processing
                return theResultICameUpWith;
            }, true);

        span.SetResult(result.ToString());
        return result;
    }
}


private List<string> GetSomeDataFromSomewhere()
{
    using(var span = CustomSpan.Create())
    {
        List<string> result = span.WrapAction(
            ()=>{
                // read data from somewhere...
                return theListICameUpWith;
            }, true);

        span.SetResult(result.ToString());
        return result;
    }
}

此操作的结果将是一个条目范围,并将中间范围作为子代。 从技术上讲,嵌套对深度没有限制,但是您不应在深度递归中创建范围。

查看分布式 distributed 追踪中的

到目前为止,我们一直关注的所有范围都仅限于一个服务。 它们从未接触过其他组件,这些组件也会跟踪其活动。 要实现跨服务边界的真正分布式跟踪,您需要应用一些关联。

跟踪中的关联描述了如何在出口调用上设置关联数据,以及如何获取此数据并在另一个组件进入时“继续”此上下文。

为实现此目的,CustomSpan 具有 CustomSpan.CreateExitCustomSpan.CreateEntry 方法的超负载。

虽然 CustomSpan.CreateExit 可以使用 Action<string, string> 作为自变量,但是 CustomSpan.CreateEntry 将采用 Func<DistributedTraceInformation>

如何运作的呢?

假设我们有一个 Message 类,我们将它传递给要调用的远程服务。

    public class Message
    {
        public Message()
        {
            this.Tags = new Dictionary<string, string>();
        }
        public Dictionary<string, string> Tags { get; private set; }
        public int Payload { get; set; }

        public void AddTag(string tagName, string tagValue)
        {
            this.Tags.Add(tagName, tagValue);
        }
    }

此处的相关部分是 AddTag 方法,它采用两个字符串。 这是我们需要提供给 CustomSpan.CreateExit 的特征符。

在调用 CreateExit 时,使用此方法将把相关数据写入我们的 Message 实例。

public void MyLocalEntryMethod()
{
    // this methd will create an entry span and then call our method
    // that communicates with the remote-service (and thus create our exit span)
    using(var span = CustomSpan.CreateEntry())
    {
        span.WrapAction(()=>{
            CallRemoteService();
        })
    }
}

public void CallRemoteService()
{
    Message message = new Message();
    using(var exitSpan = CustomSpan.CreateExit(this, message.AddTag))
    {
        exitSpan.WrapAction( ()=>{
            var service = new RemoteService();
            service.ValidateRequest(message);
        }
    }
}

因此,当我们的消息通过调用 service.ValidateRequest(message) 离开本地组件的范围时,它会在其标记列表中携带相关数据。 让我们看看如何在被调用者站点上抽取这些内容。

public void ValidateRequest(Message message)
{
    using(var span = CustomSpan.CreateEntry(this, ()=>ExtractCorrelationData(message))
    {
        // do whatever this method is supposed to do, we only care for extraction
        // if the correlation-data anyway :-)
    }
}

private DistributedTraceInformation ExtractCorrelationData(Message message)
{
    var dti = new DistributedTraceInformation();
    dti.ParentSpanId = Convert.ToInt64(message.Tags[TracingConstants.ExternalParentSpanIdHeader], 16);
    dti.TraceId = Convert.ToInt64(message.Tags[TracingConstants.ExternalTraceIdHeader], 16);
    return dti;
}

此处的相关部分是 ExtractCorrelationData。 由于在创建出口时使用了 Message.AddTag 方法,因此 SDK 将在将消息发送到服务之前将相关标识(parent-span-id 和 trace-id)写入消息的 tags-list。

现在,该服务可以通过读取标签再次提取这些值(您可以通过前面的代码示例中用作键的常量来识别它们)。 然后,我们创建的 DistributedTraceInformation 实例将由 SDK 用于将新创建的出口附加到已存在的跟踪中。 因此,我们创建的条目范围将是本地服务上的出口的对应项。

拆分轨迹

有一些自动化跟踪执行过多的示例,并且创建的跟踪太长而无法理解。 例如,在长时间运行的后台任务的进度中,当服务器通过回调将更新推送到客户机时,这可能发生在长双工 WCF 通信中。 这种类型的通信将导致可能过长的跟踪,而无法显示在 UI 中。

在这种情况下,可以选择将跟踪分解为多个跟踪,更具体地说,将客户机的每个回调分解为一个单独的跟踪。 要执行此操作,您可以使用 CustomSpan 中的 CreateEntryForNewTrace 来停止当前跟踪,并从该点创建新的跟踪。 您可能想要在服务器端执行此操作,就在调用客户机之前:

var callbackChanell = OperationContext.Current.GetCallbackChannel<IMathResult>();
if (callbackChanell != null)
{
    using (var span = CustomSpan.CreateEntryForNewTrace(this))
    {
        callbackChanell.SendStatusUpdate(new MathArguments() { InParam = args.InParam, Progress = (float)i / args.InParam, Result = generator.Next(1000, 9999) });
    }
}

明白了吗?

现在,应该能够使用我们的 SDK 创建您的第一个定制跟踪。 如果您有任何问题 (或愿望!) 有关 SDK ,请随时通过 支持与我们联系。 快乐跟踪!