Observability — Logging and Tracing

Benevia Core uses standard .NET telemetry: Microsoft.Extensions.Logging for logs and System.Diagnostics.ActivitySource (OpenTelemetry-compatible) for traces. There is no custom abstraction.

What Core emits

  • An ActivitySource named Benevia.Core with spans for entity events (Compute, Validate, Changed, PreSave, Added, Deleted), OData operations, and workflow steps.
  • Standard tags on the active ASP.NET Core request span:
    • tenant.id and user.id after authentication
    • workflow.id and workflow.index when those headers are present

Wiring up OpenTelemetry

Core does not configure an exporter — that is the host application's choice. A typical setup adds the Benevia.Core source, plus Npgsql for database spans:

builder.Services
    .AddOpenTelemetry()
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddSource("Benevia.Core")
        .AddSource("Npgsql")
        .AddOtlpExporter())
    .WithLogging();

Send to whichever backend you prefer (Jaeger, Sentry via the OpenTelemetry bridge, Application Insights, etc.).

Logging from business logic

Inject ILogger<T> like any other service. Use structured logging so fields stay searchable.

using Microsoft.Extensions.Logging;

[Logic]
public class SalesOrderBL(ILogger<SalesOrderBL> logger)
{
    public void Submit(SalesOrder.Logic salesOrder)
    {
        salesOrder.OnSubmit(order =>
        {
            logger.LogInformation("Submitting order {OrderId} for {CustomerId}",
                order.Id, order.CustomerId);

            // ...
        });
    }
}

Tagging the current request span

To enrich the active span without creating a new one, set tags on Activity.Current:

using System.Diagnostics;

Activity.Current?.SetTag("checkout.path", "express");

These tags appear on the request span in your tracing backend and are searchable like any other attribute.