.NET Framework 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 Tracing SDK as detailed as follows.

Installing the SDK

We provide the SDK for .NET as a nuget-package on nuget.org's official feed. Search for Instana.ManagedTracing.Sdk to find and add it to your project. After installation, your project will have two additional references (Instana.ManagedTracing.Sdk and Instana.ManagedTracing.Api).

Tracing your own code

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

The SDK is under active development, so make sure to revisit this page whenever updating to ensure up-to-date APIs usage.

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 code above will create an intermediate span, representing your method and the time spent within it.

If instead of using an intermediate you rather wanted to create an Entry or an Exit-span, there are convenience APIs for that (called CustomSpan.CreateEntry and CustomSpan.CreateExit respectively).

Create a simple span and capture exceptions

In case you want to annotate the created spans with any errors occurring during the processing of the method's body, you could of course do it manually, using the CustomSpan.SetError API like this.

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

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 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 we know how to create entries, exits and intermediates. We also learned how to capture exceptions using either the SetError or WrapAction / Wrap<T> APIs.

Adding data to your spans

A span by itself merely consists of a timing, a callstack and a name. That's good, but not very helpful in most scenarios. So how about adding 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 backend and can be downloaded from the UI by downloading a trace. However, the data put 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 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 will be 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. This is as easy as calling the SetServicename API, which simply takes a string. To distinguish between endpoints implemented 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, it is important to notice that these settings will be discarded in case of INTERMEDIATE spans (they will be inherited from the last preceeding ENTRY)

Capturing the result of your span

Say the method you instrument returns a value. Let's assume that having this value in your trace would be helpful for 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 completeness, here is an example. We 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 would be an entry-span, having an intermediate span as child. Technically, nesting has no limits on depth, but you should not create spans in a deep recursion.

Looking at the distributed in distributed tracing

All the spans we have been looking at so far were limited to one service. They never reached out to some other component, which would also trace 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, CustomSpan has overloads of the CustomSpan.CreateExit and CustomSpan.CreateEntry methods.

While CustomSpan.CreateExit can consume an Action<string, string> as an argument, CustomSpan.CreateEntry will take a Func<DistributedTraceInformation>.

So how does that work?

Assume we have a small Message class, which we pass to the remote service we 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 here is the AddTag method, which takes two strings. This is the signature we need to provide to CustomSpan.CreateExit.

Using this method when calling CreateExit will write the correlation-data to our 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 our message leaves the scope of our local component with a call to service.ValidateRequest(message), it carries the correlation data in its list of tags. Let's see how these would be 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 here is ExtractCorrelationData. Since we used the Message.AddTag method on creating the exit, the SDK would write the relevant id (parent-span-id and trace-id) to the tags-list of the message before sending it to the service.

Now the service can extract these values again by reading the tags (we can identify them by the constants used as keys in the code above). The instance of DistributedTraceInformation which we create will then be used by the SDK to append the newly created exit to the already existing trace. The entry span we create will thus be the counterpart of the exit on the local service.

Splitting traces

There are examples when automated tracing does too much, and the trace that gets created is too long to be comprehended. For instance, this can happen in long duplex WCF communication when the server pushes updates to the clients via callback on the progress of a long-running background task. This kind of communication will result in a long trace that could potentially be too long to be displayed in the UI.

In such instances, there is an option to break the trace into multiple traces, more specifically, to break each callback to the client into a separate trace. To do so, you can use CreateEntryForNewTrace from CustomSpan to stop the current trace and to create a new one from that point on. You would want to 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) });
    }
}

Got it?

By now you should be able to create your first custom traces using our SDK. If you have any questions (or wishes!) regarding the SDK, feel free to contact us via support. Happy tracing!