Neos tracer
Neos provides the INeosTracer interface to create custom tracing activities in your business code. This allows you to add detailed spans for specific operations, making it easier to debug and monitor your application in Jaeger.
Injecting the tracer
The INeosTracer is available through dependency injection. Simply inject it into your class constructor:
using GroupeIsa.Neos.Shared.Tracing;
public class OrderProcessingService
{
private readonly INeosTracer _tracer;
public OrderProcessingService(INeosTracer tracer)
{
_tracer = tracer;
}
}
Starting an activity
Use StartActivity to create a new tracing span. The activity is automatically linked to the current trace context:
public async Task ProcessOrderAsync(Order order)
{
// Create a new activity for this operation
using INeosActivity activity = _tracer.StartActivity("ProcessOrder");
// Add tags to provide context in Jaeger
activity.SetTag("order.id", order.Id);
activity.SetTag("order.amount", order.TotalAmount);
// Your business logic here
await ValidateOrderAsync(order);
await SaveOrderAsync(order);
// The activity is automatically stopped when disposed
}
Activity kinds
You can specify the type of activity using ActivityKind:
| Kind | Description | Usage |
|---|---|---|
Internal |
Default. Internal operation within your application | Business logic, data processing |
Client |
Outgoing request to another service | HTTP calls, service invocations |
Server |
Incoming request handler | API endpoints |
Producer |
Message producer | Publishing events to a topic |
Consumer |
Message consumer | Handling subscribed events |
// Example: tracing an outgoing HTTP call
using INeosActivity activity = _tracer.StartActivity("CallExternalApi", ActivityKind.Client);
activity.SetTag("http.url", apiUrl);
activity.SetTag("http.method", "POST");
Setting tags
Tags provide searchable metadata for your traces in Jaeger. The INeosActivity interface offers several methods:
using INeosActivity activity = _tracer.StartActivity("ImportData");
// Set individual tags
activity.SetTag("file.name", fileName);
activity.SetTag("record.count", recordCount);
// Set multiple tags from a dictionary
activity.SetTags(new Dictionary<string, string>
{
["source.type"] = "CSV",
["encoding"] = "UTF-8",
});
// Fluent API with WithTag
using INeosActivity activity2 = _tracer.StartActivity("ExportData")
.WithTag("format", "JSON")
.WithTag("compress", true);
Setting activity status
Mark the activity status to indicate success or failure:
using INeosActivity activity = _tracer.StartActivity("ProcessPayment");
try
{
await ProcessPaymentAsync();
activity.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
{
activity.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
Root activities
Use StartRootActivity when you need to start a new trace that is not linked to the current context:
// Start a completely new trace (useful for background jobs)
using INeosActivity rootActivity = _tracer.StartRootActivity("BackgroundJob.ProcessQueue");
Activities with parent ID
When receiving a trace context from an external source (e.g., message queue metadata), you can link your activity to it:
public async Task HandleMessageAsync(Message message)
{
// Get the trace parent from the message metadata
string? traceParent = message.Metadata.GetValueOrDefault("traceParentId");
// Create an activity linked to the parent trace
using INeosActivity activity = _tracer.StartActivity("HandleMessage", traceParent, ActivityKind.Consumer);
activity.SetTag("message.type", message.Type);
await ProcessMessageAsync(message);
}
Best practices
Tip
Naming conventions: Use descriptive names with a prefix emoji for visual identification in Jaeger:
✉️for messaging operations↔️for service invocations📦for data operations⚙️for internal processing
Important
Always wrap activities in a using statement to ensure they are properly disposed and their duration is recorded.
Caution
Avoid creating too many fine-grained activities as this can impact performance and make traces harder to read. Focus on significant operations.
Complete example
Here is a complete example showing tracing in a business service:
using System.Diagnostics;
using GroupeIsa.Neos.Shared.Tracing;
public class OrderProcessingService
{
private readonly INeosTracer _tracer;
private readonly IOrderRepository _orderRepository;
private readonly IPaymentService _paymentService;
public OrderProcessingService(
INeosTracer tracer,
IOrderRepository orderRepository,
IPaymentService paymentService)
{
_tracer = tracer;
_orderRepository = orderRepository;
_paymentService = paymentService;
}
public async Task<OrderResult> ProcessOrderAsync(Order order)
{
using INeosActivity activity = _tracer.StartActivity("📦 ProcessOrder");
activity.SetTag("order.id", order.Id);
activity.SetTag("order.customer", order.CustomerId);
try
{
// Validate order
using (INeosActivity validateActivity = _tracer.StartActivity("⚙️ ValidateOrder"))
{
validateActivity.SetTag("items.count", order.Items.Count);
await ValidateOrderAsync(order);
}
// Process payment
using (INeosActivity paymentActivity = _tracer.StartActivity("↔️ ProcessPayment", ActivityKind.Client))
{
paymentActivity.SetTag("payment.amount", order.TotalAmount);
paymentActivity.SetTag("payment.currency", order.Currency);
await _paymentService.ChargeAsync(order);
}
// Save order
using (INeosActivity saveActivity = _tracer.StartActivity("📦 SaveOrder"))
{
await _orderRepository.SaveAsync(order);
}
activity.SetStatus(ActivityStatusCode.Ok);
return new OrderResult { Success = true };
}
catch (Exception ex)
{
activity.SetStatus(ActivityStatusCode.Error, ex.Message);
activity.SetTag("error.type", ex.GetType().Name);
throw;
}
}
}