WIP
This commit is contained in:
parent
9971ef17cb
commit
628aef0702
3
go.mod
3
go.mod
@ -19,6 +19,8 @@ require (
|
|||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/klauspost/compress v1.17.8 // indirect
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
@ -37,4 +39,5 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||||
google.golang.org/grpc v1.63.2 // indirect
|
google.golang.org/grpc v1.63.2 // indirect
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gorm.io/gorm v1.25.11 // indirect
|
||||||
)
|
)
|
||||||
|
6
go.sum
6
go.sum
@ -22,6 +22,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
@ -104,3 +108,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||||
|
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
|
@ -58,6 +58,7 @@ const (
|
|||||||
ServiceLayerKey = attribute.Key("service.layer")
|
ServiceLayerKey = attribute.Key("service.layer")
|
||||||
ServiceLayerNameKey = attribute.Key("service.layer_name")
|
ServiceLayerNameKey = attribute.Key("service.layer_name")
|
||||||
DBExecutionTimeMsKey = attribute.Key("db.execution_time_ms")
|
DBExecutionTimeMsKey = attribute.Key("db.execution_time_ms")
|
||||||
|
DBRowsAffectedKey = attribute.Key("db.rows_affected")
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceArchitectureLayer string
|
type ServiceArchitectureLayer string
|
||||||
|
207
pkg/gorm_tracing/middleware.go
Normal file
207
pkg/gorm_tracing/middleware.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package gorm_tracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.ma-al.com/maal-libraries/observer/pkg/fiber_tracing"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dbRowsAffected = attribute.Key("db.rows_affected")
|
||||||
|
|
||||||
|
type Option func(p *gormPlugin)
|
||||||
|
|
||||||
|
// WithTracerProvider configures a tracer provider that is used to create a tracer.
|
||||||
|
func WithTracerProvider(provider trace.TracerProvider) Option {
|
||||||
|
return func(p *gormPlugin) {
|
||||||
|
p.provider = provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAttributes configures attributes that are used to create a span.
|
||||||
|
func WithAttributes(attrs ...attribute.KeyValue) Option {
|
||||||
|
return func(p *gormPlugin) {
|
||||||
|
p.attrs = append(p.attrs, attrs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDBName configures a db.name attribute.
|
||||||
|
func WithDBName(name string) Option {
|
||||||
|
return func(p *gormPlugin) {
|
||||||
|
p.attrs = append(p.attrs, semconv.DBNameKey.String(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutQueryVariables configures the db.statement attribute to exclude query variables
|
||||||
|
func WithoutQueryVariables() Option {
|
||||||
|
return func(p *gormPlugin) {
|
||||||
|
p.excludeQueryVars = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithQueryFormatter configures a query formatter
|
||||||
|
func WithQueryFormatter(queryFormatter func(query string) string) Option {
|
||||||
|
return func(p *gormPlugin) {
|
||||||
|
p.queryFormatter = queryFormatter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutMetrics prevents DBStats metrics from being reported.
|
||||||
|
func WithoutMetrics() Option {
|
||||||
|
return func(p *gormPlugin) {
|
||||||
|
p.excludeMetrics = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gormPlugin struct {
|
||||||
|
provider trace.TracerProvider
|
||||||
|
tracer trace.Tracer
|
||||||
|
attrs []attribute.KeyValue
|
||||||
|
excludeQueryVars bool
|
||||||
|
excludeMetrics bool
|
||||||
|
queryFormatter func(query string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGormPlugin(opts ...Option) gorm.Plugin {
|
||||||
|
p := &gormPlugin{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.provider == nil {
|
||||||
|
p.provider = otel.GetTracerProvider()
|
||||||
|
}
|
||||||
|
p.tracer = p.provider.Tracer("git.ma-al.com/maal-libraries/observer/pkg/gorm_tracing")
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p gormPlugin) Name() string {
|
||||||
|
return "observerGorm"
|
||||||
|
}
|
||||||
|
|
||||||
|
type gormHookFunc func(tx *gorm.DB)
|
||||||
|
|
||||||
|
type gormRegister interface {
|
||||||
|
Register(name string, fn func(*gorm.DB)) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p gormPlugin) Initialize(db *gorm.DB) (err error) {
|
||||||
|
// if !p.excludeMetrics {
|
||||||
|
// if db, ok := db.ConnPool.(*sql.DB); ok {
|
||||||
|
// metrics.ReportDBStatsMetrics(db)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
cb := db.Callback()
|
||||||
|
hooks := []struct {
|
||||||
|
callback gormRegister
|
||||||
|
hook gormHookFunc
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
{cb.Create().Before("gorm:create"), p.before("gorm.Create"), "before:create"},
|
||||||
|
{cb.Create().After("gorm:create"), p.after(), "after:create"},
|
||||||
|
|
||||||
|
{cb.Query().Before("gorm:query"), p.before("gorm.Select"), "before:select"},
|
||||||
|
{cb.Query().After("gorm:query"), p.after(), "after:select"},
|
||||||
|
|
||||||
|
{cb.Delete().Before("gorm:delete"), p.before("gorm.Delete"), "before:delete"},
|
||||||
|
{cb.Delete().After("gorm:delete"), p.after(), "after:delete"},
|
||||||
|
|
||||||
|
{cb.Update().Before("gorm:update"), p.before("gorm.Update"), "before:update"},
|
||||||
|
{cb.Update().After("gorm:update"), p.after(), "after:update"},
|
||||||
|
|
||||||
|
{cb.Row().Before("gorm:row"), p.before("gorm.Row"), "before:row"},
|
||||||
|
{cb.Row().After("gorm:row"), p.after(), "after:row"},
|
||||||
|
|
||||||
|
{cb.Raw().Before("gorm:raw"), p.before("gorm.Raw"), "before:raw"},
|
||||||
|
{cb.Raw().After("gorm:raw"), p.after(), "after:raw"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
|
||||||
|
for _, h := range hooks {
|
||||||
|
if err := h.callback.Register("observer:"+h.name, h.hook); err != nil && firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("callback register %s failed: %w", h.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *gormPlugin) before(spanName string) gormHookFunc {
|
||||||
|
return func(tx *gorm.DB) {
|
||||||
|
ctx := tx.Statement.Context
|
||||||
|
tx.Statement.Context, _ = fiber_tracing.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindClient))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *gormPlugin) after() gormHookFunc {
|
||||||
|
return func(tx *gorm.DB) {
|
||||||
|
span := trace.SpanFromContext(tx.Statement.Context)
|
||||||
|
if !span.IsRecording() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer span.End()
|
||||||
|
// fmt.Printf("%#v\n", span)
|
||||||
|
|
||||||
|
attrs := make([]attribute.KeyValue, 0, len(p.attrs)+4)
|
||||||
|
attrs = append(attrs, p.attrs...)
|
||||||
|
|
||||||
|
if sys := dbSystem(tx); sys.Valid() {
|
||||||
|
attrs = append(attrs, sys)
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := tx.Statement.Vars
|
||||||
|
if p.excludeQueryVars {
|
||||||
|
// Replace query variables with '?' to mask them
|
||||||
|
vars = make([]interface{}, len(tx.Statement.Vars))
|
||||||
|
|
||||||
|
for i := 0; i < len(vars); i++ {
|
||||||
|
vars[i] = "?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := tx.Dialector.Explain(tx.Statement.SQL.String(), vars...)
|
||||||
|
|
||||||
|
attrs = append(attrs, semconv.DBStatementKey.String(p.formatQuery(query)))
|
||||||
|
if tx.Statement.Table != "" {
|
||||||
|
attrs = append(attrs, semconv.DBSQLTableKey.String(tx.Statement.Table))
|
||||||
|
}
|
||||||
|
if tx.Statement.RowsAffected != -1 {
|
||||||
|
attrs = append(attrs, dbRowsAffected.Int64(tx.Statement.RowsAffected))
|
||||||
|
}
|
||||||
|
|
||||||
|
span.SetAttributes(attrs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *gormPlugin) formatQuery(query string) string {
|
||||||
|
if p.queryFormatter != nil {
|
||||||
|
return p.queryFormatter(query)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbSystem(tx *gorm.DB) attribute.KeyValue {
|
||||||
|
switch tx.Dialector.Name() {
|
||||||
|
case "mysql":
|
||||||
|
return semconv.DBSystemMySQL
|
||||||
|
case "mssql":
|
||||||
|
return semconv.DBSystemMSSQL
|
||||||
|
case "postgres", "postgresql":
|
||||||
|
return semconv.DBSystemPostgreSQL
|
||||||
|
case "sqlite":
|
||||||
|
return semconv.DBSystemSqlite
|
||||||
|
case "sqlserver":
|
||||||
|
return semconv.DBSystemKey.String("sqlserver")
|
||||||
|
case "clickhouse":
|
||||||
|
return semconv.DBSystemKey.String("clickhouse")
|
||||||
|
default:
|
||||||
|
return attribute.KeyValue{}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user