.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.
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 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.