OTEL Abstraction Example
I've been meaning to try and capture my thoughts around how Labkit will eventually evolve and provide value. With the request to add OTEL logging, metrics, and tracing, this has felt a little more urgent.
The pattern captured in this MR is something that has been previously used to good effect at a previous employer that opted for a SOA-style architecture. It provides a light abstraction over the implementation specifics around OTEL which can be expanded over time. With the push for a chocolate cookie architecture nailing these primitives is going to be more important in the long-term.
Note: the app package included in this request represents the package that will be ported to Labkit
Application Startup:
The key entry point for this would be at application startup:
application, err := app.NewApp(context.Background())
if err != nil {
return err
}
We can then pass down the instantiated reporter through our code:
domainSvc := domain.New(application.Reporter)
The hope is that the application
struct will handle some of the complexities around things like readiness and liveness checks, consistently setting up reporters, and component lifecycle management (cleaning up connections on shutdown etc).
We'll want the additional features to be opt-in so that we can serve the application use-case and say the CLI use-cases.
Reporter:
The reporter concept is something that abstracts the likes of metrics, traces and logs and provides a simple abstraction over these things.
This is instantiated within the application and can be passed to components via their constructors: domain.New(app.Reporter)
It can then be invoked like so:
s.reporter.Info(ctx, "hello method hit")
It's fairly straightforward, but it requires you to pass in the ctx
key as the first argument. This is important as it'll allow us to propagate contextual information along the lifecycle of a request.
One off fields you'd like to emit in a log message can be done like so:
s.reporter.Info(ctx, "another thing happened", app.Fields{
"something": "else",
})
This concept will be extended in the future to also support both metrics and tracing - this does represent a bit of a DSL to learn, but with IDE setups, this shouldn't be a large hurdle.
Tracing
Tracing is yet to be implemented, but it would be straightforward to extend the interface that is exposed so that we can expose simple methods:
// psuedocode examples:
s.reporter.Span(ctx, "my-span-name")
s.reporter.SpanAttributes(ctx, app.Fields{"http.status": 200})
s.reporter.SpanError(ctx, err)
s.reporter.SpanEvent(ctx, "event-name", app.Fields{})
Metrics
Same as above, we could enable metrics like so:
s.reporter.Incr(ctx, "my-metric")
s.reporter.Decr(ctx, "my-metric")
s.reporter.Timing(ctx, "timing-metric", time.Duration, app.Fields{})
Example log output:
{"Timestamp":"2025-10-22T17:47:36.341322+01:00","ObservedTimestamp":"2025-10-22T17:47:36.341337+01:00","Severity":9,"SeverityText":"info","Body":{"Type":"String","Value":"hello method hit"},"Attributes":[{"Key":"hello","Value":{"Type":"String","Value":"world"}}],"TraceID":"00000000000000000000000000000000","SpanID":"0000000000000000","TraceFlags":"00","Resource":[{"Key":"service.name","Value":{"Type":"STRING","Value":"unknown_service:main"}},{"Key":"telemetry.sdk.language","Value":{"Type":"STRING","Value":"go"}},{"Key":"telemetry.sdk.name","Value":{"Type":"STRING","Value":"opentelemetry"}},{"Key":"telemetry.sdk.version","Value":{"Type":"STRING","Value":"1.38.0"}}],"Scope":{"Name":"go-service-template","Version":"","SchemaURL":"","Attributes":{}},"DroppedAttributes":0}
{"Timestamp":"2025-10-22T17:47:36.341515+01:00","ObservedTimestamp":"2025-10-22T17:47:36.341516+01:00","Severity":9,"SeverityText":"info","Body":{"Type":"String","Value":"another thing happened"},"Attributes":[{"Key":"hello","Value":{"Type":"String","Value":"world"}}],"TraceID":"00000000000000000000000000000000","SpanID":"0000000000000000","TraceFlags":"00","Resource":[{"Key":"service.name","Value":{"Type":"STRING","Value":"unknown_service:main"}},{"Key":"telemetry.sdk.language","Value":{"Type":"STRING","Value":"go"}},{"Key":"telemetry.sdk.name","Value":{"Type":"STRING","Value":"opentelemetry"}},{"Key":"telemetry.sdk.version","Value":{"Type":"STRING","Value":"1.38.0"}}],"Scope":{"Name":"go-service-template","Version":"","SchemaURL":"","Attributes":{}},"DroppedAttributes":0}