Tracing
- Prerequisites
- Run it locally
- Configure Exporter
- Configure Sampler
- Using the Tracer
- Create a Span
- Create a Child Span
- Shutdown the Tracer
- References
Prerequisites
- .NET Core 2.0+ .NET Framework 4.6.1+ is also supported.
- Docker for Desktop
Run it locally
- Clone the Opencensus C# repository:
git clone https://github.com/census-instrumentation/opencensus-csharp.git
- Change to the example directory:
cd src/Samples
- Build the sample by
dotnet build
- Run Zipkin in Docker container:
docker run -d -p 9411:9411 openzipkin/zipkin
- If you don’t have “Docker for Desktop” installed, follow this:
https://zipkin.io/pages/quickstart
dotnet run zipkin --uri=http://localhost:9411/api/v2/spans
- Navigate to Zipkin Web UI:
http://localhost:9411
- Click Find Traces, and you should see a trace.
- Click into that, and you should see the details.
How does it work
internal static object Run(string zipkinUri)
{
// 1. Configure exporter to export traces to Zipkin
var exporter = new ZipkinTraceExporter(
new ZipkinTraceExporterOptions()
{
Endpoint = new Uri(zipkinUri),
ServiceName = "tracing-to-zipkin-service",
},
Tracing.ExportComponent);
exporter.Start();
// 2. Configure 100% sample rate for the purposes of the demo
ITraceConfig traceConfig = Tracing.TraceConfig;
ITraceParams currentConfig = traceConfig.ActiveTraceParams;
var newConfig = currentConfig.ToBuilder()
.SetSampler(Samplers.AlwaysSample)
.Build();
traceConfig.UpdateActiveTraceParams(newConfig);
// 3. Tracer is global singleton. You can register it via dependency injection if it exists
// but if not - you can use it as follows:
var tracer = Tracing.Tracer;
// 4. Create a scoped span. It will end automatically when using statement ends
using (var scope = tracer.SpanBuilder("Main").StartScopedSpan())
{
Console.WriteLine("About to do a busy work");
for (int i = 0; i < 10; i++)
{
DoWork(i);
}
}
// 5. Gracefully shutdown the exporter so it'll flush queued traces to Zipkin.
Tracing.ExportComponent.SpanExporter.Dispose();
return null;
}
namespace Samples
{
using System;
using System.Collections.Generic;
using System.Threading;
using OpenCensus.Exporter.Zipkin;
using OpenCensus.Trace;
using OpenCensus.Trace.Config;
using OpenCensus.Trace.Sampler;
internal class TestZipkin
{
internal static object Run(string zipkinUri)
{
// 1. Configure exporter to export traces to Zipkin
var exporter = new ZipkinTraceExporter(
new ZipkinTraceExporterOptions()
{
Endpoint = new Uri(zipkinUri),
ServiceName = "tracing-to-zipkin-service",
},
Tracing.ExportComponent);
exporter.Start();
// 2. Configure 100% sample rate for the purposes of the demo
ITraceConfig traceConfig = Tracing.TraceConfig;
ITraceParams currentConfig = traceConfig.ActiveTraceParams;
var newConfig = currentConfig.ToBuilder()
.SetSampler(Samplers.AlwaysSample)
.Build();
traceConfig.UpdateActiveTraceParams(newConfig);
// 3. Tracer is global singleton. You can register it via dependency injection if it exists
// but if not - you can use it as follows:
var tracer = Tracing.Tracer;
// 4. Create a scoped span. It will end automatically when using statement ends
using (var scope = tracer.SpanBuilder("Main").StartScopedSpan())
{
Console.WriteLine("About to do a busy work");
for (int i = 0; i < 10; i++)
{
DoWork(i);
}
}
// 5. Gracefully shutdown the exporter so it'll flush queued traces to Zipkin.
Tracing.ExportComponent.SpanExporter.Dispose();
return null;
}
private static void DoWork(int i)
{
// 6. Get the global singleton Tracer object
ITracer tracer = Tracing.Tracer;
// 7. Start another span. If another span was already started, it'll use that span as the parent span.
// In this example, the main method already started a span, so that'll be the parent span, and this will be
// a child span.
using (OpenCensus.Common.IScope scope = tracer.SpanBuilder("DoWork").StartScopedSpan())
{
// Simulate some work.
ISpan span = tracer.CurrentSpan;
try
{
Console.WriteLine("Doing busy work");
Thread.Sleep(1000);
}
catch (ArgumentOutOfRangeException e)
{
// 6. Set status upon error
span.Status = Status.Internal.WithDescription(e.ToString());
}
// 7. Annotate our span to capture metadata about our operation
var attributes = new Dictionary<string, IAttributeValue>();
attributes.Add("use", AttributeValue.StringAttributeValue("demo"));
span.AddAnnotation("Invoking DoWork", attributes);
}
}
}
}
Configure Exporter
OpenCensus can export traces to different distributed tracing stores (such as Zipkin, Jeager, Stackdriver Trace). In (1), we configure OpenCensus to export to Zipkin, which is listening on localhost
port 9411
, and all of the traces from this program will be associated with a service name tracing-to-zipkin-service
.
// 1. Configure exporter to export traces to Zipkin.
ZipkinTraceExporter.createAndRegister(
"http://localhost:9411/api/v2/spans", "tracing-to-zipkin-service");
You can export trace data to different backends. Learn more in OpenCensus Supported Exporters.
Configure Sampler
Configure 100% sample rate, otherwise, few traces will be sampled.
// 2. Configure 100% sample rate, otherwise, few traces will be sampled.
ITraceConfig traceConfig = Tracing.TraceConfig;
ITraceParams currentConfig = traceConfig.ActiveTraceParams;
var newConfig = currentConfig.ToBuilder()
.SetSampler(Samplers.AlwaysSample)
.Build();
traceConfig.UpdateActiveTraceParams(newConfig);
There are multiple ways to configure how OpenCensus sample traces. Learn more in OpenCensus Sampling.
Using the Tracer
To start a trace, you first need to get a reference to the Tracer
(3). It can be retrieved as a global singleton.
// 3. Tracer is global singleton. You can register it via dependency injection if it exists
// but if not - you can use it as follows:
var tracer = Tracing.Tracer;
Create a Span
To create a span in a trace, we used the Tracer
to start a new span (4). A span must be closed in order to mark the end of the span. A scoped span (Scope
) implements IDisposable
, so when used within a using
block, the span will be closed automatically when exiting the block.
// 4. Create a scoped span, a scoped span will automatically end when closed.
// It implements AutoClosable, so it'll be closed when the try block ends.
using (var scope = tracer.SpanBuilder("Main").StartScopedSpan())
{
Console.WriteLine("About to do a busy work");
for (int i = 0; i < 10; i++)
{
DoWork(i);
}
}
Create a Child Span
The Run
method calls DoWork
a number of times. Each invocation also generates a child span. Take a look at DoWork
method.
private static void DoWork(int i)
{
// 6. Get the global singleton Tracer object
ITracer tracer = Tracing.Tracer;
// 7. Start another span. If another span was already started, it'll use that span as the parent span.
// In this example, the main method already started a span, so that'll be the parent span, and this will be
// a child span.
using (OpenCensus.Common.IScope scope = tracer.SpanBuilder("DoWork").StartScopedSpan())
{
// Simulate some work.
ISpan span = tracer.CurrentSpan;
try
{
Console.WriteLine("Doing busy work");
Thread.Sleep(1000);
}
catch (ArgumentOutOfRangeException e)
{
// 6. Set status upon error
span.Status = Status.Internal.WithDescription(e.ToString());
}
// 7. Annotate our span to capture metadata about our operation
var attributes = new Dictionary<string, IAttributeValue>();
attributes.Add("use", AttributeValue.StringAttributeValue("demo"));
span.AddAnnotation("Invoking DoWork", attributes);
}
}
Shutdown the Tracer
Traces are queued up in memory and flushed to the trace store (in this case, Zipkin) periodically, and/or when the buffer is full. In (5), we need to make sure that any buffered traces that had yet been sent are flushed for a graceful shutdown.
// 5. Gracefully shutdown the exporter so it'll flush queued traces to Zipkin.
Tracing.ExportComponent.SpanExporter.Dispose();
Set the Status of the span
We can set the status of our span to create more observability of our traced operations.
// 6. Set status upon error
span.Status = Status.Internal.WithDescription(e.ToString());
Create an Annotation
An annotation tells a descriptive story in text of an event that occurred during a span’s lifetime.
// 7. Annotate our span to capture metadata about our operation
var attributes = new Dictionary<string, IAttributeValue>();
attributes.Add("use", AttributeValue.StringAttributeValue("demo"));
span.AddAnnotation("Invoking DoWork", attributes);
References
Resource | URL |
---|---|
Zipkin project | https://zipkin.io/ |
Setting up Zipkin | Zipkin Codelab |
Zipkin C# exporter | https://www.nuget.org/packages/OpenCensus.Exporter.Zipkin |
C# exporters | C# exporters |
OpenCensus C# Trace package | https://www.nuget.org/packages/OpenCensus/ |