Reference
OpenTelemetry
Reference for go-sequent's optional obs package: spans and metrics for command latency, success and error counts, optimistic-concurrency conflicts and outbox depth, plus provider setup and limits.
OpenTelemetry
Package: obs
The obs package gives operators a view of the write path through the vendor-neutral OpenTelemetry API. It is optional. With no provider configured, every instrument is a no-op.
The instrumentation name is github.com/go-sequent/go-sequent.
Purpose
CommandService.Execute is where latency and conflicts show up for a ledger: transaction, append, SyncInTxn projectors. obs exposes command duration, outcome, optimistic-concurrency conflicts and, optionally, outbox depth. You do not have to pick an APM inside the library.
OpenTelemetry data is a runtime signal. It is not the audit source of truth. Reconciliation and compliance still rely on the event stream, the hash chain, and the command records (command.AuditOf, commands table).
What you can see
- Slow commands:
sequent.command.duration_msbycommand.type. - Success and error rates:
sequent.command.totalwithstatus=ok|error. - Conflict storms on hot aggregates:
sequent.optimistic_conflict.total. - Outbox backlog:
sequent.outbox.depth, when you supply a depth function.
Wiring
obs is not command middleware. Attach it with the façade option, which sets App.Obs and Service.Obs:
// App side: install a TracerProvider and MeterProvider and an exporter first.
inst, err := obs.New(obs.Config{
OutboxDepth: func(ctx context.Context) int64 {
n, _ := ob.Depth(ctx)
return n
}, // optional; nil skips sequent.outbox.depth
})
app := sequent.New(sequent.Config{},
sequent.WithObs(inst),
sequent.WithStore(store, tx),
)
You can also assign svc.Obs = inst directly. nil means no telemetry.
You own the providers
obs.New uses the global otel.Tracer and otel.Meter. If your application has not called otel.SetTracerProvider and otel.SetMeterProvider, the instruments are no-ops, which is safe in tests. The library does not ship OTLP, Prometheus or stdout exporters. The host process installs those.
What Execute does with it
StartCommandstarts the spansequent.Execute.- The transaction and UnitOfWork run.
- On
ErrOptimisticConcurrencyfrom Commit,RecordConflictincrements the conflict counter. RecordCommandrecords latency and the outcome counter, then ends the span.
A batched Execute uses command.type equal to the first command’s type plus +batch.
Emitted spans and metrics
| Kind | Name | Notes |
|---|---|---|
| Span | sequent.Execute |
Attribute command.type. On error: RecordError and status Error. |
| Histogram | sequent.command.duration_ms |
command.type; milliseconds |
| Counter | sequent.command.total |
command.type, status = ok or error |
| Counter | sequent.optimistic_conflict.total |
No extra attributes |
| Counter | sequent.events.appended |
The API exists. The framework does not call it on append. |
| Observable gauge | sequent.outbox.depth |
Only if Config.OutboxDepth is set |
The public API is Instruments, Config, New, StartCommand, RecordCommand, RecordConflict and RecordEvents.
What it does not do
- No bundled exporter, and it does not create providers.
- No automatic HTTP or gRPC instrumentation.
- No spans for projectors, workflows or the outbox poller.
RecordEventsis not hooked intoRepository.CommitorEventStore.Append. It exists for apps to call at their own boundary, sosequent.events.appendedstays at zero unless you do.- It does not replace the audit trail.
Keep secrets and personal data out of span attributes and logs. The built-in attributes contain only the command type.
Using the signals
- Alert on a rising
errorrate and on p99 ofsequent.command.duration_msper command type. - A growing
sequent.optimistic_conflict.totalpoints to contended aggregates or to a failover catching up. See Unique keys and OCC and Failover. - A steadily climbing
sequent.outbox.depthmeans the poller is behind or stuck. See Publisher.
Related
Docs track the go-sequent library. When APIs drift, the source wins.