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