diff options
Diffstat (limited to 'vendor/github.com/uptrace/bun')
6 files changed, 276 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)}, + )) +} |
