summaryrefslogtreecommitdiff
path: root/vendor/github.com/uptrace
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/uptrace')
-rw-r--r--vendor/github.com/uptrace/bun/extra/bunotel/LICENSE24
-rw-r--r--vendor/github.com/uptrace/bun/extra/bunotel/README.md3
-rw-r--r--vendor/github.com/uptrace/bun/extra/bunotel/option.go32
-rw-r--r--vendor/github.com/uptrace/bun/extra/bunotel/otel.go188
-rw-r--r--vendor/github.com/uptrace/bun/extra/bunotel/safe.go11
-rw-r--r--vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go18
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/.golangci.yml5
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/LICENSE24
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md118
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/driver.go460
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/otel.go254
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/stmt.go120
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/tx.go38
-rw-r--r--vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go6
14 files changed, 1301 insertions, 0 deletions
diff --git a/vendor/github.com/uptrace/bun/extra/bunotel/LICENSE b/vendor/github.com/uptrace/bun/extra/bunotel/LICENSE
new file mode 100644
index 000000000..7ec81810c
--- /dev/null
+++ b/vendor/github.com/uptrace/bun/extra/bunotel/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2021 Vladimir Mihailenco. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/uptrace/bun/extra/bunotel/README.md b/vendor/github.com/uptrace/bun/extra/bunotel/README.md
new file mode 100644
index 000000000..50b3e6c48
--- /dev/null
+++ b/vendor/github.com/uptrace/bun/extra/bunotel/README.md
@@ -0,0 +1,3 @@
+# OpenTelemetry instrumentation for Bun
+
+See [example](../example/opentelemetry) for details.
diff --git a/vendor/github.com/uptrace/bun/extra/bunotel/option.go b/vendor/github.com/uptrace/bun/extra/bunotel/option.go
new file mode 100644
index 000000000..dc294ffa5
--- /dev/null
+++ b/vendor/github.com/uptrace/bun/extra/bunotel/option.go
@@ -0,0 +1,32 @@
+package bunotel
+
+import (
+ "go.opentelemetry.io/otel/attribute"
+ semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
+)
+
+type Option func(h *QueryHook)
+
+// WithAttributes configures attributes that are used to create a span.
+func WithAttributes(attrs ...attribute.KeyValue) Option {
+ return func(h *QueryHook) {
+ h.attrs = append(h.attrs, attrs...)
+ }
+}
+
+// WithDBName configures a db.name attribute.
+func WithDBName(name string) Option {
+ return func(h *QueryHook) {
+ h.attrs = append(h.attrs, semconv.DBNameKey.String(name))
+ }
+}
+
+// WithFormattedQueries enables formatting of the query that is added
+// as the statement attribute to the trace.
+// This means that all placeholders and arguments will be filled first
+// and the query will contain all information as sent to the database.
+func WithFormattedQueries(format bool) Option {
+ return func(h *QueryHook) {
+ h.formatQueries = format
+ }
+}
diff --git a/vendor/github.com/uptrace/bun/extra/bunotel/otel.go b/vendor/github.com/uptrace/bun/extra/bunotel/otel.go
new file mode 100644
index 000000000..25000307d
--- /dev/null
+++ b/vendor/github.com/uptrace/bun/extra/bunotel/otel.go
@@ -0,0 +1,188 @@
+package bunotel
+
+import (
+ "context"
+ "database/sql"
+ "runtime"
+ "strings"
+ "time"
+
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+ "go.opentelemetry.io/otel/metric/global"
+ "go.opentelemetry.io/otel/metric/instrument"
+ semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
+ "go.opentelemetry.io/otel/trace"
+
+ "github.com/uptrace/bun"
+ "github.com/uptrace/bun/dialect"
+ "github.com/uptrace/bun/schema"
+ "github.com/uptrace/opentelemetry-go-extra/otelsql"
+)
+
+var (
+ tracer = otel.Tracer("github.com/uptrace/bun")
+ meter = global.Meter("github.com/uptrace/bun")
+
+ queryHistogram, _ = meter.Int64Histogram(
+ "go.sql.query_timing",
+ instrument.WithDescription("Timing of processed queries"),
+ instrument.WithUnit("milliseconds"),
+ )
+)
+
+type QueryHook struct {
+ attrs []attribute.KeyValue
+ formatQueries bool
+}
+
+var _ bun.QueryHook = (*QueryHook)(nil)
+
+func NewQueryHook(opts ...Option) *QueryHook {
+ h := new(QueryHook)
+ for _, opt := range opts {
+ opt(h)
+ }
+ return h
+}
+
+func (h *QueryHook) Init(db *bun.DB) {
+ labels := make([]attribute.KeyValue, 0, len(h.attrs)+1)
+ labels = append(labels, h.attrs...)
+ if sys := dbSystem(db); sys.Valid() {
+ labels = append(labels, sys)
+ }
+
+ otelsql.ReportDBStatsMetrics(db.DB, otelsql.WithAttributes(labels...))
+}
+
+func (h *QueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {
+ ctx, _ = tracer.Start(ctx, "", trace.WithSpanKind(trace.SpanKindClient))
+ return ctx
+}
+
+func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) {
+ operation := event.Operation()
+ dbOperation := semconv.DBOperationKey.String(operation)
+
+ labels := make([]attribute.KeyValue, 0, len(h.attrs)+2)
+ labels = append(labels, h.attrs...)
+ labels = append(labels, dbOperation)
+ if event.IQuery != nil {
+ if tableName := event.IQuery.GetTableName(); tableName != "" {
+ labels = append(labels, semconv.DBSQLTableKey.String(tableName))
+ }
+ }
+
+ queryHistogram.Record(ctx, time.Since(event.StartTime).Milliseconds(), labels...)
+
+ span := trace.SpanFromContext(ctx)
+ if !span.IsRecording() {
+ return
+ }
+
+ span.SetName(operation)
+ defer span.End()
+
+ query := h.eventQuery(event)
+ fn, file, line := funcFileLine("github.com/uptrace/bun")
+
+ attrs := make([]attribute.KeyValue, 0, 10)
+ attrs = append(attrs, h.attrs...)
+ attrs = append(attrs,
+ dbOperation,
+ semconv.DBStatementKey.String(query),
+ semconv.CodeFunctionKey.String(fn),
+ semconv.CodeFilepathKey.String(file),
+ semconv.CodeLineNumberKey.Int(line),
+ )
+
+ if sys := dbSystem(event.DB); sys.Valid() {
+ attrs = append(attrs, sys)
+ }
+ if event.Result != nil {
+ if n, _ := event.Result.RowsAffected(); n > 0 {
+ attrs = append(attrs, attribute.Int64("db.rows_affected", n))
+ }
+ }
+
+ switch event.Err {
+ case nil, sql.ErrNoRows, sql.ErrTxDone:
+ // ignore
+ default:
+ span.RecordError(event.Err)
+ span.SetStatus(codes.Error, event.Err.Error())
+ }
+
+ span.SetAttributes(attrs...)
+}
+
+func funcFileLine(pkg string) (string, string, int) {
+ const depth = 16
+ var pcs [depth]uintptr
+ n := runtime.Callers(3, pcs[:])
+ ff := runtime.CallersFrames(pcs[:n])
+
+ var fn, file string
+ var line int
+ for {
+ f, ok := ff.Next()
+ if !ok {
+ break
+ }
+ fn, file, line = f.Function, f.File, f.Line
+ if !strings.Contains(fn, pkg) {
+ break
+ }
+ }
+
+ if ind := strings.LastIndexByte(fn, '/'); ind != -1 {
+ fn = fn[ind+1:]
+ }
+
+ return fn, file, line
+}
+
+func (h *QueryHook) eventQuery(event *bun.QueryEvent) string {
+ const softQueryLimit = 8000
+ const hardQueryLimit = 16000
+
+ var query string
+
+ if h.formatQueries && len(event.Query) <= softQueryLimit {
+ query = event.Query
+ } else {
+ query = unformattedQuery(event)
+ }
+
+ if len(query) > hardQueryLimit {
+ query = query[:hardQueryLimit]
+ }
+
+ return query
+}
+
+func unformattedQuery(event *bun.QueryEvent) string {
+ if event.IQuery != nil {
+ if b, err := event.IQuery.AppendQuery(schema.NewNopFormatter(), nil); err == nil {
+ return bytesToString(b)
+ }
+ }
+ return string(event.QueryTemplate)
+}
+
+func dbSystem(db *bun.DB) attribute.KeyValue {
+ switch db.Dialect().Name() {
+ case dialect.PG:
+ return semconv.DBSystemPostgreSQL
+ case dialect.MySQL:
+ return semconv.DBSystemMySQL
+ case dialect.SQLite:
+ return semconv.DBSystemSqlite
+ case dialect.MSSQL:
+ return semconv.DBSystemMSSQL
+ default:
+ return attribute.KeyValue{}
+ }
+}
diff --git a/vendor/github.com/uptrace/bun/extra/bunotel/safe.go b/vendor/github.com/uptrace/bun/extra/bunotel/safe.go
new file mode 100644
index 000000000..fab151a78
--- /dev/null
+++ b/vendor/github.com/uptrace/bun/extra/bunotel/safe.go
@@ -0,0 +1,11 @@
+// +build appengine
+
+package internal
+
+func bytesToString(b []byte) string {
+ return string(b)
+}
+
+func stringToBytes(s string) []byte {
+ return []byte(s)
+}
diff --git a/vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go b/vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go
new file mode 100644
index 000000000..23accd40e
--- /dev/null
+++ b/vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go
@@ -0,0 +1,18 @@
+// +build !appengine
+
+package bunotel
+
+import "unsafe"
+
+func bytesToString(b []byte) string {
+ return *(*string)(unsafe.Pointer(&b))
+}
+
+func stringToBytes(s string) []byte {
+ return *(*[]byte)(unsafe.Pointer(
+ &struct {
+ string
+ Cap int
+ }{s, len(s)},
+ ))
+}
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/.golangci.yml b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/.golangci.yml
new file mode 100644
index 000000000..65b3c9e6e
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/.golangci.yml
@@ -0,0 +1,5 @@
+issues:
+ exclude-rules:
+ - text: 'Drivers should implement'
+ linters:
+ - staticcheck
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/LICENSE b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/LICENSE
new file mode 100644
index 000000000..83bbb00f4
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2020 github.com/uptrace/opentelemetry-go-extra Contributors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md
new file mode 100644
index 000000000..dbded166d
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md
@@ -0,0 +1,118 @@
+[![PkgGoDev](https://pkg.go.dev/badge/github.com/uptrace/opentelemetry-go-extra/otelsql)](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql)
+
+# database/sql instrumentation for OpenTelemetry Go
+
+[database/sql OpenTelemetry instrumentation](https://uptrace.dev/opentelemetry/instrumentations/go-database-sql.html)
+records database queries (including `Tx` and `Stmt` queries) and reports `DBStats` metrics.
+
+## Installation
+
+```shell
+go get github.com/uptrace/opentelemetry-go-extra/otelsql
+```
+
+## Usage
+
+To instrument database/sql, you need to connect to a database using the API provided by otelsql:
+
+| sql | otelsql |
+| --------------------------- | ------------------------------- |
+| `sql.Open(driverName, dsn)` | `otelsql.Open(driverName, dsn)` |
+| `sql.OpenDB(connector)` | `otelsql.OpenDB(connector)` |
+
+```go
+import (
+ "github.com/uptrace/opentelemetry-go-extra/otelsql"
+ semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
+)
+
+db, err := otelsql.Open("sqlite", "file::memory:?cache=shared",
+ otelsql.WithAttributes(semconv.DBSystemSqlite),
+ otelsql.WithDBName("mydb"))
+if err != nil {
+ panic(err)
+}
+
+// db is *sql.DB
+```
+
+And then use context-aware API to propagate the active span via
+[context](https://uptrace.dev/opentelemetry/go-tracing.html#context):
+
+```go
+var num int
+if err := db.QueryRowContext(ctx, "SELECT 42").Scan(&num); err != nil {
+ panic(err)
+}
+```
+
+See [example](/example/) for details.
+
+## Options
+
+Both [otelsql.Open](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql#Open) and
+[otelsql.OpenDB](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql#OpenDB) accept
+the same [options](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql#Option):
+
+- [WithAttributes](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql#WithAttributes)
+ configures attributes that are used to create a span.
+- [WithDBName](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql#WithDBName)
+ configures a `db.name` attribute.
+- [WithDBSystem](https://pkg.go.dev/github.com/uptrace/opentelemetry-go-extra/otelsql#WithDBSystem)
+ configures a `db.system` attribute. When possible, you should prefer using WithAttributes and
+ [semconv](https://pkg.go.dev/go.opentelemetry.io/otel/semconv/v1.10.0), for example,
+ `otelsql.WithAttributes(semconv.DBSystemSqlite)`.
+
+## sqlboiler
+
+You can use otelsql to instrument [sqlboiler](https://github.com/volatiletech/sqlboiler) ORM:
+
+```go
+import (
+ "github.com/uptrace/opentelemetry-go-extra/otelsql"
+ semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
+)
+
+db, err := otelsql.Open("postgres", "dbname=fun user=abc",
+ otelsql.WithAttributes(semconv.DBSystemPostgreSQL))
+if err != nil {
+ return err
+}
+
+boil.SetDB(db)
+```
+
+## GORM 1
+
+You can use otelsql to instrument [GORM 1](https://v1.gorm.io/):
+
+```go
+import (
+ "github.com/jinzhu/gorm"
+ "github.com/uptrace/opentelemetry-go-extra/otelsql"
+ semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
+)
+
+// gormOpen is like gorm.Open, but it uses otelsql to instrument the database.
+func gormOpen(driverName, dataSourceName string, opts ...otelsql.Option) (*gorm.DB, error) {
+ db, err := otelsql.Open(driverName, dataSourceName, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return gorm.Open(driverName, db)
+}
+
+db, err := gormOpen("mysql", "user:password@/dbname",
+ otelsql.WithAttributes(semconv.DBSystemMySQL))
+if err != nil {
+ panic(err)
+}
+```
+
+To instrument GORM 2, use
+[otelgorm](https://github.com/uptrace/opentelemetry-go-extra/tree/main/otelgorm).
+
+## Alternatives
+
+- https://github.com/XSAM/otelsql - different driver registration and no metrics.
+- https://github.com/j2gg0s/otsql - like XSAM/otelsql but with Prometheus metrics.
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/driver.go b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/driver.go
new file mode 100644
index 000000000..056af3c6c
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/driver.go
@@ -0,0 +1,460 @@
+package otelsql
+
+import (
+ "context"
+ "database/sql"
+ "database/sql/driver"
+ "errors"
+
+ "go.opentelemetry.io/otel/trace"
+)
+
+// Open is a wrapper over sql.Open that instruments the sql.DB to record executed queries
+// using OpenTelemetry API.
+func Open(driverName, dsn string, opts ...Option) (*sql.DB, error) {
+ db, err := sql.Open(driverName, dsn)
+ if err != nil {
+ return nil, err
+ }
+ return patchDB(db, dsn, opts...)
+}
+
+func patchDB(db *sql.DB, dsn string, opts ...Option) (*sql.DB, error) {
+ dbDriver := db.Driver()
+ d := newDriver(dbDriver, opts)
+
+ if _, ok := dbDriver.(driver.DriverContext); ok {
+ connector, err := d.OpenConnector(dsn)
+ if err != nil {
+ return nil, err
+ }
+ return sqlOpenDB(connector, d.instrum), nil
+ }
+
+ return sqlOpenDB(&dsnConnector{
+ driver: d,
+ dsn: dsn,
+ }, d.instrum), nil
+}
+
+// OpenDB is a wrapper over sql.OpenDB that instruments the sql.DB to record executed queries
+// using OpenTelemetry API.
+func OpenDB(connector driver.Connector, opts ...Option) *sql.DB {
+ instrum := newDBInstrum(opts)
+ c := newConnector(connector.Driver(), connector, instrum)
+ return sqlOpenDB(c, instrum)
+}
+
+func sqlOpenDB(connector driver.Connector, instrum *dbInstrum) *sql.DB {
+ db := sql.OpenDB(connector)
+ ReportDBStatsMetrics(db, WithMeterProvider(instrum.meterProvider), WithAttributes(instrum.attrs...))
+ return db
+}
+
+type dsnConnector struct {
+ driver *otelDriver
+ dsn string
+}
+
+func (c *dsnConnector) Connect(ctx context.Context) (driver.Conn, error) {
+ var conn driver.Conn
+ err := c.driver.instrum.withSpan(ctx, "db.Connect", "",
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ conn, err = c.driver.Open(c.dsn)
+ return err
+ })
+ return conn, err
+}
+
+func (c *dsnConnector) Driver() driver.Driver {
+ return c.driver
+}
+
+//------------------------------------------------------------------------------
+
+type otelDriver struct {
+ driver driver.Driver
+ driverCtx driver.DriverContext
+ instrum *dbInstrum
+}
+
+var _ driver.DriverContext = (*otelDriver)(nil)
+
+func newDriver(dr driver.Driver, opts []Option) *otelDriver {
+ driverCtx, _ := dr.(driver.DriverContext)
+ d := &otelDriver{
+ driver: dr,
+ driverCtx: driverCtx,
+ instrum: newDBInstrum(opts),
+ }
+ return d
+}
+
+func (d *otelDriver) Open(name string) (driver.Conn, error) {
+ conn, err := d.driver.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ return newConn(conn, d.instrum), nil
+}
+
+func (d *otelDriver) OpenConnector(dsn string) (driver.Connector, error) {
+ connector, err := d.driverCtx.OpenConnector(dsn)
+ if err != nil {
+ return nil, err
+ }
+ return newConnector(d, connector, d.instrum), nil
+}
+
+//------------------------------------------------------------------------------
+
+type connector struct {
+ driver.Connector
+ driver driver.Driver
+ instrum *dbInstrum
+}
+
+var _ driver.Connector = (*connector)(nil)
+
+func newConnector(d driver.Driver, c driver.Connector, instrum *dbInstrum) *connector {
+ return &connector{
+ driver: d,
+ Connector: c,
+ instrum: instrum,
+ }
+}
+
+func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
+ var conn driver.Conn
+ if err := c.instrum.withSpan(ctx, "db.Connect", "",
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ conn, err = c.Connector.Connect(ctx)
+ return err
+ }); err != nil {
+ return nil, err
+ }
+ return newConn(conn, c.instrum), nil
+}
+
+func (c *connector) Driver() driver.Driver {
+ return c.driver
+}
+
+//------------------------------------------------------------------------------
+
+type otelConn struct {
+ driver.Conn
+
+ instrum *dbInstrum
+
+ ping pingFunc
+ exec execFunc
+ execCtx execCtxFunc
+ query queryFunc
+ queryCtx queryCtxFunc
+ prepareCtx prepareCtxFunc
+ beginTx beginTxFunc
+ resetSession resetSessionFunc
+ checkNamedValue checkNamedValueFunc
+}
+
+var _ driver.Conn = (*otelConn)(nil)
+
+func newConn(conn driver.Conn, instrum *dbInstrum) *otelConn {
+ cn := &otelConn{
+ Conn: conn,
+ instrum: instrum,
+ }
+
+ cn.ping = cn.createPingFunc(conn)
+ cn.exec = cn.createExecFunc(conn)
+ cn.execCtx = cn.createExecCtxFunc(conn)
+ cn.query = cn.createQueryFunc(conn)
+ cn.queryCtx = cn.createQueryCtxFunc(conn)
+ cn.prepareCtx = cn.createPrepareCtxFunc(conn)
+ cn.beginTx = cn.createBeginTxFunc(conn)
+ cn.resetSession = cn.createResetSessionFunc(conn)
+ cn.checkNamedValue = cn.createCheckNamedValueFunc(conn)
+
+ return cn
+}
+
+var _ driver.Pinger = (*otelConn)(nil)
+
+func (c *otelConn) Ping(ctx context.Context) error {
+ return c.ping(ctx)
+}
+
+type pingFunc func(ctx context.Context) error
+
+func (c *otelConn) createPingFunc(conn driver.Conn) pingFunc {
+ if pinger, ok := conn.(driver.Pinger); ok {
+ return func(ctx context.Context) error {
+ return c.instrum.withSpan(ctx, "db.Ping", "",
+ func(ctx context.Context, span trace.Span) error {
+ return pinger.Ping(ctx)
+ })
+ }
+ }
+ return func(ctx context.Context) error {
+ return driver.ErrSkip
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.Execer = (*otelConn)(nil)
+
+func (c *otelConn) Exec(query string, args []driver.Value) (driver.Result, error) {
+ return c.exec(query, args)
+}
+
+type execFunc func(query string, args []driver.Value) (driver.Result, error)
+
+func (c *otelConn) createExecFunc(conn driver.Conn) execFunc {
+ if execer, ok := conn.(driver.Execer); ok {
+ return func(query string, args []driver.Value) (driver.Result, error) {
+ return execer.Exec(query, args)
+ }
+ }
+ return func(query string, args []driver.Value) (driver.Result, error) {
+ return nil, driver.ErrSkip
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.ExecerContext = (*otelConn)(nil)
+
+func (c *otelConn) ExecContext(
+ ctx context.Context, query string, args []driver.NamedValue,
+) (driver.Result, error) {
+ return c.execCtx(ctx, query, args)
+}
+
+type execCtxFunc func(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error)
+
+func (c *otelConn) createExecCtxFunc(conn driver.Conn) execCtxFunc {
+ var fn execCtxFunc
+
+ if execer, ok := conn.(driver.ExecerContext); ok {
+ fn = execer.ExecContext
+ } else {
+ fn = func(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+ vArgs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+ return c.exec(query, vArgs)
+ }
+ }
+
+ return func(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
+ var res driver.Result
+ if err := c.instrum.withSpan(ctx, "db.Exec", query,
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ res, err = fn(ctx, query, args)
+ if err != nil {
+ return err
+ }
+
+ if span.IsRecording() {
+ rows, err := res.RowsAffected()
+ if err == nil {
+ span.SetAttributes(dbRowsAffected.Int64(rows))
+ }
+ }
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return res, nil
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.Queryer = (*otelConn)(nil)
+
+func (c *otelConn) Query(query string, args []driver.Value) (driver.Rows, error) {
+ return c.query(query, args)
+}
+
+type queryFunc func(query string, args []driver.Value) (driver.Rows, error)
+
+func (c *otelConn) createQueryFunc(conn driver.Conn) queryFunc {
+ if queryer, ok := c.Conn.(driver.Queryer); ok {
+ return func(query string, args []driver.Value) (driver.Rows, error) {
+ return queryer.Query(query, args)
+ }
+ }
+ return func(query string, args []driver.Value) (driver.Rows, error) {
+ return nil, driver.ErrSkip
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.QueryerContext = (*otelConn)(nil)
+
+func (c *otelConn) QueryContext(
+ ctx context.Context, query string, args []driver.NamedValue,
+) (driver.Rows, error) {
+ return c.queryCtx(ctx, query, args)
+}
+
+type queryCtxFunc func(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error)
+
+func (c *otelConn) createQueryCtxFunc(conn driver.Conn) queryCtxFunc {
+ var fn queryCtxFunc
+
+ if queryer, ok := c.Conn.(driver.QueryerContext); ok {
+ fn = queryer.QueryContext
+ } else {
+ fn = func(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
+ vArgs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+ return c.query(query, vArgs)
+ }
+ }
+
+ return func(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
+ var rows driver.Rows
+ err := c.instrum.withSpan(ctx, "db.Query", query,
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ rows, err = fn(ctx, query, args)
+ return err
+ })
+ return rows, err
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.ConnPrepareContext = (*otelConn)(nil)
+
+func (c *otelConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
+ return c.prepareCtx(ctx, query)
+}
+
+type prepareCtxFunc func(ctx context.Context, query string) (driver.Stmt, error)
+
+func (c *otelConn) createPrepareCtxFunc(conn driver.Conn) prepareCtxFunc {
+ var fn prepareCtxFunc
+
+ if preparer, ok := c.Conn.(driver.ConnPrepareContext); ok {
+ fn = preparer.PrepareContext
+ } else {
+ fn = func(ctx context.Context, query string) (driver.Stmt, error) {
+ return c.Conn.Prepare(query)
+ }
+ }
+
+ return func(ctx context.Context, query string) (driver.Stmt, error) {
+ var stmt driver.Stmt
+ if err := c.instrum.withSpan(ctx, "db.Prepare", query,
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ stmt, err = fn(ctx, query)
+ return err
+ }); err != nil {
+ return nil, err
+ }
+ return newStmt(stmt, query, c.instrum), nil
+ }
+}
+
+var _ driver.ConnBeginTx = (*otelConn)(nil)
+
+func (c *otelConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+ return c.beginTx(ctx, opts)
+}
+
+type beginTxFunc func(ctx context.Context, opts driver.TxOptions) (driver.Tx, error)
+
+func (c *otelConn) createBeginTxFunc(conn driver.Conn) beginTxFunc {
+ var fn beginTxFunc
+
+ if txor, ok := conn.(driver.ConnBeginTx); ok {
+ fn = txor.BeginTx
+ } else {
+ fn = func(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+ return conn.Begin()
+ }
+ }
+
+ return func(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
+ var tx driver.Tx
+ if err := c.instrum.withSpan(ctx, "db.Begin", "",
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ tx, err = fn(ctx, opts)
+ return err
+ }); err != nil {
+ return nil, err
+ }
+ return newTx(ctx, tx, c.instrum), nil
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.SessionResetter = (*otelConn)(nil)
+
+func (c *otelConn) ResetSession(ctx context.Context) error {
+ return c.resetSession(ctx)
+}
+
+type resetSessionFunc func(ctx context.Context) error
+
+func (c *otelConn) createResetSessionFunc(conn driver.Conn) resetSessionFunc {
+ if resetter, ok := c.Conn.(driver.SessionResetter); ok {
+ return func(ctx context.Context) error {
+ return resetter.ResetSession(ctx)
+ }
+ }
+ return func(ctx context.Context) error {
+ return driver.ErrSkip
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.NamedValueChecker = (*otelConn)(nil)
+
+func (c *otelConn) CheckNamedValue(value *driver.NamedValue) error {
+ return c.checkNamedValue(value)
+}
+
+type checkNamedValueFunc func(*driver.NamedValue) error
+
+func (c *otelConn) createCheckNamedValueFunc(conn driver.Conn) checkNamedValueFunc {
+ if checker, ok := c.Conn.(driver.NamedValueChecker); ok {
+ return func(value *driver.NamedValue) error {
+ return checker.CheckNamedValue(value)
+ }
+ }
+ return func(value *driver.NamedValue) error {
+ return driver.ErrSkip
+ }
+}
+
+//------------------------------------------------------------------------------
+
+func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
+ args := make([]driver.Value, len(named))
+ for n, param := range named {
+ if len(param.Name) > 0 {
+ return nil, errors.New("otelsql: driver does not support named parameters")
+ }
+ args[n] = param.Value
+ }
+ return args, nil
+}
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/otel.go b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/otel.go
new file mode 100644
index 000000000..0932e2759
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/otel.go
@@ -0,0 +1,254 @@
+package otelsql
+
+import (
+ "context"
+ "database/sql"
+ "database/sql/driver"
+ "io"
+ "time"
+
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+ "go.opentelemetry.io/otel/metric"
+ "go.opentelemetry.io/otel/metric/global"
+ "go.opentelemetry.io/otel/metric/instrument"
+ semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
+ "go.opentelemetry.io/otel/trace"
+)
+
+const instrumName = "github.com/uptrace/opentelemetry-go-extra/otelsql"
+
+var dbRowsAffected = attribute.Key("db.rows_affected")
+
+type config struct {
+ tracerProvider trace.TracerProvider
+ tracer trace.Tracer //nolint:structcheck
+
+ meterProvider metric.MeterProvider
+ meter metric.Meter
+
+ attrs []attribute.KeyValue
+
+ queryFormatter func(query string) string
+}
+
+func newConfig(opts []Option) *config {
+ c := &config{
+ tracerProvider: otel.GetTracerProvider(),
+ meterProvider: global.MeterProvider(),
+ }
+ for _, opt := range opts {
+ opt(c)
+ }
+ return c
+}
+
+func (c *config) formatQuery(query string) string {
+ if c.queryFormatter != nil {
+ return c.queryFormatter(query)
+ }
+ return query
+}
+
+type dbInstrum struct {
+ *config
+
+ queryHistogram instrument.Int64Histogram
+}
+
+func newDBInstrum(opts []Option) *dbInstrum {
+ t := &dbInstrum{
+ config: newConfig(opts),
+ }
+
+ if t.tracer == nil {
+ t.tracer = t.tracerProvider.Tracer(instrumName)
+ }
+ if t.meter == nil {
+ t.meter = t.meterProvider.Meter(instrumName)
+ }
+
+ var err error
+ t.queryHistogram, err = t.meter.Int64Histogram(
+ "go.sql.query_timing",
+ instrument.WithDescription("Timing of processed queries"),
+ instrument.WithUnit("milliseconds"),
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ return t
+}
+
+func (t *dbInstrum) withSpan(
+ ctx context.Context,
+ spanName string,
+ query string,
+ fn func(ctx context.Context, span trace.Span) error,
+) error {
+ var startTime time.Time
+ if query != "" {
+ startTime = time.Now()
+ }
+
+ attrs := make([]attribute.KeyValue, 0, len(t.attrs)+1)
+ attrs = append(attrs, t.attrs...)
+ if query != "" {
+ attrs = append(attrs, semconv.DBStatementKey.String(t.formatQuery(query)))
+ }
+
+ ctx, span := t.tracer.Start(ctx, spanName,
+ trace.WithSpanKind(trace.SpanKindClient),
+ trace.WithAttributes(attrs...))
+ err := fn(ctx, span)
+ span.End()
+
+ if query != "" {
+ t.queryHistogram.Record(ctx, time.Since(startTime).Milliseconds(), t.attrs...)
+ }
+
+ if !span.IsRecording() {
+ return err
+ }
+
+ switch err {
+ case nil,
+ driver.ErrSkip,
+ io.EOF, // end of rows iterator
+ sql.ErrNoRows:
+ // ignore
+ default:
+ span.RecordError(err)
+ span.SetStatus(codes.Error, err.Error())
+ }
+
+ return err
+}
+
+type Option func(c *config)
+
+// WithTracerProvider configures a tracer provider that is used to create a tracer.
+func WithTracerProvider(tracerProvider trace.TracerProvider) Option {
+ return func(c *config) {
+ c.tracerProvider = tracerProvider
+ }
+}
+
+// WithAttributes configures attributes that are used to create a span.
+func WithAttributes(attrs ...attribute.KeyValue) Option {
+ return func(c *config) {
+ c.attrs = append(c.attrs, attrs...)
+ }
+}
+
+// WithDBSystem configures a db.system attribute. You should prefer using
+// WithAttributes and semconv, for example, `otelsql.WithAttributes(semconv.DBSystemSqlite)`.
+func WithDBSystem(system string) Option {
+ return func(c *config) {
+ c.attrs = append(c.attrs, semconv.DBSystemKey.String(system))
+ }
+}
+
+// WithDBName configures a db.name attribute.
+func WithDBName(name string) Option {
+ return func(c *config) {
+ c.attrs = append(c.attrs, semconv.DBNameKey.String(name))
+ }
+}
+
+// WithMeterProvider configures a metric.Meter used to create instruments.
+func WithMeterProvider(meterProvider metric.MeterProvider) Option {
+ return func(c *config) {
+ c.meterProvider = meterProvider
+ }
+}
+
+// WithQueryFormatter configures a query formatter
+func WithQueryFormatter(queryFormatter func(query string) string) Option {
+ return func(c *config) {
+ c.queryFormatter = queryFormatter
+ }
+}
+
+// ReportDBStatsMetrics reports DBStats metrics using OpenTelemetry Metrics API.
+func ReportDBStatsMetrics(db *sql.DB, opts ...Option) {
+ cfg := newConfig(opts)
+
+ if cfg.meter == nil {
+ cfg.meter = cfg.meterProvider.Meter(instrumName)
+ }
+
+ meter := cfg.meter
+ labels := cfg.attrs
+
+ maxOpenConns, _ := meter.Int64ObservableGauge(
+ "go.sql.connections_max_open",
+ instrument.WithDescription("Maximum number of open connections to the database"),
+ )
+ openConns, _ := meter.Int64ObservableGauge(
+ "go.sql.connections_open",
+ instrument.WithDescription("The number of established connections both in use and idle"),
+ )
+ inUseConns, _ := meter.Int64ObservableGauge(
+ "go.sql.connections_in_use",
+ instrument.WithDescription("The number of connections currently in use"),
+ )
+ idleConns, _ := meter.Int64ObservableGauge(
+ "go.sql.connections_idle",
+ instrument.WithDescription("The number of idle connections"),
+ )
+ connsWaitCount, _ := meter.Int64ObservableCounter(
+ "go.sql.connections_wait_count",
+ instrument.WithDescription("The total number of connections waited for"),
+ )
+ connsWaitDuration, _ := meter.Int64ObservableCounter(
+ "go.sql.connections_wait_duration",
+ instrument.WithDescription("The total time blocked waiting for a new connection"),
+ instrument.WithUnit("nanoseconds"),
+ )
+ connsClosedMaxIdle, _ := meter.Int64ObservableCounter(
+ "go.sql.connections_closed_max_idle",
+ instrument.WithDescription("The total number of connections closed due to SetMaxIdleConns"),
+ )
+ connsClosedMaxIdleTime, _ := meter.Int64ObservableCounter(
+ "go.sql.connections_closed_max_idle_time",
+ instrument.WithDescription("The total number of connections closed due to SetConnMaxIdleTime"),
+ )
+ connsClosedMaxLifetime, _ := meter.Int64ObservableCounter(
+ "go.sql.connections_closed_max_lifetime",
+ instrument.WithDescription("The total number of connections closed due to SetConnMaxLifetime"),
+ )
+
+ if _, err := meter.RegisterCallback(
+ func(ctx context.Context, o metric.Observer) error {
+ stats := db.Stats()
+
+ o.ObserveInt64(maxOpenConns, int64(stats.MaxOpenConnections), labels...)
+
+ o.ObserveInt64(openConns, int64(stats.OpenConnections), labels...)
+ o.ObserveInt64(inUseConns, int64(stats.InUse), labels...)
+ o.ObserveInt64(idleConns, int64(stats.Idle), labels...)
+
+ o.ObserveInt64(connsWaitCount, stats.WaitCount, labels...)
+ o.ObserveInt64(connsWaitDuration, int64(stats.WaitDuration), labels...)
+ o.ObserveInt64(connsClosedMaxIdle, stats.MaxIdleClosed, labels...)
+ o.ObserveInt64(connsClosedMaxIdleTime, stats.MaxIdleTimeClosed, labels...)
+ o.ObserveInt64(connsClosedMaxLifetime, stats.MaxLifetimeClosed, labels...)
+
+ return nil
+ },
+ maxOpenConns,
+ openConns,
+ inUseConns,
+ idleConns,
+ connsWaitCount,
+ connsWaitDuration,
+ connsClosedMaxIdle,
+ connsClosedMaxIdleTime,
+ connsClosedMaxLifetime,
+ ); err != nil {
+ panic(err)
+ }
+}
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/stmt.go b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/stmt.go
new file mode 100644
index 000000000..e87a1e73f
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/stmt.go
@@ -0,0 +1,120 @@
+package otelsql
+
+import (
+ "context"
+ "database/sql/driver"
+
+ "go.opentelemetry.io/otel/trace"
+)
+
+type otelStmt struct {
+ driver.Stmt
+
+ query string
+ instrum *dbInstrum
+
+ execCtx stmtExecCtxFunc
+ queryCtx stmtQueryCtxFunc
+}
+
+var _ driver.Stmt = (*otelStmt)(nil)
+
+func newStmt(stmt driver.Stmt, query string, instrum *dbInstrum) *otelStmt {
+ s := &otelStmt{
+ Stmt: stmt,
+ query: query,
+ instrum: instrum,
+ }
+ s.execCtx = s.createExecCtxFunc(stmt)
+ s.queryCtx = s.createQueryCtxFunc(stmt)
+ return s
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.StmtExecContext = (*otelStmt)(nil)
+
+func (stmt *otelStmt) ExecContext(
+ ctx context.Context, args []driver.NamedValue,
+) (driver.Result, error) {
+ return stmt.execCtx(ctx, args)
+}
+
+type stmtExecCtxFunc func(ctx context.Context, args []driver.NamedValue) (driver.Result, error)
+
+func (s *otelStmt) createExecCtxFunc(stmt driver.Stmt) stmtExecCtxFunc {
+ var fn stmtExecCtxFunc
+
+ if execer, ok := s.Stmt.(driver.StmtExecContext); ok {
+ fn = execer.ExecContext
+ } else {
+ fn = func(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
+ vArgs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+ return stmt.Exec(vArgs)
+ }
+ }
+
+ return func(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
+ var res driver.Result
+ err := s.instrum.withSpan(ctx, "stmt.Exec", s.query,
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ res, err = fn(ctx, args)
+ if err != nil {
+ return err
+ }
+
+ if span.IsRecording() {
+ rows, err := res.RowsAffected()
+ if err == nil {
+ span.SetAttributes(dbRowsAffected.Int64(rows))
+ }
+ }
+
+ return nil
+ })
+ return res, err
+ }
+}
+
+//------------------------------------------------------------------------------
+
+var _ driver.StmtQueryContext = (*otelStmt)(nil)
+
+func (stmt *otelStmt) QueryContext(
+ ctx context.Context, args []driver.NamedValue,
+) (driver.Rows, error) {
+ return stmt.queryCtx(ctx, args)
+}
+
+type stmtQueryCtxFunc func(ctx context.Context, args []driver.NamedValue) (driver.Rows, error)
+
+func (s *otelStmt) createQueryCtxFunc(stmt driver.Stmt) stmtQueryCtxFunc {
+ var fn stmtQueryCtxFunc
+
+ if queryer, ok := s.Stmt.(driver.StmtQueryContext); ok {
+ fn = queryer.QueryContext
+ } else {
+ fn = func(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
+ vArgs, err := namedValueToValue(args)
+ if err != nil {
+ return nil, err
+ }
+ return s.Query(vArgs)
+ }
+ }
+
+ return func(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
+ var rows driver.Rows
+ err := s.instrum.withSpan(ctx, "stmt.Query", s.query,
+ func(ctx context.Context, span trace.Span) error {
+ var err error
+ rows, err = fn(ctx, args)
+ return err
+ })
+ return rows, err
+ }
+}
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/tx.go b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/tx.go
new file mode 100644
index 000000000..c4bd55e13
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/tx.go
@@ -0,0 +1,38 @@
+package otelsql
+
+import (
+ "context"
+ "database/sql/driver"
+
+ "go.opentelemetry.io/otel/trace"
+)
+
+type otelTx struct {
+ ctx context.Context
+ tx driver.Tx
+ instrum *dbInstrum
+}
+
+var _ driver.Tx = (*otelTx)(nil)
+
+func newTx(ctx context.Context, tx driver.Tx, instrum *dbInstrum) *otelTx {
+ return &otelTx{
+ ctx: ctx,
+ tx: tx,
+ instrum: instrum,
+ }
+}
+
+func (tx *otelTx) Commit() error {
+ return tx.instrum.withSpan(tx.ctx, "tx.Commit", "",
+ func(ctx context.Context, span trace.Span) error {
+ return tx.tx.Commit()
+ })
+}
+
+func (tx *otelTx) Rollback() error {
+ return tx.instrum.withSpan(tx.ctx, "tx.Rollback", "",
+ func(ctx context.Context, span trace.Span) error {
+ return tx.tx.Rollback()
+ })
+}
diff --git a/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go
new file mode 100644
index 000000000..97134301d
--- /dev/null
+++ b/vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go
@@ -0,0 +1,6 @@
+package otelsql
+
+// Version is the current release version.
+func Version() string {
+ return "0.1.21"
+}