OpenTelemetry instrumentation for Go tests. Make your tests observable and traceable by streaming spans, metrics, and log events to any OTLP collector.
- Automatic span creation for tests and subtests
- Log interception (
t.Log→ OTEL span events) - Metrics recording (duration histogram, test counters)
- Setup/teardown tracing
- Manual span creation for operations under test
- OTLP export to any compatible collector
go get github.com/monkescience/spectraInitialize once in TestMain, then wrap each test with sp.New(t). The
examples below assume a package-level sp *spectra.Spectra so every test in
the package can use it.
package mypkg_test
import (
"log""os""testing""github.com/monkescience/spectra"
)
varsp*spectra.SpectrafuncTestMain(m*testing.M) {
varerrerrorsp, err=spectra.Init(
spectra.WithServiceName("my-service-tests"),
spectra.WithEndpoint("grpc://localhost:4317"),
spectra.WithInsecure(), // skip TLS verification for local collectors
)
iferr!=nil {
log.Fatalf("spectra init: %v", err)
}
defersp.Shutdown()
os.Exit(m.Run())
}funcTestFeature(t*testing.T) {
st, err:=sp.New(t)
iferr!=nil {
t.Fatalf("spectra: %v", err)
}
// Logs become span eventsst.Log("starting test")
// Add custom attributes to the test spanst.SetAttributes(attribute.String("feature", "login"))
// Subtests become child spansst.Run("validates_input", func(st*spectra.T) {
// test code...
})
}funcTestDatabaseQuery(t*testing.T) {
st, err:=sp.New(t)
iferr!=nil {
t.Fatalf("spectra: %v", err)
}
// Create a child span for the operation you're testingctx, span:=st.StartSpan("db-query")
deferspan.End()
_, err=db.Query(ctx, "SELECT ...")
iferr!=nil {
t.Fatalf("query: %v", err)
}
}funcTestWithFixtures(t*testing.T) {
st, err:=sp.New(t)
iferr!=nil {
t.Fatalf("spectra: %v", err)
}
// Setup gets its own spanst.Setup(func(ctx context.Context) {
seedDatabase(ctx)
})
// Teardown runs on cleanup with its own spanst.Teardown(func(ctx context.Context) {
cleanupDatabase(ctx)
})
st.Run("query", func(st*spectra.T) {
// test with seeded data...
})
}By default, spectra keeps its providers local so it does not interfere with
code under test that reads otel.GetTracerProvider(). If your code relies
on the global providers being spectra's, opt in:
sp, err:=spectra.Init(
spectra.WithServiceName("my-service-tests"),
spectra.WithEndpoint("grpc://localhost:4317"),
spectra.WithSetGlobalProviders(), // previous globals are restored on Shutdown
)| Option | Description |
|---|---|
WithServiceName(name) | Service name for telemetry (required) |
WithEndpoint(endpoint) | OTLP collector endpoint with scheme (required) |
WithInsecure() | gRPC: disable TLS. HTTPS: skip cert verification |
WithShutdownTimeout(d) | Graceful shutdown timeout (default: 5s) |
WithLogger(logger) | *slog.Logger for spectra's own operational messages (default: slog.Default()) |
WithTracerProvider(tp) | Use an existing trace.TracerProvider instead of creating one |
WithSetGlobalProviders() | Install spectra's providers as OTEL globals (off by default) |
WithoutTraces() | Disable trace collection |
WithoutMetrics() | Disable metrics collection |
WithoutLogs() | Disable log capture as span events |
The endpoint must include a scheme:
| Scheme | Protocol | TLS |
|---|---|---|
grpc://host:port | gRPC | Yes, use WithInsecure() to disable |
http://host:port | HTTP | No |
https://host:port | HTTPS | Yes, use WithInsecure() to skip cert verification |
Spectra surfaces a small set of sentinel errors you can branch on with
errors.Is:
| Error | When | Resolution |
|---|---|---|
ErrMissingServiceName | spectra.Init called without WithServiceName | Pass WithServiceName("…") |
ErrMissingEndpoint | spectra.Init called without WithEndpoint | Pass WithEndpoint("grpc://…") |
ErrInvalidEndpoint | Endpoint missing a scheme | Use grpc://, http://, or https:// |
ErrNotInitialized | sp.New(t) called before spectra.Init() or on a nil *Spectra | Call spectra.Init() in TestMain first |
ErrAlreadyShutdown | Operations attempted after sp.Shutdown() | Ensure tests run before shutdown |
- Test span per
sp.New()call - Child spans for subtests via
st.Run() - Setup/teardown spans
- Custom spans via
st.StartSpan() - Span status reflects test pass/fail/skip
| Metric | Type | Description |
|---|---|---|
test.duration | Histogram | Test execution time in seconds |
test.count | Counter | Number of tests by status (pass/fail/skip) |
All t.Log(), t.Error(), t.Fatal(), and t.Skip() calls are captured as span events with appropriate severity levels.
MIT