This package is meant to simplify wrapping Gorm requests to databases with OpenTelemetry Tracing Spans. The functionality within the package is as of OpenTelemetry-go v0.2.1 and is subject to change fairly rapidly as the standard is evolved.
This package is B.Y.O.E. (Bring Your Own Exporter)
Metrics support coming soon!
Make sure you have the following:
- Docker
- Go 1.13
- cURL or Postman for testing
Run the following commands to create the testing environment:
docker run -d -p 5432:5432 -e POSTGRES_USER=testuser -e POSTGRES_PASSWORD=password! -e POSTGRES_DB=test --name postgres postgres:alpinedocker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 jaegertracing/all-in-one:1.16
JAEGER_HOST=127.0.0.1
DB_USER=testuser
DB_PASS=password!
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=test
DB_SSLMODE=disable
package main
import (
"context""encoding/json""fmt""io""io/ioutil""log""net/http""os""time""github.com/jdefrank/otgorm""github.com/go-chi/chi""github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/joho/godotenv/autoload""go.opentelemetry.io/otel/api/global""go.opentelemetry.io/otel/exporter/trace/jaeger"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
typeuserstruct {
IDuint`gorm:"primary_key" json:"id"`FirstNamestring`json:"firstName"`LastNamestring`json:"lastName"`CreatedAt time.Time`json:"createdAt"`UpdatedAt time.Time`json:"updatedAt"`
}
funcreadBody(bodyreader io.ReadCloser) (data []byte, errerror) {
body, err:=ioutil.ReadAll(io.LimitReader(bodyreader, 1048576))
iferr!=nil {
returnnil, err
}
iferr:=bodyreader.Close(); err!=nil {
returnnil, err
}
returnbody, nil
}
funchttpTraceWrapper(h http.Handler) http.Handler {
fn:=func(w http.ResponseWriter, r*http.Request) {
t:=global.TraceProvider().Tracer("component-http")
ctx, span:=t.Start(r.Context(), r.URL.Path)
r=r.WithContext(ctx)
h.ServeHTTP(w, r)
span.End()
}
returnhttp.HandlerFunc(fn)
}
// NOTE: I've found that if you'd like to separate services in the Jaeger UI,// you'll need to create multiple exporters which in turn will show you different colors// per service.funcinitTracer() func() {
//Create Jaeger exporterexporter, err:=jaeger.NewExporter(
jaeger.WithCollectorEndpoint(fmt.Sprintf("http://%s:14268/api/traces", os.Getenv("JAEGER_HOST"))),
jaeger.WithProcess(jaeger.Process{
ServiceName: "go-otel-gorm",
}),
)
iferr!=nil {
log.Fatal(err)
}
// For demoing purposes, always sample. In a production application, you should// configure this to a trace.ProbabilitySampler set at the desired// probability.tp, err:=sdktrace.NewProvider(
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithSyncer(exporter))
iferr!=nil {
log.Fatal(err)
}
global.SetTraceProvider(tp)
returnfunc() {
exporter.Flush()
}
}
funcmain() {
//Setup the exporter and defer close until main exitsfn:=initTracer()
deferfn()
// Connect to databaseconnString:=fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s",
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_USER"),
os.Getenv("DB_NAME"),
os.Getenv("DB_PASS"),
os.Getenv("DB_SSLMODE"),
)
db, err:=gorm.Open("postgres", connString)
iferr!=nil {
panic(err)
}
//Register callbacks for GORM, while also passing in config Optsotgorm.RegisterCallbacks(db,
otgorm.WithTracer(global.TraceProvider().Tracer("component-gorm")),
otgorm.Query(true),
otgorm.AllowRoot(true),
)
//Run migration and create a recorddb.AutoMigrate(user{})
newUser:=user{
FirstName: "John",
LastName: "Smith",
}
//Since this first DB call is outside of a parent,//lets set up empty context and the DB client with that contextctx:=context.Background()
orm:=otgorm.WithContext(ctx, db)
err=orm.Create(&newUser).Erroriferr!=nil {
log.Print(err)
}
//Create routerr:=chi.NewRouter()
//Register Endpoints for the APIr.Post("/user", func(w http.ResponseWriter, r*http.Request) {
orm:=otgorm.WithContext(r.Context(), db)
varuuserbody, err:=readBody(r.Body)
iferr!=nil {
log.Print(err)
return
}
err=json.Unmarshal(body, &u)
iferr!=nil {
log.Print(err)
return
}
err=orm.Create(&u).Erroriferr!=nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("{\"Error\":\""+err.Error() +"\"")
return
}
w.WriteHeader(http.StatusOK)
return
})
http.ListenAndServe(":3000", httpTraceWrapper(r))
}The MIT License (MIT). Please see License File for more information.