mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 18:32:26 -05:00
feat: initial tracing support (#1623)
This commit is contained in:
parent
878ed48de3
commit
6392e00653
472 changed files with 102600 additions and 12 deletions
24
vendor/github.com/uptrace/bun/extra/bunotel/LICENSE
generated
vendored
Normal file
24
vendor/github.com/uptrace/bun/extra/bunotel/LICENSE
generated
vendored
Normal file
|
|
@ -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.
|
||||
3
vendor/github.com/uptrace/bun/extra/bunotel/README.md
generated
vendored
Normal file
3
vendor/github.com/uptrace/bun/extra/bunotel/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# OpenTelemetry instrumentation for Bun
|
||||
|
||||
See [example](../example/opentelemetry) for details.
|
||||
32
vendor/github.com/uptrace/bun/extra/bunotel/option.go
generated
vendored
Normal file
32
vendor/github.com/uptrace/bun/extra/bunotel/option.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
188
vendor/github.com/uptrace/bun/extra/bunotel/otel.go
generated
vendored
Normal file
188
vendor/github.com/uptrace/bun/extra/bunotel/otel.go
generated
vendored
Normal file
|
|
@ -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{}
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/uptrace/bun/extra/bunotel/safe.go
generated
vendored
Normal file
11
vendor/github.com/uptrace/bun/extra/bunotel/safe.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// +build appengine
|
||||
|
||||
package internal
|
||||
|
||||
func bytesToString(b []byte) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func stringToBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
18
vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go
generated
vendored
Normal file
18
vendor/github.com/uptrace/bun/extra/bunotel/unsafe.go
generated
vendored
Normal file
|
|
@ -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)},
|
||||
))
|
||||
}
|
||||
5
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/.golangci.yml
generated
vendored
Normal file
5
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/.golangci.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
issues:
|
||||
exclude-rules:
|
||||
- text: 'Drivers should implement'
|
||||
linters:
|
||||
- staticcheck
|
||||
24
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/LICENSE
generated
vendored
Normal file
24
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/LICENSE
generated
vendored
Normal file
|
|
@ -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.
|
||||
118
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md
generated
vendored
Normal file
118
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
[](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.
|
||||
460
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/driver.go
generated
vendored
Normal file
460
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/driver.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
254
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/otel.go
generated
vendored
Normal file
254
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/otel.go
generated
vendored
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
120
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/stmt.go
generated
vendored
Normal file
120
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/stmt.go
generated
vendored
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/tx.go
generated
vendored
Normal file
38
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/tx.go
generated
vendored
Normal file
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
6
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go
generated
vendored
Normal file
6
vendor/github.com/uptrace/opentelemetry-go-extra/otelsql/version.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package otelsql
|
||||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "0.1.21"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue