summaryrefslogtreecommitdiff
path: root/vendor/github.com/uptrace/bun
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/uptrace/bun')
-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
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)},
+ ))
+}