From ebff350053a752b3b9d162a0d94a29a3b37fd529 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Oct 2026 12:35:16 +0000 Subject: [PATCH] fix(deps): update module github.com/exaring/otelpgx to v0.13.0 --- go.mod | 2 +- go.sum | 2 + .../exaring/otelpgx/operation_name.go | 45 ++++++ .../exaring/otelpgx/operation_name_go124.go | 19 --- .../exaring/otelpgx/operation_name_legacy.go | 27 ---- vendor/github.com/exaring/otelpgx/options.go | 28 ++++ vendor/github.com/exaring/otelpgx/tracer.go | 145 ++++++++++++------ vendor/modules.txt | 2 +- 8 files changed, 176 insertions(+), 94 deletions(-) create mode 100644 vendor/github.com/exaring/otelpgx/operation_name.go delete mode 100644 vendor/github.com/exaring/otelpgx/operation_name_go124.go delete mode 100644 vendor/github.com/exaring/otelpgx/operation_name_legacy.go diff --git a/go.mod b/go.mod index ce508716a..3fb94a8d8 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/coreos/go-oidc/v3 v3.21.0 github.com/envoyproxy/go-control-plane v0.14.0 github.com/envoyproxy/go-control-plane/envoy v1.39.0 - github.com/exaring/otelpgx v0.12.0 + github.com/exaring/otelpgx v0.13.0 github.com/goccy/go-yaml v1.19.2 github.com/gogo/protobuf v1.3.2 github.com/google/go-cmp v0.7.0 diff --git a/go.sum b/go.sum index 7bd11ba10..4a4c1606e 100644 --- a/go.sum +++ b/go.sum @@ -186,6 +186,8 @@ github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMD github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/exaring/otelpgx v0.12.0 h1:K3NG2YUiYB384YWptKglk8gLDYek5YptMdm1b0G4pQM= github.com/exaring/otelpgx v0.12.0/go.mod h1:3OojrUKhhy3lTbYIMBijP3YjMey/jo14eHAW5cXcUdk= +github.com/exaring/otelpgx v0.13.0 h1:Vi0I2GWRS3nnVzgr73L/XbSLqthR0h6A1rm4Wt3W4aA= +github.com/exaring/otelpgx v0.13.0/go.mod h1:3OojrUKhhy3lTbYIMBijP3YjMey/jo14eHAW5cXcUdk= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/vendor/github.com/exaring/otelpgx/operation_name.go b/vendor/github.com/exaring/otelpgx/operation_name.go new file mode 100644 index 000000000..8b3609bc3 --- /dev/null +++ b/vendor/github.com/exaring/otelpgx/operation_name.go @@ -0,0 +1,45 @@ +package otelpgx + +import ( + "context" + "strings" + "unicode" +) + +// SQLOperationName attempts to get the first 'word' from a given SQL query, +// which usually is the operation name (e.g. 'SELECT'). Leading SQL comments +// are skipped. It's the default [SpanNameCtxFunc], and can also be passed to +// [WithMetricOperationName] to use the same low-cardinality parser for +// db.operation.name on metrics. +func SQLOperationName(_ context.Context, stmt string) string { + for word := range strings.FieldsSeq(skipLeadingComments(stmt)) { + return strings.ToUpper(word) + } + + return sqlOperationUnknown +} + +// skipLeadingComments strips leading whitespace, "--" line comments and "/* */" +// block comments (not nested); an unterminated block comment yields "". Needed +// because tools like sqlc prefix every query with a "-- name: ..." comment. +func skipLeadingComments(stmt string) string { + for { + stmt = strings.TrimLeftFunc(stmt, unicode.IsSpace) + switch { + case strings.HasPrefix(stmt, "--"): + _, rest, ok := strings.Cut(stmt, "\n") + if !ok { + return "" + } + stmt = rest + case strings.HasPrefix(stmt, "/*"): + _, rest, ok := strings.Cut(stmt[2:], "*/") + if !ok { + return "" + } + stmt = rest + default: + return stmt + } + } +} diff --git a/vendor/github.com/exaring/otelpgx/operation_name_go124.go b/vendor/github.com/exaring/otelpgx/operation_name_go124.go deleted file mode 100644 index 0639ed27c..000000000 --- a/vendor/github.com/exaring/otelpgx/operation_name_go124.go +++ /dev/null @@ -1,19 +0,0 @@ -//go:build go1.24 -// +build go1.24 - -package otelpgx - -import ( - "context" - "strings" -) - -// defaultSpanNameCtxFunc attempts to get the first 'word' from a given SQL query, which usually -// is the operation name (e.g. 'SELECT'). -func defaultSpanNameCtxFunc(_ context.Context, stmt string) string { - for word := range strings.FieldsSeq(stmt) { - return strings.ToUpper(word) - } - - return sqlOperationUnknown -} diff --git a/vendor/github.com/exaring/otelpgx/operation_name_legacy.go b/vendor/github.com/exaring/otelpgx/operation_name_legacy.go deleted file mode 100644 index a1086592b..000000000 --- a/vendor/github.com/exaring/otelpgx/operation_name_legacy.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build !go1.24 -// +build !go1.24 - -package otelpgx - -import ( - "context" - "strings" - "unicode" -) - -// defaultSpanNameCtxFunc attempts to get the first 'word' from a given SQL query, which usually -// is the operation name (e.g. 'SELECT'). -func defaultSpanNameCtxFunc(_ context.Context, stmt string) string { - stmt = strings.TrimSpace(stmt) - end := strings.IndexFunc(stmt, unicode.IsSpace) - if end < 0 && len(stmt) > 0 { - // No space found, use the whole statement. - end = len(stmt) - } else if end < 0 { - // Fall back to a fixed value to prevent creating lots of tracing operations - // differing only by the amount of whitespace in them (in case we'd fall back - // to the full query or a cut-off version). - return sqlOperationUnknown - } - return strings.ToUpper(stmt[:end]) -} diff --git a/vendor/github.com/exaring/otelpgx/options.go b/vendor/github.com/exaring/otelpgx/options.go index d1859fee3..9081f903d 100644 --- a/vendor/github.com/exaring/otelpgx/options.go +++ b/vendor/github.com/exaring/otelpgx/options.go @@ -185,6 +185,34 @@ func WithIncludeQueryParameters(filter ...QueryParametersFilterFunc) Option { }) } +// OperationNameFunc returns the value for the db.operation.name attribute on +// the db.client.operation.duration and db.client.operation.errors metrics, +// given the SQL statement for a query, prepare, or batch-query call. Return +// "" to omit the attribute for a particular call. +type OperationNameFunc func(ctx context.Context, sql string) string + +// WithMetricOperationName enables the db.operation.name attribute on the +// db.client.operation.duration and db.client.operation.errors metrics for +// query, prepare, and per-statement batch-query calls, using fn to derive it +// from each call's SQL statement. By default this is unset and these metrics +// carry no operation name. +// +// This is deliberately independent of the span-naming hooks +// (WithSpanNameFunc/WithSpanNameCtxFunc): metric attributes are recorded +// regardless of trace sampling, so once set, fn runs on every call, sampled +// or not. Pass [SQLOperationName] for the same low-cardinality parser the +// span path uses by default, or a custom function — its output cardinality +// becomes the metric's cardinality. +// +// connect/acquire/copy calls have no SQL statement to derive a name from, and +// the aggregate duration/error count for a whole batch has no single +// operation name to describe it; fn is never called for any of these. +func WithMetricOperationName(fn OperationNameFunc) Option { + return optionFunc(func(cfg *tracerConfig) { + cfg.metricOperationNameFunc = fn + }) +} + // StatsOption allows for managing RecordStats configuration using functional options. type StatsOption interface { applyStatsOptions(o *statsOptions) diff --git a/vendor/github.com/exaring/otelpgx/tracer.go b/vendor/github.com/exaring/otelpgx/tracer.go index 31b4bb656..ad663d027 100644 --- a/vendor/github.com/exaring/otelpgx/tracer.go +++ b/vendor/github.com/exaring/otelpgx/tracer.go @@ -57,6 +57,10 @@ const ( type startTimeCtxKey struct{} +// metricOperationNameCtxKey threads WithMetricOperationName's computed value +// from Trace*Start to Trace*End (query, prepare). +type metricOperationNameCtxKey struct{} + var _ pgxpool.AcquireTracer = (*Tracer)(nil) // Tracer is a wrapper around the pgx tracer interfaces which instrument @@ -75,14 +79,15 @@ type Tracer struct { operationDuration dbconv.ClientOperationDuration operationErrors metric.Int64Counter - fullQuerySpanName bool - spanNameCtxFunc SpanNameCtxFunc - prefixQuerySpanName bool - logSQLStatement bool - logConnectionDetails bool - includeParams bool - queryParamsFilter QueryParametersFilterFunc - disableAcquireTracer bool + fullQuerySpanName bool + spanNameCtxFunc SpanNameCtxFunc + prefixQuerySpanName bool + logSQLStatement bool + logConnectionDetails bool + includeParams bool + queryParamsFilter QueryParametersFilterFunc + disableAcquireTracer bool + metricOperationNameFunc OperationNameFunc } type tracerConfig struct { @@ -92,14 +97,15 @@ type tracerConfig struct { tracerAttrs []attribute.KeyValue meterAttrs []attribute.KeyValue - fullQuerySpanName bool - spanNameCtxFunc SpanNameCtxFunc - prefixQuerySpanName bool - logSQLStatement bool - logConnectionDetails bool - includeParams bool - queryParamsFilter QueryParametersFilterFunc - disableAcquireTracer bool + fullQuerySpanName bool + spanNameCtxFunc SpanNameCtxFunc + prefixQuerySpanName bool + logSQLStatement bool + logConnectionDetails bool + includeParams bool + queryParamsFilter QueryParametersFilterFunc + disableAcquireTracer bool + metricOperationNameFunc OperationNameFunc } // NewTracer returns a new Tracer. @@ -114,7 +120,7 @@ func NewTracer(opts ...Option) *Tracer { semconv.DBSystemNamePostgreSQL, }, fullQuerySpanName: false, - spanNameCtxFunc: defaultSpanNameCtxFunc, + spanNameCtxFunc: SQLOperationName, prefixQuerySpanName: false, logSQLStatement: true, logConnectionDetails: true, @@ -141,16 +147,17 @@ func NewTracer(opts ...Option) *Tracer { return &s }, }, - tracerAttrs: cfg.tracerAttrs, - meterAttrs: cfg.meterAttrs, - fullQuerySpanName: cfg.fullQuerySpanName, - spanNameCtxFunc: cfg.spanNameCtxFunc, - prefixQuerySpanName: cfg.prefixQuerySpanName, - logSQLStatement: cfg.logSQLStatement, - logConnectionDetails: cfg.logConnectionDetails, - includeParams: cfg.includeParams, - queryParamsFilter: cfg.queryParamsFilter, - disableAcquireTracer: cfg.disableAcquireTracer, + tracerAttrs: cfg.tracerAttrs, + meterAttrs: cfg.meterAttrs, + fullQuerySpanName: cfg.fullQuerySpanName, + spanNameCtxFunc: cfg.spanNameCtxFunc, + prefixQuerySpanName: cfg.prefixQuerySpanName, + logSQLStatement: cfg.logSQLStatement, + logConnectionDetails: cfg.logConnectionDetails, + includeParams: cfg.includeParams, + queryParamsFilter: cfg.queryParamsFilter, + disableAcquireTracer: cfg.disableAcquireTracer, + metricOperationNameFunc: cfg.metricOperationNameFunc, } tracer.createMetrics() @@ -216,21 +223,45 @@ func recordSpanError(span trace.Span, err error) { // incrementOperationErrorCount will increment the operation error count metric for any provided error // that is non-nil and not sql.ErrNoRows. Otherwise, incrementOperationErrorCount becomes a no-op. -func (t *Tracer) incrementOperationErrorCount(ctx context.Context, err error, pgxOperation string) { +// +// operationName is the SQL operation name (e.g. "SELECT") to additionally +// attach as db.operation.name, or "" when none applies to this call (e.g. +// connect/acquire/copy, or the aggregate end of a batch). +func (t *Tracer) incrementOperationErrorCount(ctx context.Context, err error, pgxOperation, operationName string) { if err != nil && !errors.Is(err, sql.ErrNoRows) { t.operationErrors.Add(ctx, 1, metric.WithAttributeSet( - t.metricAttrs[pgxOperation], + t.attributeSetFor(pgxOperation, operationName), )) } } -// recordOperationDuration will compute and record the time since the start of an operation. -func (t *Tracer) recordOperationDuration(ctx context.Context, pgxOperation string) { +// recordOperationDuration will compute and record the time since the start of +// an operation. See incrementOperationErrorCount for the operationName parameter. +func (t *Tracer) recordOperationDuration(ctx context.Context, pgxOperation, operationName string) { if startTime, ok := ctx.Value(startTimeCtxKey{}).(time.Time); ok { - t.operationDuration.RecordSet(ctx, time.Since(startTime).Seconds(), t.metricAttrs[pgxOperation]) + t.operationDuration.RecordSet(ctx, time.Since(startTime).Seconds(), t.attributeSetFor(pgxOperation, operationName)) } } +// attributeSetFor returns the attribute.Set to record metrics against for a +// given pgx operation kind, additionally carrying db.operation.name when +// operationName is non-empty. This isn't cached: benchmarking showed building +// a fresh attribute.Set costs ~150ns/3 allocs versus a cached lookup, which is +// immaterial next to a database round trip, and a cache keyed on +// operationName could grow unboundedly large for a caller-supplied +// OperationNameFunc with high-cardinality output. +func (t *Tracer) attributeSetFor(pgxOperation, operationName string) attribute.Set { + if operationName == "" { + return t.metricAttrs[pgxOperation] + } + + attrs := append(append([]attribute.KeyValue{}, t.meterAttrs...), + PGXOperationTypeKey.String(pgxOperation), + t.operationDuration.AttrOperationName(operationName), + ) + return attribute.NewSet(attrs...) +} + // connectionAttributesFromConfig returns a SpanStartOption that contains // attributes from the given connection config. func connectionAttributesFromConfig(config *pgx.ConnConfig) []attribute.KeyValue { @@ -251,6 +282,11 @@ func connectionAttributesFromConfig(config *pgx.ConnConfig) []attribute.KeyValue func (t *Tracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context { ctx = context.WithValue(ctx, startTimeCtxKey{}, time.Now()) + // Runs regardless of trace sampling, unlike the span-naming hooks below. + if t.metricOperationNameFunc != nil { + ctx = context.WithValue(ctx, metricOperationNameCtxKey{}, t.metricOperationNameFunc(ctx, data.SQL)) + } + if !trace.SpanFromContext(ctx).IsRecording() { return ctx } @@ -318,8 +354,9 @@ func (t *Tracer) spanName(sql, operationName, prefix string) string { // TraceQueryEnd is called at the end of Query, QueryRow, and Exec calls. func (t *Tracer) TraceQueryEnd(ctx context.Context, _ *pgx.Conn, data pgx.TraceQueryEndData) { span := trace.SpanFromContext(ctx) - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationQuery) - t.recordOperationDuration(ctx, pgxOperationQuery) + operationName, _ := ctx.Value(metricOperationNameCtxKey{}).(string) + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationQuery, operationName) + t.recordOperationDuration(ctx, pgxOperationQuery, operationName) if !span.IsRecording() { return @@ -373,8 +410,8 @@ func (t *Tracer) TraceCopyFromStart(ctx context.Context, conn *pgx.Conn, data pg // TraceCopyFromEnd is called at the end of CopyFrom calls. func (t *Tracer) TraceCopyFromEnd(ctx context.Context, _ *pgx.Conn, data pgx.TraceCopyFromEndData) { span := trace.SpanFromContext(ctx) - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationCopy) - t.recordOperationDuration(ctx, pgxOperationCopy) + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationCopy, "") + t.recordOperationDuration(ctx, pgxOperationCopy, "") if !span.IsRecording() { return @@ -431,7 +468,13 @@ func (t *Tracer) TraceBatchStart(ctx context.Context, conn *pgx.Conn, data pgx.T // TraceBatchQuery is called at the after each query in a batch. func (t *Tracer) TraceBatchQuery(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData) { - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationBatch) + // Unlike the aggregate TraceBatchEnd, each call here describes exactly one + // statement, so a single operation name always applies. + var metricOperationName string + if t.metricOperationNameFunc != nil { + metricOperationName = t.metricOperationNameFunc(ctx, data.SQL) + } + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationBatch, metricOperationName) if !trace.SpanFromContext(ctx).IsRecording() { return @@ -478,11 +521,15 @@ func (t *Tracer) TraceBatchQuery(ctx context.Context, conn *pgx.Conn, data pgx.T span.End() } -// TraceBatchEnd is called at the end of SendBatch calls. +// TraceBatchEnd is called at the end of SendBatch calls. The aggregate +// duration/error count for the whole batch is recorded with no +// db.operation.name: a batch may mix operation types, so there is no single +// name that describes it (per-statement names are attached in +// TraceBatchQuery instead). func (t *Tracer) TraceBatchEnd(ctx context.Context, _ *pgx.Conn, data pgx.TraceBatchEndData) { span := trace.SpanFromContext(ctx) - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationBatch) - t.recordOperationDuration(ctx, pgxOperationBatch) + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationBatch, "") + t.recordOperationDuration(ctx, pgxOperationBatch, "") if !span.IsRecording() { return @@ -530,8 +577,8 @@ func (t *Tracer) TraceConnectStart(ctx context.Context, data pgx.TraceConnectSta // TraceConnectEnd is called at the end of Connect and ConnectConfig calls. func (t *Tracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEndData) { span := trace.SpanFromContext(ctx) - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationConnect) - t.recordOperationDuration(ctx, pgxOperationConnect) + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationConnect, "") + t.recordOperationDuration(ctx, pgxOperationConnect, "") if !span.IsRecording() { return @@ -547,6 +594,11 @@ func (t *Tracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEndDa func (t *Tracer) TracePrepareStart(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context { ctx = context.WithValue(ctx, startTimeCtxKey{}, time.Now()) + // Runs regardless of trace sampling, unlike the span-naming hooks below. + if t.metricOperationNameFunc != nil { + ctx = context.WithValue(ctx, metricOperationNameCtxKey{}, t.metricOperationNameFunc(ctx, data.SQL)) + } + if !trace.SpanFromContext(ctx).IsRecording() { return ctx } @@ -592,8 +644,9 @@ func (t *Tracer) TracePrepareStart(ctx context.Context, conn *pgx.Conn, data pgx // TracePrepareEnd is called at the end of Prepare calls. func (t *Tracer) TracePrepareEnd(ctx context.Context, _ *pgx.Conn, data pgx.TracePrepareEndData) { span := trace.SpanFromContext(ctx) - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationPrepare) - t.recordOperationDuration(ctx, pgxOperationPrepare) + operationName, _ := ctx.Value(metricOperationNameCtxKey{}).(string) + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationPrepare, operationName) + t.recordOperationDuration(ctx, pgxOperationPrepare, operationName) if !span.IsRecording() { return @@ -650,8 +703,8 @@ func (t *Tracer) TraceAcquireEnd(ctx context.Context, _ *pgxpool.Pool, data pgxp } span := trace.SpanFromContext(ctx) - t.incrementOperationErrorCount(ctx, data.Err, pgxOperationAcquire) - t.recordOperationDuration(ctx, pgxOperationAcquire) + t.incrementOperationErrorCount(ctx, data.Err, pgxOperationAcquire, "") + t.recordOperationDuration(ctx, pgxOperationAcquire, "") if !span.IsRecording() { return diff --git a/vendor/modules.txt b/vendor/modules.txt index 8dec6feab..10e46b261 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -382,7 +382,7 @@ github.com/envoyproxy/go-control-plane/ratelimit/service/ratelimit/v3 # github.com/envoyproxy/protoc-gen-validate v1.3.3 ## explicit; go 1.24.1 github.com/envoyproxy/protoc-gen-validate/validate -# github.com/exaring/otelpgx v0.12.0 +# github.com/exaring/otelpgx v0.13.0 ## explicit; go 1.25.0 github.com/exaring/otelpgx # github.com/felixge/httpsnoop v1.1.0