Tracing

An activity is a trace that represents a single operation happening within a service.
Typically you use it to measure how fast parts were what happened.
| Thing | Purpose |
ActivitySource | Factory for activities |
Activity | One trace or span |
Activity.Current | The current active activity in this async flow |
Tags | Searchable metadata on this flow |
Status | Whether the activity succeeded or failed |
Basic Setup
using System.Diagnostics;
namespace MyService.Tracing;
public static class MyActivitySource
{
public const string Name = "MyService";
public static readonly ActivitySource Instance = new(Name);
}
Within the worker
using var activity = MyActivitySource.Instance.StartActivity(
"rabbit.process_message",
ActivityKind.Consumer);Set Tags
activity?.SetTag("messaging.system", "rabbitmq");
activity?.SetTag("messaging.destination.name", queueName);
activity?.SetTag("messaging.rabbitmq.routing_key", routingKey);
activity?.SetTag("app.transaction_id", transactionId);Set Success
activity?.SetTag("app.message_outcome", "completed");
activity?.SetStatus(ActivityStatusCode.Ok);Set Failure
activity?.SetTag("app.message_outcome", "dead_lettered");
activity?.SetTag("app.message_failure_reason", "retry_exhausted");
activity?.SetStatus(ActivityStatusCode.Error, "retry_exhausted");Activity Current
Just allows you to grab activity from the current async context
Activity.Current?.SetTag("app.message_outcome", "sent_to_retry");
Activity.Current?.SetStatus(ActivityStatusCode.Error, "service_error");Nesting Activities
You can nest activities, and the child activities will be linked to their parent.
Measure Speed
Note that the timer only gets set after the activity was disposed.
using (var activity = TransactionTaggingActivities.Start(ActivityName))
{
val = await SomeMethod();
activity?.SetTag(TagName, val.someMeaningfullValue);
}Testing
For local testing you will need to add an activity listener. Without this the activity will be null and not populate.
ActivitySource.AddActivityListener(new ActivityListener
{
ShouldListenTo = source =>
source.Name == ActivityNames.SourceName,
Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllDataAndRecorded,
SampleUsingParentId = (ref ActivityCreationOptions<string> _) =>
ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = activity =>
{
Console.WriteLine($"START {activity.OperationName}");
},
ActivityStopped = activity =>
{
Console.WriteLine($"STOP {activity.OperationName}");
}
});Start your service, then in a separate terminal run.
dotnet-trace ps
dotnet-trace collect --process-id <PID> --providers "Microsoft-Diagnostics-DiagnosticSource:0xFFFFFFFFFFFFFFFF:5"Then open PerfView, and open the generated .nettrace file.
What tags should I use?
https://opentelemetry.io/docs/specs/semconv/registry/attributes/messaging/
If using OTel I recommend using as much as possible from the list above, it is great because it points you for specific things like GCP and Rabbit.
Then anything that is specific to your service.
Example
using var activity = MyActivitySource.Instance.StartActivity(
"rabbit.process_message",
ActivityKind.Consumer);
activity?.SetTag("messaging.system", "rabbitmq");
activity?.SetTag("messaging.destination.name", queueName);
activity?.SetTag("messaging.rabbitmq.routing_key", routingKey);
try
{
await ProcessMessage();
activity?.SetTag("app.message_outcome", "completed");
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity?.SetTag("app.message_outcome", "failed");
activity?.SetTag("exception.type", ex.GetType().FullName);
activity?.SetTag("exception.message", ex.Message);
activity?.SetStatus(ActivityStatusCode.Error, "unexpected_exception");
throw;
}