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 }