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