OtelWrap is a tool that generates a decorator implementation of any interfaces that can be used for instrumentation with Go OpenTelemetry library. Inspired by https://github.com/matryer/moq
Supporting:
- Any interface and any method with context.Context as the first parameter.
- Detecting error return and set the span's error status accordingly.
- Only tested using go generate.
- Interface embedding.
Using conventional tools.go file for pinning version in go.mod / go.sum.
// +build tools
package tools
import (
_ "github.com/QuangTung97/otelwrap"
)
And then download and install the binary with commands:
$ go mod tidy
$ go install github.com/QuangTung97/otelwrap
otelwrap [flags] -source-dir interface [interface2 interface3 ...]
--out string (required)
output file
Using go generate:
package example
import "context"
//go:generate otelwrap --out interface_wrappers.go . MyInterface
type MyInterface interface {
Method1(ctx context.Context) error
Method2(ctx context.Context, x int)
Method3()
}
The run go generate ./...
in your module. The generated file looks like:
// Code generated by otelwrap; DO NOT EDIT.
// github.com/QuangTung97/otelwrap
package example
import (
"context"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
// MyInterfaceWrapper wraps OpenTelemetry's span
type MyInterfaceWrapper struct {
MyInterface
tracer trace.Tracer
prefix string
}
// NewMyInterfaceWrapper creates a wrapper
func NewMyInterfaceWrapper(wrapped MyInterface, tracer trace.Tracer, prefix string) *MyInterfaceWrapper {
return &MyInterfaceWrapper{
MyInterface: wrapped,
tracer: tracer,
prefix: prefix,
}
}
// Method1 ...
func (w *MyInterfaceWrapper) Method1(ctx context.Context) (err error) {
ctx, span := w.tracer.Start(ctx, w.prefix+"Method1")
defer span.End()
err = w.MyInterface.Method1(ctx)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}
return err
}
// Method2 ...
func (w *MyInterfaceWrapper) Method2(ctx context.Context, x int) {
ctx, span := w.tracer.Start(ctx, w.prefix+"Method2")
defer span.End()
w.MyInterface.Method2(ctx, x)
}
To use the generated struct, simply wraps the original implementation. The generated code is very easy to read.
package example
import "go.opentelemetry.io/otel"
func InitMyInterface() MyInterface {
original := NewMyInterfaceImpl()
return NewMyInterfaceWrapper(original, otel.GetTracerProvider().Tracer("example"), "prefix")
}
Can also generate for interfaces in other packages:
package example
import "path/to/another"
var _ another.Interface1 // not necessary, only for keeping the import statement
//go:generate otelwrap --out interface_wrappers.go . another.Interface1 another.Interface2
Or generate to another package:
package example
import "context"
//go:generate otelwrap --out ../another/interface_wrappers.go . MyInterface
type MyInterface interface {
Method1(ctx context.Context) error
Method2(ctx context.Context, x int)
Method3()
}