Skip to content

Observability

Granit.Observability wires Serilog structured logging and OpenTelemetry (traces + metrics) into a single AddGranitObservability() call. Logs ship to Loki, traces to Tempo, and metrics to Mimir — all via an OTLP collector.

PackageRoleDepends on
Granit.ObservabilitySerilog + OpenTelemetry (traces, metrics, logs)Granit.Core
graph LR
    App[ASP.NET Core App] --> Serilog
    App --> OTel[OpenTelemetry SDK]

    Serilog -->|WriteTo.OpenTelemetry| Collector[OTLP Collector :4317]
    OTel -->|OTLP gRPC| Collector

    Collector --> Loki[Loki — Logs]
    Collector --> Tempo[Tempo — Traces]
    Collector --> Mimir[Mimir — Metrics]

    Loki --> Grafana
    Tempo --> Grafana
    Mimir --> Grafana
[DependsOn(typeof(GranitObservabilityModule))]
public class AppModule : GranitModule { }
{
"Observability": {
"ServiceName": "my-backend",
"ServiceVersion": "1.2.0",
"OtlpEndpoint": "http://otel-collector:4317",
"ServiceNamespace": "my-company",
"Environment": "production"
}
}

AddGranitObservability() configures two Serilog sinks:

SinkPurpose
ConsoleLocal development, [HH:mm:ss LEV] SourceContext Message
OpenTelemetryOTLP export to Loki via the collector

Every log entry is enriched with ServiceName, ServiceVersion, and Environment properties, matching the OpenTelemetry resource attributes for correlation.

Additional Serilog settings (minimum level, overrides, extra sinks) can be added via standard Serilog configuration in appsettings.jsonReadFrom.Configuration is called before the Granit enrichers.

Three built-in instrumentations are registered automatically:

InstrumentationWhat it captures
ASP.NET CoreInbound HTTP requests (method, route, status code)
HttpClientOutbound HTTP calls (dependency tracking)
EF CoreDatabase queries (command text, duration)

Health check endpoints (/health/*) are filtered out of traces to avoid noise.

Granit modules register their own ActivitySource names via GranitActivitySourceRegistry.Register() during host configuration. AddGranitObservability() reads the registry and calls AddSource() for each — no manual wiring needed.

// Inside a module's AddGranit*() extension method
GranitActivitySourceRegistry.Register("Granit.Workflow");
ActivitySourceSpan names
Granit.Vaultvault.encrypt, vault.decrypt, vault.get-secret, vault.check-rotation
Granit.Vault.Azureakv.encrypt, akv.decrypt, akv.get-secret, akv.check-rotation
Granit.Wolverinewolverine.send, wolverine.handle
Granit.Notificationsnotification.dispatch, notification.deliver
Granit.Notifications.Email.Smtpsmtp.send
Granit.Notifications.Email.AwsSesses.send
Granit.Notifications.Email.AzureCommunicationServicesacs-email.send
Granit.Notifications.Sms.AzureCommunicationServicesacs-sms.send
Granit.Notifications.MobilePush.AzureNotificationHubsanh.send
Granit.Notifications.Brevobrevo.send
Granit.Notifications.Zulipzulip.send
Granit.Workflowworkflow.transition
Granit.BlobStorageblob.upload, blob.download, blob.delete
Granit.DataExchangeimport.execute, export.execute
PropertyTypeDefaultDescription
ServiceNamestring"unknown-service"Service name for OTEL resource
ServiceVersionstring"0.0.0"Service version
OtlpEndpointstring"http://localhost:4317"OTLP gRPC endpoint
ServiceNamespacestring"my-company"Service namespace
Environmentstring"development"Deployment environment
EnableTracingbooltrueEnable trace export via OTLP
EnableMetricsbooltrueEnable metrics export via OTLP
CategoryKey typesPackage
ModuleGranitObservabilityModule
OptionsObservabilityOptionsGranit.Observability
RegistryGranitActivitySourceRegistryGranit.Core
ExtensionsAddGranitObservability()Granit.Observability