OpenTelemetry Package for the Gorm ORM
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!
Example Usage
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:alpine
docker 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
.env File
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
Example App
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"
)
type user struct {
ID uint `gorm:"primary_key" json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func readBody(bodyreader io.ReadCloser) (data []byte, err error) {
body, err := ioutil.ReadAll(io.LimitReader(bodyreader, 1048576))
if err != nil {
return nil, err
}
if err := bodyreader.Close(); err != nil {
return nil, err
}
return body, nil
}
func httpTraceWrapper(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()
}
return http.HandlerFunc(fn)
}
func initTracer() func() {
exporter, err := jaeger.NewExporter(
jaeger.WithCollectorEndpoint(fmt.Sprintf("http://%s:14268/api/traces", os.Getenv("JAEGER_HOST"))),
jaeger.WithProcess(jaeger.Process{
ServiceName: "go-otel-gorm",
}),
)
if err != nil {
log.Fatal(err)
}
tp, err := sdktrace.NewProvider(
sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
sdktrace.WithSyncer(exporter))
if err != nil {
log.Fatal(err)
}
global.SetTraceProvider(tp)
return func() {
exporter.Flush()
}
}
func main() {
fn := initTracer()
defer fn()
connString := 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)
if err != nil {
panic(err)
}
otgorm.RegisterCallbacks(db,
otgorm.WithTracer(global.TraceProvider().Tracer("component-gorm")),
otgorm.Query(true),
otgorm.AllowRoot(true),
)
db.AutoMigrate(user{})
newUser := user{
FirstName: "John",
LastName: "Smith",
}
ctx := context.Background()
orm := otgorm.WithContext(ctx, db)
err = orm.Create(&newUser).Error
if err != nil {
log.Print(err)
}
r := chi.NewRouter()
r.Post("/user", func(w http.ResponseWriter, r *http.Request) {
orm := otgorm.WithContext(r.Context(), db)
var u user
body, err := readBody(r.Body)
if err != nil {
log.Print(err)
return
}
err = json.Unmarshal(body, &u)
if err != nil {
log.Print(err)
return
}
err = orm.Create(&u).Error
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode("{\"Error\":\"" + err.Error() + "\"")
return
}
w.WriteHeader(http.StatusOK)
return
})
http.ListenAndServe(":3000", httpTraceWrapper(r))
}
License
The MIT License (MIT). Please see License File for more information.