move wrapper.sql to hook/sql
This commit is contained in:
51
hooks/sql/common.go
Normal file
51
hooks/sql/common.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate sh -c "go run gen.go > wrap_gen.go"
|
||||||
|
|
||||||
|
// namedValueToValue converts driver arguments of NamedValue format to Value format. Implemented in the same way as in
|
||||||
|
// database/sql ctxutil.go.
|
||||||
|
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||||
|
dargs := make([]driver.Value, len(named))
|
||||||
|
for n, param := range named {
|
||||||
|
if len(param.Name) > 0 {
|
||||||
|
return nil, errors.New("sql: driver does not support the use of Named Parameters")
|
||||||
|
}
|
||||||
|
dargs[n] = param.Value
|
||||||
|
}
|
||||||
|
return dargs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedValueToLabels convert driver arguments to interface{} slice
|
||||||
|
func namedValueToLabels(named []driver.NamedValue) []interface{} {
|
||||||
|
largs := make([]interface{}, 0, len(named)*2)
|
||||||
|
var name string
|
||||||
|
for _, param := range named {
|
||||||
|
if param.Name != "" {
|
||||||
|
name = param.Name
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("$%d", param.Ordinal)
|
||||||
|
}
|
||||||
|
largs = append(largs, fmt.Sprintf("%s=%v", name, param.Value))
|
||||||
|
}
|
||||||
|
return largs
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCallerName get the name of the function A where A() -> B() -> GetFunctionCallerName()
|
||||||
|
func getCallerName() string {
|
||||||
|
pc, _, _, ok := runtime.Caller(3)
|
||||||
|
details := runtime.FuncForPC(pc)
|
||||||
|
var callerName string
|
||||||
|
if ok && details != nil {
|
||||||
|
callerName = details.Name()
|
||||||
|
} else {
|
||||||
|
callerName = labelUnknown
|
||||||
|
}
|
||||||
|
return callerName
|
||||||
|
}
|
467
hooks/sql/conn.go
Normal file
467
hooks/sql/conn.go
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v3/hooks/requestid"
|
||||||
|
"go.unistack.org/micro/v3/tracer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ driver.Conn = (*wrapperConn)(nil)
|
||||||
|
_ driver.ConnBeginTx = (*wrapperConn)(nil)
|
||||||
|
_ driver.ConnPrepareContext = (*wrapperConn)(nil)
|
||||||
|
_ driver.Pinger = (*wrapperConn)(nil)
|
||||||
|
_ driver.Validator = (*wrapperConn)(nil)
|
||||||
|
_ driver.Queryer = (*wrapperConn)(nil) // nolint:staticcheck
|
||||||
|
_ driver.QueryerContext = (*wrapperConn)(nil)
|
||||||
|
_ driver.Execer = (*wrapperConn)(nil) // nolint:staticcheck
|
||||||
|
_ driver.ExecerContext = (*wrapperConn)(nil)
|
||||||
|
// _ driver.Connector
|
||||||
|
// _ driver.Driver
|
||||||
|
// _ driver.DriverContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// wrapperConn defines a wrapper for driver.Conn
|
||||||
|
type wrapperConn struct {
|
||||||
|
d *wrapperDriver
|
||||||
|
dname string
|
||||||
|
conn driver.Conn
|
||||||
|
opts Options
|
||||||
|
ctx context.Context
|
||||||
|
span tracer.Span
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements driver.Conn Close
|
||||||
|
func (w *wrapperConn) Close() error {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
labels := []string{labelMethod, "Close"}
|
||||||
|
ts := time.Now()
|
||||||
|
err := w.conn.Close()
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Close", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin implements driver.Conn Begin
|
||||||
|
func (w *wrapperConn) Begin() (driver.Tx, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := []string{labelMethod, "Begin"}
|
||||||
|
ts := time.Now()
|
||||||
|
tx, err := w.conn.Begin() // nolint:staticcheck
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Begin", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Begin", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return &wrapperTx{tx: tx, opts: w.opts, ctx: ctx}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTx implements driver.ConnBeginTx BeginTx
|
||||||
|
func (w *wrapperConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
name := getQueryName(ctx)
|
||||||
|
nctx, span := w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
span.AddLabels("db.method", "BeginTx")
|
||||||
|
span.AddLabels("db.statement", name)
|
||||||
|
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
||||||
|
span.AddLabels("x-request-id", id)
|
||||||
|
}
|
||||||
|
labels := []string{labelMethod, "BeginTx", labelQuery, name}
|
||||||
|
|
||||||
|
connBeginTx, ok := w.conn.(driver.ConnBeginTx)
|
||||||
|
if !ok {
|
||||||
|
return w.Begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
tx, err := connBeginTx.BeginTx(nctx, opts)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "BeginTx", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "BeginTx", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return &wrapperTx{tx: tx, opts: w.opts, ctx: ctx, span: span}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare implements driver.Conn Prepare
|
||||||
|
func (w *wrapperConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
labels := []string{labelMethod, "Prepare", labelQuery, getCallerName()}
|
||||||
|
ts := time.Now()
|
||||||
|
stmt, err := w.conn.Prepare(query)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Prepare", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Prepare", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return wrapStmt(stmt, query, w.opts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareContext implements driver.ConnPrepareContext PrepareContext
|
||||||
|
func (w *wrapperConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
var nctx context.Context
|
||||||
|
var span tracer.Span
|
||||||
|
|
||||||
|
name := getQueryName(ctx)
|
||||||
|
if w.ctx != nil {
|
||||||
|
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
} else {
|
||||||
|
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
}
|
||||||
|
span.AddLabels("db.method", "PrepareContext")
|
||||||
|
span.AddLabels("db.statement", name)
|
||||||
|
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
||||||
|
span.AddLabels("x-request-id", id)
|
||||||
|
}
|
||||||
|
labels := []string{labelMethod, "PrepareContext", labelQuery, name}
|
||||||
|
conn, ok := w.conn.(driver.ConnPrepareContext)
|
||||||
|
if !ok {
|
||||||
|
return w.Prepare(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
stmt, err := conn.PrepareContext(nctx, query)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "PrepareContext", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "PrepareContext", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return wrapStmt(stmt, query, w.opts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec implements driver.Execer Exec
|
||||||
|
func (w *wrapperConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
labels := []string{labelMethod, "Exec", labelQuery, getCallerName()}
|
||||||
|
|
||||||
|
// nolint:staticcheck
|
||||||
|
conn, ok := w.conn.(driver.Execer)
|
||||||
|
if !ok {
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
res, err := conn.Exec(query, args)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Exec", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec implements driver.StmtExecContext ExecContext
|
||||||
|
func (w *wrapperConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
var nctx context.Context
|
||||||
|
var span tracer.Span
|
||||||
|
|
||||||
|
name := getQueryName(ctx)
|
||||||
|
if w.ctx != nil {
|
||||||
|
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
} else {
|
||||||
|
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
}
|
||||||
|
span.AddLabels("db.method", "ExecContext")
|
||||||
|
span.AddLabels("db.statement", name)
|
||||||
|
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
||||||
|
span.AddLabels("x-request-id", id)
|
||||||
|
}
|
||||||
|
defer span.Finish()
|
||||||
|
if len(args) > 0 {
|
||||||
|
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
||||||
|
}
|
||||||
|
labels := []string{labelMethod, "ExecContext", labelQuery, name}
|
||||||
|
|
||||||
|
conn, ok := w.conn.(driver.ExecerContext)
|
||||||
|
if !ok {
|
||||||
|
// nolint:staticcheck
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
res, err := conn.ExecContext(nctx, query, args)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping implements driver.Pinger Ping
|
||||||
|
func (w *wrapperConn) Ping(ctx context.Context) error {
|
||||||
|
conn, ok := w.conn.(driver.Pinger)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// fallback path to check db alive
|
||||||
|
pc, err := w.d.Open(w.dname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var nctx context.Context
|
||||||
|
nctx = ctx
|
||||||
|
/*
|
||||||
|
var span tracer.Span
|
||||||
|
if w.ctx != nil {
|
||||||
|
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
} else {
|
||||||
|
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
}
|
||||||
|
span.AddLabels("db.method", "Ping")
|
||||||
|
defer span.Finish()
|
||||||
|
*/
|
||||||
|
labels := []string{labelMethod, "Ping"}
|
||||||
|
ts := time.Now()
|
||||||
|
err := conn.Ping(nctx)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
// span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Ping", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query implements driver.Queryer Query
|
||||||
|
func (w *wrapperConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
// nolint:staticcheck
|
||||||
|
conn, ok := w.conn.(driver.Queryer)
|
||||||
|
if !ok {
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := []string{labelMethod, "Query", labelQuery, getCallerName()}
|
||||||
|
ts := time.Now()
|
||||||
|
rows, err := conn.Query(query, args)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Query", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryContext implements Driver.QueryerContext QueryContext
|
||||||
|
func (w *wrapperConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
var nctx context.Context
|
||||||
|
var span tracer.Span
|
||||||
|
|
||||||
|
name := getQueryName(ctx)
|
||||||
|
if w.ctx != nil {
|
||||||
|
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
} else {
|
||||||
|
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
}
|
||||||
|
span.AddLabels("db.method", "QueryContext")
|
||||||
|
span.AddLabels("db.statement", name)
|
||||||
|
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
||||||
|
span.AddLabels("x-request-id", id)
|
||||||
|
}
|
||||||
|
defer span.Finish()
|
||||||
|
if len(args) > 0 {
|
||||||
|
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
||||||
|
}
|
||||||
|
labels := []string{labelMethod, "QueryContext", labelQuery, name}
|
||||||
|
conn, ok := w.conn.(driver.QueryerContext)
|
||||||
|
if !ok {
|
||||||
|
return nil, driver.ErrSkip
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
rows, err := conn.QueryContext(nctx, query, args)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckNamedValue implements driver.NamedValueChecker
|
||||||
|
func (w *wrapperConn) CheckNamedValue(v *driver.NamedValue) error {
|
||||||
|
s, ok := w.conn.(driver.NamedValueChecker)
|
||||||
|
if !ok {
|
||||||
|
return driver.ErrSkip
|
||||||
|
}
|
||||||
|
return s.CheckNamedValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid implements driver.Validator
|
||||||
|
func (w *wrapperConn) IsValid() bool {
|
||||||
|
v, ok := w.conn.(driver.Validator)
|
||||||
|
if !ok {
|
||||||
|
return w.conn != nil
|
||||||
|
}
|
||||||
|
return v.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapperConn) ResetSession(ctx context.Context) error {
|
||||||
|
s, ok := w.conn.(driver.SessionResetter)
|
||||||
|
if !ok {
|
||||||
|
return driver.ErrSkip
|
||||||
|
}
|
||||||
|
return s.ResetSession(ctx)
|
||||||
|
}
|
92
hooks/sql/driver.go
Normal file
92
hooks/sql/driver.go
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// _ driver.DriverContext = (*wrapperDriver)(nil)
|
||||||
|
// _ driver.Connector = (*wrapperDriver)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type conn interface {
|
||||||
|
driver.Pinger
|
||||||
|
driver.Execer
|
||||||
|
driver.ExecerContext
|
||||||
|
driver.Queryer
|
||||||
|
driver.QueryerContext
|
||||||
|
driver.Conn
|
||||||
|
driver.ConnPrepareContext
|
||||||
|
driver.ConnBeginTx
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapperDriver defines a wrapper for driver.Driver
|
||||||
|
type wrapperDriver struct {
|
||||||
|
driver driver.Driver
|
||||||
|
opts Options
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWrapper creates and returns a new SQL driver with passed capabilities
|
||||||
|
func NewWrapper(d driver.Driver, opts ...Option) driver.Driver {
|
||||||
|
return &wrapperDriver{driver: d, opts: NewOptions(opts...), ctx: context.Background()}
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappedConnector struct {
|
||||||
|
connector driver.Connector
|
||||||
|
name string
|
||||||
|
opts Options
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWrapperConnector(c driver.Connector, opts ...Option) driver.Connector {
|
||||||
|
return &wrappedConnector{connector: c, opts: NewOptions(opts...), ctx: context.Background()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect implements driver.Driver Connect
|
||||||
|
func (w *wrappedConnector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
|
return w.connector.Connect(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver implements driver.Driver Driver
|
||||||
|
func (w *wrappedConnector) Driver() driver.Driver {
|
||||||
|
return w.connector.Driver()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Connect implements driver.Driver OpenConnector
|
||||||
|
func (w *wrapperDriver) OpenConnector(name string) (driver.Conn, error) {
|
||||||
|
return &wrapperConnector{driver: w.driver, name: name, opts: w.opts}, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Open implements driver.Driver Open
|
||||||
|
func (w *wrapperDriver) Open(name string) (driver.Conn, error) {
|
||||||
|
// ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) // Ensure eventual timeout
|
||||||
|
// defer cancel()
|
||||||
|
|
||||||
|
/*
|
||||||
|
connector, err := w.OpenConnector(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return connector.Connect(ctx)
|
||||||
|
*/
|
||||||
|
|
||||||
|
ts := time.Now()
|
||||||
|
c, err := w.driver.Open(name)
|
||||||
|
td := time.Since(ts)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled {
|
||||||
|
w.opts.Logger.Log(w.ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(w.ctx, "Open", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
_ = td
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapConn(c, w.opts), nil
|
||||||
|
}
|
167
hooks/sql/gen.go
Normal file
167
hooks/sql/gen.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var connIfaces = []string{
|
||||||
|
"driver.ConnBeginTx",
|
||||||
|
"driver.ConnPrepareContext",
|
||||||
|
"driver.Execer",
|
||||||
|
"driver.ExecerContext",
|
||||||
|
"driver.NamedValueChecker",
|
||||||
|
"driver.Pinger",
|
||||||
|
"driver.Queryer",
|
||||||
|
"driver.QueryerContext",
|
||||||
|
"driver.SessionResetter",
|
||||||
|
"driver.Validator",
|
||||||
|
}
|
||||||
|
|
||||||
|
var stmtIfaces = []string{
|
||||||
|
"driver.StmtExecContext",
|
||||||
|
"driver.StmtQueryContext",
|
||||||
|
"driver.ColumnConverter",
|
||||||
|
"driver.NamedValueChecker",
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHash(s []string) string {
|
||||||
|
h := md5.New()
|
||||||
|
io.WriteString(h, strings.Join(s, "|"))
|
||||||
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
comboConn := all(connIfaces)
|
||||||
|
|
||||||
|
sort.Slice(comboConn, func(i, j int) bool {
|
||||||
|
return len(comboConn[i]) < len(comboConn[j])
|
||||||
|
})
|
||||||
|
|
||||||
|
comboStmt := all(stmtIfaces)
|
||||||
|
|
||||||
|
sort.Slice(comboStmt, func(i, j int) bool {
|
||||||
|
return len(comboStmt[i]) < len(comboStmt[j])
|
||||||
|
})
|
||||||
|
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
b.WriteString("// Code generated. DO NOT EDIT.\n\n")
|
||||||
|
b.WriteString("package sql\n\n")
|
||||||
|
b.WriteString(`import "database/sql/driver"`)
|
||||||
|
b.WriteString("\n\n")
|
||||||
|
|
||||||
|
b.WriteString("func wrapConn(dc driver.Conn, opts Options) driver.Conn {\n")
|
||||||
|
b.WriteString("\tc := &wrapperConn{conn: dc, opts: opts}\n")
|
||||||
|
|
||||||
|
for idx := len(comboConn) - 1; idx >= 0; idx-- {
|
||||||
|
ifaces := comboConn[idx]
|
||||||
|
n := len(ifaces)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h := getHash(ifaces)
|
||||||
|
b.WriteString(fmt.Sprintf("\tif _, ok := dc.(wrapConn%04d_%s); ok {\n", n, h))
|
||||||
|
b.WriteString("\treturn struct {\n")
|
||||||
|
b.WriteString("\t\tdriver.Conn\n")
|
||||||
|
b.WriteString(fmt.Sprintf("\t\t\t%s", strings.Join(ifaces, "\n\t\t\t")))
|
||||||
|
b.WriteString("\t\t\n}{")
|
||||||
|
for idx := range ifaces {
|
||||||
|
if idx > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
b.WriteString("c")
|
||||||
|
} else if idx == 0 {
|
||||||
|
b.WriteString("c")
|
||||||
|
} else {
|
||||||
|
b.WriteString("c")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.WriteString(", c}\n")
|
||||||
|
b.WriteString("}\n\n")
|
||||||
|
}
|
||||||
|
b.WriteString("return c\n")
|
||||||
|
b.WriteString("}\n")
|
||||||
|
|
||||||
|
for idx := len(comboConn) - 1; idx >= 0; idx-- {
|
||||||
|
ifaces := comboConn[idx]
|
||||||
|
n := len(ifaces)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h := getHash(ifaces)
|
||||||
|
b.WriteString(fmt.Sprintf("// %s\n", strings.Join(ifaces, "|")))
|
||||||
|
b.WriteString(fmt.Sprintf("type wrapConn%04d_%s interface {\n", n, h))
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
b.WriteString(fmt.Sprintf("\t%s\n", iface))
|
||||||
|
}
|
||||||
|
b.WriteString("}\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("func wrapStmt(stmt driver.Stmt, query string, opts Options) driver.Stmt {\n")
|
||||||
|
b.WriteString("\tc := &wrapperStmt{stmt: stmt, query: query, opts: opts}\n")
|
||||||
|
|
||||||
|
for idx := len(comboStmt) - 1; idx >= 0; idx-- {
|
||||||
|
ifaces := comboStmt[idx]
|
||||||
|
n := len(ifaces)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h := getHash(ifaces)
|
||||||
|
b.WriteString(fmt.Sprintf("\tif _, ok := stmt.(wrapStmt%04d_%s); ok {\n", n, h))
|
||||||
|
b.WriteString("\treturn struct {\n")
|
||||||
|
b.WriteString("\t\tdriver.Stmt\n")
|
||||||
|
b.WriteString(fmt.Sprintf("\t\t\t%s", strings.Join(ifaces, "\n\t\t\t")))
|
||||||
|
b.WriteString("\t\t\n}{")
|
||||||
|
for idx := range ifaces {
|
||||||
|
if idx > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
b.WriteString("c")
|
||||||
|
} else if idx == 0 {
|
||||||
|
b.WriteString("c")
|
||||||
|
} else {
|
||||||
|
b.WriteString("c")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.WriteString(", c}\n")
|
||||||
|
b.WriteString("}\n\n")
|
||||||
|
}
|
||||||
|
b.WriteString("return c\n")
|
||||||
|
b.WriteString("}\n")
|
||||||
|
|
||||||
|
for idx := len(comboStmt) - 1; idx >= 0; idx-- {
|
||||||
|
ifaces := comboStmt[idx]
|
||||||
|
n := len(ifaces)
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h := getHash(ifaces)
|
||||||
|
b.WriteString(fmt.Sprintf("// %s\n", strings.Join(ifaces, "|")))
|
||||||
|
b.WriteString(fmt.Sprintf("type wrapStmt%04d_%s interface {\n", n, h))
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
b.WriteString(fmt.Sprintf("\t%s\n", iface))
|
||||||
|
}
|
||||||
|
b.WriteString("}\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// all returns all combinations for a given string array.
|
||||||
|
func all[T any](set []T) (subsets [][]T) {
|
||||||
|
length := uint(len(set))
|
||||||
|
for subsetBits := 1; subsetBits < (1 << length); subsetBits++ {
|
||||||
|
var subset []T
|
||||||
|
for object := uint(0); object < length; object++ {
|
||||||
|
if (subsetBits>>object)&1 == 1 {
|
||||||
|
subset = append(subset, set[object])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subsets = append(subsets, subset)
|
||||||
|
}
|
||||||
|
return subsets
|
||||||
|
}
|
172
hooks/sql/options.go
Normal file
172
hooks/sql/options.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v3/logger"
|
||||||
|
"go.unistack.org/micro/v3/meter"
|
||||||
|
"go.unistack.org/micro/v3/tracer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultMeterStatsInterval holds default stats interval
|
||||||
|
DefaultMeterStatsInterval = 5 * time.Second
|
||||||
|
// DefaultLoggerObserver used to prepare labels for logger
|
||||||
|
DefaultLoggerObserver = func(ctx context.Context, method string, query string, td time.Duration, err error) []interface{} {
|
||||||
|
labels := []interface{}{"db.method", method, "took", fmt.Sprintf("%v", td)}
|
||||||
|
if err != nil {
|
||||||
|
labels = append(labels, "error", err.Error())
|
||||||
|
}
|
||||||
|
if query != labelUnknown {
|
||||||
|
labels = append(labels, "query", query)
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MaxOpenConnections = "micro_sql_max_open_conn"
|
||||||
|
OpenConnections = "micro_sql_open_conn"
|
||||||
|
InuseConnections = "micro_sql_inuse_conn"
|
||||||
|
IdleConnections = "micro_sql_idle_conn"
|
||||||
|
WaitConnections = "micro_sql_waited_conn"
|
||||||
|
BlockedSeconds = "micro_sql_blocked_seconds"
|
||||||
|
MaxIdleClosed = "micro_sql_max_idle_closed"
|
||||||
|
MaxIdletimeClosed = "micro_sql_closed_max_idle"
|
||||||
|
MaxLifetimeClosed = "micro_sql_closed_max_lifetime"
|
||||||
|
|
||||||
|
meterRequestTotal = "micro_sql_request_total"
|
||||||
|
meterRequestLatencyMicroseconds = "micro_sql_latency_microseconds"
|
||||||
|
meterRequestDurationSeconds = "micro_sql_request_duration_seconds"
|
||||||
|
|
||||||
|
labelUnknown = "unknown"
|
||||||
|
labelQuery = "db_statement"
|
||||||
|
labelMethod = "db_method"
|
||||||
|
labelStatus = "status"
|
||||||
|
labelSuccess = "success"
|
||||||
|
labelFailure = "failure"
|
||||||
|
labelHost = "db_host"
|
||||||
|
labelDatabase = "db_name"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options struct holds wrapper options
|
||||||
|
type Options struct {
|
||||||
|
Logger logger.Logger
|
||||||
|
Meter meter.Meter
|
||||||
|
Tracer tracer.Tracer
|
||||||
|
DatabaseHost string
|
||||||
|
DatabaseName string
|
||||||
|
MeterStatsInterval time.Duration
|
||||||
|
LoggerLevel logger.Level
|
||||||
|
LoggerEnabled bool
|
||||||
|
LoggerObserver func(ctx context.Context, method string, name string, td time.Duration, err error) []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option func signature
|
||||||
|
type Option func(*Options)
|
||||||
|
|
||||||
|
// NewOptions create new Options struct from provided option slice
|
||||||
|
func NewOptions(opts ...Option) Options {
|
||||||
|
options := Options{
|
||||||
|
Logger: logger.DefaultLogger,
|
||||||
|
Meter: meter.DefaultMeter,
|
||||||
|
Tracer: tracer.DefaultTracer,
|
||||||
|
MeterStatsInterval: DefaultMeterStatsInterval,
|
||||||
|
LoggerLevel: logger.ErrorLevel,
|
||||||
|
LoggerObserver: DefaultLoggerObserver,
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
options.Meter = options.Meter.Clone(
|
||||||
|
meter.Labels(
|
||||||
|
labelHost, options.DatabaseHost,
|
||||||
|
labelDatabase, options.DatabaseName,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
options.Logger = options.Logger.Clone(logger.WithAddCallerSkipCount(1))
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// MetricInterval specifies stats interval for *sql.DB
|
||||||
|
func MetricInterval(td time.Duration) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.MeterStatsInterval = td
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DatabaseHost(host string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.DatabaseHost = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DatabaseName(name string) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.DatabaseName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meter passes meter.Meter to wrapper
|
||||||
|
func Meter(m meter.Meter) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Meter = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger passes logger.Logger to wrapper
|
||||||
|
func Logger(l logger.Logger) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Logger = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerEnabled enable sql logging
|
||||||
|
func LoggerEnabled(b bool) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.LoggerEnabled = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerLevel passes logger.Level option
|
||||||
|
func LoggerLevel(lvl logger.Level) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.LoggerLevel = lvl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerObserver passes observer to fill logger fields
|
||||||
|
func LoggerObserver(obs func(context.Context, string, string, time.Duration, error) []interface{}) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.LoggerObserver = obs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracer passes tracer.Tracer to wrapper
|
||||||
|
func Tracer(t tracer.Tracer) Option {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Tracer = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryNameKey struct{}
|
||||||
|
|
||||||
|
// QueryName passes query name to wrapper func
|
||||||
|
func QueryName(ctx context.Context, name string) context.Context {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
return context.WithValue(ctx, queryNameKey{}, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueryName(ctx context.Context) string {
|
||||||
|
if v, ok := ctx.Value(queryNameKey{}).(string); ok && v != labelUnknown {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return getCallerName()
|
||||||
|
}
|
41
hooks/sql/stats.go
Normal file
41
hooks/sql/stats.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Statser interface {
|
||||||
|
Stats() sql.DBStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatsMeter(ctx context.Context, db Statser, opts ...Option) {
|
||||||
|
options := NewOptions(opts...)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(options.MeterStatsInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if db == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stats := db.Stats()
|
||||||
|
options.Meter.Counter(MaxOpenConnections).Set(uint64(stats.MaxOpenConnections))
|
||||||
|
options.Meter.Counter(OpenConnections).Set(uint64(stats.OpenConnections))
|
||||||
|
options.Meter.Counter(InuseConnections).Set(uint64(stats.InUse))
|
||||||
|
options.Meter.Counter(IdleConnections).Set(uint64(stats.Idle))
|
||||||
|
options.Meter.Counter(WaitConnections).Set(uint64(stats.WaitCount))
|
||||||
|
options.Meter.FloatCounter(BlockedSeconds).Set(stats.WaitDuration.Seconds())
|
||||||
|
options.Meter.Counter(MaxIdleClosed).Set(uint64(stats.MaxIdleClosed))
|
||||||
|
options.Meter.Counter(MaxIdletimeClosed).Set(uint64(stats.MaxIdleTimeClosed))
|
||||||
|
options.Meter.Counter(MaxLifetimeClosed).Set(uint64(stats.MaxLifetimeClosed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
287
hooks/sql/stmt.go
Normal file
287
hooks/sql/stmt.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v3/hooks/requestid"
|
||||||
|
"go.unistack.org/micro/v3/tracer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ driver.Stmt = (*wrapperStmt)(nil)
|
||||||
|
_ driver.StmtQueryContext = (*wrapperStmt)(nil)
|
||||||
|
_ driver.StmtExecContext = (*wrapperStmt)(nil)
|
||||||
|
_ driver.NamedValueChecker = (*wrapperStmt)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// wrapperStmt defines a wrapper for driver.Stmt
|
||||||
|
type wrapperStmt struct {
|
||||||
|
stmt driver.Stmt
|
||||||
|
opts Options
|
||||||
|
query string
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements driver.Stmt Close
|
||||||
|
func (w *wrapperStmt) Close() error {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
labels := []string{labelMethod, "Close"}
|
||||||
|
ts := time.Now()
|
||||||
|
err := w.stmt.Close()
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Close", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumInput implements driver.Stmt NumInput
|
||||||
|
func (w *wrapperStmt) NumInput() int {
|
||||||
|
return w.stmt.NumInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckNamedValue implements driver.NamedValueChecker
|
||||||
|
func (w *wrapperStmt) CheckNamedValue(v *driver.NamedValue) error {
|
||||||
|
s, ok := w.stmt.(driver.NamedValueChecker)
|
||||||
|
if !ok {
|
||||||
|
return driver.ErrSkip
|
||||||
|
}
|
||||||
|
return s.CheckNamedValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec implements driver.Stmt Exec
|
||||||
|
func (w *wrapperStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
labels := []string{labelMethod, "Exec"}
|
||||||
|
ts := time.Now()
|
||||||
|
res, err := w.stmt.Exec(args) // nolint:staticcheck
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Exec", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query implements driver.Stmt Query
|
||||||
|
func (w *wrapperStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
if w.ctx != nil {
|
||||||
|
ctx = w.ctx
|
||||||
|
} else {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
_ = ctx
|
||||||
|
labels := []string{labelMethod, "Query"}
|
||||||
|
ts := time.Now()
|
||||||
|
rows, err := w.stmt.Query(args) // nolint:staticcheck
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "Query", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColumnConverter implements driver.ColumnConverter
|
||||||
|
func (w *wrapperStmt) ColumnConverter(idx int) driver.ValueConverter {
|
||||||
|
s, ok := w.stmt.(driver.ColumnConverter) // nolint:staticcheck
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.ColumnConverter(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecContext implements driver.StmtExecContext ExecContext
|
||||||
|
func (w *wrapperStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
var nctx context.Context
|
||||||
|
var span tracer.Span
|
||||||
|
|
||||||
|
name := getQueryName(ctx)
|
||||||
|
if w.ctx != nil {
|
||||||
|
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
} else {
|
||||||
|
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
}
|
||||||
|
span.AddLabels("db.method", "ExecContext")
|
||||||
|
span.AddLabels("db.statement", name)
|
||||||
|
defer span.Finish()
|
||||||
|
if len(args) > 0 {
|
||||||
|
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
||||||
|
}
|
||||||
|
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
||||||
|
span.AddLabels("x-request-id", id)
|
||||||
|
}
|
||||||
|
labels := []string{labelMethod, "ExecContext", labelQuery, name}
|
||||||
|
|
||||||
|
if conn, ok := w.stmt.(driver.StmtExecContext); ok {
|
||||||
|
ts := time.Now()
|
||||||
|
res, err := conn.ExecContext(nctx, args)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", name, td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
values, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", name, 0, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts := time.Now()
|
||||||
|
res, err := w.Exec(values) // nolint:staticcheck
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "ExecContext", name, td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryContext implements driver.StmtQueryContext StmtQueryContext
|
||||||
|
func (w *wrapperStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
var nctx context.Context
|
||||||
|
var span tracer.Span
|
||||||
|
|
||||||
|
name := getQueryName(ctx)
|
||||||
|
if w.ctx != nil {
|
||||||
|
nctx, span = w.opts.Tracer.Start(w.ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
} else {
|
||||||
|
nctx, span = w.opts.Tracer.Start(ctx, "sdk.database", tracer.WithSpanKind(tracer.SpanKindClient))
|
||||||
|
}
|
||||||
|
span.AddLabels("db.method", "QueryContext")
|
||||||
|
span.AddLabels("db.statement", name)
|
||||||
|
defer span.Finish()
|
||||||
|
if len(args) > 0 {
|
||||||
|
span.AddLabels("db.args", fmt.Sprintf("%v", namedValueToLabels(args)))
|
||||||
|
}
|
||||||
|
if id, ok := ctx.Value(requestid.XRequestIDKey{}).(string); ok {
|
||||||
|
span.AddLabels("x-request-id", id)
|
||||||
|
}
|
||||||
|
labels := []string{labelMethod, "QueryContext", labelQuery, name}
|
||||||
|
if conn, ok := w.stmt.(driver.StmtQueryContext); ok {
|
||||||
|
ts := time.Now()
|
||||||
|
rows, err := conn.QueryContext(nctx, args)
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", name, td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
values, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", name, 0, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts := time.Now()
|
||||||
|
rows, err := w.Query(values) // nolint:staticcheck
|
||||||
|
td := time.Since(ts)
|
||||||
|
te := td.Seconds()
|
||||||
|
if err != nil {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelFailure)...).Inc()
|
||||||
|
span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
} else {
|
||||||
|
w.opts.Meter.Counter(meterRequestTotal, append(labels, labelStatus, labelSuccess)...).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.opts.Meter.Summary(meterRequestLatencyMicroseconds, labels...).Update(te)
|
||||||
|
w.opts.Meter.Histogram(meterRequestDurationSeconds, labels...).Update(te)
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(ctx, "QueryContext", name, td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return rows, err
|
||||||
|
}
|
63
hooks/sql/tx.go
Normal file
63
hooks/sql/tx.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.unistack.org/micro/v3/tracer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ driver.Tx = (*wrapperTx)(nil)
|
||||||
|
|
||||||
|
// wrapperTx defines a wrapper for driver.Tx
|
||||||
|
type wrapperTx struct {
|
||||||
|
tx driver.Tx
|
||||||
|
span tracer.Span
|
||||||
|
opts Options
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit implements driver.Tx Commit
|
||||||
|
func (w *wrapperTx) Commit() error {
|
||||||
|
ts := time.Now()
|
||||||
|
err := w.tx.Commit()
|
||||||
|
td := time.Since(ts)
|
||||||
|
_ = td
|
||||||
|
if w.span != nil {
|
||||||
|
if err != nil {
|
||||||
|
w.span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
}
|
||||||
|
w.span.Finish()
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(w.ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(w.ctx, "Commit", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
w.ctx = nil
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rollback implements driver.Tx Rollback
|
||||||
|
func (w *wrapperTx) Rollback() error {
|
||||||
|
ts := time.Now()
|
||||||
|
err := w.tx.Rollback()
|
||||||
|
td := time.Since(ts)
|
||||||
|
_ = td
|
||||||
|
if w.span != nil {
|
||||||
|
if err != nil {
|
||||||
|
w.span.SetStatus(tracer.SpanStatusError, err.Error())
|
||||||
|
}
|
||||||
|
w.span.Finish()
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if w.opts.LoggerEnabled && w.opts.Logger.V(w.opts.LoggerLevel) {
|
||||||
|
w.opts.Logger.Log(w.ctx, w.opts.LoggerLevel, w.opts.LoggerObserver(w.ctx, "Rollback", getCallerName(), td, err)...)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
w.ctx = nil
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
17
hooks/sql/wrap.go
Normal file
17
hooks/sql/wrap.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func wrapDriver(d driver.Driver, opts Options) driver.Driver {
|
||||||
|
if _, ok := d.(driver.DriverContext); ok {
|
||||||
|
return &wrapperDriver{driver: d, opts: opts}
|
||||||
|
}
|
||||||
|
return struct{ driver.Driver }{&wrapperDriver{driver: d, opts: opts}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapConn allows an existing driver.Conn to be wrapped.
|
||||||
|
func WrapConn(c driver.Conn, opts ...Option) driver.Conn {
|
||||||
|
return wrapConn(c, NewOptions(opts...))
|
||||||
|
}
|
20699
hooks/sql/wrap_gen.go
Normal file
20699
hooks/sql/wrap_gen.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user