module/apmsql/driver.go (134 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package apmsql // import "go.elastic.co/apm/module/apmsql/v2" import ( "context" "database/sql" "database/sql/driver" "fmt" "sync" "go.elastic.co/apm/v2" "go.elastic.co/apm/v2/sqlutil" ) // DriverPrefix should be used as a driver name prefix when // registering via sql.Register. const DriverPrefix = "apm/" var ( driversMu sync.RWMutex drivers = make(map[string]*tracingDriver) ) // Register registers a traced version of the given driver. // // The name and driver values should be the same as given to // sql.Register: the name of the driver (e.g. "postgres"), and // the driver (e.g. &github.com/lib/pq.Driver{}). func Register(name string, driver driver.Driver, opts ...WrapOption) { driversMu.Lock() defer driversMu.Unlock() wrapped := newTracingDriver(driver, opts...) sql.Register(DriverPrefix+name, wrapped) drivers[name] = wrapped } // Open opens a database with the given driver and data source names, // as in sql.Open. The driver name should be one registered via the // Register function in this package. func Open(driverName, dataSourceName string) (*sql.DB, error) { return sql.Open(DriverPrefix+driverName, dataSourceName) } // Wrap wraps a database/sql/driver.Driver such that // the driver's database methods are traced. The tracer // will be obtained from the context supplied to methods // that accept it. func Wrap(driver driver.Driver, opts ...WrapOption) driver.Driver { return newTracingDriver(driver, opts...) } func newTracingDriver(driver driver.Driver, opts ...WrapOption) *tracingDriver { d := &tracingDriver{ Driver: driver, } for _, opt := range opts { opt(d) } if d.driverName == "" { d.driverName = sqlutil.DriverName(driver) } if d.dsnParser == nil { d.dsnParser = genericDSNParser } // store span types to avoid repeat allocations d.connectSpanType = d.formatSpanType("connect") d.pingSpanType = d.formatSpanType("ping") d.prepareSpanType = d.formatSpanType("prepare") d.querySpanType = d.formatSpanType("query") d.execSpanType = d.formatSpanType("exec") return d } // DriverDSNParser returns the DSNParserFunc for the registered driver. // If there is no such registered driver, the parser function that is // returned will return empty DSNInfo structures. func DriverDSNParser(driverName string) DSNParserFunc { driversMu.RLock() driver := drivers[driverName] defer driversMu.RUnlock() if driver == nil { return genericDSNParser } return driver.dsnParser } // WrapOption is an option that can be supplied to Wrap. type WrapOption func(*tracingDriver) // WithDriverName returns a WrapOption which sets the underlying // driver name to the specified value. If WithDriverName is not // supplied to Wrap, the driver name will be inferred from the // driver supplied to Wrap. func WithDriverName(name string) WrapOption { return func(d *tracingDriver) { d.driverName = name } } // WithDSNParser returns a WrapOption which sets the function to // use for parsing the data source name. If WithDSNParser is not // supplied to Wrap, the function to use will be inferred from // the driver name. func WithDSNParser(f DSNParserFunc) WrapOption { return func(d *tracingDriver) { d.dsnParser = f } } type tracingDriver struct { driver.Driver driverName string dsnParser DSNParserFunc connectSpanType string execSpanType string pingSpanType string prepareSpanType string querySpanType string } func (d *tracingDriver) formatSpanType(suffix string) string { return fmt.Sprintf("db.%s.%s", d.driverName, suffix) } // querySignature returns the value to use in Span.Name for // a database query. func (d *tracingDriver) querySignature(query string) string { return QuerySignature(query) } // Unwrap returns the wrapped database/sql/driver.Driver. func (d *tracingDriver) Unwrap() driver.Driver { return d.Driver } func (d *tracingDriver) Open(name string) (driver.Conn, error) { conn, err := d.Driver.Open(name) if err != nil { return nil, err } return newConn(conn, d, d.dsnParser(name)), nil } func (d *tracingDriver) OpenConnector(name string) (driver.Connector, error) { if dc, ok := d.Driver.(driver.DriverContext); ok { oc, err := dc.OpenConnector(name) if err != nil { return nil, err } return &driverConnector{oc.Connect, d, name}, nil } connect := func(context.Context) (driver.Conn, error) { return d.Driver.Open(name) } return &driverConnector{connect, d, name}, nil } type driverConnector struct { connect func(context.Context) (driver.Conn, error) driver *tracingDriver name string } func (d *driverConnector) Connect(ctx context.Context) (driver.Conn, error) { span, ctx := apm.StartSpanOptions(ctx, "connect", d.driver.connectSpanType, apm.SpanOptions{ ExitSpan: true, }) defer span.End() dsnInfo := d.driver.dsnParser(d.name) if !span.Dropped() { span.Context.SetDatabase(apm.DatabaseSpanContext{ Instance: dsnInfo.Database, Type: "sql", User: dsnInfo.User, }) } conn, err := d.connect(ctx) if err != nil { return nil, err } return newConn(conn, d.driver, dsnInfo), nil } func (d *driverConnector) Driver() driver.Driver { return d.driver }