.NET or .NET Core Tracing SDK

Tracing with Instana is automatic but if you want even more visibility into custom code, a specific application area, or some in-house component, you can use Instana's .NET or .NET Core Tracing SDK.

Installing the SDK

Instana provides the SDK for .NET or .NET Core as a nuget package on nuget.org's official feed. Search for Instana.Tracing.Core.Sdk to find and add it to your project.

Tracing your own code

The following examples demonstrate how to add distributed tracing capabilities to your code.

The SDK is under active development, so make sure to check this page whenever you update to ensure that you use the latest APIs.

Create a simple span

The simplest way to trace a method invocation is to use the CustomSpan.Create API.

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

The preceding code creates an intermediate span, representing your method and the time spent within it.

If you want to create an entry or exit span instead of using an intermediate span, you can use convenience APIs (called CustomSpan.CreateEntry for entry span and CustomSpan.CreateExit for exit span).

Create a simple span and capture exceptions

If you want to annotate the created spans with any errors that occur during the processing of the method's body, you can do it manually by using the CustomSpan.SetError API as follows:

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

A simpler and preferred approach is to use the CustomSpan.WrapAction or CustomSpan.Wrap<T> APIs instead.

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);
    }
}

If the code block you want to wrap returns something that you need for further processing, use the CustomSpan.Wrap<T> API instead.

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);
    }
}

So now you know how to create entries, exits, and intermediates. You also learned how to capture exceptions by using either the SetError or WrapAction (or Wrap<T>) APIs.

Adding data to your spans

A span by itself merely consists of a timing, callstack, and name. It is not that helpful in most scenarios. So you need to add some data to your span. A span can contain Data and Tags, for which the CustomSpan class offers simple APIs.

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);
    }
}

Data is being transported to the Instana backend and can be downloaded from the Instana UI by downloading a trace. However, the data that is shown here is not displayed in the Instana UI.

Adding tags to your spans

Instead of using SetData, you can also use SetTag, which takes an array of strings as the key, which can be used to structure the data that you pass to the span as a hierarchy.

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);
    }
}

Tags are displayed in the Call Details view directly and can also be searched for in Unbounded analytics.

Mapping your custom spans to a service

You usually want to associate your custom spans with a logical service in Instana's application perspectives, which is as easy as calling the SetServicename API (which just takes a string). To distinguish between endpoints that are implemented by using the SDK, you can also provide an endpoint for more detailed mapping with the SetEndpointName API.

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

While you can set a service and endpoint on every span, these settings are discarded in INTERMEDIATE spans (they are inherited from the last preceding ENTRY).

Capturing the results of your span

Say the method that you instrument returns a value. You can assume that having this value in your trace helps in troubleshooting. Enter the 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;
    }
}

Nesting spans

Nesting spans is as easy as calling one method from another. For example, you can assume that one method serves as an entry, while its child span is an intermediate.

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;
    }
}

The result of this span is an entry-span with an intermediate span as child. Technically, nesting has no limits on depth, but you must not create spans in a deep recursion.

Looking at the distributed in distributed tracing

All the spans that were discussed so far were limited to one service. They never reached out to some other component, which also traced its activity. To achieve a true distributed trace across service boundaries, you need to apply some correlation.

Correlation in tracing describes how to set the correlation data on an exit call, and how you obtain this data back and continue this context on the entry of the other component.

To achieve this correlation, CustomSpan has overloads of the CustomSpan.CreateExit and CustomSpan.CreateEntry methods.

While the CustomSpan.CreateExit method can use Action<string, string> as an argument, CustomSpan.CreateEntry takes Func<DistributedTraceInformation>.

For example, assume that you have a small Message class, which you pass to the remote service that you are calling.

    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);
        }
    }

The relevant part in the message is the AddTag method, which takes two strings. This method is the signature that you need to provide to CustomSpan.CreateExit.

By using this method when you call CreateExit, you write the correlation-data to the instance of 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);
        }
    }
}

So when your message leaves the scope of your local component with a call to service.ValidateRequest(message), the message carries the correlation data in its list of tags. You can see how these tags are extracted on the callee site.

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;
}

The relevant part in the message is ExtractCorrelationData. Because you used the Message.AddTag method on creating the exit, the SDK writes the relevant id (parent-span-id and trace-id) to the tags-list of the message before the SDK sends it to the service.

Now the service can extract these values again by reading the tags (you can identify them by the constants that are used as keys in the preceding code). The instance of DistributedTraceInformation, which you create is then used by the SDK to append the newly created exit to the existing trace. The entry span that you create are thus the counterpart of the exit on the local service.

Splitting traces

You can find examples where automated tracing does too much, and the trace that gets created is too long to be comprehended. For example, such instances can happen in long duplex WCF communication when the server pushes updates to the clients through callback on the progress of a long-running background task. Such communication results in a long trace that can potentially be too long to be displayed in the Instana UI.

In such instances, you can break the trace into multiple traces. More specifically, you can break each callback to the client into a separate trace. To use this option, you can use CreateEntryForNewTrace from CustomSpan to stop the current trace and to create a new one from that point on. You can do that on the server side, just before the call to the client:

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) });
    }
}

If you have any questions about the .NET or .NET Core SDK, contact IBM support.