gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

otel.go (6894B)


      1 package otelsql
      2 
      3 import (
      4 	"context"
      5 	"database/sql"
      6 	"database/sql/driver"
      7 	"io"
      8 	"time"
      9 
     10 	"go.opentelemetry.io/otel"
     11 	"go.opentelemetry.io/otel/attribute"
     12 	"go.opentelemetry.io/otel/codes"
     13 	"go.opentelemetry.io/otel/metric"
     14 	semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
     15 	"go.opentelemetry.io/otel/trace"
     16 )
     17 
     18 const instrumName = "github.com/uptrace/opentelemetry-go-extra/otelsql"
     19 
     20 var dbRowsAffected = attribute.Key("db.rows_affected")
     21 
     22 type config struct {
     23 	tracerProvider trace.TracerProvider
     24 	tracer         trace.Tracer //nolint:structcheck
     25 
     26 	meterProvider metric.MeterProvider
     27 	meter         metric.Meter
     28 
     29 	attrs []attribute.KeyValue
     30 
     31 	queryFormatter func(query string) string
     32 }
     33 
     34 func newConfig(opts []Option) *config {
     35 	c := &config{
     36 		tracerProvider: otel.GetTracerProvider(),
     37 		meterProvider:  otel.GetMeterProvider(),
     38 	}
     39 	for _, opt := range opts {
     40 		opt(c)
     41 	}
     42 	return c
     43 }
     44 
     45 func (c *config) formatQuery(query string) string {
     46 	if c.queryFormatter != nil {
     47 		return c.queryFormatter(query)
     48 	}
     49 	return query
     50 }
     51 
     52 type dbInstrum struct {
     53 	*config
     54 
     55 	queryHistogram metric.Int64Histogram
     56 }
     57 
     58 func newDBInstrum(opts []Option) *dbInstrum {
     59 	t := &dbInstrum{
     60 		config: newConfig(opts),
     61 	}
     62 
     63 	if t.tracer == nil {
     64 		t.tracer = t.tracerProvider.Tracer(instrumName)
     65 	}
     66 	if t.meter == nil {
     67 		t.meter = t.meterProvider.Meter(instrumName)
     68 	}
     69 
     70 	var err error
     71 	t.queryHistogram, err = t.meter.Int64Histogram(
     72 		"go.sql.query_timing",
     73 		metric.WithDescription("Timing of processed queries"),
     74 		metric.WithUnit("milliseconds"),
     75 	)
     76 	if err != nil {
     77 		panic(err)
     78 	}
     79 
     80 	return t
     81 }
     82 
     83 func (t *dbInstrum) withSpan(
     84 	ctx context.Context,
     85 	spanName string,
     86 	query string,
     87 	fn func(ctx context.Context, span trace.Span) error,
     88 ) error {
     89 	var startTime time.Time
     90 	if query != "" {
     91 		startTime = time.Now()
     92 	}
     93 
     94 	attrs := make([]attribute.KeyValue, 0, len(t.attrs)+1)
     95 	attrs = append(attrs, t.attrs...)
     96 	if query != "" {
     97 		attrs = append(attrs, semconv.DBStatementKey.String(t.formatQuery(query)))
     98 	}
     99 
    100 	ctx, span := t.tracer.Start(ctx, spanName,
    101 		trace.WithSpanKind(trace.SpanKindClient),
    102 		trace.WithAttributes(attrs...))
    103 	err := fn(ctx, span)
    104 	span.End()
    105 
    106 	if query != "" {
    107 		t.queryHistogram.Record(ctx, time.Since(startTime).Milliseconds(), metric.WithAttributes(t.attrs...))
    108 	}
    109 
    110 	if !span.IsRecording() {
    111 		return err
    112 	}
    113 
    114 	switch err {
    115 	case nil,
    116 		driver.ErrSkip,
    117 		io.EOF, // end of rows iterator
    118 		sql.ErrNoRows:
    119 		// ignore
    120 	default:
    121 		span.RecordError(err)
    122 		span.SetStatus(codes.Error, err.Error())
    123 	}
    124 
    125 	return err
    126 }
    127 
    128 type Option func(c *config)
    129 
    130 // WithTracerProvider configures a tracer provider that is used to create a tracer.
    131 func WithTracerProvider(tracerProvider trace.TracerProvider) Option {
    132 	return func(c *config) {
    133 		c.tracerProvider = tracerProvider
    134 	}
    135 }
    136 
    137 // WithAttributes configures attributes that are used to create a span.
    138 func WithAttributes(attrs ...attribute.KeyValue) Option {
    139 	return func(c *config) {
    140 		c.attrs = append(c.attrs, attrs...)
    141 	}
    142 }
    143 
    144 // WithDBSystem configures a db.system attribute. You should prefer using
    145 // WithAttributes and semconv, for example, `otelsql.WithAttributes(semconv.DBSystemSqlite)`.
    146 func WithDBSystem(system string) Option {
    147 	return func(c *config) {
    148 		c.attrs = append(c.attrs, semconv.DBSystemKey.String(system))
    149 	}
    150 }
    151 
    152 // WithDBName configures a db.name attribute.
    153 func WithDBName(name string) Option {
    154 	return func(c *config) {
    155 		c.attrs = append(c.attrs, semconv.DBNameKey.String(name))
    156 	}
    157 }
    158 
    159 // WithMeterProvider configures a metric.Meter used to create instruments.
    160 func WithMeterProvider(meterProvider metric.MeterProvider) Option {
    161 	return func(c *config) {
    162 		c.meterProvider = meterProvider
    163 	}
    164 }
    165 
    166 // WithQueryFormatter configures a query formatter
    167 func WithQueryFormatter(queryFormatter func(query string) string) Option {
    168 	return func(c *config) {
    169 		c.queryFormatter = queryFormatter
    170 	}
    171 }
    172 
    173 // ReportDBStatsMetrics reports DBStats metrics using OpenTelemetry Metrics API.
    174 func ReportDBStatsMetrics(db *sql.DB, opts ...Option) {
    175 	cfg := newConfig(opts)
    176 
    177 	if cfg.meter == nil {
    178 		cfg.meter = cfg.meterProvider.Meter(instrumName)
    179 	}
    180 
    181 	meter := cfg.meter
    182 	labels := cfg.attrs
    183 
    184 	maxOpenConns, _ := meter.Int64ObservableGauge(
    185 		"go.sql.connections_max_open",
    186 		metric.WithDescription("Maximum number of open connections to the database"),
    187 	)
    188 	openConns, _ := meter.Int64ObservableGauge(
    189 		"go.sql.connections_open",
    190 		metric.WithDescription("The number of established connections both in use and idle"),
    191 	)
    192 	inUseConns, _ := meter.Int64ObservableGauge(
    193 		"go.sql.connections_in_use",
    194 		metric.WithDescription("The number of connections currently in use"),
    195 	)
    196 	idleConns, _ := meter.Int64ObservableGauge(
    197 		"go.sql.connections_idle",
    198 		metric.WithDescription("The number of idle connections"),
    199 	)
    200 	connsWaitCount, _ := meter.Int64ObservableCounter(
    201 		"go.sql.connections_wait_count",
    202 		metric.WithDescription("The total number of connections waited for"),
    203 	)
    204 	connsWaitDuration, _ := meter.Int64ObservableCounter(
    205 		"go.sql.connections_wait_duration",
    206 		metric.WithDescription("The total time blocked waiting for a new connection"),
    207 		metric.WithUnit("nanoseconds"),
    208 	)
    209 	connsClosedMaxIdle, _ := meter.Int64ObservableCounter(
    210 		"go.sql.connections_closed_max_idle",
    211 		metric.WithDescription("The total number of connections closed due to SetMaxIdleConns"),
    212 	)
    213 	connsClosedMaxIdleTime, _ := meter.Int64ObservableCounter(
    214 		"go.sql.connections_closed_max_idle_time",
    215 		metric.WithDescription("The total number of connections closed due to SetConnMaxIdleTime"),
    216 	)
    217 	connsClosedMaxLifetime, _ := meter.Int64ObservableCounter(
    218 		"go.sql.connections_closed_max_lifetime",
    219 		metric.WithDescription("The total number of connections closed due to SetConnMaxLifetime"),
    220 	)
    221 
    222 	if _, err := meter.RegisterCallback(
    223 		func(ctx context.Context, o metric.Observer) error {
    224 			stats := db.Stats()
    225 
    226 			o.ObserveInt64(maxOpenConns, int64(stats.MaxOpenConnections), metric.WithAttributes(labels...))
    227 
    228 			o.ObserveInt64(openConns, int64(stats.OpenConnections), metric.WithAttributes(labels...))
    229 			o.ObserveInt64(inUseConns, int64(stats.InUse), metric.WithAttributes(labels...))
    230 			o.ObserveInt64(idleConns, int64(stats.Idle), metric.WithAttributes(labels...))
    231 
    232 			o.ObserveInt64(connsWaitCount, stats.WaitCount, metric.WithAttributes(labels...))
    233 			o.ObserveInt64(connsWaitDuration, int64(stats.WaitDuration), metric.WithAttributes(labels...))
    234 			o.ObserveInt64(connsClosedMaxIdle, stats.MaxIdleClosed, metric.WithAttributes(labels...))
    235 			o.ObserveInt64(connsClosedMaxIdleTime, stats.MaxIdleTimeClosed, metric.WithAttributes(labels...))
    236 			o.ObserveInt64(connsClosedMaxLifetime, stats.MaxLifetimeClosed, metric.WithAttributes(labels...))
    237 
    238 			return nil
    239 		},
    240 		maxOpenConns,
    241 		openConns,
    242 		inUseConns,
    243 		idleConns,
    244 		connsWaitCount,
    245 		connsWaitDuration,
    246 		connsClosedMaxIdle,
    247 		connsClosedMaxIdleTime,
    248 		connsClosedMaxLifetime,
    249 	); err != nil {
    250 		panic(err)
    251 	}
    252 }