go.undefinedlabs.com/scopeagent@v0.4.2/instrumentation/sql/driver.go (about)

     1  package sql
     2  
     3  import (
     4  	"context"
     5  	"database/sql/driver"
     6  	"errors"
     7  	"fmt"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/opentracing/opentracing-go"
    12  
    13  	"go.undefinedlabs.com/scopeagent/env"
    14  	scopeerrors "go.undefinedlabs.com/scopeagent/errors"
    15  	"go.undefinedlabs.com/scopeagent/instrumentation"
    16  )
    17  
    18  type (
    19  	// instrumented driver wrapper
    20  	instrumentedDriver struct {
    21  		driver        driver.Driver
    22  		configuration *driverConfiguration
    23  	}
    24  
    25  	driverConfiguration struct {
    26  		t               opentracing.Tracer
    27  		statementValues bool
    28  		stacktrace      bool
    29  		connString      string
    30  		componentName   string
    31  		peerService     string
    32  		user            string
    33  		port            string
    34  		instance        string
    35  		host            string
    36  	}
    37  
    38  	Option func(*instrumentedDriver)
    39  )
    40  
    41  // Enable statement values instrumentation
    42  func WithStatementValues() Option {
    43  	return func(d *instrumentedDriver) {
    44  		d.configuration.statementValues = true
    45  	}
    46  }
    47  
    48  // Enable span stacktrace
    49  func WithStacktrace() Option {
    50  	return func(d *instrumentedDriver) {
    51  		d.configuration.stacktrace = true
    52  	}
    53  }
    54  
    55  // Wraps the current sql driver to add instrumentation
    56  func WrapDriver(d driver.Driver, options ...Option) driver.Driver {
    57  	wrapper := &instrumentedDriver{
    58  		driver: d,
    59  		configuration: &driverConfiguration{
    60  			t:               instrumentation.Tracer(),
    61  			statementValues: false,
    62  		},
    63  	}
    64  	for _, option := range options {
    65  		option(wrapper)
    66  	}
    67  	wrapper.configuration.statementValues = wrapper.configuration.statementValues || env.ScopeInstrumentationDbStatementValues.Value
    68  	wrapper.configuration.stacktrace = wrapper.configuration.stacktrace || env.ScopeInstrumentationDbStacktrace.Value
    69  	return wrapper
    70  }
    71  
    72  // Open returns a new connection to the database.
    73  // The name is a string in a driver-specific format.
    74  //
    75  // Open may return a cached connection (one previously
    76  // closed), but doing so is unnecessary; the sql package
    77  // maintains a pool of idle connections for efficient re-use.
    78  //
    79  // The returned connection is only used by one goroutine at a
    80  // time.
    81  func (w *instrumentedDriver) Open(name string) (driver.Conn, error) {
    82  	conn, err := w.driver.Open(name)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	w.callVendorsExtensions(name)
    87  	return &instrumentedConn{conn: conn, configuration: w.configuration}, nil
    88  }
    89  
    90  // namedValueToValue converts driver arguments of NamedValue format to Value format. Implemented in the same way as in
    91  // database/sql ctxutil.go.
    92  func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
    93  	dargs := make([]driver.Value, len(named))
    94  	for n, param := range named {
    95  		if len(param.Name) > 0 {
    96  			return nil, errors.New("sql: driver does not support the use of Named Parameters")
    97  		}
    98  		dargs[n] = param.Value
    99  	}
   100  	return dargs, nil
   101  }
   102  
   103  // newSpan creates a new opentracing.Span instance from the given context.
   104  func (t *driverConfiguration) newSpan(operationName string, query string, args []driver.NamedValue, c *driverConfiguration, ctx context.Context) opentracing.Span {
   105  	var opts []opentracing.StartSpanOption
   106  	parent := opentracing.SpanFromContext(ctx)
   107  	if parent != nil {
   108  		opts = append(opts, opentracing.ChildOf(parent.Context()))
   109  	}
   110  	opts = append(opts, opentracing.Tags{
   111  		"db.type":       "sql",
   112  		"span.kind":     "client",
   113  		"component":     c.componentName,
   114  		"db.conn":       c.connString,
   115  		"peer.service":  c.peerService,
   116  		"db.user":       c.user,
   117  		"peer.port":     c.port,
   118  		"db.instance":   c.instance,
   119  		"peer.hostname": c.host,
   120  	})
   121  	if t.stacktrace {
   122  		opts = append(opts, opentracing.Tags{
   123  			"stacktrace": scopeerrors.GetCurrentStackTrace(2),
   124  		})
   125  	}
   126  	if query != "" {
   127  		stIndex := strings.IndexRune(query, ' ')
   128  		var method string
   129  		if stIndex >= 0 {
   130  			method = strings.ToUpper(query[:stIndex])
   131  		}
   132  		opts = append(opts, opentracing.Tags{
   133  			"db.prepare_statement": query,
   134  			"db.method":            method,
   135  		})
   136  		operationName = fmt.Sprintf("%s:%s", c.peerService, method)
   137  	} else {
   138  		operationName = fmt.Sprintf("%s:%s", c.peerService, strings.ToUpper(operationName))
   139  	}
   140  	if c.statementValues && args != nil && len(args) > 0 {
   141  		dbParams := map[string]interface{}{}
   142  		for _, item := range args {
   143  			name := item.Name
   144  			if name == "" {
   145  				name = fmt.Sprintf("$%v", item.Ordinal)
   146  			}
   147  			dbParams[name] = map[string]interface{}{
   148  				"type":  reflect.TypeOf(item.Value).String(),
   149  				"value": fmt.Sprint(item.Value),
   150  			}
   151  		}
   152  		opts = append(opts, opentracing.Tags{
   153  			"db.params": dbParams,
   154  		})
   155  	}
   156  	span := t.t.StartSpan(operationName, opts...)
   157  	return span
   158  }
   159  
   160  func (w *instrumentedDriver) callVendorsExtensions(name string) {
   161  	w.configuration.connString = name
   162  	w.configuration.componentName = reflect.TypeOf(w.driver).Elem().String()
   163  	for _, vendor := range vendorExtensions {
   164  		if vendor.IsCompatible(w.configuration.componentName) {
   165  			vendor.ProcessConnectionString(name, w.configuration)
   166  			break
   167  		}
   168  	}
   169  }