forked from DataDog/dd-trace-go
/
option.go
264 lines (239 loc) · 7.88 KB
/
option.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.
package sql
import (
"database/sql/driver"
"fmt"
"math"
"os"
"reflect"
"strings"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema"
)
type config struct {
serviceName string
spanName string
analyticsRate float64
dsn string
ignoreQueryTypes map[QueryType]struct{}
childSpansOnly bool
errCheck func(err error) bool
tags map[string]interface{}
dbmPropagationMode tracer.DBMPropagationMode
}
func (c *config) checkDBMPropagation(driverName string, driver driver.Driver, dsn string) {
if c.dbmPropagationMode == tracer.DBMPropagationModeFull {
if dsn == "" {
dsn = c.dsn
}
if dbSystem, ok := dbmFullModeUnsupported(driverName, driver, dsn); ok {
log.Warn("Using DBM_PROPAGATION_MODE in 'full' mode is not supported for %s, downgrading to 'service' mode. "+
"See https://docs.datadoghq.com/database_monitoring/connect_dbm_and_apm/ for more info.",
dbSystem,
)
c.dbmPropagationMode = tracer.DBMPropagationModeService
}
}
}
func dbmFullModeUnsupported(driverName string, driver driver.Driver, dsn string) (string, bool) {
const (
sqlServer = "SQL Server"
oracle = "Oracle"
)
// check if the driver package path is one of the unsupported ones.
if tp := reflect.TypeOf(driver); tp != nil && (tp.Kind() == reflect.Pointer || tp.Kind() == reflect.Struct) {
pkgPath := ""
switch tp.Kind() {
case reflect.Pointer:
pkgPath = tp.Elem().PkgPath()
case reflect.Struct:
pkgPath = tp.PkgPath()
}
driverPkgs := [][3]string{
{"github.com", "denisenkom/go-mssqldb", sqlServer},
{"github.com", "microsoft/go-mssqldb", sqlServer},
{"github.com", "sijms/go-ora", oracle},
}
for _, dp := range driverPkgs {
prefix, pkgName, dbSystem := dp[0], dp[1], dp[2]
// compare without the prefix to make it work for vendoring.
// also, compare only the prefix to make the comparison work when using major versions
// of the libraries or subpackages.
if strings.HasPrefix(strings.TrimPrefix(pkgPath, prefix+"/"), pkgName) {
return dbSystem, true
}
}
}
// check the DSN if provided.
if dsn != "" {
prefixes := [][2]string{
{"oracle://", oracle},
{"sqlserver://", sqlServer},
}
for _, pr := range prefixes {
prefix, dbSystem := pr[0], pr[1]
if strings.HasPrefix(dsn, prefix) {
return dbSystem, true
}
}
}
// lastly, check if the registered driver name is one of the unsupported ones.
driverNames := [][2]string{
{"sqlserver", sqlServer},
{"mssql", sqlServer},
{"azuresql", sqlServer},
{"oracle", oracle},
}
for _, dn := range driverNames {
name, dbSystem := dn[0], dn[1]
if name == driverName {
return dbSystem, true
}
}
return "", false
}
// Option represents an option that can be passed to Register, Open or OpenDB.
type Option func(*config)
type registerConfig = config
// RegisterOption has been deprecated in favor of Option.
type RegisterOption = Option
func defaults(cfg *config, driverName string, rc *registerConfig) {
// cfg.analyticsRate = globalconfig.AnalyticsRate()
if internal.BoolEnv("DD_TRACE_SQL_ANALYTICS_ENABLED", false) {
cfg.analyticsRate = 1.0
} else {
cfg.analyticsRate = math.NaN()
}
mode := os.Getenv("DD_DBM_PROPAGATION_MODE")
if mode == "" {
mode = os.Getenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE")
}
cfg.dbmPropagationMode = tracer.DBMPropagationMode(mode)
cfg.serviceName = getServiceName(driverName, rc)
cfg.spanName = getSpanName(driverName)
if rc != nil {
// use registered config as the default value for some options
if math.IsNaN(cfg.analyticsRate) {
cfg.analyticsRate = rc.analyticsRate
}
if cfg.dbmPropagationMode == tracer.DBMPropagationModeUndefined {
cfg.dbmPropagationMode = rc.dbmPropagationMode
}
cfg.errCheck = rc.errCheck
cfg.ignoreQueryTypes = rc.ignoreQueryTypes
cfg.childSpansOnly = rc.childSpansOnly
}
}
func getServiceName(driverName string, rc *registerConfig) string {
defaultServiceName := fmt.Sprintf("%s.db", driverName)
if rc != nil {
// if service name was set during Register, we use that value as default instead of
// the one calculated above.
defaultServiceName = rc.serviceName
}
return namingschema.ServiceNameOverrideV0(defaultServiceName, defaultServiceName)
}
func getSpanName(driverName string) string {
dbSystem := driverName
if normalizedDBSystem, ok := normalizeDBSystem(driverName); ok {
dbSystem = normalizedDBSystem
}
return namingschema.DBOpName(dbSystem, fmt.Sprintf("%s.query", driverName))
}
// WithServiceName sets the given service name when registering a driver,
// or opening a database connection.
func WithServiceName(name string) Option {
return func(cfg *config) {
cfg.serviceName = name
}
}
// WithAnalytics enables Trace Analytics for all started spans.
func WithAnalytics(on bool) Option {
return func(cfg *config) {
if on {
cfg.analyticsRate = 1.0
} else {
cfg.analyticsRate = math.NaN()
}
}
}
// WithAnalyticsRate sets the sampling rate for Trace Analytics events
// correlated to started spans.
func WithAnalyticsRate(rate float64) Option {
return func(cfg *config) {
if rate >= 0.0 && rate <= 1.0 {
cfg.analyticsRate = rate
} else {
cfg.analyticsRate = math.NaN()
}
}
}
// WithDSN allows the data source name (DSN) to be provided when
// using OpenDB and a driver.Connector.
// The value is used to automatically set tags on spans.
func WithDSN(name string) Option {
return func(cfg *config) {
cfg.dsn = name
}
}
// WithIgnoreQueryTypes specifies the query types for which spans should not be
// created.
func WithIgnoreQueryTypes(qtypes ...QueryType) Option {
return func(cfg *config) {
if cfg.ignoreQueryTypes == nil {
cfg.ignoreQueryTypes = make(map[QueryType]struct{})
}
for _, qt := range qtypes {
cfg.ignoreQueryTypes[qt] = struct{}{}
}
}
}
// WithChildSpansOnly causes spans to be created only when
// there is an existing parent span in the Context.
func WithChildSpansOnly() Option {
return func(cfg *config) {
cfg.childSpansOnly = true
}
}
// WithErrorCheck specifies a function fn which determines whether the passed
// error should be marked as an error. The fn is called whenever a database/sql operation
// finishes with an error
func WithErrorCheck(fn func(err error) bool) Option {
return func(cfg *config) {
cfg.errCheck = fn
}
}
// WithCustomTag will attach the value to the span tagged by the key
func WithCustomTag(key string, value interface{}) Option {
return func(cfg *config) {
if cfg.tags == nil {
cfg.tags = make(map[string]interface{})
}
cfg.tags[key] = value
}
}
// WithSQLCommentInjection enables injection of tags as sql comments on traced queries.
// This includes dynamic values like span id, trace id and sampling priority which can make queries
// unique for some cache implementations.
//
// Deprecated: Use WithDBMPropagation instead.
func WithSQLCommentInjection(mode tracer.SQLCommentInjectionMode) Option {
return WithDBMPropagation(tracer.DBMPropagationMode(mode))
}
// WithDBMPropagation enables injection of tags as sql comments on traced queries.
// This includes dynamic values like span id, trace id and the sampled flag which can make queries
// unique for some cache implementations. Use DBMPropagationModeService if this is a concern.
//
// Note that enabling sql comment propagation results in potentially confidential data (service names)
// being stored in the databases which can then be accessed by other 3rd parties that have been granted
// access to the database.
func WithDBMPropagation(mode tracer.DBMPropagationMode) Option {
return func(cfg *config) {
cfg.dbmPropagationMode = mode
}
}