Table of Contents

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;
        }
    }
}

See also