2021-10-27 18:49:33 +03:00
|
|
|
package wrapper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-04-11 11:38:09 +03:00
|
|
|
"database/sql"
|
|
|
|
"errors"
|
2023-01-06 23:24:39 +03:00
|
|
|
"fmt"
|
2021-10-27 18:49:33 +03:00
|
|
|
"time"
|
|
|
|
|
2023-06-10 13:09:25 +03:00
|
|
|
"go.unistack.org/micro/v4/logger"
|
|
|
|
"go.unistack.org/micro/v4/meter"
|
|
|
|
"go.unistack.org/micro/v4/tracer"
|
2021-10-27 18:49:33 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// DefaultMeterStatsInterval holds default stats interval
|
|
|
|
DefaultMeterStatsInterval = 5 * time.Second
|
|
|
|
// DefaultMeterMetricPrefix holds default metric prefix
|
|
|
|
DefaultMeterMetricPrefix = "micro_sql_"
|
2023-01-06 23:24:39 +03:00
|
|
|
// DefaultLoggerObserver used to prepare labels for logger
|
|
|
|
DefaultLoggerObserver = func(ctx context.Context, method string, query string, td time.Duration, err error) []interface{} {
|
2023-09-01 14:48:57 +03:00
|
|
|
labels := []interface{}{"db.method", method, "took", fmt.Sprintf("%v", td)}
|
2024-04-11 11:38:09 +03:00
|
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
2023-01-06 23:24:39 +03:00
|
|
|
labels = append(labels, "error", err.Error())
|
|
|
|
}
|
|
|
|
if query != labelUnknown {
|
|
|
|
labels = append(labels, "query", query)
|
|
|
|
}
|
|
|
|
return labels
|
|
|
|
}
|
2021-10-27 18:49:33 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2023-09-01 00:21:08 +03:00
|
|
|
MaxOpenConnections = "max_open_conn"
|
|
|
|
OpenConnections = "open_conn"
|
|
|
|
InuseConnections = "inuse_conn"
|
|
|
|
IdleConnections = "idle_conn"
|
|
|
|
WaitConnections = "waited_conn"
|
2021-10-27 18:49:33 +03:00
|
|
|
BlockedSeconds = "blocked_seconds"
|
2022-12-25 15:35:05 +03:00
|
|
|
MaxIdleClosed = "max_idle_closed"
|
2023-09-01 00:21:08 +03:00
|
|
|
MaxIdletimeClosed = "closed_max_idle"
|
|
|
|
MaxLifetimeClosed = "closed_max_lifetime"
|
2021-10-27 18:49:33 +03:00
|
|
|
|
2023-01-05 16:03:21 +03:00
|
|
|
meterRequestTotal = "request_total"
|
2023-09-01 00:21:08 +03:00
|
|
|
meterRequestLatencyMicroseconds = "latency_microseconds"
|
2023-01-05 16:03:21 +03:00
|
|
|
meterRequestDurationSeconds = "request_duration_seconds"
|
|
|
|
|
|
|
|
labelUnknown = "unknown"
|
2023-09-05 07:08:08 +03:00
|
|
|
labelQuery = "db_statement"
|
|
|
|
labelMethod = "db_method"
|
2023-01-05 16:03:21 +03:00
|
|
|
labelStatus = "status"
|
2021-10-27 18:49:33 +03:00
|
|
|
labelSuccess = "success"
|
|
|
|
labelFailure = "failure"
|
2023-09-05 07:08:08 +03:00
|
|
|
labelHost = "db_host"
|
|
|
|
labelDatabase = "db_name"
|
2021-10-27 18:49:33 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Options struct holds wrapper options
|
|
|
|
type Options struct {
|
|
|
|
Logger logger.Logger
|
|
|
|
Meter meter.Meter
|
|
|
|
Tracer tracer.Tracer
|
|
|
|
DatabaseHost string
|
|
|
|
DatabaseName string
|
|
|
|
MeterMetricPrefix string
|
|
|
|
MeterStatsInterval time.Duration
|
|
|
|
LoggerLevel logger.Level
|
2023-01-06 23:24:39 +03:00
|
|
|
LoggerEnabled bool
|
|
|
|
LoggerObserver func(ctx context.Context, method string, name string, td time.Duration, err error) []interface{}
|
2021-10-27 18:49:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
MeterMetricPrefix: DefaultMeterMetricPrefix,
|
2023-09-01 00:21:08 +03:00
|
|
|
LoggerLevel: logger.ErrorLevel,
|
2023-01-06 23:24:39 +03:00
|
|
|
LoggerObserver: DefaultLoggerObserver,
|
2021-10-27 18:49:33 +03:00
|
|
|
}
|
|
|
|
for _, o := range opts {
|
|
|
|
o(&options)
|
|
|
|
}
|
2022-12-25 15:35:05 +03:00
|
|
|
|
|
|
|
options.Meter = options.Meter.Clone(
|
|
|
|
meter.MetricPrefix(options.MeterMetricPrefix),
|
|
|
|
meter.Labels(
|
|
|
|
labelHost, options.DatabaseHost,
|
|
|
|
labelDatabase, options.DatabaseName,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2023-01-06 23:24:39 +03:00
|
|
|
options.Logger = options.Logger.Clone(logger.WithCallerSkipCount(1))
|
|
|
|
|
2021-10-27 18:49:33 +03:00
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
|
|
|
// MetricInterval specifies stats interval for *sql.DB
|
|
|
|
func MetricInterval(td time.Duration) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.MeterStatsInterval = td
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MetricPrefix specifies prefix for each metric
|
|
|
|
func MetricPrefix(pref string) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.MeterMetricPrefix = pref
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 23:24:39 +03:00
|
|
|
// LoggerEnabled enable sql logging
|
|
|
|
func LoggerEnabled(b bool) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.LoggerEnabled = b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-05 16:03:21 +03:00
|
|
|
// LoggerLevel passes logger.Level option
|
|
|
|
func LoggerLevel(lvl logger.Level) Option {
|
|
|
|
return func(o *Options) {
|
|
|
|
o.LoggerLevel = lvl
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 23:24:39 +03:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-27 18:49:33 +03:00
|
|
|
// 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)
|
|
|
|
}
|
2022-12-25 15:35:05 +03:00
|
|
|
|
|
|
|
func getQueryName(ctx context.Context) string {
|
2023-09-01 00:21:08 +03:00
|
|
|
if v, ok := ctx.Value(queryNameKey{}).(string); ok && v != labelUnknown {
|
2022-12-25 15:35:05 +03:00
|
|
|
return v
|
|
|
|
}
|
2023-09-01 14:48:57 +03:00
|
|
|
return getCallerName()
|
2022-12-25 15:35:05 +03:00
|
|
|
}
|