From ad88ff6c884032ea15be9986ec1c66054d7cb471 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 17 Mar 2022 16:47:30 -0700 Subject: [PATCH 001/104] Add sqlcommenter comments to traced queries --- contrib/database/sql/comment/tagger.go | 81 ++++++++++++++ contrib/database/sql/comment/tagger_test.go | 99 ++++++++++++++++++ contrib/database/sql/conn.go | 110 ++++++++++++++++---- contrib/database/sql/sql.go | 8 +- contrib/database/sql/stmt.go | 40 ++++++- contrib/database/sql/tx.go | 15 ++- 6 files changed, 323 insertions(+), 30 deletions(-) create mode 100644 contrib/database/sql/comment/tagger.go create mode 100644 contrib/database/sql/comment/tagger_test.go diff --git a/contrib/database/sql/comment/tagger.go b/contrib/database/sql/comment/tagger.go new file mode 100644 index 0000000000..ad2c7e604d --- /dev/null +++ b/contrib/database/sql/comment/tagger.go @@ -0,0 +1,81 @@ +package comment + +import ( + "fmt" + "net/url" + "sort" + "strings" +) + +func OnQuery(query string, tagSets ...map[string]string) (commentedQuery string) { + // Don't comment the query if there's no query to tag + if len(query) == 0 { + return query + } + + comment := WithTags(tagSets...) + if len(comment) == 0 { + return query + } + + return fmt.Sprintf("%s %s", query, comment) +} + +// totalLen returns the maximum total number of elements in all maps. +// Duplicate keys are counted as multiple elements +func totalLen(tagSets ...map[string]string) (length int) { + length = 0 + for _, t := range tagSets { + length += len(t) + } + + return length +} + +func WithTags(tagSets ...map[string]string) (comment string) { + tagCount := totalLen(tagSets...) + if tagCount == 0 { + return "" + } + + serializedTags := make([]string, 0, tagCount) + for _, ts := range tagSets { + for k, v := range ts { + serializedTags = append(serializedTags, serializeTag(k, v)) + } + } + + sort.Strings(serializedTags) + comment = strings.Join(serializedTags, ",") + return fmt.Sprintf("/* %s */", comment) +} + +func serializeTag(key string, value string) (serialized string) { + sKey := serializeKey(key) + sValue := serializeValue(value) + + return fmt.Sprintf("%s=%s", sKey, sValue) +} + +func serializeKey(key string) (encoded string) { + urlEncoded := url.PathEscape(key) + escapedMeta := escapeMetaChars(urlEncoded) + + return escapedMeta +} + +func serializeValue(value string) (encoded string) { + urlEncoded := url.PathEscape(value) + escapedMeta := escapeMetaChars(urlEncoded) + escaped := escapeSQL(escapedMeta) + + return escaped +} + +func escapeSQL(value string) (escaped string) { + return fmt.Sprintf("'%s'", value) +} + +func escapeMetaChars(value string) (escaped string) { + return strings.ReplaceAll(value, "'", "\\'") +} diff --git a/contrib/database/sql/comment/tagger_test.go b/contrib/database/sql/comment/tagger_test.go new file mode 100644 index 0000000000..ed71945213 --- /dev/null +++ b/contrib/database/sql/comment/tagger_test.go @@ -0,0 +1,99 @@ +package comment + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestOnQuery(t *testing.T) { + testCases := []struct { + name string + query string + tags map[string]string + commented string + }{ + { + name: "query with tag list", + query: "SELECT * from FOO", + tags: map[string]string{"service": "mine", "operation": "checkout"}, + commented: "SELECT * from FOO /* operation='checkout',service='mine' */", + }, + { + name: "empty query", + query: "", + tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, + commented: "", + }, + { + name: "query with existing comment", + query: "SELECT * from FOO -- test query", + tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, + commented: "SELECT * from FOO -- test query /* operation='elmer%27s%20glue',service='mine' */", + }, + { + name: "no tags", + query: "SELECT * from FOO", + tags: map[string]string{}, + commented: "SELECT * from FOO", + }, + { + name: "nil tags", + query: "SELECT * from FOO", + tags: nil, + commented: "SELECT * from FOO", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + commented := OnQuery(tc.query, tc.tags) + assert.Equal(t, tc.commented, commented) + }) + } +} + +func TestWithTags(t *testing.T) { + testCases := []struct { + name string + tags map[string]string + comment string + }{ + { + name: "simple tag", + tags: map[string]string{"service": "mine"}, + comment: "/* service='mine' */", + }, + { + name: "tag list", + tags: map[string]string{"service": "mine", "operation": "checkout"}, + comment: "/* operation='checkout',service='mine' */", + }, + { + name: "tag value with single quote", + tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, + comment: "/* operation='elmer%27s%20glue',service='mine' */", + }, + { + name: "tag key with space", + tags: map[string]string{"service name": "mine"}, + comment: "/* service%20name='mine' */", + }, + { + name: "no tags", + tags: map[string]string{}, + comment: "", + }, + { + name: "nil tags", + tags: nil, + comment: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + comment := WithTags(tc.tags) + assert.Equal(t, tc.comment, comment) + }) + } +} diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index d382301a5d..f560575dce 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -9,7 +9,9 @@ import ( "context" "database/sql/driver" "fmt" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "math" + "strconv" "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" @@ -42,14 +44,24 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - tc.tryTrace(ctx, queryTypeBegin, "", start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, err) + if span != nil { + defer func() { + span.Finish(tracer.WithError(err)) + }() + } if err != nil { return nil, err } return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - tc.tryTrace(ctx, queryTypeBegin, "", start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, err) + if span != nil { + defer func() { + span.Finish(tracer.WithError(err)) + }() + } if err != nil { return nil, err } @@ -58,27 +70,53 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() + q := query if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - stmt, err := connPrepareCtx.PrepareContext(ctx, query) - tc.tryTrace(ctx, queryTypePrepare, query, start, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + q = tc.commentedQuery(query, span.Context()) + } + stmt, err := connPrepareCtx.PrepareContext(ctx, q) if err != nil { return nil, err } - return &tracedStmt{stmt, tc.traceParams, ctx, query}, nil + + return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - stmt, err = tc.Prepare(query) - tc.tryTrace(ctx, queryTypePrepare, query, start, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + q = tc.commentedQuery(query, span.Context()) + } + stmt, err = tc.Prepare(q) if err != nil { return nil, err } - return &tracedStmt{stmt, tc.traceParams, ctx, query}, nil + + return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil +} + +func (tc *tracedConn) commentedQuery(query string, spanCtx ddtrace.SpanContext) string { + return comment.OnQuery(query, map[string]string{ext.ServiceName: tc.cfg.serviceName, "dd.span_id": strconv.FormatUint(spanCtx.SpanID(), 10), "dd.trace_id": strconv.FormatUint(spanCtx.TraceID(), 10)}, tc.meta) } func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() + q := query if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - r, err := execContext.ExecContext(ctx, query, args) - tc.tryTrace(ctx, queryTypeExec, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, err) + if span != nil { + defer func() { + span.Finish(tracer.WithError(err)) + }() + q = tc.commentedQuery(query, span.Context()) + } + r, err := execContext.ExecContext(ctx, q, args) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -91,8 +129,13 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - r, err = execer.Exec(query, dargs) - tc.tryTrace(ctx, queryTypeExec, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, err) + if span != nil { + defer func() { + span.Finish(tracer.WithError(err)) + }() + } + r, err = execer.Exec(tc.commentedQuery(query, span.Context()), dargs) return r, err } return nil, driver.ErrSkip @@ -104,15 +147,28 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - tc.tryTrace(ctx, queryTypePing, "", start, err) + span := tc.tryStartTrace(ctx, queryTypePing, "", start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } return err } func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() + q := query if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - rows, err := queryerContext.QueryContext(ctx, query, args) - tc.tryTrace(ctx, queryTypeQuery, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + q = tc.commentedQuery(query, span.Context()) + } + rows, err := queryerContext.QueryContext(ctx, q, args) + return rows, err } if queryer, ok := tc.Conn.(driver.Queryer); ok { @@ -125,8 +181,15 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - rows, err = queryer.Query(query, dargs) - tc.tryTrace(ctx, queryTypeQuery, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + // TODO: Add arg values as sql comments? + q = tc.commentedQuery(query, span.Context()) + } + rows, err = queryer.Query(q, dargs) return rows, err } return nil, driver.ErrSkip @@ -167,8 +230,10 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { return context.WithValue(ctx, spanTagsKey, tags) } -// tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error) { +// TODO: migrate this to a try start trace that can be finished after execution so that we can keep the span id and the trace id +// and use it as comments on the executed query +// tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. +func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error) (span tracer.Span) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -177,7 +242,7 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri return } if _, exists := tracer.SpanFromContext(ctx); tp.cfg.childSpansOnly && !exists { - return + return nil } name := fmt.Sprintf("%s.query", tp.driverName) opts := []ddtrace.StartSpanOption{ @@ -188,7 +253,7 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri if !math.IsNaN(tp.cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, tp.cfg.analyticsRate)) } - span, _ := tracer.StartSpanFromContext(ctx, name, opts...) + span, _ = tracer.StartSpanFromContext(ctx, name, opts...) resource := string(qtype) if query != "" { resource = query @@ -203,5 +268,6 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri span.SetTag(k, v) } } - span.Finish(tracer.WithError(err)) + + return span } diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 2383f4a499..1aad76151f 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -21,6 +21,7 @@ import ( "database/sql" "database/sql/driver" "errors" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "reflect" "time" @@ -141,7 +142,12 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - tp.tryTrace(ctx, queryTypeConnect, "", start, err) + span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } if err != nil { return nil, err } diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index a1f419d1ac..f9d7e071d0 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -9,6 +9,7 @@ import ( "context" "database/sql/driver" "errors" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -26,7 +27,12 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - s.tryTrace(s.ctx, queryTypeClose, "", start, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } return err } @@ -34,8 +40,14 @@ func (s *tracedStmt) Close() (err error) { func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } res, err := stmtExecContext.ExecContext(ctx, args) - s.tryTrace(ctx, queryTypeExec, s.query, start, err) + return res, err } dargs, err := namedValueToValue(args) @@ -47,8 +59,14 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } res, err = s.Exec(dargs) - s.tryTrace(ctx, queryTypeExec, s.query, start, err) + return res, err } @@ -56,8 +74,14 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } rows, err := stmtQueryContext.QueryContext(ctx, args) - s.tryTrace(ctx, queryTypeQuery, s.query, start, err) + return rows, err } dargs, err := namedValueToValue(args) @@ -69,8 +93,14 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } rows, err = s.Query(dargs) - s.tryTrace(ctx, queryTypeQuery, s.query, start, err) + return rows, err } diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 66d4a040f9..ca10315955 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -8,6 +8,7 @@ package sql import ( "context" "database/sql/driver" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -23,15 +24,25 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() + span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } err = t.Tx.Commit() - t.tryTrace(t.ctx, queryTypeCommit, "", start, err) return err } // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() + span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, err) err = t.Tx.Rollback() - t.tryTrace(t.ctx, queryTypeRollback, "", start, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } return err } From 58df219ac0a8c24997a7aa6b7a4a24b839444434 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 11:57:00 -0700 Subject: [PATCH 002/104] Move comments as query prefix --- contrib/database/sql/comment/tagger.go | 3 ++- contrib/database/sql/comment/tagger_test.go | 4 ++-- contrib/database/sql/conn.go | 4 +--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contrib/database/sql/comment/tagger.go b/contrib/database/sql/comment/tagger.go index ad2c7e604d..1602652a9c 100644 --- a/contrib/database/sql/comment/tagger.go +++ b/contrib/database/sql/comment/tagger.go @@ -18,7 +18,8 @@ func OnQuery(query string, tagSets ...map[string]string) (commentedQuery string) return query } - return fmt.Sprintf("%s %s", query, comment) + // Diverge from the sqlcommenter spec because we want to prioritize comments not being truncated + return fmt.Sprintf("%s %s", comment, query) } // totalLen returns the maximum total number of elements in all maps. diff --git a/contrib/database/sql/comment/tagger_test.go b/contrib/database/sql/comment/tagger_test.go index ed71945213..e22a33599e 100644 --- a/contrib/database/sql/comment/tagger_test.go +++ b/contrib/database/sql/comment/tagger_test.go @@ -16,7 +16,7 @@ func TestOnQuery(t *testing.T) { name: "query with tag list", query: "SELECT * from FOO", tags: map[string]string{"service": "mine", "operation": "checkout"}, - commented: "SELECT * from FOO /* operation='checkout',service='mine' */", + commented: "/* operation='checkout',service='mine' */ SELECT * from FOO", }, { name: "empty query", @@ -28,7 +28,7 @@ func TestOnQuery(t *testing.T) { name: "query with existing comment", query: "SELECT * from FOO -- test query", tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, - commented: "SELECT * from FOO -- test query /* operation='elmer%27s%20glue',service='mine' */", + commented: "/* operation='elmer%27s%20glue',service='mine' */ SELECT * from FOO -- test query", }, { name: "no tags", diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index f560575dce..1d48ea1a13 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -9,11 +9,11 @@ import ( "context" "database/sql/driver" "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "math" "strconv" "time" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -230,8 +230,6 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { return context.WithValue(ctx, spanTagsKey, tags) } -// TODO: migrate this to a try start trace that can be finished after execution so that we can keep the span id and the trace id -// and use it as comments on the executed query // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error) (span tracer.Span) { if err == driver.ErrSkip { From 3177df6795bef319e02e79206ebef4fcca0c686f Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 15:02:40 -0700 Subject: [PATCH 003/104] Use QueryTextCarrier for propagating tags to query text --- contrib/database/sql/comment/query.go | 42 +++++++++++++++++++++ contrib/database/sql/conn.go | 53 ++++++++++++++------------- contrib/database/sql/sql.go | 2 +- contrib/database/sql/stmt.go | 10 ++--- contrib/database/sql/tx.go | 4 +- 5 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 contrib/database/sql/comment/query.go diff --git a/contrib/database/sql/comment/query.go b/contrib/database/sql/comment/query.go new file mode 100644 index 0000000000..72b033c29b --- /dev/null +++ b/contrib/database/sql/comment/query.go @@ -0,0 +1,42 @@ +package comment + +import ( + "fmt" + "strings" +) + +type QueryTextCarrier strings.Builder + +// Set implements TextMapWriter. +func (c QueryTextCarrier) Set(key, val string) { + b := strings.Builder(c) + if b.Len() == 0 { + b.WriteString("/* ") + } + + if !strings.HasSuffix(b.String(), ",") { + b.WriteRune(',') + } + + b.WriteString(serializeTag(key, val)) +} + +func (c QueryTextCarrier) CommentedQuery(query string) (commented string) { + builder := strings.Builder(c) + if builder.Len() > 0 { + builder.WriteString(" */") + } + comment := builder.String() + + if comment == "" || query == "" { + return query + } + + return fmt.Sprintf("%s %s", comment, query) +} + +// ForeachKey implements TextMapReader. +func (c QueryTextCarrier) ForeachKey(handler func(key, val string) error) error { + // TODO: implement this for completeness. We don't really have a use-case for this at the moment. + return nil +} diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 1d48ea1a13..47b033ac77 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -10,7 +10,7 @@ import ( "database/sql/driver" "fmt" "math" - "strconv" + "os" "time" "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" @@ -44,7 +44,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, nil, start, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -56,7 +56,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, nil, start, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -72,12 +72,11 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr start := time.Now() q := query if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, &query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() - q = tc.commentedQuery(query, span.Context()) } stmt, err := connPrepareCtx.PrepareContext(ctx, q) if err != nil { @@ -86,12 +85,11 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, &query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() - q = tc.commentedQuery(query, span.Context()) } stmt, err = tc.Prepare(q) if err != nil { @@ -101,20 +99,19 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } -func (tc *tracedConn) commentedQuery(query string, spanCtx ddtrace.SpanContext) string { - return comment.OnQuery(query, map[string]string{ext.ServiceName: tc.cfg.serviceName, "dd.span_id": strconv.FormatUint(spanCtx.SpanID(), 10), "dd.trace_id": strconv.FormatUint(spanCtx.TraceID(), 10)}, tc.meta) -} +//func (tc *tracedConn) commentedQuery(query string, spanCtx ddtrace.SpanContext) string { +// return comment.OnQuery(query, map[string]string{ext.ServiceName: tc.cfg.serviceName, "dd.span_id": strconv.FormatUint(spanCtx.SpanID(), 10), "dd.trace_id": strconv.FormatUint(spanCtx.TraceID(), 10)}, tc.meta) +//} func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() q := query if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, nil, start, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) }() - q = tc.commentedQuery(query, span.Context()) } r, err := execContext.ExecContext(ctx, q, args) return r, err @@ -129,13 +126,13 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeExec, &query, start, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) }() } - r, err = execer.Exec(tc.commentedQuery(query, span.Context()), dargs) + r, err = execer.Exec(query, dargs) return r, err } return nil, driver.ErrSkip @@ -147,7 +144,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - span := tc.tryStartTrace(ctx, queryTypePing, "", start, err) + span := tc.tryStartTrace(ctx, queryTypePing, nil, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -158,16 +155,14 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() - q := query if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, &query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() - q = tc.commentedQuery(query, span.Context()) } - rows, err := queryerContext.QueryContext(ctx, q, args) + rows, err := queryerContext.QueryContext(ctx, query, args) return rows, err } @@ -181,15 +176,13 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, &query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() - // TODO: Add arg values as sql comments? - q = tc.commentedQuery(query, span.Context()) } - rows, err = queryer.Query(q, dargs) + rows, err = queryer.Query(query, dargs) return rows, err } return nil, driver.ErrSkip @@ -231,7 +224,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error) (span tracer.Span) { +func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query *string, startTime time.Time, err error) (span tracer.Span) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -253,8 +246,16 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } span, _ = tracer.StartSpanFromContext(ctx, name, opts...) resource := string(qtype) - if query != "" { - resource = query + if query != nil { + resource = *query + queryTextCarrier := comment.QueryTextCarrier{} + err := tracer.Inject(span.Context(), queryTextCarrier) + if err != nil { + // this should never happen + fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) + } + commented := queryTextCarrier.CommentedQuery(*query) + query = &commented } span.SetTag("sql.query_type", string(qtype)) span.SetTag(ext.ResourceName, resource) diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 1aad76151f..82d089b330 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -142,7 +142,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, err) + span := tp.tryStartTrace(ctx, queryTypeConnect, nil, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index f9d7e071d0..49df1b30f7 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -27,7 +27,7 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, nil, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -40,7 +40,7 @@ func (s *tracedStmt) Close() (err error) { func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeExec, &s.query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -59,7 +59,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeExec, &s.query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -74,7 +74,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeQuery, &s.query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -93,7 +93,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeQuery, &s.query, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index ca10315955..c22fb189ab 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -24,7 +24,7 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, err) + span := t.tryStartTrace(t.ctx, queryTypeCommit, nil, start, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -37,7 +37,7 @@ func (t *tracedTx) Commit() (err error) { // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, err) + span := t.tryStartTrace(t.ctx, queryTypeRollback, nil, start, err) err = t.Tx.Rollback() if span != nil { go func() { From 409dcc33990629417b8cc0ffe8739b526602a595 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 15:52:03 -0700 Subject: [PATCH 004/104] Fix Query Not Mutated --- contrib/database/sql/conn.go | 41 ++++++++++++++++++------------------ contrib/database/sql/sql.go | 3 ++- contrib/database/sql/stmt.go | 11 +++++----- contrib/database/sql/tx.go | 5 +++-- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 47b033ac77..20cc2e771d 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -44,7 +44,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - span := tc.tryStartTrace(ctx, queryTypeBegin, nil, start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, comment.QueryTextCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -56,7 +56,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - span := tc.tryStartTrace(ctx, queryTypeBegin, nil, start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, comment.QueryTextCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -70,28 +70,29 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() - q := query if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - span := tc.tryStartTrace(ctx, queryTypePrepare, &query, start, err) + queryTextCarrier := comment.QueryTextCarrier{} + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, queryTextCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - stmt, err := connPrepareCtx.PrepareContext(ctx, q) + stmt, err := connPrepareCtx.PrepareContext(ctx, queryTextCarrier.CommentedQuery(query)) if err != nil { return nil, err } return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - span := tc.tryStartTrace(ctx, queryTypePrepare, &query, start, err) + queryTextCarrier := comment.QueryTextCarrier{} + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, queryTextCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - stmt, err = tc.Prepare(q) + stmt, err = tc.Prepare(queryTextCarrier.CommentedQuery(query)) if err != nil { return nil, err } @@ -107,7 +108,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv start := time.Now() q := query if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeBegin, nil, start, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, comment.QueryTextCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -126,13 +127,14 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - span := tc.tryStartTrace(ctx, queryTypeExec, &query, start, err) + queryTextCarrier := comment.QueryTextCarrier{} + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, queryTextCarrier, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) }() } - r, err = execer.Exec(query, dargs) + r, err = execer.Exec(queryTextCarrier.CommentedQuery(query), dargs) return r, err } return nil, driver.ErrSkip @@ -144,7 +146,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - span := tc.tryStartTrace(ctx, queryTypePing, nil, start, err) + span := tc.tryStartTrace(ctx, queryTypePing, "", start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -156,7 +158,8 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeQuery, &query, start, err) + queryTextCarrier := comment.QueryTextCarrier{} + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, queryTextCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -176,13 +179,14 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - span := tc.tryStartTrace(ctx, queryTypeQuery, &query, start, err) + queryTextCarrier := comment.QueryTextCarrier{} + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, queryTextCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - rows, err = queryer.Query(query, dargs) + rows, err = queryer.Query(queryTextCarrier.CommentedQuery(query), dargs) return rows, err } return nil, driver.ErrSkip @@ -224,7 +228,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query *string, startTime time.Time, err error) (span tracer.Span) { +func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, queryTextCarrier comment.QueryTextCarrier, err error) (span tracer.Span) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -246,16 +250,13 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } span, _ = tracer.StartSpanFromContext(ctx, name, opts...) resource := string(qtype) - if query != nil { - resource = *query - queryTextCarrier := comment.QueryTextCarrier{} + if query != "" { + resource = query err := tracer.Inject(span.Context(), queryTextCarrier) if err != nil { // this should never happen fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) } - commented := queryTextCarrier.CommentedQuery(*query) - query = &commented } span.SetTag("sql.query_type", string(qtype)) span.SetTag(ext.ResourceName, resource) diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 82d089b330..bc9e76bbe8 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -21,6 +21,7 @@ import ( "database/sql" "database/sql/driver" "errors" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "reflect" @@ -142,7 +143,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - span := tp.tryStartTrace(ctx, queryTypeConnect, nil, start, err) + span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 49df1b30f7..bbc10e2cf9 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -9,6 +9,7 @@ import ( "context" "database/sql/driver" "errors" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -27,7 +28,7 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - span := s.tryStartTrace(s.ctx, queryTypeClose, nil, start, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -40,7 +41,7 @@ func (s *tracedStmt) Close() (err error) { func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { - span := s.tryStartTrace(ctx, queryTypeExec, &s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -59,7 +60,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeExec, &s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -74,7 +75,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { - span := s.tryStartTrace(ctx, queryTypeQuery, &s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -93,7 +94,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeQuery, &s.query, start, err) + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index c22fb189ab..0612fdce7e 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -8,6 +8,7 @@ package sql import ( "context" "database/sql/driver" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -24,7 +25,7 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeCommit, nil, start, err) + span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, comment.QueryTextCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -37,7 +38,7 @@ func (t *tracedTx) Commit() (err error) { // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeRollback, nil, start, err) + span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, comment.QueryTextCarrier{}, err) err = t.Tx.Rollback() if span != nil { go func() { From 4345bab8736e72063755623b3a4d5c3ca27f8a1d Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 17:15:03 -0700 Subject: [PATCH 005/104] Refactor to SQLCommentCarrier --- contrib/database/sql/comment/query.go | 42 -------- contrib/database/sql/comment/tagger_test.go | 99 ------------------- contrib/database/sql/conn.go | 41 ++++---- contrib/database/sql/sql.go | 3 +- contrib/database/sql/stmt.go | 11 +-- contrib/database/sql/tx.go | 5 +- .../tagger.go => ddtrace/tracer/sqlcomment.go | 61 ++++++------ ddtrace/tracer/sqlcomment_test.go | 61 ++++++++++++ 8 files changed, 119 insertions(+), 204 deletions(-) delete mode 100644 contrib/database/sql/comment/query.go delete mode 100644 contrib/database/sql/comment/tagger_test.go rename contrib/database/sql/comment/tagger.go => ddtrace/tracer/sqlcomment.go (52%) create mode 100644 ddtrace/tracer/sqlcomment_test.go diff --git a/contrib/database/sql/comment/query.go b/contrib/database/sql/comment/query.go deleted file mode 100644 index 72b033c29b..0000000000 --- a/contrib/database/sql/comment/query.go +++ /dev/null @@ -1,42 +0,0 @@ -package comment - -import ( - "fmt" - "strings" -) - -type QueryTextCarrier strings.Builder - -// Set implements TextMapWriter. -func (c QueryTextCarrier) Set(key, val string) { - b := strings.Builder(c) - if b.Len() == 0 { - b.WriteString("/* ") - } - - if !strings.HasSuffix(b.String(), ",") { - b.WriteRune(',') - } - - b.WriteString(serializeTag(key, val)) -} - -func (c QueryTextCarrier) CommentedQuery(query string) (commented string) { - builder := strings.Builder(c) - if builder.Len() > 0 { - builder.WriteString(" */") - } - comment := builder.String() - - if comment == "" || query == "" { - return query - } - - return fmt.Sprintf("%s %s", comment, query) -} - -// ForeachKey implements TextMapReader. -func (c QueryTextCarrier) ForeachKey(handler func(key, val string) error) error { - // TODO: implement this for completeness. We don't really have a use-case for this at the moment. - return nil -} diff --git a/contrib/database/sql/comment/tagger_test.go b/contrib/database/sql/comment/tagger_test.go deleted file mode 100644 index e22a33599e..0000000000 --- a/contrib/database/sql/comment/tagger_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package comment - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestOnQuery(t *testing.T) { - testCases := []struct { - name string - query string - tags map[string]string - commented string - }{ - { - name: "query with tag list", - query: "SELECT * from FOO", - tags: map[string]string{"service": "mine", "operation": "checkout"}, - commented: "/* operation='checkout',service='mine' */ SELECT * from FOO", - }, - { - name: "empty query", - query: "", - tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, - commented: "", - }, - { - name: "query with existing comment", - query: "SELECT * from FOO -- test query", - tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, - commented: "/* operation='elmer%27s%20glue',service='mine' */ SELECT * from FOO -- test query", - }, - { - name: "no tags", - query: "SELECT * from FOO", - tags: map[string]string{}, - commented: "SELECT * from FOO", - }, - { - name: "nil tags", - query: "SELECT * from FOO", - tags: nil, - commented: "SELECT * from FOO", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - commented := OnQuery(tc.query, tc.tags) - assert.Equal(t, tc.commented, commented) - }) - } -} - -func TestWithTags(t *testing.T) { - testCases := []struct { - name string - tags map[string]string - comment string - }{ - { - name: "simple tag", - tags: map[string]string{"service": "mine"}, - comment: "/* service='mine' */", - }, - { - name: "tag list", - tags: map[string]string{"service": "mine", "operation": "checkout"}, - comment: "/* operation='checkout',service='mine' */", - }, - { - name: "tag value with single quote", - tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, - comment: "/* operation='elmer%27s%20glue',service='mine' */", - }, - { - name: "tag key with space", - tags: map[string]string{"service name": "mine"}, - comment: "/* service%20name='mine' */", - }, - { - name: "no tags", - tags: map[string]string{}, - comment: "", - }, - { - name: "nil tags", - tags: nil, - comment: "", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - comment := WithTags(tc.tags) - assert.Equal(t, tc.comment, comment) - }) - } -} diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 20cc2e771d..bd7be7475e 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -13,7 +13,6 @@ import ( "os" "time" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -44,7 +43,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, comment.QueryTextCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -56,7 +55,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, comment.QueryTextCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -71,28 +70,28 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - queryTextCarrier := comment.QueryTextCarrier{} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, queryTextCarrier, err) + sqlCommentCarrier := tracer.SQLCommentCarrier{} + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - stmt, err := connPrepareCtx.PrepareContext(ctx, queryTextCarrier.CommentedQuery(query)) + stmt, err := connPrepareCtx.PrepareContext(ctx, sqlCommentCarrier.CommentedQuery(query)) if err != nil { return nil, err } return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - queryTextCarrier := comment.QueryTextCarrier{} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, queryTextCarrier, err) + sqlCommentCarrier := tracer.SQLCommentCarrier{} + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - stmt, err = tc.Prepare(queryTextCarrier.CommentedQuery(query)) + stmt, err = tc.Prepare(sqlCommentCarrier.CommentedQuery(query)) if err != nil { return nil, err } @@ -108,7 +107,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv start := time.Now() q := query if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, comment.QueryTextCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -127,14 +126,14 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - queryTextCarrier := comment.QueryTextCarrier{} - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, queryTextCarrier, err) + sqlCommentCarrier := tracer.SQLCommentCarrier{} + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, sqlCommentCarrier, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) }() } - r, err = execer.Exec(queryTextCarrier.CommentedQuery(query), dargs) + r, err = execer.Exec(sqlCommentCarrier.CommentedQuery(query), dargs) return r, err } return nil, driver.ErrSkip @@ -146,7 +145,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - span := tc.tryStartTrace(ctx, queryTypePing, "", start, comment.QueryTextCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypePing, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -158,8 +157,8 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - queryTextCarrier := comment.QueryTextCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, queryTextCarrier, err) + sqlCommentCarrier := tracer.SQLCommentCarrier{} + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -179,14 +178,14 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - queryTextCarrier := comment.QueryTextCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, queryTextCarrier, err) + sqlCommentCarrier := tracer.SQLCommentCarrier{} + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - rows, err = queryer.Query(queryTextCarrier.CommentedQuery(query), dargs) + rows, err = queryer.Query(sqlCommentCarrier.CommentedQuery(query), dargs) return rows, err } return nil, driver.ErrSkip @@ -228,7 +227,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, queryTextCarrier comment.QueryTextCarrier, err error) (span tracer.Span) { +func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier tracer.SQLCommentCarrier, err error) (span tracer.Span) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -252,7 +251,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query resource := string(qtype) if query != "" { resource = query - err := tracer.Inject(span.Context(), queryTextCarrier) + err := tracer.Inject(span.Context(), sqlCommentCarrier) if err != nil { // this should never happen fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index bc9e76bbe8..17ce5b2017 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -21,7 +21,6 @@ import ( "database/sql" "database/sql/driver" "errors" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "reflect" @@ -143,7 +142,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, comment.QueryTextCarrier{}, err) + span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index bbc10e2cf9..7fbda10ecb 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -9,7 +9,6 @@ import ( "context" "database/sql/driver" "errors" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -28,7 +27,7 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, comment.QueryTextCarrier{}, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -41,7 +40,7 @@ func (s *tracedStmt) Close() (err error) { func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, comment.QueryTextCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -60,7 +59,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, comment.QueryTextCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -75,7 +74,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, comment.QueryTextCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -94,7 +93,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, comment.QueryTextCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 0612fdce7e..869890182a 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -8,7 +8,6 @@ package sql import ( "context" "database/sql/driver" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/comment" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -25,7 +24,7 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, comment.QueryTextCarrier{}, err) + span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -38,7 +37,7 @@ func (t *tracedTx) Commit() (err error) { // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, comment.QueryTextCarrier{}, err) + span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, tracer.SQLCommentCarrier{}, err) err = t.Tx.Rollback() if span != nil { go func() { diff --git a/contrib/database/sql/comment/tagger.go b/ddtrace/tracer/sqlcomment.go similarity index 52% rename from contrib/database/sql/comment/tagger.go rename to ddtrace/tracer/sqlcomment.go index 1602652a9c..0dab6ccf02 100644 --- a/contrib/database/sql/comment/tagger.go +++ b/ddtrace/tracer/sqlcomment.go @@ -1,4 +1,4 @@ -package comment +package tracer import ( "fmt" @@ -7,48 +7,47 @@ import ( "strings" ) -func OnQuery(query string, tagSets ...map[string]string) (commentedQuery string) { - // Don't comment the query if there's no query to tag - if len(query) == 0 { - return query +type SQLCommentCarrier struct { + tags map[string]string +} + +func commentWithTags(tags map[string]string) (comment string) { + if len(tags) == 0 { + return "" } - comment := WithTags(tagSets...) - if len(comment) == 0 { - return query + serializedTags := make([]string, 0, len(tags)) + for k, v := range tags { + serializedTags = append(serializedTags, serializeTag(k, v)) } - // Diverge from the sqlcommenter spec because we want to prioritize comments not being truncated - return fmt.Sprintf("%s %s", comment, query) + sort.Strings(serializedTags) + comment = strings.Join(serializedTags, ",") + return fmt.Sprintf("/* %s */", comment) } -// totalLen returns the maximum total number of elements in all maps. -// Duplicate keys are counted as multiple elements -func totalLen(tagSets ...map[string]string) (length int) { - length = 0 - for _, t := range tagSets { - length += len(t) +// Set implements TextMapWriter. +func (c *SQLCommentCarrier) Set(key, val string) { + if c.tags == nil { + c.tags = make(map[string]string) } - - return length + c.tags[key] = val } -func WithTags(tagSets ...map[string]string) (comment string) { - tagCount := totalLen(tagSets...) - if tagCount == 0 { - return "" - } +func (c *SQLCommentCarrier) CommentedQuery(query string) (commented string) { + comment := commentWithTags(c.tags) - serializedTags := make([]string, 0, tagCount) - for _, ts := range tagSets { - for k, v := range ts { - serializedTags = append(serializedTags, serializeTag(k, v)) - } + if comment == "" || query == "" { + return query } - sort.Strings(serializedTags) - comment = strings.Join(serializedTags, ",") - return fmt.Sprintf("/* %s */", comment) + return fmt.Sprintf("%s %s", comment, query) +} + +// ForeachKey implements TextMapReader. +func (c SQLCommentCarrier) ForeachKey(handler func(key, val string) error) error { + // TODO: implement this for completeness. We don't really have a use-case for this at the moment. + return nil } func serializeTag(key string, value string) (serialized string) { diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go new file mode 100644 index 0000000000..d31a68915d --- /dev/null +++ b/ddtrace/tracer/sqlcomment_test.go @@ -0,0 +1,61 @@ +package tracer + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "testing" +) + +func TestQueryTextCarrier(t *testing.T) { + testCases := []struct { + name string + query string + tags map[string]string + commented string + }{ + { + name: "query with tag list", + query: "SELECT * from FOO", + tags: map[string]string{"service": "mine", "operation": "checkout"}, + commented: "/* bg-operation='checkout',bg-service='mine',span-id='1',trace-id='1',x-datadog-sampling-priority='1' */ SELECT * from FOO", + }, + { + name: "empty query", + query: "", + tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, + commented: "", + }, + { + name: "query with existing comment", + query: "SELECT * from FOO -- test query", + tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, + commented: "/* bg-operation='elmer%27s%20glue',bg-service='mine',span-id='1',trace-id='1',x-datadog-sampling-priority='1' */ SELECT * from FOO -- test query", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + propagator := NewPropagator(&PropagatorConfig{ + BaggagePrefix: "bg-", + TraceHeader: "trace-id", + ParentHeader: "span-id", + }) + tracer := newTracer(WithPropagator(propagator)) + + root := tracer.StartSpan("web.request", WithSpanID(1)).(*span) + for k, v := range tc.tags { + root.SetBaggageItem(k, v) + } + + root.SetTag(ext.SamplingPriority, 1) + ctx := root.Context() + + carrier := SQLCommentCarrier{} + err := tracer.Inject(ctx, &carrier) + require.NoError(t, err) + + assert.Equal(t, tc.commented, carrier.CommentedQuery(tc.query)) + }) + } +} From 3ddbbef8d43d3838de03c3e5471c5ccd46e44868 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 17:35:04 -0700 Subject: [PATCH 006/104] Fix SQLCommentCarrier passed as value instead of pointer --- contrib/database/sql/conn.go | 20 ++++++++++---------- contrib/database/sql/sql.go | 2 +- contrib/database/sql/stmt.go | 10 +++++----- contrib/database/sql/tx.go | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index bd7be7475e..d7b689cc14 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -43,7 +43,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -55,7 +55,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -71,7 +71,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -85,7 +85,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -107,7 +107,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv start := time.Now() q := query if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -127,7 +127,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv default: } sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -145,7 +145,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - span := tc.tryStartTrace(ctx, queryTypePing, "", start, tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypePing, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -158,7 +158,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -179,7 +179,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri default: } sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -227,7 +227,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier tracer.SQLCommentCarrier, err error) (span tracer.Span) { +func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier *tracer.SQLCommentCarrier, err error) (span tracer.Span) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 17ce5b2017..38f302fe95 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -142,7 +142,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, tracer.SQLCommentCarrier{}, err) + span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 7fbda10ecb..34c4846e71 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -27,7 +27,7 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, tracer.SQLCommentCarrier{}, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -40,7 +40,7 @@ func (s *tracedStmt) Close() (err error) { func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, tracer.SQLCommentCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -59,7 +59,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, tracer.SQLCommentCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -74,7 +74,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, tracer.SQLCommentCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -93,7 +93,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, tracer.SQLCommentCarrier{}, err) + span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 869890182a..27fc4786ff 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -24,7 +24,7 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, tracer.SQLCommentCarrier{}, err) + span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -37,7 +37,7 @@ func (t *tracedTx) Commit() (err error) { // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, tracer.SQLCommentCarrier{}, err) + span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, &tracer.SQLCommentCarrier{}, err) err = t.Tx.Rollback() if span != nil { go func() { From ef7a6f64ea0fe70ec146f4003c61a6e32f16b39c Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 17:59:59 -0700 Subject: [PATCH 007/104] Add Missing calls to CommentQuery --- contrib/database/sql/conn.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index d7b689cc14..c7d681f838 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -105,15 +105,15 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() - q := query if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, &tracer.SQLCommentCarrier{}, err) + sqlCommentCarrier := tracer.SQLCommentCarrier{} + span := tc.tryStartTrace(ctx, queryTypeBegin, query, start, &sqlCommentCarrier, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) }() } - r, err := execContext.ExecContext(ctx, q, args) + r, err := execContext.ExecContext(ctx, sqlCommentCarrier.CommentedQuery(query), args) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -164,7 +164,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri span.Finish(tracer.WithError(err)) }() } - rows, err := queryerContext.QueryContext(ctx, query, args) + rows, err := queryerContext.QueryContext(ctx, sqlCommentCarrier.CommentedQuery(query), args) return rows, err } From 79cd4b272f7ec518b8d1cc4f5909fd1d9b7d694c Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 18:22:22 -0700 Subject: [PATCH 008/104] Move call to inject context into SQL Comments Further Down --- contrib/database/sql/conn.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index c7d681f838..35ac43f9af 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -251,11 +251,6 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query resource := string(qtype) if query != "" { resource = query - err := tracer.Inject(span.Context(), sqlCommentCarrier) - if err != nil { - // this should never happen - fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) - } } span.SetTag("sql.query_type", string(qtype)) span.SetTag(ext.ResourceName, resource) @@ -268,5 +263,11 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } + err = tracer.Inject(span.Context(), sqlCommentCarrier) + if err != nil { + // this should never happen + fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) + } + return span } From f2b78d030b847e7c10436f88c6f4aae064704401 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 29 Mar 2022 18:39:45 -0700 Subject: [PATCH 009/104] Add godoc and move main function to top --- ddtrace/tracer/sqlcomment.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 0dab6ccf02..f6ffbdef5d 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -7,10 +7,19 @@ import ( "strings" ) +// SQLCommentCarrier holds tags to be serialized as a SQL Comment type SQLCommentCarrier struct { tags map[string]string } +// Set implements TextMapWriter. +func (c *SQLCommentCarrier) Set(key, val string) { + if c.tags == nil { + c.tags = make(map[string]string) + } + c.tags[key] = val +} + func commentWithTags(tags map[string]string) (comment string) { if len(tags) == 0 { return "" @@ -26,14 +35,8 @@ func commentWithTags(tags map[string]string) (comment string) { return fmt.Sprintf("/* %s */", comment) } -// Set implements TextMapWriter. -func (c *SQLCommentCarrier) Set(key, val string) { - if c.tags == nil { - c.tags = make(map[string]string) - } - c.tags[key] = val -} - +// CommentedQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a +// prepended SQL comment func (c *SQLCommentCarrier) CommentedQuery(query string) (commented string) { comment := commentWithTags(c.tags) From 33a62883de315e440c010bf19fc3d1c957052e38 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 30 Mar 2022 10:05:59 -0700 Subject: [PATCH 010/104] Add hack to set service name and meta tags to sql comments --- contrib/database/sql/conn.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 35ac43f9af..ab910663ea 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -260,6 +260,8 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query if meta, ok := ctx.Value(spanTagsKey).(map[string]string); ok { for k, v := range meta { span.SetTag(k, v) + // TODO: Figure out if there's a better way to do this + sqlCommentCarrier.Set(k, v) } } @@ -268,6 +270,8 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query // this should never happen fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) } + // TODO: Figure out if there's a better way to do this + sqlCommentCarrier.Set(ext.ServiceName, tp.cfg.serviceName) return span } From 4c45603543af3592b7c385f518f2aee9d39170d7 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 30 Mar 2022 10:28:40 -0700 Subject: [PATCH 011/104] Set trace meta as tags --- contrib/database/sql/conn.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index ab910663ea..0c4dc8e420 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -260,8 +260,6 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query if meta, ok := ctx.Value(spanTagsKey).(map[string]string); ok { for k, v := range meta { span.SetTag(k, v) - // TODO: Figure out if there's a better way to do this - sqlCommentCarrier.Set(k, v) } } @@ -270,8 +268,11 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query // this should never happen fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) } - // TODO: Figure out if there's a better way to do this + // TODO: Figure out if there's a better way to add those additional tags sqlCommentCarrier.Set(ext.ServiceName, tp.cfg.serviceName) - + for k, v := range tp.meta { + sqlCommentCarrier.Set(k, v) + } + return span } From 44fec1fb8cdc6fbafd904f029a3c8d95792db48e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 30 Mar 2022 11:06:40 -0700 Subject: [PATCH 012/104] Remove unused function --- contrib/database/sql/conn.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 0c4dc8e420..aa106462fb 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -99,10 +99,6 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } -//func (tc *tracedConn) commentedQuery(query string, spanCtx ddtrace.SpanContext) string { -// return comment.OnQuery(query, map[string]string{ext.ServiceName: tc.cfg.serviceName, "dd.span_id": strconv.FormatUint(spanCtx.SpanID(), 10), "dd.trace_id": strconv.FormatUint(spanCtx.TraceID(), 10)}, tc.meta) -//} - func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { @@ -273,6 +269,6 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query for k, v := range tp.meta { sqlCommentCarrier.Set(k, v) } - + return span } From df7baa0aae42d3d0250b411cc727f162f8531222 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 14 Apr 2022 13:42:48 -0700 Subject: [PATCH 013/104] DBM-1020 Remove meta info from sql comments and shorten sql comment tag keys --- contrib/database/sql/conn.go | 5 +---- ddtrace/tracer/sqlcomment.go | 23 +++++++++++++++++++++-- ddtrace/tracer/sqlcomment_test.go | 20 ++++++++------------ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index aa106462fb..36264b6e39 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -265,10 +265,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) } // TODO: Figure out if there's a better way to add those additional tags - sqlCommentCarrier.Set(ext.ServiceName, tp.cfg.serviceName) - for k, v := range tp.meta { - sqlCommentCarrier.Set(k, v) - } + sqlCommentCarrier.Set(tracer.ServiceNameSQLCommentKey, tp.cfg.serviceName) return span } diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index f6ffbdef5d..aa1d4f4f8d 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -12,12 +12,31 @@ type SQLCommentCarrier struct { tags map[string]string } +const ( + samplingPrioritySQLCommentKey = "ddsp" + traceIDSQLCommentKey = "ddtid" + spanIDSQLCommentKey = "ddsid" + ServiceNameSQLCommentKey = "ddsn" +) + // Set implements TextMapWriter. func (c *SQLCommentCarrier) Set(key, val string) { if c.tags == nil { c.tags = make(map[string]string) } - c.tags[key] = val + + // Remap the default long key names to short versions specifically for SQL comments that prioritize size + // while trying to avoid conflicts + switch key { + case DefaultPriorityHeader: + c.tags[samplingPrioritySQLCommentKey] = val + case DefaultTraceIDHeader: + c.tags[traceIDSQLCommentKey] = val + case DefaultParentIDHeader: + c.tags[spanIDSQLCommentKey] = val + default: + c.tags[key] = val + } } func commentWithTags(tags map[string]string) (comment string) { @@ -32,7 +51,7 @@ func commentWithTags(tags map[string]string) (comment string) { sort.Strings(serializedTags) comment = strings.Join(serializedTags, ",") - return fmt.Sprintf("/* %s */", comment) + return fmt.Sprintf("/*%s*/", comment) } // CommentedQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index d31a68915d..49ef49d0b5 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -17,38 +17,34 @@ func TestQueryTextCarrier(t *testing.T) { { name: "query with tag list", query: "SELECT * from FOO", - tags: map[string]string{"service": "mine", "operation": "checkout"}, - commented: "/* bg-operation='checkout',bg-service='mine',span-id='1',trace-id='1',x-datadog-sampling-priority='1' */ SELECT * from FOO", + tags: map[string]string{"operation": "checkout"}, + commented: "/*ddsid='10',ddsp='2',ddtid='10',ot-baggage-operation='checkout'*/ SELECT * from FOO", }, { name: "empty query", query: "", - tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, + tags: map[string]string{"operation": "elmer's glue"}, commented: "", }, { name: "query with existing comment", query: "SELECT * from FOO -- test query", - tags: map[string]string{"service": "mine", "operation": "elmer's glue"}, - commented: "/* bg-operation='elmer%27s%20glue',bg-service='mine',span-id='1',trace-id='1',x-datadog-sampling-priority='1' */ SELECT * from FOO -- test query", + tags: map[string]string{"operation": "elmer's glue"}, + commented: "/*ddsid='10',ddsp='2',ddtid='10',ot-baggage-operation='elmer%27s%20glue'*/ SELECT * from FOO -- test query", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - propagator := NewPropagator(&PropagatorConfig{ - BaggagePrefix: "bg-", - TraceHeader: "trace-id", - ParentHeader: "span-id", - }) + propagator := NewPropagator(&PropagatorConfig{}) tracer := newTracer(WithPropagator(propagator)) - root := tracer.StartSpan("web.request", WithSpanID(1)).(*span) + root := tracer.StartSpan("web.request", WithSpanID(10)).(*span) for k, v := range tc.tags { root.SetBaggageItem(k, v) } - root.SetTag(ext.SamplingPriority, 1) + root.SetTag(ext.SamplingPriority, 2) ctx := root.Context() carrier := SQLCommentCarrier{} From 6960385f58e92edca498f16f52f5d0d698b506f6 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 26 Apr 2022 15:11:47 -0700 Subject: [PATCH 014/104] Add SQL Commenting Injection Mode Feature, Env and Version --- README.md | 8 +++++-- contrib/database/sql/conn.go | 33 ++++++++++++++++++++------ contrib/database/sql/option.go | 43 ++++++++++++++++++++++++++++++---- ddtrace/tracer/sqlcomment.go | 15 ++++++++---- 4 files changed, 81 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c8627621fb..7ad9abd48a 100644 --- a/README.md +++ b/README.md @@ -80,5 +80,9 @@ might be running versions different from the vendored one, creating hard to debu To run integration tests locally, you should set the `INTEGRATION` environment variable. The dependencies of the integration tests are best run via Docker. To get an idea about the versions and the set-up take a look at our [CI config](./.circleci/config.yml). -The best way to run the entire test suite is using the [CircleCI CLI](https://circleci.com/docs/2.0/local-jobs/). Simply run `circleci build` -in the repository root. Note that you might have to increase the resources dedicated to Docker to around 4GB. +The best way to run the entire test suite is using the [CircleCI CLI](https://circleci.com/docs/2.0/local-cli/). In order to run +jobs locally, you'll first need to convert the Circle CI configuration to a format accepted by the `circleci` cli tool: + * `circleci config process .circleci/config.yml > process.yml` (from the repository root) + +Once you have a converted `process.yml`, simply run `circleci local execute -c process.yml --job `. +Note that you might have to increase the resources dedicated to Docker to around 4GB. diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 36264b6e39..d39fddd264 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -9,6 +9,7 @@ import ( "context" "database/sql/driver" "fmt" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "math" "os" "time" @@ -70,7 +71,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - sqlCommentCarrier := tracer.SQLCommentCarrier{} + sqlCommentCarrier := tracer.SQLCommentCarrier{KeepOnlyStaticTags: true} span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { go func() { @@ -259,13 +260,31 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - err = tracer.Inject(span.Context(), sqlCommentCarrier) - if err != nil { - // this should never happen - fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) + if tp.cfg.sqlCommentInjectionMode == FullSQLCommentInjection && !sqlCommentCarrier.KeepOnlyStaticTags { + err = tracer.Inject(span.Context(), sqlCommentCarrier) + if err != nil { + // this should never happen + fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) + } + } + + if tp.cfg.sqlCommentInjectionMode == StaticTagsSQLCommentInjection || tp.cfg.sqlCommentInjectionMode == FullSQLCommentInjection { + injectStaticTagsSQLComments(sqlCommentCarrier) } - // TODO: Figure out if there's a better way to add those additional tags - sqlCommentCarrier.Set(tracer.ServiceNameSQLCommentKey, tp.cfg.serviceName) return span } + +func injectStaticTagsSQLComments(sqlCommentCarrier *tracer.SQLCommentCarrier) { + sqlCommentCarrier.Set(tracer.ServiceNameSQLCommentKey, globalconfig.ServiceName()) + + // TODO: The following two values bypass any override set via the calling application via tracer options + // Figure out a clean way to get those values from the private tracer configuration instead + if env := os.Getenv("DD_ENV"); env != "" { + sqlCommentCarrier.Set(tracer.ServiceEnvironmentSQLCommentKey, env) + } + + if ver := os.Getenv("DD_VERSION"); ver != "" { + sqlCommentCarrier.Set(tracer.ServiceVersionSQLCommentKey, ver) + } +} diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 47bd4208bd..08c0b8f7ca 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -12,12 +12,22 @@ import ( ) type config struct { - serviceName string - analyticsRate float64 - dsn string - childSpansOnly bool + serviceName string + analyticsRate float64 + dsn string + childSpansOnly bool + sqlCommentInjectionMode SQLCommentInjectionMode } +// SQLCommentInjectionMode represents the mode of sql comment injection +type SQLCommentInjectionMode int + +const ( + SQLCommentInjectionDisabled SQLCommentInjectionMode = 0 // Default value, sql comment injection disabled + FullSQLCommentInjection SQLCommentInjectionMode = 1 // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. + StaticTagsSQLCommentInjection SQLCommentInjectionMode = 2 // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. +) + // Option represents an option that can be passed to Register, Open or OpenDB. type Option func(*config) @@ -83,3 +93,28 @@ func WithChildSpansOnly() Option { cfg.childSpansOnly = true } } + +// 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. Use WithStaticTagsSQLCommentInjection if this is a concern. +func WithSQLCommentInjection() Option { + return func(cfg *config) { + cfg.sqlCommentInjectionMode = FullSQLCommentInjection + } +} + +// WithStaticTagsSQLCommentInjection enables injection of static tags as sql comments on traced queries. +// This excludes dynamic values like span id, trace id and sampling priority which can make a query +// unique and have side effects on caching. +func WithStaticTagsSQLCommentInjection() Option { + return func(cfg *config) { + cfg.sqlCommentInjectionMode = StaticTagsSQLCommentInjection + } +} + +// WithoutSQLCommentInjection disables injection of sql comments on traced queries. +func WithoutSQLCommentInjection() Option { + return func(cfg *config) { + cfg.sqlCommentInjectionMode = SQLCommentInjectionDisabled + } +} \ No newline at end of file diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index aa1d4f4f8d..59798348cc 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -9,14 +9,19 @@ import ( // SQLCommentCarrier holds tags to be serialized as a SQL Comment type SQLCommentCarrier struct { - tags map[string]string + // Indicates if this SQL comment carrier should only preserve static tags or also include + // dynamic ones (like trace id, span id and sampling priority) + KeepOnlyStaticTags bool + tags map[string]string } const ( - samplingPrioritySQLCommentKey = "ddsp" - traceIDSQLCommentKey = "ddtid" - spanIDSQLCommentKey = "ddsid" - ServiceNameSQLCommentKey = "ddsn" + samplingPrioritySQLCommentKey = "ddsp" + traceIDSQLCommentKey = "ddtid" + spanIDSQLCommentKey = "ddsid" + ServiceNameSQLCommentKey = "ddsn" + ServiceVersionSQLCommentKey = "ddsv" + ServiceEnvironmentSQLCommentKey = "dde" ) // Set implements TextMapWriter. From cc2223452c3c045a88710b277a3331d5083b1e0a Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 27 Apr 2022 10:45:46 -0700 Subject: [PATCH 015/104] Only inject service name in sql comments when it's not empty --- contrib/database/sql/conn.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index d39fddd264..c3012e6687 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -276,7 +276,9 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } func injectStaticTagsSQLComments(sqlCommentCarrier *tracer.SQLCommentCarrier) { - sqlCommentCarrier.Set(tracer.ServiceNameSQLCommentKey, globalconfig.ServiceName()) + if name := globalconfig.ServiceName(); name != "" { + sqlCommentCarrier.Set(tracer.ServiceNameSQLCommentKey, name) + } // TODO: The following two values bypass any override set via the calling application via tracer options // Figure out a clean way to get those values from the private tracer configuration instead From 8591b7015561fe8dd5542696e75f1811923c4dec Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 27 Apr 2022 13:47:38 -0700 Subject: [PATCH 016/104] Fix linting error --- contrib/database/sql/option.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 08c0b8f7ca..fb2c0c2b16 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -117,4 +117,4 @@ func WithoutSQLCommentInjection() Option { return func(cfg *config) { cfg.sqlCommentInjectionMode = SQLCommentInjectionDisabled } -} \ No newline at end of file +} From b728ee3b83dd3c5eaa5ac7a4130ece466852f1e3 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 27 Apr 2022 16:02:25 -0700 Subject: [PATCH 017/104] Fix broken test (bad copy-pasta) --- contrib/database/sql/conn.go | 2 +- contrib/internal/sqltest/sqltest.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index c3012e6687..3b7eb6c4db 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -104,7 +104,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeBegin, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 35dbd1ddd0..86388046c7 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -9,6 +9,7 @@ import ( "context" "database/sql" "fmt" + "github.com/stretchr/testify/require" "log" "testing" @@ -95,7 +96,7 @@ func testPing(cfg *Config) func(*testing.T) { err := cfg.DB.Ping() assert.Nil(err) spans := cfg.mockTracer.FinishedSpans() - assert.Len(spans, 2) + require.Len(t, spans, 2) verifyConnectSpan(spans[0], assert, cfg) From 5ffca621664d8aeeb55318e9915af657e404c7e5 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 27 Apr 2022 19:38:27 -0700 Subject: [PATCH 018/104] Fix linting errors --- contrib/database/sql/conn.go | 7 ++++--- contrib/database/sql/option.go | 32 ++++++++++++++--------------- contrib/database/sql/sql.go | 3 ++- contrib/database/sql/stmt.go | 3 ++- contrib/database/sql/tx.go | 3 ++- contrib/internal/sqltest/sqltest.go | 3 ++- ddtrace/tracer/sqlcomment_test.go | 4 +++- 7 files changed, 31 insertions(+), 24 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 3b7eb6c4db..167456e2fc 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -9,11 +9,12 @@ import ( "context" "database/sql/driver" "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "math" "os" "time" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -260,7 +261,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - if tp.cfg.sqlCommentInjectionMode == FullSQLCommentInjection && !sqlCommentCarrier.KeepOnlyStaticTags { + if tp.cfg.sqlCommentInjectionMode == fullSQLCommentInjection && !sqlCommentCarrier.KeepOnlyStaticTags { err = tracer.Inject(span.Context(), sqlCommentCarrier) if err != nil { // this should never happen @@ -268,7 +269,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - if tp.cfg.sqlCommentInjectionMode == StaticTagsSQLCommentInjection || tp.cfg.sqlCommentInjectionMode == FullSQLCommentInjection { + if tp.cfg.sqlCommentInjectionMode == staticTagsSQLCommentInjection || tp.cfg.sqlCommentInjectionMode == fullSQLCommentInjection { injectStaticTagsSQLComments(sqlCommentCarrier) } diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index fb2c0c2b16..30dfc57e22 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -16,16 +16,16 @@ type config struct { analyticsRate float64 dsn string childSpansOnly bool - sqlCommentInjectionMode SQLCommentInjectionMode + sqlCommentInjectionMode commentInjectionMode } -// SQLCommentInjectionMode represents the mode of sql comment injection -type SQLCommentInjectionMode int +// commentInjectionMode represents the mode of sql comment injection +type commentInjectionMode int const ( - SQLCommentInjectionDisabled SQLCommentInjectionMode = 0 // Default value, sql comment injection disabled - FullSQLCommentInjection SQLCommentInjectionMode = 1 // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. - StaticTagsSQLCommentInjection SQLCommentInjectionMode = 2 // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. + commentInjectionDisabled commentInjectionMode = 0 // Default value, sql comment injection disabled + fullSQLCommentInjection commentInjectionMode = 1 // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. + staticTagsSQLCommentInjection commentInjectionMode = 2 // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. ) // Option represents an option that can be passed to Register, Open or OpenDB. @@ -94,27 +94,27 @@ func WithChildSpansOnly() Option { } } -// WithSQLCommentInjection enables injection of tags as sql comments on traced queries. +// WithCommentInjection 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. Use WithStaticTagsSQLCommentInjection if this is a concern. -func WithSQLCommentInjection() Option { +// unique for some cache implementations. Use WithStaticTagsCommentInjection if this is a concern. +func WithCommentInjection() Option { return func(cfg *config) { - cfg.sqlCommentInjectionMode = FullSQLCommentInjection + cfg.sqlCommentInjectionMode = fullSQLCommentInjection } } -// WithStaticTagsSQLCommentInjection enables injection of static tags as sql comments on traced queries. +// WithStaticTagsCommentInjection enables injection of static tags as sql comments on traced queries. // This excludes dynamic values like span id, trace id and sampling priority which can make a query // unique and have side effects on caching. -func WithStaticTagsSQLCommentInjection() Option { +func WithStaticTagsCommentInjection() Option { return func(cfg *config) { - cfg.sqlCommentInjectionMode = StaticTagsSQLCommentInjection + cfg.sqlCommentInjectionMode = staticTagsSQLCommentInjection } } -// WithoutSQLCommentInjection disables injection of sql comments on traced queries. -func WithoutSQLCommentInjection() Option { +// WithoutCommentInjection disables injection of sql comments on traced queries. +func WithoutCommentInjection() Option { return func(cfg *config) { - cfg.sqlCommentInjectionMode = SQLCommentInjectionDisabled + cfg.sqlCommentInjectionMode = commentInjectionDisabled } } diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 38f302fe95..f09ff26ec4 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -21,11 +21,12 @@ import ( "database/sql" "database/sql/driver" "errors" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "reflect" "time" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 34c4846e71..ac0964f6a5 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -9,8 +9,9 @@ import ( "context" "database/sql/driver" "errors" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) var _ driver.Stmt = (*tracedStmt)(nil) diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 27fc4786ff..1b6a1430be 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -8,8 +8,9 @@ package sql import ( "context" "database/sql/driver" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) var _ driver.Tx = (*tracedTx)(nil) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 86388046c7..07db4e39e5 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -9,10 +9,11 @@ import ( "context" "database/sql" "fmt" - "github.com/stretchr/testify/require" "log" "testing" + "github.com/stretchr/testify/require" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 49ef49d0b5..ad83f9c6fe 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -1,10 +1,12 @@ package tracer import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "testing" ) func TestQueryTextCarrier(t *testing.T) { From 9577c6d1673da0c2673bf0c2aaa0688271c4b54f Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 5 May 2022 11:57:51 -0700 Subject: [PATCH 019/104] Refactor to handle code overrides --- contrib/database/sql/conn.go | 34 ++------ contrib/database/sql/option.go | 24 ++++++ ddtrace/ddtrace.go | 30 +++++++ ddtrace/internal/globaltracer.go | 5 ++ ddtrace/mocktracer/mocktracer.go | 55 +++++++++++++ ddtrace/tracer/option.go | 47 +++++++++++ ddtrace/tracer/propagator.go | 3 + ddtrace/tracer/spancontext.go | 5 ++ ddtrace/tracer/sqlcomment.go | 25 ++---- ddtrace/tracer/textmap.go | 132 +++++++++++++++++++++++++++++++ ddtrace/tracer/tracer.go | 13 +++ 11 files changed, 327 insertions(+), 46 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 167456e2fc..82f59770c6 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -13,8 +13,6 @@ import ( "os" "time" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -72,7 +70,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - sqlCommentCarrier := tracer.SQLCommentCarrier{KeepOnlyStaticTags: true} + sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { go func() { @@ -261,33 +259,13 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - if tp.cfg.sqlCommentInjectionMode == fullSQLCommentInjection && !sqlCommentCarrier.KeepOnlyStaticTags { - err = tracer.Inject(span.Context(), sqlCommentCarrier) - if err != nil { - // this should never happen - fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) - } - } + injectionOpts := injectionOptionsForMode(tp.cfg.sqlCommentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) - if tp.cfg.sqlCommentInjectionMode == staticTagsSQLCommentInjection || tp.cfg.sqlCommentInjectionMode == fullSQLCommentInjection { - injectStaticTagsSQLComments(sqlCommentCarrier) + err = tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) + if err != nil { + // this should never happen + fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) } return span } - -func injectStaticTagsSQLComments(sqlCommentCarrier *tracer.SQLCommentCarrier) { - if name := globalconfig.ServiceName(); name != "" { - sqlCommentCarrier.Set(tracer.ServiceNameSQLCommentKey, name) - } - - // TODO: The following two values bypass any override set via the calling application via tracer options - // Figure out a clean way to get those values from the private tracer configuration instead - if env := os.Getenv("DD_ENV"); env != "" { - sqlCommentCarrier.Set(tracer.ServiceEnvironmentSQLCommentKey, env) - } - - if ver := os.Getenv("DD_VERSION"); ver != "" { - sqlCommentCarrier.Set(tracer.ServiceVersionSQLCommentKey, ver) - } -} diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 30dfc57e22..4efc3dec8a 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -8,6 +8,8 @@ package sql import ( "math" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal" ) @@ -118,3 +120,25 @@ func WithoutCommentInjection() Option { cfg.sqlCommentInjectionMode = commentInjectionDisabled } } + +func injectionOptionsForMode(mode commentInjectionMode, discardDynamicTags bool) (opts []tracer.InjectionOption) { + switch { + case mode == fullSQLCommentInjection && !discardDynamicTags: + return []tracer.InjectionOption{ + tracer.WithTraceIDKey(tracer.TraceIDSQLCommentKey), + tracer.WithSpanIDKey(tracer.SpanIDSQLCommentKey), + tracer.WithSamplingPriorityKey(tracer.SamplingPrioritySQLCommentKey), + tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), + tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), + tracer.WithVersionKey(tracer.ServiceVersionSQLCommentKey), + } + case mode == fullSQLCommentInjection && discardDynamicTags || mode == staticTagsSQLCommentInjection: + return []tracer.InjectionOption{ + tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), + tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), + tracer.WithVersionKey(tracer.ServiceVersionSQLCommentKey), + } + default: + return []tracer.InjectionOption{} + } +} diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index 06a1732fc1..ad5cd78c2e 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -33,6 +33,10 @@ type Tracer interface { // Inject injects a span context into the given carrier. Inject(context SpanContext, carrier interface{}) error + // InjectWithOptions injects a span context into the given carrier with options. + // This method is experimental and subject to removal or modification + InjectWithOptions(context SpanContext, carrier interface{}, opts ...InjectionOption) error + // Stop stops the tracer. Calls to Stop should be idempotent. Stop() } @@ -135,3 +139,29 @@ type Logger interface { // Log prints the given message. Log(msg string) } + +// InjectionOption is a configuration option that can be used with a Tracer's InjectWithOptions method. +type InjectionOption func(cfg *InjectionConfig) + +// InjectionConfig holds the configuration for injection a span into a carrier. It is usually passed +// around by reference to one or more InjectionOption functions which shape it into its +// final form. +type InjectionConfig struct { + // TraceIDKey defines the key to use to inject the trade id. The trace id is only injected if this value + // is not empty + TraceIDKey string + // SpanIDKey defines the key to use to inject the span id. The span id is only injected if this value + // is not empty + SpanIDKey string + // SamplingPriorityKey defines the key to use to inject the sampling priority. The sampling priority is only + // injected if this value is not empty + SamplingPriorityKey string + // ServiceNameKey defines the key to use to inject the service name. The service name is only + // injected if this value is not empty + ServiceNameKey string + // EnvKey defines the key to use to inject the environment. The environment is only injected if this value is not + // empty + EnvKey string + // VersionKey defines the key to use to inject the version. The version is only injected if this value is not empty + VersionKey string +} diff --git a/ddtrace/internal/globaltracer.go b/ddtrace/internal/globaltracer.go index 239e976267..bc0fade507 100644 --- a/ddtrace/internal/globaltracer.go +++ b/ddtrace/internal/globaltracer.go @@ -62,6 +62,11 @@ func (NoopTracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { // Inject implements ddtrace.Tracer. func (NoopTracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { return nil } +// InjectWithOptions implements ddtrace.Tracer. +func (NoopTracer) InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { + return nil +} + // Stop implements ddtrace.Tracer. func (NoopTracer) Stop() {} diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index bcdb6953e9..4341244d39 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -17,6 +17,8 @@ import ( "strings" "sync" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -192,3 +194,56 @@ func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) er }) return nil } + +func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...tracer.InjectionOption) error { + writer, ok := carrier.(tracer.TextMapWriter) + if !ok { + return tracer.ErrInvalidCarrier + } + ctx, ok := context.(*spanContext) + if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + return tracer.ErrInvalidSpanContext + } + + cfg := ddtrace.InjectionConfig{} + for _, apply := range opts { + apply(&cfg) + } + + if cfg.TraceIDKey != "" { + writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) + } + + if cfg.SpanIDKey != "" { + writer.Set(cfg.SpanIDKey, strconv.FormatUint(ctx.spanID, 10)) + } + + if cfg.SamplingPriorityKey != "" { + if ctx.hasSamplingPriority() { + writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(ctx.priority)) + } + } + + if cfg.EnvKey != "" { + envRaw := ctx.span.Tag(ext.Environment) + if env, ok := envRaw.(string); ok { + writer.Set(cfg.EnvKey, env) + } + } + + if cfg.VersionKey != "" { + versionRaw := ctx.span.Tag(ext.Version) + if version, ok := versionRaw.(string); ok { + writer.Set(cfg.VersionKey, version) + } + } + + if cfg.ServiceNameKey != "" { + serviceNameRaw := ctx.span.Tag(ext.ServiceName) + if serviceName, ok := serviceNameRaw.(string); ok { + writer.Set(cfg.ServiceNameKey, serviceName) + } + } + + return nil +} diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index f74232ad0d..bad3fee6b4 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -889,3 +889,50 @@ func WithUserScope(scope string) UserMonitoringOption { s.SetTag("usr.scope", scope) } } + +// InjectionOption is a configuration option for InjectWithOptions. It is aliased in order +// to help godoc group all the functions returning it together. It is considered +// more correct to refer to it as the type as the origin, ddtrace.InjectionOption. +type InjectionOption = ddtrace.InjectionOption + +// WithSpanIDKey returns the option setting the span id key +func WithSpanIDKey(spanIDKey string) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.SpanIDKey = spanIDKey + } +} + +// WithTraceIDKey returns the option setting the trace id key +func WithTraceIDKey(traceIDKey string) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.TraceIDKey = traceIDKey + } +} + +// WithSamplingPriorityKey returns the option setting the sampling priority key +func WithSamplingPriorityKey(samplingPriorityKey string) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.SamplingPriorityKey = samplingPriorityKey + } +} + +// WithEnvironmentKey returns the option setting the environment key +func WithEnvironmentKey(envKey string) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.EnvKey = envKey + } +} + +// WithServiceNameKey returns the option setting the service name key +func WithServiceNameKey(serviceNameKey string) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.ServiceNameKey = serviceNameKey + } +} + +// WithVersionKey returns the option setting the version key +func WithVersionKey(versionKey string) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.VersionKey = versionKey + } +} diff --git a/ddtrace/tracer/propagator.go b/ddtrace/tracer/propagator.go index 93c596227b..7c0c6e3a37 100644 --- a/ddtrace/tracer/propagator.go +++ b/ddtrace/tracer/propagator.go @@ -17,6 +17,9 @@ type Propagator interface { // Inject takes the SpanContext and injects it into the carrier. Inject(context ddtrace.SpanContext, carrier interface{}) error + // InjectWithOptions takes the SpanContext and injects it into the carrier according to the given options. + InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error + // Extract returns the SpanContext from the given carrier. Extract(carrier interface{}) (ddtrace.SpanContext, error) } diff --git a/ddtrace/tracer/spancontext.go b/ddtrace/tracer/spancontext.go index 0349ef1618..a001f34d89 100644 --- a/ddtrace/tracer/spancontext.go +++ b/ddtrace/tracer/spancontext.go @@ -127,6 +127,11 @@ func (c *spanContext) baggageItem(key string) string { return c.baggage[key] } +func (c *spanContext) meta(key string) (val string, ok bool) { + val, ok = c.span.Meta[key] + return val, ok +} + // finish marks this span as finished in the trace. func (c *spanContext) finish() { c.trace.finishedOne(c.span) } diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 59798348cc..b2a198fb80 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -9,16 +9,16 @@ import ( // SQLCommentCarrier holds tags to be serialized as a SQL Comment type SQLCommentCarrier struct { - // Indicates if this SQL comment carrier should only preserve static tags or also include - // dynamic ones (like trace id, span id and sampling priority) - KeepOnlyStaticTags bool + // Indicates if this SQL comment carrier should only discard dynamic tags + // (like trace id, span id and sampling priority) + DiscardDynamicTags bool tags map[string]string } const ( - samplingPrioritySQLCommentKey = "ddsp" - traceIDSQLCommentKey = "ddtid" - spanIDSQLCommentKey = "ddsid" + SamplingPrioritySQLCommentKey = "ddsp" + TraceIDSQLCommentKey = "ddtid" + SpanIDSQLCommentKey = "ddsid" ServiceNameSQLCommentKey = "ddsn" ServiceVersionSQLCommentKey = "ddsv" ServiceEnvironmentSQLCommentKey = "dde" @@ -30,18 +30,7 @@ func (c *SQLCommentCarrier) Set(key, val string) { c.tags = make(map[string]string) } - // Remap the default long key names to short versions specifically for SQL comments that prioritize size - // while trying to avoid conflicts - switch key { - case DefaultPriorityHeader: - c.tags[samplingPrioritySQLCommentKey] = val - case DefaultTraceIDHeader: - c.tags[traceIDSQLCommentKey] = val - case DefaultParentIDHeader: - c.tags[spanIDSQLCommentKey] = val - default: - c.tags[key] = val - } + c.tags[key] = val } func commentWithTags(tags map[string]string) (comment string) { diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index c281bce69e..862527ccf5 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -13,6 +13,8 @@ import ( "strconv" "strings" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" @@ -205,6 +207,19 @@ func (p *chainedPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interfac return nil } +// InjectWithOptions defines the Propagator to propagate SpanContext data +// out of the current process. The implementation propagates only the information +// for which keys are specified by the InjectionConfig build via the options. +func (p *chainedPropagator) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { + for _, v := range p.injectors { + err := v.InjectWithOptions(spanCtx, carrier, opts...) + if err != nil { + return err + } + } + return nil +} + // Extract implements Propagator. func (p *chainedPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { for _, v := range p.extractors { @@ -258,6 +273,59 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr return nil } +func (p *propagator) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { + switch c := carrier.(type) { + case TextMapWriter: + return p.injectTextMapWithOptions(spanCtx, c, opts...) + default: + return ErrInvalidCarrier + } +} + +func (p *propagator) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { + ctx, ok := spanCtx.(*spanContext) + if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + return ErrInvalidSpanContext + } + + cfg := ddtrace.InjectionConfig{} + for _, apply := range opts { + apply(&cfg) + } + + if cfg.TraceIDKey != "" { + writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) + } + + if cfg.SpanIDKey != "" { + writer.Set(cfg.SpanIDKey, strconv.FormatUint(ctx.spanID, 10)) + } + + if cfg.SamplingPriorityKey != "" { + if sp, ok := ctx.samplingPriority(); ok { + writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(sp)) + } + } + + if cfg.EnvKey != "" { + if env, ok := ctx.meta(ext.Environment); ok { + writer.Set(cfg.EnvKey, env) + } + } + + if cfg.VersionKey != "" { + if version, ok := ctx.meta(ext.Version); ok { + writer.Set(cfg.VersionKey, version) + } + } + + if cfg.ServiceNameKey != "" { + writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) + } + + return nil +} + func (p *propagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { switch c := carrier.(type) { case TextMapReader: @@ -352,6 +420,70 @@ func (*propagatorB3) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr return nil } +func (p *propagatorB3) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { + switch c := carrier.(type) { + case TextMapWriter: + return p.injectTextMapWithOptions(spanCtx, c, opts...) + default: + return ErrInvalidCarrier + } +} + +func (*propagatorB3) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { + ctx, ok := spanCtx.(*spanContext) + if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + return ErrInvalidSpanContext + } + + cfg := ddtrace.InjectionConfig{ + TraceIDKey: b3TraceIDHeader, + SpanIDKey: b3SpanIDHeader, + SamplingPriorityKey: b3SpanIDHeader, + } + + for _, apply := range opts { + apply(&cfg) + } + + if cfg.TraceIDKey != "" { + writer.Set(cfg.TraceIDKey, fmt.Sprintf("%016x", ctx.traceID)) + } + + if cfg.SpanIDKey != "" { + writer.Set(cfg.SpanIDKey, fmt.Sprintf("%016x", ctx.spanID)) + } + + if cfg.SamplingPriorityKey != "" { + if p, ok := ctx.samplingPriority(); ok { + if p >= ext.PriorityAutoKeep { + writer.Set(cfg.SamplingPriorityKey, "1") + } else { + writer.Set(cfg.SamplingPriorityKey, "0") + } + } + } + + if cfg.EnvKey != "" { + if env, ok := ctx.meta(ext.Environment); ok { + writer.Set(cfg.EnvKey, env) + } + } + + if cfg.VersionKey != "" { + if version, ok := ctx.meta(ext.Version); ok { + writer.Set(cfg.VersionKey, version) + } + } + + if cfg.ServiceNameKey != "" { + if serviceName := globalconfig.ServiceName(); serviceName != "" { + writer.Set(cfg.ServiceNameKey, serviceName) + } + } + + return nil +} + func (p *propagatorB3) Extract(carrier interface{}) (ddtrace.SpanContext, error) { switch c := carrier.(type) { case TextMapReader: diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index ee32b00efb..79f688803c 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -154,6 +154,14 @@ func Inject(ctx ddtrace.SpanContext, carrier interface{}) error { return internal.GetGlobalTracer().Inject(ctx, carrier) } +// InjectWithOptions injects the given SpanContext into the carrier with options. This +// is similar to Inject but adds options to specify which info to inject in the carrier +// along with the keys to use. The carrier is still expected to implement TextMapWriter, +// otherwise an error is returned. If the tracer is not started, calling this function is a no-op. +func InjectWithOptions(ctx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { + return internal.GetGlobalTracer().InjectWithOptions(ctx, carrier, opts...) +} + // SetUser associates user information to the current trace which the // provided span belongs to. The options can be used to tune which user // bit of information gets monitored. @@ -500,6 +508,11 @@ func (t *tracer) Inject(ctx ddtrace.SpanContext, carrier interface{}) error { return t.config.propagator.Inject(ctx, carrier) } +// InjectWithOptions uses the configured or default TextMap Propagator. +func (t *tracer) InjectWithOptions(ctx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { + return t.config.propagator.InjectWithOptions(ctx, carrier, opts...) +} + // Extract uses the configured or default TextMap Propagator. func (t *tracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { return t.config.propagator.Extract(carrier) From ef456e4786b83c70cb47093ad1c50d1d297cbb0f Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 28 Apr 2022 09:29:39 -0700 Subject: [PATCH 020/104] Create new parent version key for propagation of the parent service version into sql comments --- contrib/database/sql/option.go | 4 ++-- ddtrace/ddtrace.go | 4 ++-- ddtrace/ext/tags.go | 5 +++++ ddtrace/mocktracer/mocktracer.go | 6 +++--- ddtrace/tracer/option.go | 6 +++--- ddtrace/tracer/textmap.go | 10 +++++----- ddtrace/tracer/tracer.go | 2 ++ 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 4efc3dec8a..0d714da8d4 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -130,13 +130,13 @@ func injectionOptionsForMode(mode commentInjectionMode, discardDynamicTags bool) tracer.WithSamplingPriorityKey(tracer.SamplingPrioritySQLCommentKey), tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), - tracer.WithVersionKey(tracer.ServiceVersionSQLCommentKey), + tracer.WithParentVersionKey(tracer.ServiceVersionSQLCommentKey), } case mode == fullSQLCommentInjection && discardDynamicTags || mode == staticTagsSQLCommentInjection: return []tracer.InjectionOption{ tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), - tracer.WithVersionKey(tracer.ServiceVersionSQLCommentKey), + tracer.WithParentVersionKey(tracer.ServiceVersionSQLCommentKey), } default: return []tracer.InjectionOption{} diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index ad5cd78c2e..add620b33d 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -162,6 +162,6 @@ type InjectionConfig struct { // EnvKey defines the key to use to inject the environment. The environment is only injected if this value is not // empty EnvKey string - // VersionKey defines the key to use to inject the version. The version is only injected if this value is not empty - VersionKey string + // ParentVersionKey defines the key to use to inject the version. The version is only injected if this value is not empty + ParentVersionKey string } diff --git a/ddtrace/ext/tags.go b/ddtrace/ext/tags.go index 767bd4ea7c..8c627a8380 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -53,6 +53,11 @@ const ( // Version is a tag that specifies the current application version. Version = "version" + // ParentVersion is a tag that specifies the parent's application version + // (i.e. for database service spans, this would be the version of the running application rather than + // the database version). + ParentVersion = "parent.version" + // ResourceName defines the Resource name for the Span. ResourceName = "resource.name" diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 4341244d39..8a630c8ee3 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -231,10 +231,10 @@ func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier inte } } - if cfg.VersionKey != "" { - versionRaw := ctx.span.Tag(ext.Version) + if cfg.ParentVersionKey != "" { + versionRaw := ctx.span.Tag(ext.ParentVersion) if version, ok := versionRaw.(string); ok { - writer.Set(cfg.VersionKey, version) + writer.Set(cfg.ParentVersionKey, version) } } diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index bad3fee6b4..6f4632272c 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -930,9 +930,9 @@ func WithServiceNameKey(serviceNameKey string) InjectionOption { } } -// WithVersionKey returns the option setting the version key -func WithVersionKey(versionKey string) InjectionOption { +// WithParentVersionKey returns the option setting the parent version key +func WithParentVersionKey(versionKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { - cfg.VersionKey = versionKey + cfg.ParentVersionKey = versionKey } } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 862527ccf5..6cc75bd76d 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -313,9 +313,9 @@ func (p *propagator) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, write } } - if cfg.VersionKey != "" { - if version, ok := ctx.meta(ext.Version); ok { - writer.Set(cfg.VersionKey, version) + if cfg.ParentVersionKey != "" { + if version, ok := ctx.meta(ext.ParentVersion); ok { + writer.Set(cfg.ParentVersionKey, version) } } @@ -469,9 +469,9 @@ func (*propagatorB3) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, write } } - if cfg.VersionKey != "" { + if cfg.ParentVersionKey != "" { if version, ok := ctx.meta(ext.Version); ok { - writer.Set(cfg.VersionKey, version) + writer.Set(cfg.ParentVersionKey, version) } } diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 79f688803c..f3fa9247fb 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -433,6 +433,8 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt if t.config.version != "" { if t.config.universalVersion || (!t.config.universalVersion && span.Service == t.config.serviceName) { span.setMeta(ext.Version, t.config.version) + } else { + span.setMeta(ext.ParentVersion, t.config.version) } } if t.config.env != "" { From a754c9ba6b5646073555c326b0d71b194423e0b4 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 28 Apr 2022 09:51:11 -0700 Subject: [PATCH 021/104] Add explicit guard around sql comment injection --- contrib/database/sql/conn.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 82f59770c6..0389058b22 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -259,12 +259,13 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - injectionOpts := injectionOptionsForMode(tp.cfg.sqlCommentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) - - err = tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) - if err != nil { - // this should never happen - fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) + if tp.cfg.sqlCommentInjectionMode != commentInjectionDisabled { + injectionOpts := injectionOptionsForMode(tp.cfg.sqlCommentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) + err = tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) + if err != nil { + // this should never happen + fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) + } } return span From 069967353267ea7aa93bfbea9fa2fc938a33f4cf Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 28 Apr 2022 09:52:42 -0700 Subject: [PATCH 022/104] Rename sqlCommentInjectionMode --- contrib/database/sql/conn.go | 4 ++-- contrib/database/sql/option.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 0389058b22..92b287f4f4 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -259,8 +259,8 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - if tp.cfg.sqlCommentInjectionMode != commentInjectionDisabled { - injectionOpts := injectionOptionsForMode(tp.cfg.sqlCommentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) + if tp.cfg.commentInjectionMode != commentInjectionDisabled { + injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) err = tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) if err != nil { // this should never happen diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 0d714da8d4..e7b9aff436 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -14,11 +14,11 @@ import ( ) type config struct { - serviceName string - analyticsRate float64 - dsn string - childSpansOnly bool - sqlCommentInjectionMode commentInjectionMode + serviceName string + analyticsRate float64 + dsn string + childSpansOnly bool + commentInjectionMode commentInjectionMode } // commentInjectionMode represents the mode of sql comment injection @@ -101,7 +101,7 @@ func WithChildSpansOnly() Option { // unique for some cache implementations. Use WithStaticTagsCommentInjection if this is a concern. func WithCommentInjection() Option { return func(cfg *config) { - cfg.sqlCommentInjectionMode = fullSQLCommentInjection + cfg.commentInjectionMode = fullSQLCommentInjection } } @@ -110,14 +110,14 @@ func WithCommentInjection() Option { // unique and have side effects on caching. func WithStaticTagsCommentInjection() Option { return func(cfg *config) { - cfg.sqlCommentInjectionMode = staticTagsSQLCommentInjection + cfg.commentInjectionMode = staticTagsSQLCommentInjection } } // WithoutCommentInjection disables injection of sql comments on traced queries. func WithoutCommentInjection() Option { return func(cfg *config) { - cfg.sqlCommentInjectionMode = commentInjectionDisabled + cfg.commentInjectionMode = commentInjectionDisabled } } From ceab980be6cff9c67b57ec0bb074055e738938f3 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 3 May 2022 19:44:29 -0700 Subject: [PATCH 023/104] Add ExperimentalInjector interface --- ddtrace/mocktracer/mocktracer.go | 23 +++- ddtrace/tracer/option.go | 6 + ddtrace/tracer/propagator.go | 13 ++- ddtrace/tracer/textmap.go | 193 ++++++++++--------------------- ddtrace/tracer/tracer.go | 2 +- 5 files changed, 100 insertions(+), 137 deletions(-) diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 8a630c8ee3..38caf31404 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -35,6 +35,9 @@ type Tracer interface { // FinishedSpans returns the set of finished spans. FinishedSpans() []Span + // InjectedComments returns the set of sql comments injected on queries. + InjectedComments() []string + // Reset resets the spans and services recorded in the tracer. This is // especially useful when running tests in a loop, where a clean start // is desired for FinishedSpans calls. @@ -57,9 +60,10 @@ func Start() Tracer { } type mocktracer struct { - sync.RWMutex // guards below spans - finishedSpans []Span - openSpans map[uint64]Span + sync.RWMutex // guards below spans + finishedSpans []Span + injectedComments []string + openSpans map[uint64]Span } func newMockTracer() *mocktracer { @@ -104,12 +108,19 @@ func (t *mocktracer) FinishedSpans() []Span { return t.finishedSpans } +func (t *mocktracer) InjectedComments() []string { + t.RLock() + defer t.RUnlock() + return t.injectedComments +} + func (t *mocktracer) Reset() { t.Lock() defer t.Unlock() for k := range t.openSpans { delete(t.openSpans, k) } + t.injectedComments = nil t.finishedSpans = nil } @@ -245,5 +256,11 @@ func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier inte } } + sqlCommentCarrier, ok := carrier.(tracer.SQLCommentCarrier) + if ok { + // Save injected comments to assert the sql commenting behavior + t.injectedComments = append(t.injectedComments, sqlCommentCarrier.CommentedQuery("")) + } + return nil } diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 6f4632272c..8bd042b602 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -96,6 +96,9 @@ type config struct { // propagator propagates span context cross-process propagator Propagator + // injector injects span context cross-process + injector ExperimentalInjector + // httpClient specifies the HTTP client to be used by the agent's transport. httpClient *http.Client @@ -266,6 +269,9 @@ func newConfig(opts ...StartOption) *config { MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), }) } + if c.injector == nil { + c.injector = NewInjector() + } if c.logger != nil { log.UseLogger(c.logger) } diff --git a/ddtrace/tracer/propagator.go b/ddtrace/tracer/propagator.go index 7c0c6e3a37..f01366d486 100644 --- a/ddtrace/tracer/propagator.go +++ b/ddtrace/tracer/propagator.go @@ -17,13 +17,20 @@ type Propagator interface { // Inject takes the SpanContext and injects it into the carrier. Inject(context ddtrace.SpanContext, carrier interface{}) error - // InjectWithOptions takes the SpanContext and injects it into the carrier according to the given options. - InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error - // Extract returns the SpanContext from the given carrier. Extract(carrier interface{}) (ddtrace.SpanContext, error) } +// ExperimentalInjector implementations should be able to inject +// SpanContexts into an implementation specific carrier. It is closely related to +// Propagator except that it defines an experimental interface allowing more flexibility on +// which keys are injected. +// Note that this interface isn't meant to be public and used for other usages than internal ones. +type ExperimentalInjector interface { + // InjectWithOptions takes the SpanContext and injects it into the carrier according to the given options. + InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error +} + // TextMapWriter allows setting key/value pairs of strings on the underlying // data structure. Carriers implementing TextMapWriter are compatible to be // used with Datadog's TextMapPropagator. diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 6cc75bd76d..928204ba60 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -148,6 +148,69 @@ func NewPropagator(cfg *PropagatorConfig) Propagator { } } +// NewInjector returns a new experimental injector which uses TextMap to inject +// and extract values. It propagates static tags (service name, environment and version) as well +// as dynamic trace attributes (span id, trace id and sampling priority). +func NewInjector() ExperimentalInjector { + return &experimentalInjector{} +} + +type experimentalInjector struct { +} + +func (i *experimentalInjector) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { + switch c := carrier.(type) { + case TextMapWriter: + return i.injectTextMapWithOptions(spanCtx, c, opts...) + default: + return ErrInvalidCarrier + } +} + +func (i *experimentalInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { + ctx, ok := spanCtx.(*spanContext) + if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + return ErrInvalidSpanContext + } + + cfg := ddtrace.InjectionConfig{} + for _, apply := range opts { + apply(&cfg) + } + + if cfg.TraceIDKey != "" { + writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) + } + + if cfg.SpanIDKey != "" { + writer.Set(cfg.SpanIDKey, strconv.FormatUint(ctx.spanID, 10)) + } + + if cfg.SamplingPriorityKey != "" { + if sp, ok := ctx.samplingPriority(); ok { + writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(sp)) + } + } + + if cfg.EnvKey != "" { + if env, ok := ctx.meta(ext.Environment); ok { + writer.Set(cfg.EnvKey, env) + } + } + + if cfg.ParentVersionKey != "" { + if version, ok := ctx.meta(ext.ParentVersion); ok { + writer.Set(cfg.ParentVersionKey, version) + } + } + + if cfg.ServiceNameKey != "" { + writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) + } + + return nil +} + // chainedPropagator implements Propagator and applies a list of injectors and extractors. // When injecting, all injectors are called to propagate the span context. // When extracting, it tries each extractor, selecting the first successful one. @@ -207,19 +270,6 @@ func (p *chainedPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interfac return nil } -// InjectWithOptions defines the Propagator to propagate SpanContext data -// out of the current process. The implementation propagates only the information -// for which keys are specified by the InjectionConfig build via the options. -func (p *chainedPropagator) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { - for _, v := range p.injectors { - err := v.InjectWithOptions(spanCtx, carrier, opts...) - if err != nil { - return err - } - } - return nil -} - // Extract implements Propagator. func (p *chainedPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { for _, v := range p.extractors { @@ -273,59 +323,6 @@ func (p *propagator) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr return nil } -func (p *propagator) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { - switch c := carrier.(type) { - case TextMapWriter: - return p.injectTextMapWithOptions(spanCtx, c, opts...) - default: - return ErrInvalidCarrier - } -} - -func (p *propagator) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { - ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { - return ErrInvalidSpanContext - } - - cfg := ddtrace.InjectionConfig{} - for _, apply := range opts { - apply(&cfg) - } - - if cfg.TraceIDKey != "" { - writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) - } - - if cfg.SpanIDKey != "" { - writer.Set(cfg.SpanIDKey, strconv.FormatUint(ctx.spanID, 10)) - } - - if cfg.SamplingPriorityKey != "" { - if sp, ok := ctx.samplingPriority(); ok { - writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(sp)) - } - } - - if cfg.EnvKey != "" { - if env, ok := ctx.meta(ext.Environment); ok { - writer.Set(cfg.EnvKey, env) - } - } - - if cfg.ParentVersionKey != "" { - if version, ok := ctx.meta(ext.ParentVersion); ok { - writer.Set(cfg.ParentVersionKey, version) - } - } - - if cfg.ServiceNameKey != "" { - writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) - } - - return nil -} - func (p *propagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { switch c := carrier.(type) { case TextMapReader: @@ -420,70 +417,6 @@ func (*propagatorB3) injectTextMap(spanCtx ddtrace.SpanContext, writer TextMapWr return nil } -func (p *propagatorB3) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { - switch c := carrier.(type) { - case TextMapWriter: - return p.injectTextMapWithOptions(spanCtx, c, opts...) - default: - return ErrInvalidCarrier - } -} - -func (*propagatorB3) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { - ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { - return ErrInvalidSpanContext - } - - cfg := ddtrace.InjectionConfig{ - TraceIDKey: b3TraceIDHeader, - SpanIDKey: b3SpanIDHeader, - SamplingPriorityKey: b3SpanIDHeader, - } - - for _, apply := range opts { - apply(&cfg) - } - - if cfg.TraceIDKey != "" { - writer.Set(cfg.TraceIDKey, fmt.Sprintf("%016x", ctx.traceID)) - } - - if cfg.SpanIDKey != "" { - writer.Set(cfg.SpanIDKey, fmt.Sprintf("%016x", ctx.spanID)) - } - - if cfg.SamplingPriorityKey != "" { - if p, ok := ctx.samplingPriority(); ok { - if p >= ext.PriorityAutoKeep { - writer.Set(cfg.SamplingPriorityKey, "1") - } else { - writer.Set(cfg.SamplingPriorityKey, "0") - } - } - } - - if cfg.EnvKey != "" { - if env, ok := ctx.meta(ext.Environment); ok { - writer.Set(cfg.EnvKey, env) - } - } - - if cfg.ParentVersionKey != "" { - if version, ok := ctx.meta(ext.Version); ok { - writer.Set(cfg.ParentVersionKey, version) - } - } - - if cfg.ServiceNameKey != "" { - if serviceName := globalconfig.ServiceName(); serviceName != "" { - writer.Set(cfg.ServiceNameKey, serviceName) - } - } - - return nil -} - func (p *propagatorB3) Extract(carrier interface{}) (ddtrace.SpanContext, error) { switch c := carrier.(type) { case TextMapReader: diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index f3fa9247fb..d79450282d 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -512,7 +512,7 @@ func (t *tracer) Inject(ctx ddtrace.SpanContext, carrier interface{}) error { // InjectWithOptions uses the configured or default TextMap Propagator. func (t *tracer) InjectWithOptions(ctx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { - return t.config.propagator.InjectWithOptions(ctx, carrier, opts...) + return t.config.injector.InjectWithOptions(ctx, carrier, opts...) } // Extract uses the configured or default TextMap Propagator. From ba1ee994a279908696c11af952dc37a2a8787a90 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 3 May 2022 22:05:04 -0700 Subject: [PATCH 024/104] Fix broken tests --- contrib/database/sql/conn.go | 16 +++++++++------- contrib/database/sql/sql.go | 4 ++-- contrib/database/sql/stmt.go | 5 +++-- contrib/database/sql/tx.go | 3 ++- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 92b287f4f4..cf2236082c 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -77,7 +77,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr span.Finish(tracer.WithError(err)) }() } - stmt, err := connPrepareCtx.PrepareContext(ctx, sqlCommentCarrier.CommentedQuery(query)) + stmt, err := connPrepareCtx.PrepareContext(tracer.ContextWithSpan(ctx, span), sqlCommentCarrier.CommentedQuery(query)) if err != nil { return nil, err } @@ -139,14 +139,15 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv func (tc *tracedConn) Ping(ctx context.Context) (err error) { start := time.Now() if pinger, ok := tc.Conn.(driver.Pinger); ok { + span := tc.tryStartTrace(ctx, queryTypePing, "", start, &tracer.SQLCommentCarrier{}, err) + if span != nil { + go func() { + span.Finish(tracer.WithError(err)) + }() + } err = pinger.Ping(ctx) } - span := tc.tryStartTrace(ctx, queryTypePing, "", start, &tracer.SQLCommentCarrier{}, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } + return err } @@ -224,6 +225,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier *tracer.SQLCommentCarrier, err error) (span tracer.Span) { + fmt.Printf("Executing query type %s\n", qtype) if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index f09ff26ec4..4683111f31 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -131,7 +131,7 @@ type tracedConnector struct { cfg *config } -func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { +func (t *tracedConnector) Connect(ctx context.Context) (c driver.Conn, err error) { tp := &traceParams{ driverName: t.driverName, cfg: t.cfg, @@ -142,13 +142,13 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { tp.meta, _ = internal.ParseDSN(t.driverName, t.cfg.dsn) } start := time.Now() - conn, err := t.connector.Connect(ctx) span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } + conn, err := t.connector.Connect(tracer.ContextWithSpan(ctx, span)) if err != nil { return nil, err } diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index ac0964f6a5..bf07ce7e82 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -27,13 +27,14 @@ type tracedStmt struct { // Close sends a span before closing a statement func (s *tracedStmt) Close() (err error) { start := time.Now() - err = s.Stmt.Close() span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } + err = s.Stmt.Close() + return err } @@ -75,13 +76,13 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { + rows, err := stmtQueryContext.QueryContext(ctx, args) span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, &tracer.SQLCommentCarrier{}, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } - rows, err := stmtQueryContext.QueryContext(ctx, args) return rows, err } diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 1b6a1430be..4c43264067 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -39,11 +39,12 @@ func (t *tracedTx) Commit() (err error) { func (t *tracedTx) Rollback() (err error) { start := time.Now() span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, &tracer.SQLCommentCarrier{}, err) - err = t.Tx.Rollback() if span != nil { go func() { span.Finish(tracer.WithError(err)) }() } + err = t.Tx.Rollback() + return err } From 51b2c2ec76bf8abf89f241e9d8512d94e3e66c1c Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 4 May 2022 13:11:03 -0700 Subject: [PATCH 025/104] Add commenting mode tests --- contrib/database/sql/conn.go | 11 ++--- contrib/database/sql/sql.go | 5 +- contrib/database/sql/sql_test.go | 75 +++++++++++++++++++++++++++++ contrib/database/sql/stmt.go | 2 +- contrib/database/sql/tx.go | 4 +- contrib/internal/sqltest/sqltest.go | 54 +++++++++++++++++++-- ddtrace/mocktracer/mockspan.go | 5 ++ ddtrace/mocktracer/mocktracer.go | 31 +++++------- ddtrace/tracer/sqlcomment.go | 7 ++- 9 files changed, 158 insertions(+), 36 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index cf2236082c..6a073f6a41 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -43,7 +43,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, &tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, nil, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -55,7 +55,7 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, &tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, nil, err) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -84,7 +84,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - sqlCommentCarrier := tracer.SQLCommentCarrier{} + sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { go func() { @@ -139,7 +139,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv func (tc *tracedConn) Ping(ctx context.Context) (err error) { start := time.Now() if pinger, ok := tc.Conn.(driver.Pinger); ok { - span := tc.tryStartTrace(ctx, queryTypePing, "", start, &tracer.SQLCommentCarrier{}, err) + span := tc.tryStartTrace(ctx, queryTypePing, "", start, nil, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -225,7 +225,6 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier *tracer.SQLCommentCarrier, err error) (span tracer.Span) { - fmt.Printf("Executing query type %s\n", qtype) if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -261,7 +260,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query } } - if tp.cfg.commentInjectionMode != commentInjectionDisabled { + if sqlCommentCarrier != nil && tp.cfg.commentInjectionMode != commentInjectionDisabled { injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) err = tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) if err != nil { diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 4683111f31..85e6313e0e 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -142,7 +142,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (c driver.Conn, err error tp.meta, _ = internal.ParseDSN(t.driverName, t.cfg.dsn) } start := time.Now() - span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, &tracer.SQLCommentCarrier{}, err) + span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, nil, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -193,6 +193,9 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { if math.IsNaN(cfg.analyticsRate) { cfg.analyticsRate = rc.analyticsRate } + if cfg.commentInjectionMode == 0 { + cfg.commentInjectionMode = rc.commentInjectionMode + } cfg.childSpansOnly = rc.childSpansOnly tc := &tracedConnector{ connector: c, diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 9022305927..1785112526 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -202,6 +202,81 @@ func TestOpenOptions(t *testing.T) { }) } +func TestCommentInjectionModes(t *testing.T) { + testCases := []struct { + name string + options []Option + expectedInjectedTags sqltest.TagInjectionExpectation + }{ + { + name: "default (no injection)", + options: []Option{}, + expectedInjectedTags: sqltest.TagInjectionExpectation{ + StaticTags: false, + DynamicTags: false, + }, + }, + { + name: "explicit no injection", + options: []Option{WithoutCommentInjection()}, + expectedInjectedTags: sqltest.TagInjectionExpectation{ + StaticTags: false, + DynamicTags: false, + }, + }, + { + name: "static tags injection", + options: []Option{WithStaticTagsCommentInjection()}, + expectedInjectedTags: sqltest.TagInjectionExpectation{ + StaticTags: true, + DynamicTags: false, + }, + }, + { + name: "dynamic tags injection", + options: []Option{WithCommentInjection()}, + expectedInjectedTags: sqltest.TagInjectionExpectation{ + StaticTags: true, + DynamicTags: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockTracer := mocktracer.Start() + defer mockTracer.Stop() + + Register("postgres", &pq.Driver{}, append(tc.options, WithServiceName("postgres-test"))...) + defer unregister("postgres") + + db, err := Open("postgres", "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable") + if err != nil { + log.Fatal(err) + } + defer db.Close() + + testConfig := &sqltest.Config{ + DB: db, + DriverName: "postgres", + TableName: tableName, + ExpectName: "postgres.query", + ExpectTags: map[string]interface{}{ + ext.ServiceName: "postgres-test", + ext.SpanType: ext.SpanTypeSQL, + ext.TargetHost: "127.0.0.1", + ext.TargetPort: "5432", + ext.DBUser: "postgres", + ext.DBName: "postgres", + }, + ExpectTagInjection: tc.expectedInjectedTags, + } + + sqltest.RunAll(t, testConfig) + }) + } +} + func TestMySQLUint64(t *testing.T) { Register("mysql", &mysql.MySQLDriver{}) db, err := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test") diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index bf07ce7e82..0c8c6c99b3 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -27,7 +27,7 @@ type tracedStmt struct { // Close sends a span before closing a statement func (s *tracedStmt) Close() (err error) { start := time.Now() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, &tracer.SQLCommentCarrier{}, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, nil, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 4c43264067..4d0497821f 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -25,7 +25,7 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, &tracer.SQLCommentCarrier{}, err) + span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, nil, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) @@ -38,7 +38,7 @@ func (t *tracedTx) Commit() (err error) { // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, &tracer.SQLCommentCarrier{}, err) + span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, nil, err) if span != nil { go func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 07db4e39e5..0d236285c9 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -150,6 +150,8 @@ func testQuery(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } + + assertInjectedComments(t, cfg, false) } } @@ -181,6 +183,8 @@ func testStatement(cfg *Config) func(*testing.T) { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + assertInjectedComments(t, cfg, true) + cfg.mockTracer.Reset() _, err2 := stmt.Exec("New York") assert.Equal(nil, err2) @@ -288,6 +292,8 @@ func testExec(cfg *Config) func(*testing.T) { span = s } } + assertInjectedComments(t, cfg, false) + assert.NotNil(span, "span not found") cfg.ExpectTags["sql.query_type"] = "Commit" for k, v := range cfg.ExpectTags { @@ -296,6 +302,37 @@ func testExec(cfg *Config) func(*testing.T) { } } +func assertInjectedComments(t *testing.T, cfg *Config, discardDynamicTags bool) { + c := cfg.mockTracer.InjectedComments() + carrier := tracer.SQLCommentCarrier{} + for k, v := range expectedInjectedTags(cfg, discardDynamicTags) { + carrier.Set(k, v) + } + + if !cfg.ExpectTagInjection.StaticTags && !cfg.ExpectTagInjection.DynamicTags { + assert.Len(t, c, 0) + } else { + require.Len(t, c, 1) + assert.Equal(t, carrier.CommentedQuery(""), c[0]) + } +} + +func expectedInjectedTags(cfg *Config, discardDynamicTags bool) map[string]string { + expectedInjectedTags := make(map[string]string) + // Prepare statements should never have dynamic tags injected so we only check if static tags are expected + if cfg.ExpectTagInjection.StaticTags { + expectedInjectedTags[tracer.ServiceNameSQLCommentKey] = "test-service" + expectedInjectedTags[tracer.ServiceEnvironmentSQLCommentKey] = "test-env" + expectedInjectedTags[tracer.ServiceVersionSQLCommentKey] = "v-test" + } + if cfg.ExpectTagInjection.DynamicTags && !discardDynamicTags { + expectedInjectedTags[tracer.SamplingPrioritySQLCommentKey] = "0" + expectedInjectedTags[tracer.TraceIDSQLCommentKey] = "test-trace-id" + expectedInjectedTags[tracer.SpanIDSQLCommentKey] = "test-span-id" + } + return expectedInjectedTags +} + func verifyConnectSpan(span mocktracer.Span, assert *assert.Assertions, cfg *Config) { assert.Equal(cfg.ExpectName, span.OperationName()) cfg.ExpectTags["sql.query_type"] = "Connect" @@ -304,12 +341,19 @@ func verifyConnectSpan(span mocktracer.Span, assert *assert.Assertions, cfg *Con } } +// TagInjectionExpectation holds expectations relating to tag injection +type TagInjectionExpectation struct { + StaticTags bool + DynamicTags bool +} + // Config holds the test configuration. type Config struct { *sql.DB - mockTracer mocktracer.Tracer - DriverName string - TableName string - ExpectName string - ExpectTags map[string]interface{} + mockTracer mocktracer.Tracer + DriverName string + TableName string + ExpectName string + ExpectTags map[string]interface{} + ExpectTagInjection TagInjectionExpectation } diff --git a/ddtrace/mocktracer/mockspan.go b/ddtrace/mocktracer/mockspan.go index 39a91b703b..7d95264403 100644 --- a/ddtrace/mocktracer/mockspan.go +++ b/ddtrace/mocktracer/mockspan.go @@ -118,6 +118,9 @@ func (s *mockspan) SetTag(key string, value interface{}) { if s.tags == nil { s.tags = make(map[string]interface{}, 1) } + if key == "sql.query_type" { + fmt.Printf("New span for type %s\n", value) + } if key == ext.SamplingPriority { switch p := value.(type) { case int: @@ -188,6 +191,7 @@ func (s *mockspan) SetBaggageItem(key, val string) { // Finish finishes the current span with the given options. func (s *mockspan) Finish(opts ...ddtrace.FinishOption) { + fmt.Printf("Finishing span with type %v\n", s.Tag("sql.query_type")) var cfg ddtrace.FinishConfig for _, fn := range opts { fn(&cfg) @@ -212,6 +216,7 @@ func (s *mockspan) Finish(opts ...ddtrace.FinishOption) { s.finished = true s.finishTime = t s.tracer.addFinishedSpan(s) + //fmt.Printf("Finished span with type %v\n", s.Tag("sql.query_type")) } // String implements fmt.Stringer. diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 38caf31404..d2555bd55e 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -13,12 +13,11 @@ package mocktracer import ( + "fmt" "strconv" "strings" "sync" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -105,6 +104,9 @@ func (t *mocktracer) OpenSpans() []Span { func (t *mocktracer) FinishedSpans() []Span { t.RLock() defer t.RUnlock() + for _, s := range t.finishedSpans { + fmt.Printf("returning finished span of type %v\n", s.Tag("sql.query_type")) + } return t.finishedSpans } @@ -222,41 +224,30 @@ func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier inte } if cfg.TraceIDKey != "" { - writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) + writer.Set(cfg.TraceIDKey, "test-trace-id") } if cfg.SpanIDKey != "" { - writer.Set(cfg.SpanIDKey, strconv.FormatUint(ctx.spanID, 10)) + writer.Set(cfg.SpanIDKey, "test-span-id") } if cfg.SamplingPriorityKey != "" { - if ctx.hasSamplingPriority() { - writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(ctx.priority)) - } + writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(ctx.priority)) } if cfg.EnvKey != "" { - envRaw := ctx.span.Tag(ext.Environment) - if env, ok := envRaw.(string); ok { - writer.Set(cfg.EnvKey, env) - } + writer.Set(cfg.EnvKey, "test-env") } if cfg.ParentVersionKey != "" { - versionRaw := ctx.span.Tag(ext.ParentVersion) - if version, ok := versionRaw.(string); ok { - writer.Set(cfg.ParentVersionKey, version) - } + writer.Set(cfg.ParentVersionKey, "v-test") } if cfg.ServiceNameKey != "" { - serviceNameRaw := ctx.span.Tag(ext.ServiceName) - if serviceName, ok := serviceNameRaw.(string); ok { - writer.Set(cfg.ServiceNameKey, serviceName) - } + writer.Set(cfg.ServiceNameKey, "test-service") } - sqlCommentCarrier, ok := carrier.(tracer.SQLCommentCarrier) + sqlCommentCarrier, ok := carrier.(*tracer.SQLCommentCarrier) if ok { // Save injected comments to assert the sql commenting behavior t.injectedComments = append(t.injectedComments, sqlCommentCarrier.CommentedQuery("")) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index b2a198fb80..a8f04b9d51 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -15,6 +15,7 @@ type SQLCommentCarrier struct { tags map[string]string } +// Values for sql comment keys const ( SamplingPrioritySQLCommentKey = "ddsp" TraceIDSQLCommentKey = "ddtid" @@ -53,10 +54,14 @@ func commentWithTags(tags map[string]string) (comment string) { func (c *SQLCommentCarrier) CommentedQuery(query string) (commented string) { comment := commentWithTags(c.tags) - if comment == "" || query == "" { + if comment == "" { return query } + if query == "" { + return comment + } + return fmt.Sprintf("%s %s", comment, query) } From 45bd67d17803d7b8e28da7e86d7b0a690cf96db8 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 4 May 2022 19:58:27 -0700 Subject: [PATCH 026/104] Fix sqlcomment tests --- ddtrace/tracer/sqlcomment_test.go | 65 +++++++++++++++++++++++-------- ddtrace/tracer/textmap.go | 4 +- ddtrace/tracer/tracer.go | 5 ++- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index ad83f9c6fe..a9738f02a1 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -13,44 +13,75 @@ func TestQueryTextCarrier(t *testing.T) { testCases := []struct { name string query string - tags map[string]string + options []InjectionOption commented string }{ { - name: "query with tag list", + name: "all tags injected", query: "SELECT * from FOO", - tags: map[string]string{"operation": "checkout"}, - commented: "/*ddsid='10',ddsp='2',ddtid='10',ot-baggage-operation='checkout'*/ SELECT * from FOO", + options: []InjectionOption{WithParentVersionKey("ddsv"), WithEnvironmentKey("dde"), WithServiceNameKey("ddsn"), WithSpanIDKey("ddsid"), WithTraceIDKey("ddtid"), WithSamplingPriorityKey("ddsp")}, + commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", }, { - name: "empty query", + name: "empty query, all tags injected", query: "", - tags: map[string]string{"operation": "elmer's glue"}, - commented: "", + options: []InjectionOption{WithParentVersionKey("ddsv"), WithEnvironmentKey("dde"), WithServiceNameKey("ddsn"), WithSpanIDKey("ddsid"), WithTraceIDKey("ddtis"), WithSamplingPriorityKey("ddsp")}, + commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtis='10'*/", }, { name: "query with existing comment", query: "SELECT * from FOO -- test query", - tags: map[string]string{"operation": "elmer's glue"}, - commented: "/*ddsid='10',ddsp='2',ddtid='10',ot-baggage-operation='elmer%27s%20glue'*/ SELECT * from FOO -- test query", + options: []InjectionOption{WithParentVersionKey("ddsv"), WithEnvironmentKey("dde"), WithServiceNameKey("ddsn"), WithSpanIDKey("ddsid"), WithTraceIDKey("ddtis"), WithSamplingPriorityKey("ddsp")}, + commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtis='10'*/ SELECT * from FOO -- test query", + }, + { + name: "only parent version tag", + query: "SELECT * from FOO", + options: []InjectionOption{WithParentVersionKey("ddsv")}, + commented: "/*ddsv='1.0.0'*/ SELECT * from FOO", + }, + { + name: "only env tag", + query: "SELECT * from FOO", + options: []InjectionOption{WithEnvironmentKey("dde")}, + commented: "/*dde='test-env'*/ SELECT * from FOO", + }, + { + name: "only service name tag", + query: "SELECT * from FOO", + options: []InjectionOption{WithServiceNameKey("ddsn")}, + commented: "/*ddsn='whiskey-service'*/ SELECT * from FOO", + }, + { + name: "only trace id tag", + query: "SELECT * from FOO", + options: []InjectionOption{WithTraceIDKey("ddtid")}, + commented: "/*ddtid='10'*/ SELECT * from FOO", + }, + { + name: "only span id tag", + query: "SELECT * from FOO", + options: []InjectionOption{WithSpanIDKey("ddsid")}, + commented: "/*ddsid='10'*/ SELECT * from FOO", + }, + { + name: "only sampling priority tag", + query: "SELECT * from FOO", + options: []InjectionOption{WithSamplingPriorityKey("ddsp")}, + commented: "/*ddsp='2'*/ SELECT * from FOO", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - propagator := NewPropagator(&PropagatorConfig{}) - tracer := newTracer(WithPropagator(propagator)) - - root := tracer.StartSpan("web.request", WithSpanID(10)).(*span) - for k, v := range tc.tags { - root.SetBaggageItem(k, v) - } + tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0")) + root := tracer.StartSpan("db.call", WithSpanID(10), ServiceName("whiskey-db")).(*span) root.SetTag(ext.SamplingPriority, 2) ctx := root.Context() carrier := SQLCommentCarrier{} - err := tracer.Inject(ctx, &carrier) + err := tracer.InjectWithOptions(ctx, &carrier, tc.options...) require.NoError(t, err) assert.Equal(t, tc.commented, carrier.CommentedQuery(tc.query)) diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 928204ba60..5649eb5098 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -205,7 +205,9 @@ func (i *experimentalInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanCont } if cfg.ServiceNameKey != "" { - writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) + if globalconfig.ServiceName() != "" { + writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) + } } return nil diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index d79450282d..041322ad61 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -433,7 +433,10 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt if t.config.version != "" { if t.config.universalVersion || (!t.config.universalVersion && span.Service == t.config.serviceName) { span.setMeta(ext.Version, t.config.version) - } else { + } + // If the span has a different service than the global service, attribute and set the config version as the + // parent version + if span.Service != t.config.serviceName { span.setMeta(ext.ParentVersion, t.config.version) } } From 505b8659aa5bbfca4b2b2c37b24da77f9be4e32d Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 4 May 2022 20:59:31 -0700 Subject: [PATCH 027/104] Keep tryStartTrace only on methods that can do injection --- contrib/database/sql/conn.go | 64 +++++++++++++++++++++++--------- contrib/database/sql/sql.go | 11 +----- contrib/database/sql/stmt.go | 41 +++----------------- contrib/database/sql/tx.go | 17 +-------- ddtrace/mocktracer/mocktracer.go | 1 + 5 files changed, 57 insertions(+), 77 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 6a073f6a41..912887e3c7 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -43,23 +43,16 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, nil, err) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() - } + tc.tryTrace(ctx, queryTypeBegin, "", start, err) if err != nil { return nil, err } return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - span := tc.tryStartTrace(ctx, queryTypeBegin, "", start, nil, err) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + tc.tryTrace(ctx, queryTypeBegin, "", start, err) + if err != nil { + return nil, err } if err != nil { return nil, err @@ -139,13 +132,11 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv func (tc *tracedConn) Ping(ctx context.Context) (err error) { start := time.Now() if pinger, ok := tc.Conn.(driver.Pinger); ok { - span := tc.tryStartTrace(ctx, queryTypePing, "", start, nil, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } err = pinger.Ping(ctx) + tc.tryTrace(ctx, queryTypePing, "", start, err) + if err != nil { + return err + } } return err @@ -271,3 +262,42 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query return span } + +// tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. +func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error) { + if err == driver.ErrSkip { + // Not a user error: driver is telling sql package that an + // optional interface method is not implemented. There is + // nothing to trace here. + // See: https://github.com/DataDog/dd-trace-go/issues/270 + return + } + if _, exists := tracer.SpanFromContext(ctx); tp.cfg.childSpansOnly && !exists { + return + } + name := fmt.Sprintf("%s.query", tp.driverName) + opts := []ddtrace.StartSpanOption{ + tracer.ServiceName(tp.cfg.serviceName), + tracer.SpanType(ext.SpanTypeSQL), + tracer.StartTime(startTime), + } + if !math.IsNaN(tp.cfg.analyticsRate) { + opts = append(opts, tracer.Tag(ext.EventSampleRate, tp.cfg.analyticsRate)) + } + span, _ := tracer.StartSpanFromContext(ctx, name, opts...) + resource := string(qtype) + if query != "" { + resource = query + } + span.SetTag("sql.query_type", string(qtype)) + span.SetTag(ext.ResourceName, resource) + for k, v := range tp.meta { + span.SetTag(k, v) + } + if meta, ok := ctx.Value(spanTagsKey).(map[string]string); ok { + for k, v := range meta { + span.SetTag(k, v) + } + } + span.Finish(tracer.WithError(err)) +} diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 85e6313e0e..be2f899552 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -25,8 +25,6 @@ import ( "reflect" "time" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -142,13 +140,8 @@ func (t *tracedConnector) Connect(ctx context.Context) (c driver.Conn, err error tp.meta, _ = internal.ParseDSN(t.driverName, t.cfg.dsn) } start := time.Now() - span := tp.tryStartTrace(ctx, queryTypeConnect, "", start, nil, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } - conn, err := t.connector.Connect(tracer.ContextWithSpan(ctx, span)) + conn, err := t.connector.Connect(ctx) + tp.tryTrace(ctx, queryTypeConnect, "", start, err) if err != nil { return nil, err } diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 0c8c6c99b3..9684022b8a 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -10,8 +10,6 @@ import ( "database/sql/driver" "errors" "time" - - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) var _ driver.Stmt = (*tracedStmt)(nil) @@ -27,13 +25,8 @@ type tracedStmt struct { // Close sends a span before closing a statement func (s *tracedStmt) Close() (err error) { start := time.Now() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, nil, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } err = s.Stmt.Close() + s.tryTrace(s.ctx, queryTypeClose, "", start, err) return err } @@ -42,14 +35,8 @@ func (s *tracedStmt) Close() (err error) { func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res driver.Result, err error) { start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, &tracer.SQLCommentCarrier{}, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } res, err := stmtExecContext.ExecContext(ctx, args) - + s.tryTrace(ctx, queryTypeExec, s.query, start, err) return res, err } dargs, err := namedValueToValue(args) @@ -61,14 +48,8 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeExec, s.query, start, &tracer.SQLCommentCarrier{}, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } res, err = s.Exec(dargs) - + s.tryTrace(ctx, queryTypeExec, s.query, start, err) return res, err } @@ -77,13 +58,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { rows, err := stmtQueryContext.QueryContext(ctx, args) - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, &tracer.SQLCommentCarrier{}, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } - + s.tryTrace(ctx, queryTypeQuery, s.query, start, err) return rows, err } dargs, err := namedValueToValue(args) @@ -95,14 +70,8 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return nil, ctx.Err() default: } - span := s.tryStartTrace(ctx, queryTypeQuery, s.query, start, &tracer.SQLCommentCarrier{}, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } rows, err = s.Query(dargs) - + s.tryTrace(ctx, queryTypeQuery, s.query, start, err) return rows, err } diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 4d0497821f..66d4a040f9 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -9,8 +9,6 @@ import ( "context" "database/sql/driver" "time" - - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) var _ driver.Tx = (*tracedTx)(nil) @@ -25,26 +23,15 @@ type tracedTx struct { // Commit sends a span at the end of the transaction func (t *tracedTx) Commit() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeCommit, "", start, nil, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } err = t.Tx.Commit() + t.tryTrace(t.ctx, queryTypeCommit, "", start, err) return err } // Rollback sends a span if the connection is aborted func (t *tracedTx) Rollback() (err error) { start := time.Now() - span := t.tryStartTrace(t.ctx, queryTypeRollback, "", start, nil, err) - if span != nil { - go func() { - span.Finish(tracer.WithError(err)) - }() - } err = t.Tx.Rollback() - + t.tryTrace(t.ctx, queryTypeRollback, "", start, err) return err } diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index d2555bd55e..1d975ac99f 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -133,6 +133,7 @@ func (t *mocktracer) addFinishedSpan(s Span) { if t.finishedSpans == nil { t.finishedSpans = make([]Span, 0, 1) } + fmt.Printf("adding finished span %#v\n", s) t.finishedSpans = append(t.finishedSpans, s) } From 089fa015f4ebc5423518aa828d8a4594a87abd5f Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 4 May 2022 22:02:45 -0700 Subject: [PATCH 028/104] Force close rows for more deterministic test --- contrib/database/sql/conn.go | 2 +- contrib/internal/sqltest/sqltest.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 912887e3c7..7dae0c20c7 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -221,7 +221,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query // optional interface method is not implemented. There is // nothing to trace here. // See: https://github.com/DataDog/dd-trace-go/issues/270 - return + return nil } if _, exists := tracer.SpanFromContext(ctx); tp.cfg.childSpansOnly && !exists { return nil diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 0d236285c9..0b4dde0983 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -122,15 +122,15 @@ func testQuery(cfg *Config) func(*testing.T) { cfg.mockTracer.Reset() assert := assert.New(t) rows, err := cfg.DB.Query(query) - defer rows.Close() + rows.Close() assert.Nil(err) spans := cfg.mockTracer.FinishedSpans() var querySpan mocktracer.Span if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans - //connect, prepare, and query - assert.Len(spans, 3) + //connect, prepare, query and close + assert.Len(spans, 4) span := spans[1] cfg.ExpectTags["sql.query_type"] = "Prepare" assert.Equal(cfg.ExpectName, span.OperationName()) From 29b37032cf9e3ce69d03b8ee5254a9c8b513328d Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 5 May 2022 11:48:53 -0700 Subject: [PATCH 029/104] Fix typo in defer calls --- contrib/database/sql/conn.go | 8 ++++---- contrib/database/sql/stmt.go | 9 +++++++-- ddtrace/mocktracer/mockspan.go | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 7dae0c20c7..d09469d5ab 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -66,7 +66,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { - go func() { + defer func() { span.Finish(tracer.WithError(err)) }() } @@ -80,7 +80,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) if span != nil { - go func() { + defer func() { span.Finish(tracer.WithError(err)) }() } @@ -148,7 +148,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri sqlCommentCarrier := tracer.SQLCommentCarrier{} span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier, err) if span != nil { - go func() { + defer func() { span.Finish(tracer.WithError(err)) }() } @@ -169,7 +169,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri sqlCommentCarrier := tracer.SQLCommentCarrier{} span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier, err) if span != nil { - go func() { + defer func() { span.Finish(tracer.WithError(err)) }() } diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 9684022b8a..4bfa9b35f1 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -9,6 +9,7 @@ import ( "context" "database/sql/driver" "errors" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" ) @@ -25,9 +26,13 @@ type tracedStmt struct { // Close sends a span before closing a statement func (s *tracedStmt) Close() (err error) { start := time.Now() + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, nil, err) + if span != nil { + defer func() { + span.Finish(tracer.WithError(err)) + }() + } err = s.Stmt.Close() - s.tryTrace(s.ctx, queryTypeClose, "", start, err) - return err } diff --git a/ddtrace/mocktracer/mockspan.go b/ddtrace/mocktracer/mockspan.go index 7d95264403..97ac7db4e3 100644 --- a/ddtrace/mocktracer/mockspan.go +++ b/ddtrace/mocktracer/mockspan.go @@ -216,7 +216,6 @@ func (s *mockspan) Finish(opts ...ddtrace.FinishOption) { s.finished = true s.finishTime = t s.tracer.addFinishedSpan(s) - //fmt.Printf("Finished span with type %v\n", s.Tag("sql.query_type")) } // String implements fmt.Stringer. From 1d48c37b4bc4c83300a393a5a72d4ee0c4867475 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 5 May 2022 12:56:06 -0700 Subject: [PATCH 030/104] Fix linting error --- contrib/database/sql/stmt.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 4bfa9b35f1..4fa04ca6c1 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -9,8 +9,9 @@ import ( "context" "database/sql/driver" "errors" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "time" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) var _ driver.Stmt = (*tracedStmt)(nil) From d678962507ab356d4898388dd207cffc64155f96 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 5 May 2022 15:09:05 -0700 Subject: [PATCH 031/104] Cleanup of self-review --- contrib/database/sql/conn.go | 26 ++++++++------------------ contrib/database/sql/option.go | 1 - contrib/database/sql/sql.go | 2 +- contrib/database/sql/stmt.go | 2 +- contrib/internal/sqltest/sqltest.go | 6 +++--- ddtrace/mocktracer/mockspan.go | 4 ---- ddtrace/mocktracer/mocktracer.go | 5 ----- ddtrace/tracer/textmap.go | 3 +-- 8 files changed, 14 insertions(+), 35 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index d09469d5ab..0ee7ef44d0 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -54,9 +54,6 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr if err != nil { return nil, err } - if err != nil { - return nil, err - } return &tracedTx{tx, tc.traceParams, ctx}, nil } @@ -64,7 +61,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -78,7 +75,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -96,7 +93,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -116,7 +113,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv default: } sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -146,7 +143,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -167,7 +164,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri default: } sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier, err) + span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier) if span != nil { defer func() { span.Finish(tracer.WithError(err)) @@ -215,14 +212,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier *tracer.SQLCommentCarrier, err error) (span tracer.Span) { - if err == driver.ErrSkip { - // Not a user error: driver is telling sql package that an - // optional interface method is not implemented. There is - // nothing to trace here. - // See: https://github.com/DataDog/dd-trace-go/issues/270 - return nil - } +func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier *tracer.SQLCommentCarrier) (span tracer.Span) { if _, exists := tracer.SpanFromContext(ctx); tp.cfg.childSpansOnly && !exists { return nil } @@ -253,7 +243,7 @@ func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query if sqlCommentCarrier != nil && tp.cfg.commentInjectionMode != commentInjectionDisabled { injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) - err = tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) + err := tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) if err != nil { // this should never happen fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index e7b9aff436..679b26fc7f 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -9,7 +9,6 @@ import ( "math" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/internal" ) diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index be2f899552..3349735962 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -129,7 +129,7 @@ type tracedConnector struct { cfg *config } -func (t *tracedConnector) Connect(ctx context.Context) (c driver.Conn, err error) { +func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { tp := &traceParams{ driverName: t.driverName, cfg: t.cfg, diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 4fa04ca6c1..7aa73b7f49 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -27,7 +27,7 @@ type tracedStmt struct { // Close sends a span before closing a statement func (s *tracedStmt) Close() (err error) { start := time.Now() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, nil, err) + span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, nil) if span != nil { defer func() { span.Finish(tracer.WithError(err)) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 0b4dde0983..bd27d5d401 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -122,15 +122,15 @@ func testQuery(cfg *Config) func(*testing.T) { cfg.mockTracer.Reset() assert := assert.New(t) rows, err := cfg.DB.Query(query) - rows.Close() + defer rows.Close() assert.Nil(err) spans := cfg.mockTracer.FinishedSpans() var querySpan mocktracer.Span if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans - //connect, prepare, query and close - assert.Len(spans, 4) + //connect, prepare and query + assert.Len(spans, 3) span := spans[1] cfg.ExpectTags["sql.query_type"] = "Prepare" assert.Equal(cfg.ExpectName, span.OperationName()) diff --git a/ddtrace/mocktracer/mockspan.go b/ddtrace/mocktracer/mockspan.go index 97ac7db4e3..39a91b703b 100644 --- a/ddtrace/mocktracer/mockspan.go +++ b/ddtrace/mocktracer/mockspan.go @@ -118,9 +118,6 @@ func (s *mockspan) SetTag(key string, value interface{}) { if s.tags == nil { s.tags = make(map[string]interface{}, 1) } - if key == "sql.query_type" { - fmt.Printf("New span for type %s\n", value) - } if key == ext.SamplingPriority { switch p := value.(type) { case int: @@ -191,7 +188,6 @@ func (s *mockspan) SetBaggageItem(key, val string) { // Finish finishes the current span with the given options. func (s *mockspan) Finish(opts ...ddtrace.FinishOption) { - fmt.Printf("Finishing span with type %v\n", s.Tag("sql.query_type")) var cfg ddtrace.FinishConfig for _, fn := range opts { fn(&cfg) diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 1d975ac99f..dd07326215 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -13,7 +13,6 @@ package mocktracer import ( - "fmt" "strconv" "strings" "sync" @@ -104,9 +103,6 @@ func (t *mocktracer) OpenSpans() []Span { func (t *mocktracer) FinishedSpans() []Span { t.RLock() defer t.RUnlock() - for _, s := range t.finishedSpans { - fmt.Printf("returning finished span of type %v\n", s.Tag("sql.query_type")) - } return t.finishedSpans } @@ -133,7 +129,6 @@ func (t *mocktracer) addFinishedSpan(s Span) { if t.finishedSpans == nil { t.finishedSpans = make([]Span, 0, 1) } - fmt.Printf("adding finished span %#v\n", s) t.finishedSpans = append(t.finishedSpans, s) } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 5649eb5098..4e21f7facd 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -13,10 +13,9 @@ import ( "strconv" "strings" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) From ae32223a6480150e11f0fec375aa71eee5db7c9e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 14:24:33 -0700 Subject: [PATCH 032/104] Use tryTrace instead of tryStartTrace on Close --- contrib/database/sql/stmt.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index 7aa73b7f49..a1f419d1ac 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -10,8 +10,6 @@ import ( "database/sql/driver" "errors" "time" - - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) var _ driver.Stmt = (*tracedStmt)(nil) @@ -27,13 +25,8 @@ type tracedStmt struct { // Close sends a span before closing a statement func (s *tracedStmt) Close() (err error) { start := time.Now() - span := s.tryStartTrace(s.ctx, queryTypeClose, "", start, nil) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() - } err = s.Stmt.Close() + s.tryTrace(s.ctx, queryTypeClose, "", start, err) return err } From 8d86d3f924f26bb3539fbd7b93871d84317f8166 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 14:41:19 -0700 Subject: [PATCH 033/104] Implement ForeachKey on SQLCommentCarrier --- ddtrace/tracer/sqlcomment.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index a8f04b9d51..8ecb5aacd2 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -67,7 +67,11 @@ func (c *SQLCommentCarrier) CommentedQuery(query string) (commented string) { // ForeachKey implements TextMapReader. func (c SQLCommentCarrier) ForeachKey(handler func(key, val string) error) error { - // TODO: implement this for completeness. We don't really have a use-case for this at the moment. + for k, v := range c.tags { + if err := handler(k, v); err != nil { + return err + } + } return nil } From 9a34a179108ba5b0a72d433d41a5e0c09b106282 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 14:43:29 -0700 Subject: [PATCH 034/104] Use iota for comment injection modes --- contrib/database/sql/option.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 679b26fc7f..2ac7cafbd6 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -24,9 +24,9 @@ type config struct { type commentInjectionMode int const ( - commentInjectionDisabled commentInjectionMode = 0 // Default value, sql comment injection disabled - fullSQLCommentInjection commentInjectionMode = 1 // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. - staticTagsSQLCommentInjection commentInjectionMode = 2 // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. + commentInjectionDisabled commentInjectionMode = iota // Default value, sql comment injection disabled + fullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. + staticTagsSQLCommentInjection // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. ) // Option represents an option that can be passed to Register, Open or OpenDB. From 3d3160e9904c793fb3e2c4b823d692e6a0a03d4e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 14:44:20 -0700 Subject: [PATCH 035/104] Add missing dots --- contrib/database/sql/option.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 2ac7cafbd6..f75e8ff366 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -20,11 +20,11 @@ type config struct { commentInjectionMode commentInjectionMode } -// commentInjectionMode represents the mode of sql comment injection +// commentInjectionMode represents the mode of sql comment injection. type commentInjectionMode int const ( - commentInjectionDisabled commentInjectionMode = iota // Default value, sql comment injection disabled + commentInjectionDisabled commentInjectionMode = iota // Default value, sql comment injection disabled. fullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. staticTagsSQLCommentInjection // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. ) From 3ad8cb9f07e70398dbf6780aac45eb098e65e77e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 14:59:58 -0700 Subject: [PATCH 036/104] Rename ExperimentalInjector to WithOptionsInjector --- ddtrace/tracer/option.go | 2 +- ddtrace/tracer/propagator.go | 11 +++++------ ddtrace/tracer/textmap.go | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 8bd042b602..02fd3e53ff 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -97,7 +97,7 @@ type config struct { propagator Propagator // injector injects span context cross-process - injector ExperimentalInjector + injector WithOptionsInjector // httpClient specifies the HTTP client to be used by the agent's transport. httpClient *http.Client diff --git a/ddtrace/tracer/propagator.go b/ddtrace/tracer/propagator.go index f01366d486..a9b7b14ff4 100644 --- a/ddtrace/tracer/propagator.go +++ b/ddtrace/tracer/propagator.go @@ -21,12 +21,11 @@ type Propagator interface { Extract(carrier interface{}) (ddtrace.SpanContext, error) } -// ExperimentalInjector implementations should be able to inject -// SpanContexts into an implementation specific carrier. It is closely related to -// Propagator except that it defines an experimental interface allowing more flexibility on -// which keys are injected. -// Note that this interface isn't meant to be public and used for other usages than internal ones. -type ExperimentalInjector interface { +// WithOptionsInjector implementations should be able to inject +// SpanContexts into an implementation specific carrier. It is related to +// Propagator but provides a write-only flexible interface accepting options and omits +// extraction. +type WithOptionsInjector interface { // InjectWithOptions takes the SpanContext and injects it into the carrier according to the given options. InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 4e21f7facd..8b49aa1491 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -150,7 +150,7 @@ func NewPropagator(cfg *PropagatorConfig) Propagator { // NewInjector returns a new experimental injector which uses TextMap to inject // and extract values. It propagates static tags (service name, environment and version) as well // as dynamic trace attributes (span id, trace id and sampling priority). -func NewInjector() ExperimentalInjector { +func NewInjector() WithOptionsInjector { return &experimentalInjector{} } From e0a6d43ae1e28a0e5c268e029e42ba21fb3b7bfc Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 16:04:00 -0700 Subject: [PATCH 037/104] Revert back to using tryTrace only with sql commenting done separately --- contrib/database/sql/conn.go | 138 ++++++++++++++--------------------- ddtrace/ddtrace.go | 14 ++-- ddtrace/tracer/option.go | 7 ++ ddtrace/tracer/sqlcomment.go | 5 +- ddtrace/tracer/textmap.go | 22 +++--- 5 files changed, 84 insertions(+), 102 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 0ee7ef44d0..0e0e0fa8ef 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -10,15 +10,17 @@ import ( "database/sql/driver" "fmt" "math" - "os" + "math/rand" "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) var _ driver.Conn = (*tracedConn)(nil) +var random *rand.Rand type queryType string @@ -60,28 +62,26 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + stmt, err := connPrepareCtx.PrepareContext(ctx, commentedQuery) + if spanID != nil { + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(*spanID)) + } else { + tc.tryTrace(ctx, queryTypePrepare, query, start, err) } - stmt, err := connPrepareCtx.PrepareContext(tracer.ContextWithSpan(ctx, span), sqlCommentCarrier.CommentedQuery(query)) if err != nil { return nil, err } return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - sqlCommentCarrier := tracer.SQLCommentCarrier{DiscardDynamicTags: true} - span := tc.tryStartTrace(ctx, queryTypePrepare, query, start, &sqlCommentCarrier) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + stmt, err = tc.Prepare(commentedQuery) + if spanID != nil { + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(*spanID)) + } else { + tc.tryTrace(ctx, queryTypePrepare, query, start, err) } - stmt, err = tc.Prepare(sqlCommentCarrier.CommentedQuery(query)) if err != nil { return nil, err } @@ -92,14 +92,13 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + r, err := execContext.ExecContext(ctx, commentedQuery, args) + if spanID != nil { + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(*spanID)) + } else { + tc.tryTrace(ctx, queryTypeExec, query, start, err) } - r, err := execContext.ExecContext(ctx, sqlCommentCarrier.CommentedQuery(query), args) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -112,14 +111,13 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeExec, query, start, &sqlCommentCarrier) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + r, err = execer.Exec(commentedQuery, dargs) + if spanID != nil { + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(*spanID)) + } else { + tc.tryTrace(ctx, queryTypeExec, query, start, err) } - r, err = execer.Exec(sqlCommentCarrier.CommentedQuery(query), dargs) return r, err } return nil, driver.ErrSkip @@ -142,15 +140,13 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + rows, err := queryerContext.QueryContext(ctx, commentedQuery, args) + if spanID != nil { + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(*spanID)) + } else { + tc.tryTrace(ctx, queryTypeQuery, query, start, err) } - rows, err := queryerContext.QueryContext(ctx, sqlCommentCarrier.CommentedQuery(query), args) - return rows, err } if queryer, ok := tc.Conn.(driver.Queryer); ok { @@ -163,14 +159,13 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - sqlCommentCarrier := tracer.SQLCommentCarrier{} - span := tc.tryStartTrace(ctx, queryTypeQuery, query, start, &sqlCommentCarrier) - if span != nil { - defer func() { - span.Finish(tracer.WithError(err)) - }() + commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + rows, err = queryer.Query(commentedQuery, dargs) + if spanID != nil { + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(*spanID)) + } else { + tc.tryTrace(ctx, queryTypeQuery, query, start, err) } - rows, err = queryer.Query(sqlCommentCarrier.CommentedQuery(query), dargs) return rows, err } return nil, driver.ErrSkip @@ -212,49 +207,26 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryStartTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, sqlCommentCarrier *tracer.SQLCommentCarrier) (span tracer.Span) { - if _, exists := tracer.SpanFromContext(ctx); tp.cfg.childSpansOnly && !exists { - return nil - } - name := fmt.Sprintf("%s.query", tp.driverName) - opts := []ddtrace.StartSpanOption{ - tracer.ServiceName(tp.cfg.serviceName), - tracer.SpanType(ext.SpanTypeSQL), - tracer.StartTime(startTime), - } - if !math.IsNaN(tp.cfg.analyticsRate) { - opts = append(opts, tracer.Tag(ext.EventSampleRate, tp.cfg.analyticsRate)) - } - span, _ = tracer.StartSpanFromContext(ctx, name, opts...) - resource := string(qtype) - if query != "" { - resource = query - } - span.SetTag("sql.query_type", string(qtype)) - span.SetTag(ext.ResourceName, resource) - for k, v := range tp.meta { - span.SetTag(k, v) - } - if meta, ok := ctx.Value(spanTagsKey).(map[string]string); ok { - for k, v := range meta { - span.SetTag(k, v) +func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (commentedQuery string, injectedSpanID *uint64) { + if span, ok := tracer.SpanFromContext(ctx); ok { + if tp.cfg.commentInjectionMode != commentInjectionDisabled { + sqlCommentCarrier := tracer.SQLCommentCarrier{} + spanID := random.Uint64() + injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) + err := tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, append(injectionOpts, tracer.WithInjectedSpanID(spanID))...) + if err != nil { + // this should never happen + log.Warn("contrib/database/sql: failed to inject query comments: %v", err) + } + return sqlCommentCarrier.CommentedQuery(query), &spanID } } - if sqlCommentCarrier != nil && tp.cfg.commentInjectionMode != commentInjectionDisabled { - injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, sqlCommentCarrier.DiscardDynamicTags) - err := tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, injectionOpts...) - if err != nil { - // this should never happen - fmt.Fprintf(os.Stderr, "contrib/database/sql: failed to inject query comments: %v\n", err) - } - } - - return span + return query, nil } // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error) { +func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, startSpanOptions ...ddtrace.StartSpanOption) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -266,11 +238,11 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri return } name := fmt.Sprintf("%s.query", tp.driverName) - opts := []ddtrace.StartSpanOption{ + opts := append(startSpanOptions, tracer.ServiceName(tp.cfg.serviceName), tracer.SpanType(ext.SpanTypeSQL), tracer.StartTime(startTime), - } + ) if !math.IsNaN(tp.cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, tp.cfg.analyticsRate)) } diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index add620b33d..2f2e9e57e6 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -147,21 +147,23 @@ type InjectionOption func(cfg *InjectionConfig) // around by reference to one or more InjectionOption functions which shape it into its // final form. type InjectionConfig struct { - // TraceIDKey defines the key to use to inject the trade id. The trace id is only injected if this value + // TraceIDKey defines the key to use to inject the trade id. The trace id is only injected if this value. // is not empty TraceIDKey string - // SpanIDKey defines the key to use to inject the span id. The span id is only injected if this value + // SpanIDKey defines the key to use to inject the span id. The span id is only injected if this value. // is not empty SpanIDKey string // SamplingPriorityKey defines the key to use to inject the sampling priority. The sampling priority is only - // injected if this value is not empty + // injected if this value is not empty. SamplingPriorityKey string - // ServiceNameKey defines the key to use to inject the service name. The service name is only + // ServiceNameKey defines the key to use to inject the service name. The service name is only. // injected if this value is not empty ServiceNameKey string - // EnvKey defines the key to use to inject the environment. The environment is only injected if this value is not + // EnvKey defines the key to use to inject the environment. The environment is only injected if this value is not. // empty EnvKey string - // ParentVersionKey defines the key to use to inject the version. The version is only injected if this value is not empty + // ParentVersionKey defines the key to use to inject the version. The version is only injected if this value is not empty. ParentVersionKey string + // SpanID defines the span id value to use for injection. If not present, the span id value from the SpanContext is used + SpanID uint64 } diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 02fd3e53ff..45d42ef764 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -908,6 +908,13 @@ func WithSpanIDKey(spanIDKey string) InjectionOption { } } +// WithInjectedSpanID returns the option setting the span id value +func WithInjectedSpanID(spanID uint64) InjectionOption { + return func(cfg *ddtrace.InjectionConfig) { + cfg.SpanID = spanID + } +} + // WithTraceIDKey returns the option setting the trace id key func WithTraceIDKey(traceIDKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 8ecb5aacd2..8ae9e9c74a 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -9,10 +9,7 @@ import ( // SQLCommentCarrier holds tags to be serialized as a SQL Comment type SQLCommentCarrier struct { - // Indicates if this SQL comment carrier should only discard dynamic tags - // (like trace id, span id and sampling priority) - DiscardDynamicTags bool - tags map[string]string + tags map[string]string } // Values for sql comment keys diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 8b49aa1491..ac999f05eb 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -151,13 +151,13 @@ func NewPropagator(cfg *PropagatorConfig) Propagator { // and extract values. It propagates static tags (service name, environment and version) as well // as dynamic trace attributes (span id, trace id and sampling priority). func NewInjector() WithOptionsInjector { - return &experimentalInjector{} + return &withOptionsInjector{} } -type experimentalInjector struct { +type withOptionsInjector struct { } -func (i *experimentalInjector) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { +func (i *withOptionsInjector) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { switch c := carrier.(type) { case TextMapWriter: return i.injectTextMapWithOptions(spanCtx, c, opts...) @@ -166,17 +166,21 @@ func (i *experimentalInjector) InjectWithOptions(spanCtx ddtrace.SpanContext, ca } } -func (i *experimentalInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { - ctx, ok := spanCtx.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { - return ErrInvalidSpanContext - } - +func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { cfg := ddtrace.InjectionConfig{} for _, apply := range opts { apply(&cfg) } + ctx, ok := spanCtx.(*spanContext) + spanID := cfg.SpanID + if spanID == 0 { + spanID = ctx.spanID + } + if !ok || ctx.traceID == 0 || spanID == 0 { + return ErrInvalidSpanContext + } + if cfg.TraceIDKey != "" { writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) } From 40f417f2367b74cb3186b4b0869a912ee82be0a9 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 16:14:18 -0700 Subject: [PATCH 038/104] Update WithOptionsInjector godoc --- ddtrace/tracer/propagator.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ddtrace/tracer/propagator.go b/ddtrace/tracer/propagator.go index a9b7b14ff4..213e2cd75a 100644 --- a/ddtrace/tracer/propagator.go +++ b/ddtrace/tracer/propagator.go @@ -21,10 +21,9 @@ type Propagator interface { Extract(carrier interface{}) (ddtrace.SpanContext, error) } -// WithOptionsInjector implementations should be able to inject -// SpanContexts into an implementation specific carrier. It is related to -// Propagator but provides a write-only flexible interface accepting options and omits -// extraction. +// WithOptionsInjector implementations should be able to inject SpanContexts into an implementation +// specific carrier. It is related to Propagator but provides a write-only flexible interface accepting +// options and does not include extraction. type WithOptionsInjector interface { // InjectWithOptions takes the SpanContext and injects it into the carrier according to the given options. InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error From 3ddd663bc06ca36bd8fb1bae54f342e43cacc8a0 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 10 May 2022 16:50:31 -0700 Subject: [PATCH 039/104] Update godoc and add leftover TODO task --- contrib/database/sql/conn.go | 10 ++++--- contrib/database/sql/option.go | 2 +- contrib/database/sql/rand.go | 55 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 contrib/database/sql/rand.go diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 0e0e0fa8ef..0c6dc3c54d 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -10,7 +10,6 @@ import ( "database/sql/driver" "fmt" "math" - "math/rand" "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" @@ -20,7 +19,6 @@ import ( ) var _ driver.Conn = (*tracedConn)(nil) -var random *rand.Rand type queryType string @@ -206,14 +204,18 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { return context.WithValue(ctx, spanTagsKey, tags) } -// tryStartTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. +// withSQLCommentsInjected will return the query with sql comments injected according to the comment injection mode along +// with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating +// the span following the traced database call. func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (commentedQuery string, injectedSpanID *uint64) { + // TODO: figure out why tests are failing because the span from context is nil or if there's a different way to + // get access to the trace ID if span, ok := tracer.SpanFromContext(ctx); ok { if tp.cfg.commentInjectionMode != commentInjectionDisabled { sqlCommentCarrier := tracer.SQLCommentCarrier{} spanID := random.Uint64() injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) - err := tracer.InjectWithOptions(span.Context(), sqlCommentCarrier, append(injectionOpts, tracer.WithInjectedSpanID(spanID))...) + err := tracer.InjectWithOptions(span.Context(), &sqlCommentCarrier, append(injectionOpts, tracer.WithInjectedSpanID(spanID))...) if err != nil { // this should never happen log.Warn("contrib/database/sql: failed to inject query comments: %v", err) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index f75e8ff366..64eb117fd5 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -25,8 +25,8 @@ type commentInjectionMode int const ( commentInjectionDisabled commentInjectionMode = iota // Default value, sql comment injection disabled. - fullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. staticTagsSQLCommentInjection // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. + fullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. ) // Option represents an option that can be passed to Register, Open or OpenDB. diff --git a/contrib/database/sql/rand.go b/contrib/database/sql/rand.go new file mode 100644 index 0000000000..64044482ff --- /dev/null +++ b/contrib/database/sql/rand.go @@ -0,0 +1,55 @@ +// 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 ( + cryptorand "crypto/rand" + "math" + "math/big" + "math/rand" + "sync" + "time" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" +) + +var random *rand.Rand + +func init() { + var seed int64 + n, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)) + if err == nil { + seed = n.Int64() + } else { + log.Warn("cannot generate random seed: %v; using current time", err) + seed = time.Now().UnixNano() + } + random = rand.New(&safeSource{ + source: rand.NewSource(seed), + }) +} + +// safeSource holds a thread-safe implementation of rand.Source64. +type safeSource struct { + source rand.Source + sync.Mutex +} + +func (rs *safeSource) Int63() int64 { + rs.Lock() + n := rs.source.Int63() + rs.Unlock() + + return n +} + +func (rs *safeSource) Uint64() uint64 { return uint64(rs.Int63()) } + +func (rs *safeSource) Seed(seed int64) { + rs.Lock() + rs.source.Seed(seed) + rs.Unlock() +} From 1fa47fe939764df1f94c797013a5fde92a9d3b0e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 11 May 2022 11:17:44 -0700 Subject: [PATCH 040/104] Update some godoc with dots --- ddtrace/ddtrace.go | 1 - ddtrace/tracer/option.go | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index 2f2e9e57e6..a892f8727c 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -34,7 +34,6 @@ type Tracer interface { Inject(context SpanContext, carrier interface{}) error // InjectWithOptions injects a span context into the given carrier with options. - // This method is experimental and subject to removal or modification InjectWithOptions(context SpanContext, carrier interface{}, opts ...InjectionOption) error // Stop stops the tracer. Calls to Stop should be idempotent. diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 45d42ef764..06e716a1ae 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -889,7 +889,7 @@ func WithUserRole(role string) UserMonitoringOption { } } -// WithUserScope returns the option setting the scope (authorizations) of the authenticated user +// WithUserScope returns the option setting the scope (authorizations) of the authenticated user. func WithUserScope(scope string) UserMonitoringOption { return func(s Span) { s.SetTag("usr.scope", scope) @@ -901,49 +901,49 @@ func WithUserScope(scope string) UserMonitoringOption { // more correct to refer to it as the type as the origin, ddtrace.InjectionOption. type InjectionOption = ddtrace.InjectionOption -// WithSpanIDKey returns the option setting the span id key +// WithSpanIDKey returns the option setting the span id key. func WithSpanIDKey(spanIDKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.SpanIDKey = spanIDKey } } -// WithInjectedSpanID returns the option setting the span id value +// WithInjectedSpanID returns the option setting the span id value. func WithInjectedSpanID(spanID uint64) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.SpanID = spanID } } -// WithTraceIDKey returns the option setting the trace id key +// WithTraceIDKey returns the option setting the trace id key. func WithTraceIDKey(traceIDKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.TraceIDKey = traceIDKey } } -// WithSamplingPriorityKey returns the option setting the sampling priority key +// WithSamplingPriorityKey returns the option setting the sampling priority key. func WithSamplingPriorityKey(samplingPriorityKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.SamplingPriorityKey = samplingPriorityKey } } -// WithEnvironmentKey returns the option setting the environment key +// WithEnvironmentKey returns the option setting the environment key. func WithEnvironmentKey(envKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.EnvKey = envKey } } -// WithServiceNameKey returns the option setting the service name key +// WithServiceNameKey returns the option setting the service name key. func WithServiceNameKey(serviceNameKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.ServiceNameKey = serviceNameKey } } -// WithParentVersionKey returns the option setting the parent version key +// WithParentVersionKey returns the option setting the parent version key. func WithParentVersionKey(versionKey string) InjectionOption { return func(cfg *ddtrace.InjectionConfig) { cfg.ParentVersionKey = versionKey From 4a5cd0e3585bb1fbb772c239671d988d8fe0f9ac Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 11 May 2022 11:38:21 -0700 Subject: [PATCH 041/104] Fix tests? --- contrib/database/sql/conn.go | 25 +++++++++++++------------ ddtrace/mocktracer/mocktracer.go | 11 ++++++----- ddtrace/tracer/textmap.go | 22 +++++++++++++++------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 0c6dc3c54d..204d71e51b 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -208,20 +208,21 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating // the span following the traced database call. func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (commentedQuery string, injectedSpanID *uint64) { - // TODO: figure out why tests are failing because the span from context is nil or if there's a different way to - // get access to the trace ID + var spanContext ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { - if tp.cfg.commentInjectionMode != commentInjectionDisabled { - sqlCommentCarrier := tracer.SQLCommentCarrier{} - spanID := random.Uint64() - injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) - err := tracer.InjectWithOptions(span.Context(), &sqlCommentCarrier, append(injectionOpts, tracer.WithInjectedSpanID(spanID))...) - if err != nil { - // this should never happen - log.Warn("contrib/database/sql: failed to inject query comments: %v", err) - } - return sqlCommentCarrier.CommentedQuery(query), &spanID + spanContext = span.Context() + } + + if tp.cfg.commentInjectionMode != commentInjectionDisabled { + sqlCommentCarrier := tracer.SQLCommentCarrier{} + spanID := random.Uint64() + injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) + err := tracer.InjectWithOptions(spanContext, &sqlCommentCarrier, append(injectionOpts, tracer.WithInjectedSpanID(spanID))...) + if err != nil { + // this should never happen + log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } + return sqlCommentCarrier.CommentedQuery(query), &spanID } return query, nil diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index dd07326215..98c919ecf1 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -209,15 +209,16 @@ func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier inte if !ok { return tracer.ErrInvalidCarrier } - ctx, ok := context.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { - return tracer.ErrInvalidSpanContext - } - cfg := ddtrace.InjectionConfig{} for _, apply := range opts { apply(&cfg) } + spanID := cfg.SpanID + + ctx, ok := context.(*spanContext) + if !ok && spanID == 0 { + return tracer.ErrInvalidSpanContext + } if cfg.TraceIDKey != "" { writer.Set(cfg.TraceIDKey, "test-trace-id") diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index ac999f05eb..1b7191c367 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -171,22 +171,30 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte for _, apply := range opts { apply(&cfg) } + spanID := cfg.SpanID ctx, ok := spanCtx.(*spanContext) - spanID := cfg.SpanID - if spanID == 0 { - spanID = ctx.spanID - } - if !ok || ctx.traceID == 0 || spanID == 0 { + if !ok && spanID == 0 { return ErrInvalidSpanContext } + traceID := spanID + if ok { + if ctx.TraceID() > 0 { + traceID = ctx.TraceID() + } + + if spanID == 0 { + spanID = ctx.SpanID() + } + } + if cfg.TraceIDKey != "" { - writer.Set(cfg.TraceIDKey, strconv.FormatUint(ctx.traceID, 10)) + writer.Set(cfg.TraceIDKey, strconv.FormatUint(traceID, 10)) } if cfg.SpanIDKey != "" { - writer.Set(cfg.SpanIDKey, strconv.FormatUint(ctx.spanID, 10)) + writer.Set(cfg.SpanIDKey, strconv.FormatUint(spanID, 10)) } if cfg.SamplingPriorityKey != "" { From 0d7f225ab59eba90834ccd7f3506985414bbc898 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 11 May 2022 11:50:30 -0700 Subject: [PATCH 042/104] Fix broken sampling priority injection when nil span context --- ddtrace/mocktracer/mocktracer.go | 7 ++++++- ddtrace/tracer/textmap.go | 9 ++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 98c919ecf1..7b2af8049c 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -220,6 +220,11 @@ func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier inte return tracer.ErrInvalidSpanContext } + samplingPriority := 0 + if ok { + samplingPriority = ctx.samplingPriority() + } + if cfg.TraceIDKey != "" { writer.Set(cfg.TraceIDKey, "test-trace-id") } @@ -229,7 +234,7 @@ func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier inte } if cfg.SamplingPriorityKey != "" { - writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(ctx.priority)) + writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(samplingPriority)) } if cfg.EnvKey != "" { diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 1b7191c367..f44a7c3e9d 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -179,6 +179,7 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte } traceID := spanID + samplingPriority := 0 if ok { if ctx.TraceID() > 0 { traceID = ctx.TraceID() @@ -187,6 +188,10 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte if spanID == 0 { spanID = ctx.SpanID() } + + if sp, ok := ctx.samplingPriority(); ok { + samplingPriority = sp + } } if cfg.TraceIDKey != "" { @@ -198,9 +203,7 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte } if cfg.SamplingPriorityKey != "" { - if sp, ok := ctx.samplingPriority(); ok { - writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(sp)) - } + writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(samplingPriority)) } if cfg.EnvKey != "" { From eeb5bce8a756b334789b80801e9151e29f2797f6 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 12 May 2022 07:55:21 -0700 Subject: [PATCH 043/104] Fix issue with nil span context when writing parent env and version --- ddtrace/tracer/textmap.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index f44a7c3e9d..29999df5e2 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -180,6 +180,8 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte traceID := spanID samplingPriority := 0 + env := "" + parentVersion := "" if ok { if ctx.TraceID() > 0 { traceID = ctx.TraceID() @@ -192,6 +194,14 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte if sp, ok := ctx.samplingPriority(); ok { samplingPriority = sp } + + if e, ok := ctx.meta(ext.Environment); ok { + env = e + } + + if version, ok := ctx.meta(ext.ParentVersion); ok { + parentVersion = version + } } if cfg.TraceIDKey != "" { @@ -207,15 +217,11 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte } if cfg.EnvKey != "" { - if env, ok := ctx.meta(ext.Environment); ok { - writer.Set(cfg.EnvKey, env) - } + writer.Set(cfg.EnvKey, env) } if cfg.ParentVersionKey != "" { - if version, ok := ctx.meta(ext.ParentVersion); ok { - writer.Set(cfg.ParentVersionKey, version) - } + writer.Set(cfg.ParentVersionKey, parentVersion) } if cfg.ServiceNameKey != "" { From a7f1077b51a55c2531deaa8d9b24a3a642574b41 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 13 May 2022 16:05:59 -0700 Subject: [PATCH 044/104] Formatting/styling updates --- ddtrace/tracer/textmap.go | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 29999df5e2..4d6fe965c1 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -172,7 +172,6 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte apply(&cfg) } spanID := cfg.SpanID - ctx, ok := spanCtx.(*spanContext) if !ok && spanID == 0 { return ErrInvalidSpanContext @@ -180,56 +179,45 @@ func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanConte traceID := spanID samplingPriority := 0 - env := "" - parentVersion := "" + var env, pversion string if ok { if ctx.TraceID() > 0 { traceID = ctx.TraceID() } - if spanID == 0 { spanID = ctx.SpanID() } - if sp, ok := ctx.samplingPriority(); ok { samplingPriority = sp } - if e, ok := ctx.meta(ext.Environment); ok { env = e } - if version, ok := ctx.meta(ext.ParentVersion); ok { - parentVersion = version + pversion = version } } if cfg.TraceIDKey != "" { writer.Set(cfg.TraceIDKey, strconv.FormatUint(traceID, 10)) } - if cfg.SpanIDKey != "" { writer.Set(cfg.SpanIDKey, strconv.FormatUint(spanID, 10)) } - if cfg.SamplingPriorityKey != "" { writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(samplingPriority)) } - if cfg.EnvKey != "" { writer.Set(cfg.EnvKey, env) } - if cfg.ParentVersionKey != "" { - writer.Set(cfg.ParentVersionKey, parentVersion) + writer.Set(cfg.ParentVersionKey, pversion) } - if cfg.ServiceNameKey != "" { if globalconfig.ServiceName() != "" { writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) } } - return nil } From 6d1577af0d811b7b193f544a784ba69bab4fd551 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 17 May 2022 22:09:19 -0700 Subject: [PATCH 045/104] Address some formatting/styling comments --- contrib/database/sql/conn.go | 10 +++----- contrib/database/sql/option.go | 38 +++++++++-------------------- contrib/database/sql/sql_test.go | 23 ++++++----------- contrib/internal/sqltest/sqltest.go | 16 ++++++------ 4 files changed, 30 insertions(+), 57 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 204d71e51b..0c12212475 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -212,19 +212,17 @@ func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() } - - if tp.cfg.commentInjectionMode != commentInjectionDisabled { - sqlCommentCarrier := tracer.SQLCommentCarrier{} + if tp.cfg.commentInjectionMode != CommentInjectionDisabled { + var sqlCommentCarrier tracer.SQLCommentCarrier spanID := random.Uint64() - injectionOpts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) - err := tracer.InjectWithOptions(spanContext, &sqlCommentCarrier, append(injectionOpts, tracer.WithInjectedSpanID(spanID))...) + opts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) + err := tracer.InjectWithOptions(spanContext, &sqlCommentCarrier, append(opts, tracer.WithInjectedSpanID(spanID))...) if err != nil { // this should never happen log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } return sqlCommentCarrier.CommentedQuery(query), &spanID } - return query, nil } diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 64eb117fd5..2c855f37f4 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -17,16 +17,16 @@ type config struct { analyticsRate float64 dsn string childSpansOnly bool - commentInjectionMode commentInjectionMode + commentInjectionMode CommentInjectionMode } -// commentInjectionMode represents the mode of sql comment injection. -type commentInjectionMode int +// CommentInjectionMode represents the mode of sql comment injection. +type CommentInjectionMode int const ( - commentInjectionDisabled commentInjectionMode = iota // Default value, sql comment injection disabled. - staticTagsSQLCommentInjection // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. - fullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. + CommentInjectionDisabled CommentInjectionMode = iota // Default value, sql comment injection disabled. + StaticTagsSQLCommentInjection // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. + FullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. ) // Option represents an option that can be passed to Register, Open or OpenDB. @@ -98,31 +98,15 @@ func WithChildSpansOnly() Option { // WithCommentInjection 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. Use WithStaticTagsCommentInjection if this is a concern. -func WithCommentInjection() Option { +func WithCommentInjection(mode CommentInjectionMode) Option { return func(cfg *config) { - cfg.commentInjectionMode = fullSQLCommentInjection + cfg.commentInjectionMode = mode } } -// WithStaticTagsCommentInjection enables injection of static tags as sql comments on traced queries. -// This excludes dynamic values like span id, trace id and sampling priority which can make a query -// unique and have side effects on caching. -func WithStaticTagsCommentInjection() Option { - return func(cfg *config) { - cfg.commentInjectionMode = staticTagsSQLCommentInjection - } -} - -// WithoutCommentInjection disables injection of sql comments on traced queries. -func WithoutCommentInjection() Option { - return func(cfg *config) { - cfg.commentInjectionMode = commentInjectionDisabled - } -} - -func injectionOptionsForMode(mode commentInjectionMode, discardDynamicTags bool) (opts []tracer.InjectionOption) { +func injectionOptionsForMode(mode CommentInjectionMode, discardDynamicTags bool) []tracer.InjectionOption { switch { - case mode == fullSQLCommentInjection && !discardDynamicTags: + case mode == FullSQLCommentInjection && !discardDynamicTags: return []tracer.InjectionOption{ tracer.WithTraceIDKey(tracer.TraceIDSQLCommentKey), tracer.WithSpanIDKey(tracer.SpanIDSQLCommentKey), @@ -131,7 +115,7 @@ func injectionOptionsForMode(mode commentInjectionMode, discardDynamicTags bool) tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), tracer.WithParentVersionKey(tracer.ServiceVersionSQLCommentKey), } - case mode == fullSQLCommentInjection && discardDynamicTags || mode == staticTagsSQLCommentInjection: + case mode == FullSQLCommentInjection && discardDynamicTags || mode == StaticTagsSQLCommentInjection: return []tracer.InjectionOption{ tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 1785112526..8e4b870844 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -205,36 +205,27 @@ func TestOpenOptions(t *testing.T) { func TestCommentInjectionModes(t *testing.T) { testCases := []struct { name string - options []Option + mode CommentInjectionMode expectedInjectedTags sqltest.TagInjectionExpectation }{ { - name: "default (no injection)", - options: []Option{}, + name: "default (no injection)", expectedInjectedTags: sqltest.TagInjectionExpectation{ StaticTags: false, DynamicTags: false, }, }, { - name: "explicit no injection", - options: []Option{WithoutCommentInjection()}, - expectedInjectedTags: sqltest.TagInjectionExpectation{ - StaticTags: false, - DynamicTags: false, - }, - }, - { - name: "static tags injection", - options: []Option{WithStaticTagsCommentInjection()}, + name: "static tags injection", + mode: StaticTagsSQLCommentInjection, expectedInjectedTags: sqltest.TagInjectionExpectation{ StaticTags: true, DynamicTags: false, }, }, { - name: "dynamic tags injection", - options: []Option{WithCommentInjection()}, + name: "dynamic tags injection", + mode: FullSQLCommentInjection, expectedInjectedTags: sqltest.TagInjectionExpectation{ StaticTags: true, DynamicTags: true, @@ -247,7 +238,7 @@ func TestCommentInjectionModes(t *testing.T) { mockTracer := mocktracer.Start() defer mockTracer.Stop() - Register("postgres", &pq.Driver{}, append(tc.options, WithServiceName("postgres-test"))...) + Register("postgres", &pq.Driver{}, WithCommentInjection(tc.mode), WithServiceName("postgres-test")) defer unregister("postgres") db, err := Open("postgres", "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable") diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index bd27d5d401..7326c40368 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -318,19 +318,19 @@ func assertInjectedComments(t *testing.T, cfg *Config, discardDynamicTags bool) } func expectedInjectedTags(cfg *Config, discardDynamicTags bool) map[string]string { - expectedInjectedTags := make(map[string]string) + tags := make(map[string]string) // Prepare statements should never have dynamic tags injected so we only check if static tags are expected if cfg.ExpectTagInjection.StaticTags { - expectedInjectedTags[tracer.ServiceNameSQLCommentKey] = "test-service" - expectedInjectedTags[tracer.ServiceEnvironmentSQLCommentKey] = "test-env" - expectedInjectedTags[tracer.ServiceVersionSQLCommentKey] = "v-test" + tags[tracer.ServiceNameSQLCommentKey] = "test-service" + tags[tracer.ServiceEnvironmentSQLCommentKey] = "test-env" + tags[tracer.ServiceVersionSQLCommentKey] = "v-test" } if cfg.ExpectTagInjection.DynamicTags && !discardDynamicTags { - expectedInjectedTags[tracer.SamplingPrioritySQLCommentKey] = "0" - expectedInjectedTags[tracer.TraceIDSQLCommentKey] = "test-trace-id" - expectedInjectedTags[tracer.SpanIDSQLCommentKey] = "test-span-id" + tags[tracer.SamplingPrioritySQLCommentKey] = "0" + tags[tracer.TraceIDSQLCommentKey] = "test-trace-id" + tags[tracer.SpanIDSQLCommentKey] = "test-span-id" } - return expectedInjectedTags + return tags } func verifyConnectSpan(span mocktracer.Span, assert *assert.Assertions, cfg *Config) { From 529e2e6abb19c4f229a064335f7849c9bc5ae732 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 17 May 2022 22:15:37 -0700 Subject: [PATCH 046/104] Add Missing godoc --- contrib/database/sql/option.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 2c855f37f4..aac5f90d17 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -24,9 +24,12 @@ type config struct { type CommentInjectionMode int const ( - CommentInjectionDisabled CommentInjectionMode = iota // Default value, sql comment injection disabled. - StaticTagsSQLCommentInjection // Static sql comment injection only: this includes values that are set once during the lifetime of an application: service name, env, version. - FullSQLCommentInjection // Full sql comment injection is enabled: include dynamic values like span id, trace id and sampling priority. + // CommentInjectionDisabled represents the comment injection mode where all injection is disabled. + CommentInjectionDisabled CommentInjectionMode = iota + // StaticTagsSQLCommentInjection represents the comment injection mode where only static tags are injected. Static tags include values that are set once during the lifetime of an application: service name, env, version. + StaticTagsSQLCommentInjection + // FullSQLCommentInjection represents the comment injection mode where both static and dynamic tags are injected. Dynamic tags include values like span id, trace id and sampling priority. + FullSQLCommentInjection ) // Option represents an option that can be passed to Register, Open or OpenDB. From 1b449c02f096ac396d695bb9f397d8bba69072f7 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 24 May 2022 21:26:23 -0700 Subject: [PATCH 047/104] WIP: Refactor SQL Comment Injection to New Propagator and Carrier --- contrib/database/sql/conn.go | 45 ++++---- contrib/database/sql/option.go | 55 +-------- contrib/database/sql/rand.go | 55 --------- contrib/database/sql/sql.go | 3 - contrib/database/sql/sql_test.go | 11 +- contrib/internal/sqltest/sqltest.go | 8 +- ddtrace/ddtrace.go | 6 - ddtrace/internal/globaltracer.go | 5 - ddtrace/mocktracer/mocktracer.go | 54 --------- ddtrace/tracer/option.go | 61 +--------- ddtrace/tracer/propagator.go | 8 -- ddtrace/tracer/sqlcomment.go | 169 +++++++++++++++++++++++++--- ddtrace/tracer/sqlcomment_test.go | 89 ++++++--------- ddtrace/tracer/textmap.go | 90 +++------------ ddtrace/tracer/tracer.go | 13 --- 15 files changed, 245 insertions(+), 427 deletions(-) delete mode 100644 contrib/database/sql/rand.go diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 0c12212475..391cd636a4 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -62,8 +62,8 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) stmt, err := connPrepareCtx.PrepareContext(ctx, commentedQuery) - if spanID != nil { - tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(*spanID)) + if spanID > 0 { + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) } else { tc.tryTrace(ctx, queryTypePrepare, query, start, err) } @@ -75,8 +75,8 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr } commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) stmt, err = tc.Prepare(commentedQuery) - if spanID != nil { - tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(*spanID)) + if spanID > 0 { + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) } else { tc.tryTrace(ctx, queryTypePrepare, query, start, err) } @@ -92,8 +92,8 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv if execContext, ok := tc.Conn.(driver.ExecerContext); ok { commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) r, err := execContext.ExecContext(ctx, commentedQuery, args) - if spanID != nil { - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(*spanID)) + if spanID > 0 { + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) } else { tc.tryTrace(ctx, queryTypeExec, query, start, err) } @@ -111,8 +111,8 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv } commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) r, err = execer.Exec(commentedQuery, dargs) - if spanID != nil { - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(*spanID)) + if spanID > 0 { + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) } else { tc.tryTrace(ctx, queryTypeExec, query, start, err) } @@ -140,8 +140,8 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) rows, err := queryerContext.QueryContext(ctx, commentedQuery, args) - if spanID != nil { - tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(*spanID)) + if spanID > 0 { + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) } else { tc.tryTrace(ctx, queryTypeQuery, query, start, err) } @@ -159,8 +159,8 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri } commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) rows, err = queryer.Query(commentedQuery, dargs) - if spanID != nil { - tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(*spanID)) + if spanID > 0 { + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) } else { tc.tryTrace(ctx, queryTypeQuery, query, start, err) } @@ -207,23 +207,20 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // withSQLCommentsInjected will return the query with sql comments injected according to the comment injection mode along // with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating // the span following the traced database call. -func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (commentedQuery string, injectedSpanID *uint64) { +func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (commentedQuery string, injectedSpanID uint64) { var spanContext ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() } - if tp.cfg.commentInjectionMode != CommentInjectionDisabled { - var sqlCommentCarrier tracer.SQLCommentCarrier - spanID := random.Uint64() - opts := injectionOptionsForMode(tp.cfg.commentInjectionMode, discardDynamicTags) - err := tracer.InjectWithOptions(spanContext, &sqlCommentCarrier, append(opts, tracer.WithInjectedSpanID(spanID))...) - if err != nil { - // this should never happen - log.Warn("contrib/database/sql: failed to inject query comments: %v", err) - } - return sqlCommentCarrier.CommentedQuery(query), &spanID + + sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.CommentWithDynamicTagsDiscarded(discardDynamicTags)) + err := tracer.Inject(spanContext, &sqlCommentCarrier) + if err != nil { + // this should never happen + log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } - return query, nil + + return sqlCommentCarrier.CommentQuery(query) } // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index aac5f90d17..974bbdcfd9 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -8,30 +8,16 @@ package sql import ( "math" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal" ) type config struct { - serviceName string - analyticsRate float64 - dsn string - childSpansOnly bool - commentInjectionMode CommentInjectionMode + serviceName string + analyticsRate float64 + dsn string + childSpansOnly bool } -// CommentInjectionMode represents the mode of sql comment injection. -type CommentInjectionMode int - -const ( - // CommentInjectionDisabled represents the comment injection mode where all injection is disabled. - CommentInjectionDisabled CommentInjectionMode = iota - // StaticTagsSQLCommentInjection represents the comment injection mode where only static tags are injected. Static tags include values that are set once during the lifetime of an application: service name, env, version. - StaticTagsSQLCommentInjection - // FullSQLCommentInjection represents the comment injection mode where both static and dynamic tags are injected. Dynamic tags include values like span id, trace id and sampling priority. - FullSQLCommentInjection -) - // Option represents an option that can be passed to Register, Open or OpenDB. type Option func(*config) @@ -96,35 +82,4 @@ func WithChildSpansOnly() Option { return func(cfg *config) { cfg.childSpansOnly = true } -} - -// WithCommentInjection 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. Use WithStaticTagsCommentInjection if this is a concern. -func WithCommentInjection(mode CommentInjectionMode) Option { - return func(cfg *config) { - cfg.commentInjectionMode = mode - } -} - -func injectionOptionsForMode(mode CommentInjectionMode, discardDynamicTags bool) []tracer.InjectionOption { - switch { - case mode == FullSQLCommentInjection && !discardDynamicTags: - return []tracer.InjectionOption{ - tracer.WithTraceIDKey(tracer.TraceIDSQLCommentKey), - tracer.WithSpanIDKey(tracer.SpanIDSQLCommentKey), - tracer.WithSamplingPriorityKey(tracer.SamplingPrioritySQLCommentKey), - tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), - tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), - tracer.WithParentVersionKey(tracer.ServiceVersionSQLCommentKey), - } - case mode == FullSQLCommentInjection && discardDynamicTags || mode == StaticTagsSQLCommentInjection: - return []tracer.InjectionOption{ - tracer.WithServiceNameKey(tracer.ServiceNameSQLCommentKey), - tracer.WithEnvironmentKey(tracer.ServiceEnvironmentSQLCommentKey), - tracer.WithParentVersionKey(tracer.ServiceVersionSQLCommentKey), - } - default: - return []tracer.InjectionOption{} - } -} +} \ No newline at end of file diff --git a/contrib/database/sql/rand.go b/contrib/database/sql/rand.go deleted file mode 100644 index 64044482ff..0000000000 --- a/contrib/database/sql/rand.go +++ /dev/null @@ -1,55 +0,0 @@ -// 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 ( - cryptorand "crypto/rand" - "math" - "math/big" - "math/rand" - "sync" - "time" - - "gopkg.in/DataDog/dd-trace-go.v1/internal/log" -) - -var random *rand.Rand - -func init() { - var seed int64 - n, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)) - if err == nil { - seed = n.Int64() - } else { - log.Warn("cannot generate random seed: %v; using current time", err) - seed = time.Now().UnixNano() - } - random = rand.New(&safeSource{ - source: rand.NewSource(seed), - }) -} - -// safeSource holds a thread-safe implementation of rand.Source64. -type safeSource struct { - source rand.Source - sync.Mutex -} - -func (rs *safeSource) Int63() int64 { - rs.Lock() - n := rs.source.Int63() - rs.Unlock() - - return n -} - -func (rs *safeSource) Uint64() uint64 { return uint64(rs.Int63()) } - -func (rs *safeSource) Seed(seed int64) { - rs.Lock() - rs.source.Seed(seed) - rs.Unlock() -} diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 3349735962..2383f4a499 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -186,9 +186,6 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { if math.IsNaN(cfg.analyticsRate) { cfg.analyticsRate = rc.analyticsRate } - if cfg.commentInjectionMode == 0 { - cfg.commentInjectionMode = rc.commentInjectionMode - } cfg.childSpansOnly = rc.childSpansOnly tc := &tracedConnector{ connector: c, diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 8e4b870844..7f22d3e39a 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -19,6 +19,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" @@ -205,7 +206,7 @@ func TestOpenOptions(t *testing.T) { func TestCommentInjectionModes(t *testing.T) { testCases := []struct { name string - mode CommentInjectionMode + mode tracer.SQLCommentInjectionMode expectedInjectedTags sqltest.TagInjectionExpectation }{ { @@ -217,7 +218,7 @@ func TestCommentInjectionModes(t *testing.T) { }, { name: "static tags injection", - mode: StaticTagsSQLCommentInjection, + mode: tracer.StaticTagsSQLCommentInjection, expectedInjectedTags: sqltest.TagInjectionExpectation{ StaticTags: true, DynamicTags: false, @@ -225,7 +226,7 @@ func TestCommentInjectionModes(t *testing.T) { }, { name: "dynamic tags injection", - mode: FullSQLCommentInjection, + mode: tracer.FullSQLCommentInjection, expectedInjectedTags: sqltest.TagInjectionExpectation{ StaticTags: true, DynamicTags: true, @@ -235,10 +236,12 @@ func TestCommentInjectionModes(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + // TODO: Rethink how to run that test now that the functionality is implemented in a propagator + // that we can't currently inject on the mocktracer mockTracer := mocktracer.Start() defer mockTracer.Stop() - Register("postgres", &pq.Driver{}, WithCommentInjection(tc.mode), WithServiceName("postgres-test")) + Register("postgres", &pq.Driver{}, WithServiceName("postgres-test")) defer unregister("postgres") db, err := Open("postgres", "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable") diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 7326c40368..a8c3a4c131 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -12,13 +12,11 @@ import ( "log" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - - "github.com/stretchr/testify/assert" ) // Prepare sets up a table with the given name in both the MySQL and Postgres databases and returns @@ -313,7 +311,9 @@ func assertInjectedComments(t *testing.T, cfg *Config, discardDynamicTags bool) assert.Len(t, c, 0) } else { require.Len(t, c, 1) - assert.Equal(t, carrier.CommentedQuery(""), c[0]) + commented, spanID := carrier.CommentQuery("") + assert.Equal(t, commented, c[0]) + assert.Greater(t, spanID, 0) } } diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index a892f8727c..092e03d1e2 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -33,9 +33,6 @@ type Tracer interface { // Inject injects a span context into the given carrier. Inject(context SpanContext, carrier interface{}) error - // InjectWithOptions injects a span context into the given carrier with options. - InjectWithOptions(context SpanContext, carrier interface{}, opts ...InjectionOption) error - // Stop stops the tracer. Calls to Stop should be idempotent. Stop() } @@ -139,9 +136,6 @@ type Logger interface { Log(msg string) } -// InjectionOption is a configuration option that can be used with a Tracer's InjectWithOptions method. -type InjectionOption func(cfg *InjectionConfig) - // InjectionConfig holds the configuration for injection a span into a carrier. It is usually passed // around by reference to one or more InjectionOption functions which shape it into its // final form. diff --git a/ddtrace/internal/globaltracer.go b/ddtrace/internal/globaltracer.go index bc0fade507..239e976267 100644 --- a/ddtrace/internal/globaltracer.go +++ b/ddtrace/internal/globaltracer.go @@ -62,11 +62,6 @@ func (NoopTracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { // Inject implements ddtrace.Tracer. func (NoopTracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { return nil } -// InjectWithOptions implements ddtrace.Tracer. -func (NoopTracer) InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { - return nil -} - // Stop implements ddtrace.Tracer. func (NoopTracer) Stop() {} diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 7b2af8049c..173bc3c372 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -203,57 +203,3 @@ func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) er }) return nil } - -func (t *mocktracer) InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...tracer.InjectionOption) error { - writer, ok := carrier.(tracer.TextMapWriter) - if !ok { - return tracer.ErrInvalidCarrier - } - cfg := ddtrace.InjectionConfig{} - for _, apply := range opts { - apply(&cfg) - } - spanID := cfg.SpanID - - ctx, ok := context.(*spanContext) - if !ok && spanID == 0 { - return tracer.ErrInvalidSpanContext - } - - samplingPriority := 0 - if ok { - samplingPriority = ctx.samplingPriority() - } - - if cfg.TraceIDKey != "" { - writer.Set(cfg.TraceIDKey, "test-trace-id") - } - - if cfg.SpanIDKey != "" { - writer.Set(cfg.SpanIDKey, "test-span-id") - } - - if cfg.SamplingPriorityKey != "" { - writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(samplingPriority)) - } - - if cfg.EnvKey != "" { - writer.Set(cfg.EnvKey, "test-env") - } - - if cfg.ParentVersionKey != "" { - writer.Set(cfg.ParentVersionKey, "v-test") - } - - if cfg.ServiceNameKey != "" { - writer.Set(cfg.ServiceNameKey, "test-service") - } - - sqlCommentCarrier, ok := carrier.(*tracer.SQLCommentCarrier) - if ok { - // Save injected comments to assert the sql commenting behavior - t.injectedComments = append(t.injectedComments, sqlCommentCarrier.CommentedQuery("")) - } - - return nil -} diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 06e716a1ae..90dcd034d5 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -96,9 +96,6 @@ type config struct { // propagator propagates span context cross-process propagator Propagator - // injector injects span context cross-process - injector WithOptionsInjector - // httpClient specifies the HTTP client to be used by the agent's transport. httpClient *http.Client @@ -264,14 +261,12 @@ func newConfig(opts ...StartOption) *config { if c.transport == nil { c.transport = newHTTPTransport(c.agentAddr, c.httpClient) } + // TODO: add sql commenting mode to propagator config and add env variable to control the mode if c.propagator == nil { c.propagator = NewPropagator(&PropagatorConfig{ MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), }) } - if c.injector == nil { - c.injector = NewInjector() - } if c.logger != nil { log.UseLogger(c.logger) } @@ -895,57 +890,3 @@ func WithUserScope(scope string) UserMonitoringOption { s.SetTag("usr.scope", scope) } } - -// InjectionOption is a configuration option for InjectWithOptions. It is aliased in order -// to help godoc group all the functions returning it together. It is considered -// more correct to refer to it as the type as the origin, ddtrace.InjectionOption. -type InjectionOption = ddtrace.InjectionOption - -// WithSpanIDKey returns the option setting the span id key. -func WithSpanIDKey(spanIDKey string) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.SpanIDKey = spanIDKey - } -} - -// WithInjectedSpanID returns the option setting the span id value. -func WithInjectedSpanID(spanID uint64) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.SpanID = spanID - } -} - -// WithTraceIDKey returns the option setting the trace id key. -func WithTraceIDKey(traceIDKey string) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.TraceIDKey = traceIDKey - } -} - -// WithSamplingPriorityKey returns the option setting the sampling priority key. -func WithSamplingPriorityKey(samplingPriorityKey string) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.SamplingPriorityKey = samplingPriorityKey - } -} - -// WithEnvironmentKey returns the option setting the environment key. -func WithEnvironmentKey(envKey string) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.EnvKey = envKey - } -} - -// WithServiceNameKey returns the option setting the service name key. -func WithServiceNameKey(serviceNameKey string) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.ServiceNameKey = serviceNameKey - } -} - -// WithParentVersionKey returns the option setting the parent version key. -func WithParentVersionKey(versionKey string) InjectionOption { - return func(cfg *ddtrace.InjectionConfig) { - cfg.ParentVersionKey = versionKey - } -} diff --git a/ddtrace/tracer/propagator.go b/ddtrace/tracer/propagator.go index 213e2cd75a..93c596227b 100644 --- a/ddtrace/tracer/propagator.go +++ b/ddtrace/tracer/propagator.go @@ -21,14 +21,6 @@ type Propagator interface { Extract(carrier interface{}) (ddtrace.SpanContext, error) } -// WithOptionsInjector implementations should be able to inject SpanContexts into an implementation -// specific carrier. It is related to Propagator but provides a write-only flexible interface accepting -// options and does not include extraction. -type WithOptionsInjector interface { - // InjectWithOptions takes the SpanContext and injects it into the carrier according to the given options. - InjectWithOptions(context ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error -} - // TextMapWriter allows setting key/value pairs of strings on the underlying // data structure. Carriers implementing TextMapWriter are compatible to be // used with Datadog's TextMapPropagator. diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 8ae9e9c74a..03668b6755 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -4,13 +4,25 @@ import ( "fmt" "net/url" "sort" + "strconv" "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" ) -// SQLCommentCarrier holds tags to be serialized as a SQL Comment -type SQLCommentCarrier struct { - tags map[string]string -} +// SQLCommentInjectionMode represents the mode of sql comment injection. +type SQLCommentInjectionMode int + +const ( + // CommentInjectionDisabled represents the comment injection mode where all injection is disabled. + CommentInjectionDisabled SQLCommentInjectionMode = iota + // StaticTagsSQLCommentInjection represents the comment injection mode where only static tags are injected. Static tags include values that are set once during the lifetime of an application: service name, env, version. + StaticTagsSQLCommentInjection + // FullSQLCommentInjection represents the comment injection mode where both static and dynamic tags are injected. Dynamic tags include values like span id, trace id and sampling priority. + FullSQLCommentInjection +) // Values for sql comment keys const ( @@ -22,44 +34,173 @@ const ( ServiceEnvironmentSQLCommentKey = "dde" ) +// QueryCommenter is a more specific interface implemented by carrier that implement the TextMapWriter +// as well as CommentQuery and AddSpanID methods +type QueryCommenter interface { + TextMapWriter + SetDynamicTag(key, val string) + CommentQuery(query string) (commented string, spanID uint64) + AddSpanID(spanID uint64) +} + +// SQLCommentPropagator implements the Propagator interface to inject tags +// in sql comments +type SQLCommentPropagator struct { + mode SQLCommentInjectionMode +} + +func CommentWithDynamicTagsDiscarded(discard bool) SQLCommentCarrierOption { + return func(c *SQLCommentCarrierConfig) { + c.discardDynamicTags = discard + } +} + +func NewCommentPropagator(mode SQLCommentInjectionMode) *SQLCommentPropagator { + return &SQLCommentPropagator{mode: mode} +} + +func (p *SQLCommentPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { + switch c := carrier.(type) { + case QueryCommenter: + return p.injectWithCommentCarrier(spanCtx, c) + default: + // SQLCommentPropagator only handles QueryCommenter carriers + return nil + } +} + +func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanContext, carrier QueryCommenter) error { + if p.mode == CommentInjectionDisabled { + return nil + } + + if p.mode == StaticTagsSQLCommentInjection || p.mode == FullSQLCommentInjection { + ctx, ok := spanCtx.(*spanContext) + var env, pversion string + if ok { + if e, ok := ctx.meta(ext.Environment); ok { + env = e + } + if version, ok := ctx.meta(ext.ParentVersion); ok { + pversion = version + } + } + if globalconfig.ServiceName() != "" { + carrier.Set(ServiceNameSQLCommentKey, globalconfig.ServiceName()) + } + if env != "" { + carrier.Set(ServiceEnvironmentSQLCommentKey, env) + } + if pversion != "" { + carrier.Set(ServiceVersionSQLCommentKey, pversion) + } + } + if p.mode == FullSQLCommentInjection { + samplingPriority := 0 + ctx, _ := spanCtx.(*spanContext) + if sp, ok := ctx.samplingPriority(); ok { + samplingPriority = sp + } + var traceID, spanID uint64 + if ctx.TraceID() > 0 { + traceID = ctx.TraceID() + } + if ctx.SpanID() > 0 { + spanID = ctx.SpanID() + } + + if spanID == 0 { + spanID = random.Uint64() + carrier.AddSpanID(spanID) + } + if traceID == 0 { + traceID = spanID + } + carrier.SetDynamicTag(TraceIDSQLCommentKey, strconv.FormatUint(traceID, 10)) + carrier.SetDynamicTag(SpanIDSQLCommentKey, strconv.FormatUint(spanID, 10)) + carrier.SetDynamicTag(SamplingPrioritySQLCommentKey, strconv.Itoa(samplingPriority)) + } + return nil +} + +func (p *SQLCommentPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { + return nil, fmt.Errorf("not implemented") +} + +// SQLCommentCarrierConfig holds configuration for a SQLCommentCarrier +type SQLCommentCarrierConfig struct { + discardDynamicTags bool +} + +type SQLCommentCarrierOption func(c *SQLCommentCarrierConfig) + +// SQLCommentCarrier holds tags to be serialized as a SQL Comment +type SQLCommentCarrier struct { + tags map[string]string + cfg SQLCommentCarrierConfig + spanID uint64 +} + +// NewSQLCommentCarrier returns a new SQLCommentCarrier +func NewSQLCommentCarrier(opts ...SQLCommentCarrierOption) (s *SQLCommentCarrier) { + s = new(SQLCommentCarrier) + for _, apply := range opts { + apply(&s.cfg) + } + + return s +} + // Set implements TextMapWriter. func (c *SQLCommentCarrier) Set(key, val string) { if c.tags == nil { c.tags = make(map[string]string) } + c.tags[key] = val +} +func (c *SQLCommentCarrier) SetDynamicTag(key, val string) { + if c.cfg.discardDynamicTags { + return + } + if c.tags == nil { + c.tags = make(map[string]string) + } c.tags[key] = val } +func (c *SQLCommentCarrier) AddSpanID(spanID uint64) { + c.spanID = spanID +} + +func (c *SQLCommentCarrier) SpanID() uint64 { + return c.spanID +} + func commentWithTags(tags map[string]string) (comment string) { if len(tags) == 0 { return "" } - serializedTags := make([]string, 0, len(tags)) for k, v := range tags { serializedTags = append(serializedTags, serializeTag(k, v)) } - sort.Strings(serializedTags) comment = strings.Join(serializedTags, ",") return fmt.Sprintf("/*%s*/", comment) } -// CommentedQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a +// CommentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a // prepended SQL comment -func (c *SQLCommentCarrier) CommentedQuery(query string) (commented string) { +func (c *SQLCommentCarrier) CommentQuery(query string) (commented string, spanID uint64) { comment := commentWithTags(c.tags) - if comment == "" { - return query + return query, c.spanID } - if query == "" { - return comment + return comment, c.spanID } - - return fmt.Sprintf("%s %s", comment, query) + return fmt.Sprintf("%s %s", comment, query), c.spanID } // ForeachKey implements TextMapReader. diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index a9738f02a1..881776a338 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -11,80 +11,65 @@ import ( func TestQueryTextCarrier(t *testing.T) { testCases := []struct { - name string - query string - options []InjectionOption - commented string + name string + query string + mode SQLCommentInjectionMode + carrierOpts []SQLCommentCarrierOption + commented string }{ { - name: "all tags injected", - query: "SELECT * from FOO", - options: []InjectionOption{WithParentVersionKey("ddsv"), WithEnvironmentKey("dde"), WithServiceNameKey("ddsn"), WithSpanIDKey("ddsid"), WithTraceIDKey("ddtid"), WithSamplingPriorityKey("ddsp")}, - commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", + name: "all tags injected", + query: "SELECT * from FOO", + mode: FullSQLCommentInjection, + carrierOpts: nil, + commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", }, { - name: "empty query, all tags injected", - query: "", - options: []InjectionOption{WithParentVersionKey("ddsv"), WithEnvironmentKey("dde"), WithServiceNameKey("ddsn"), WithSpanIDKey("ddsid"), WithTraceIDKey("ddtis"), WithSamplingPriorityKey("ddsp")}, - commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtis='10'*/", + name: "empty query, all tags injected", + query: "", + mode: FullSQLCommentInjection, + carrierOpts: nil, + commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/", }, { - name: "query with existing comment", - query: "SELECT * from FOO -- test query", - options: []InjectionOption{WithParentVersionKey("ddsv"), WithEnvironmentKey("dde"), WithServiceNameKey("ddsn"), WithSpanIDKey("ddsid"), WithTraceIDKey("ddtis"), WithSamplingPriorityKey("ddsp")}, - commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtis='10'*/ SELECT * from FOO -- test query", + name: "query with existing comment", + query: "SELECT * from FOO -- test query", + mode: FullSQLCommentInjection, + carrierOpts: nil, + commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", }, { - name: "only parent version tag", - query: "SELECT * from FOO", - options: []InjectionOption{WithParentVersionKey("ddsv")}, - commented: "/*ddsv='1.0.0'*/ SELECT * from FOO", + name: "discard dynamic tags", + query: "SELECT * from FOO", + mode: FullSQLCommentInjection, + carrierOpts: []SQLCommentCarrierOption{CommentWithDynamicTagsDiscarded(true)}, + commented: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", }, { - name: "only env tag", - query: "SELECT * from FOO", - options: []InjectionOption{WithEnvironmentKey("dde")}, - commented: "/*dde='test-env'*/ SELECT * from FOO", - }, - { - name: "only service name tag", - query: "SELECT * from FOO", - options: []InjectionOption{WithServiceNameKey("ddsn")}, - commented: "/*ddsn='whiskey-service'*/ SELECT * from FOO", - }, - { - name: "only trace id tag", - query: "SELECT * from FOO", - options: []InjectionOption{WithTraceIDKey("ddtid")}, - commented: "/*ddtid='10'*/ SELECT * from FOO", - }, - { - name: "only span id tag", - query: "SELECT * from FOO", - options: []InjectionOption{WithSpanIDKey("ddsid")}, - commented: "/*ddsid='10'*/ SELECT * from FOO", - }, - { - name: "only sampling priority tag", - query: "SELECT * from FOO", - options: []InjectionOption{WithSamplingPriorityKey("ddsp")}, - commented: "/*ddsp='2'*/ SELECT * from FOO", + name: "static tags only mode", + query: "SELECT * from FOO", + mode: StaticTagsSQLCommentInjection, + carrierOpts: nil, + commented: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0")) + propagator := NewCommentPropagator(tc.mode) + tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0"), WithPropagator(propagator)) root := tracer.StartSpan("db.call", WithSpanID(10), ServiceName("whiskey-db")).(*span) root.SetTag(ext.SamplingPriority, 2) ctx := root.Context() - carrier := SQLCommentCarrier{} - err := tracer.InjectWithOptions(ctx, &carrier, tc.options...) + carrier := NewSQLCommentCarrier(tc.carrierOpts...) + err := tracer.Inject(ctx, carrier) require.NoError(t, err) - assert.Equal(t, tc.commented, carrier.CommentedQuery(tc.query)) + commented, spanID := carrier.CommentQuery(tc.query) + assert.Equal(t, tc.commented, commented) + assert.Equal(t, uint64(0), spanID) }) } } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 4d6fe965c1..71bc9c53f8 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -15,7 +15,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" ) @@ -120,6 +119,8 @@ type PropagatorConfig struct { // B3 specifies if B3 headers should be added for trace propagation. // See https://github.com/openzipkin/b3-propagation B3 bool + + SQLCommentInjectionMode SQLCommentInjectionMode } // NewPropagator returns a new propagator which uses TextMap to inject @@ -147,80 +148,6 @@ func NewPropagator(cfg *PropagatorConfig) Propagator { } } -// NewInjector returns a new experimental injector which uses TextMap to inject -// and extract values. It propagates static tags (service name, environment and version) as well -// as dynamic trace attributes (span id, trace id and sampling priority). -func NewInjector() WithOptionsInjector { - return &withOptionsInjector{} -} - -type withOptionsInjector struct { -} - -func (i *withOptionsInjector) InjectWithOptions(spanCtx ddtrace.SpanContext, carrier interface{}, opts ...ddtrace.InjectionOption) error { - switch c := carrier.(type) { - case TextMapWriter: - return i.injectTextMapWithOptions(spanCtx, c, opts...) - default: - return ErrInvalidCarrier - } -} - -func (i *withOptionsInjector) injectTextMapWithOptions(spanCtx ddtrace.SpanContext, writer TextMapWriter, opts ...ddtrace.InjectionOption) error { - cfg := ddtrace.InjectionConfig{} - for _, apply := range opts { - apply(&cfg) - } - spanID := cfg.SpanID - ctx, ok := spanCtx.(*spanContext) - if !ok && spanID == 0 { - return ErrInvalidSpanContext - } - - traceID := spanID - samplingPriority := 0 - var env, pversion string - if ok { - if ctx.TraceID() > 0 { - traceID = ctx.TraceID() - } - if spanID == 0 { - spanID = ctx.SpanID() - } - if sp, ok := ctx.samplingPriority(); ok { - samplingPriority = sp - } - if e, ok := ctx.meta(ext.Environment); ok { - env = e - } - if version, ok := ctx.meta(ext.ParentVersion); ok { - pversion = version - } - } - - if cfg.TraceIDKey != "" { - writer.Set(cfg.TraceIDKey, strconv.FormatUint(traceID, 10)) - } - if cfg.SpanIDKey != "" { - writer.Set(cfg.SpanIDKey, strconv.FormatUint(spanID, 10)) - } - if cfg.SamplingPriorityKey != "" { - writer.Set(cfg.SamplingPriorityKey, strconv.Itoa(samplingPriority)) - } - if cfg.EnvKey != "" { - writer.Set(cfg.EnvKey, env) - } - if cfg.ParentVersionKey != "" { - writer.Set(cfg.ParentVersionKey, pversion) - } - if cfg.ServiceNameKey != "" { - if globalconfig.ServiceName() != "" { - writer.Set(cfg.ServiceNameKey, globalconfig.ServiceName()) - } - } - return nil -} - // chainedPropagator implements Propagator and applies a list of injectors and extractors. // When injecting, all injectors are called to propagate the span context. // When extracting, it tries each extractor, selecting the first successful one. @@ -256,6 +183,12 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { // propagatorB3 hasn't already been added, add a new one. list = append(list, &propagatorB3{}) } + // TODO: read the sql commenting mode from env variable and/or config + case "sqlcommenter": + if !cfg.B3 { + // propagatorB3 hasn't already been added, add a new one. + list = append(list, &propagatorB3{}) + } default: log.Warn("unrecognized propagator: %s\n", v) } @@ -305,8 +238,12 @@ type propagator struct { func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { + // QueryCommenter carriers are only supported by the SQLCommentPropagator + case QueryCommenter: + return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) + default: return ErrInvalidCarrier } @@ -403,6 +340,9 @@ type propagatorB3 struct{} func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { + // QueryCommenter carriers are only supported by the SQLCommentPropagator + case QueryCommenter: + return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) default: diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 041322ad61..96b3ad175f 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -154,14 +154,6 @@ func Inject(ctx ddtrace.SpanContext, carrier interface{}) error { return internal.GetGlobalTracer().Inject(ctx, carrier) } -// InjectWithOptions injects the given SpanContext into the carrier with options. This -// is similar to Inject but adds options to specify which info to inject in the carrier -// along with the keys to use. The carrier is still expected to implement TextMapWriter, -// otherwise an error is returned. If the tracer is not started, calling this function is a no-op. -func InjectWithOptions(ctx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { - return internal.GetGlobalTracer().InjectWithOptions(ctx, carrier, opts...) -} - // SetUser associates user information to the current trace which the // provided span belongs to. The options can be used to tune which user // bit of information gets monitored. @@ -513,11 +505,6 @@ func (t *tracer) Inject(ctx ddtrace.SpanContext, carrier interface{}) error { return t.config.propagator.Inject(ctx, carrier) } -// InjectWithOptions uses the configured or default TextMap Propagator. -func (t *tracer) InjectWithOptions(ctx ddtrace.SpanContext, carrier interface{}, opts ...InjectionOption) error { - return t.config.injector.InjectWithOptions(ctx, carrier, opts...) -} - // Extract uses the configured or default TextMap Propagator. func (t *tracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { return t.config.propagator.Extract(carrier) From ea8872f21e5b1cf3b9915bd841b6815ccb748ecb Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 15:17:35 -0700 Subject: [PATCH 048/104] Fix sql tests --- contrib/database/sql/conn.go | 2 +- contrib/database/sql/option.go | 2 +- contrib/database/sql/sql_test.go | 76 ++--------------------------- contrib/internal/sqltest/sqltest.go | 28 +++++++++-- ddtrace/mocktracer/mocktracer.go | 29 ++++++++++- ddtrace/tracer/sqlcomment.go | 10 ++-- ddtrace/tracer/textmap.go | 8 +-- 7 files changed, 64 insertions(+), 91 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 391cd636a4..3b59dd4473 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -214,7 +214,7 @@ func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string } sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.CommentWithDynamicTagsDiscarded(discardDynamicTags)) - err := tracer.Inject(spanContext, &sqlCommentCarrier) + err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { // this should never happen log.Warn("contrib/database/sql: failed to inject query comments: %v", err) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 974bbdcfd9..47bd4208bd 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -82,4 +82,4 @@ func WithChildSpansOnly() Option { return func(cfg *config) { cfg.childSpansOnly = true } -} \ No newline at end of file +} diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 7f22d3e39a..5ccecf7a89 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -16,15 +16,13 @@ import ( "testing" "time" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" ) // tableName holds the SQL table that these tests will be run against. It must be unique cross-repo. @@ -203,74 +201,6 @@ func TestOpenOptions(t *testing.T) { }) } -func TestCommentInjectionModes(t *testing.T) { - testCases := []struct { - name string - mode tracer.SQLCommentInjectionMode - expectedInjectedTags sqltest.TagInjectionExpectation - }{ - { - name: "default (no injection)", - expectedInjectedTags: sqltest.TagInjectionExpectation{ - StaticTags: false, - DynamicTags: false, - }, - }, - { - name: "static tags injection", - mode: tracer.StaticTagsSQLCommentInjection, - expectedInjectedTags: sqltest.TagInjectionExpectation{ - StaticTags: true, - DynamicTags: false, - }, - }, - { - name: "dynamic tags injection", - mode: tracer.FullSQLCommentInjection, - expectedInjectedTags: sqltest.TagInjectionExpectation{ - StaticTags: true, - DynamicTags: true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // TODO: Rethink how to run that test now that the functionality is implemented in a propagator - // that we can't currently inject on the mocktracer - mockTracer := mocktracer.Start() - defer mockTracer.Stop() - - Register("postgres", &pq.Driver{}, WithServiceName("postgres-test")) - defer unregister("postgres") - - db, err := Open("postgres", "postgres://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable") - if err != nil { - log.Fatal(err) - } - defer db.Close() - - testConfig := &sqltest.Config{ - DB: db, - DriverName: "postgres", - TableName: tableName, - ExpectName: "postgres.query", - ExpectTags: map[string]interface{}{ - ext.ServiceName: "postgres-test", - ext.SpanType: ext.SpanTypeSQL, - ext.TargetHost: "127.0.0.1", - ext.TargetPort: "5432", - ext.DBUser: "postgres", - ext.DBName: "postgres", - }, - ExpectTagInjection: tc.expectedInjectedTags, - } - - sqltest.RunAll(t, testConfig) - }) - } -} - func TestMySQLUint64(t *testing.T) { Register("mysql", &mysql.MySQLDriver{}) db, err := Open("mysql", "test:test@tcp(127.0.0.1:3306)/test") diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index a8c3a4c131..0877d53ef1 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -85,6 +85,8 @@ func testConnect(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + + assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -105,6 +107,8 @@ func testPing(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + + assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -125,6 +129,7 @@ func testQuery(cfg *Config) func(*testing.T) { spans := cfg.mockTracer.FinishedSpans() var querySpan mocktracer.Span + expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans //connect, prepare and query @@ -136,7 +141,9 @@ func testQuery(cfg *Config) func(*testing.T) { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } querySpan = spans[2] - + // Since SQLServer runs execute statements by doing a prepare first, the expected comment + // excludes dynamic tags which can only be injected on non-prepared statements + expectedComment = "/*dde='test-env',ddsn='test-service',ddsv='v-test'*/" } else { assert.Len(spans, 2) querySpan = spans[1] @@ -148,8 +155,9 @@ func testQuery(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } - - assertInjectedComments(t, cfg, false) + comments := cfg.mockTracer.InjectedComments() + require.Len(t, comments, 1) + assert.Equal(expectedComment, comments[0]) } } @@ -181,7 +189,9 @@ func testStatement(cfg *Config) func(*testing.T) { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComments(t, cfg, true) + comments := cfg.mockTracer.InjectedComments() + require.Len(t, comments, 1) + assert.Equal("/*dde='test-env',ddsn='test-service',ddsv='v-test'*/", comments[0]) cfg.mockTracer.Reset() _, err2 := stmt.Exec("New York") @@ -230,6 +240,8 @@ func testBeginRollback(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + + assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -254,6 +266,7 @@ func testExec(cfg *Config) func(*testing.T) { parent.Finish() // flush children spans := cfg.mockTracer.FinishedSpans() + expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared exec so there are 2 extra spans for the exec: //prepare, exec, and then a close @@ -270,6 +283,9 @@ func testExec(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + // Since SQLServer runs execute statements by doing a prepare, the expected comment + // excludes dynamic tags which can only be injected on non-prepared statements + expectedComment = "/*dde='test-env',ddsn='test-service',ddsv='v-test'*/" } else { assert.Len(spans, 5) } @@ -290,7 +306,9 @@ func testExec(cfg *Config) func(*testing.T) { span = s } } - assertInjectedComments(t, cfg, false) + comments := cfg.mockTracer.InjectedComments() + require.Len(t, comments, 1) + assert.Equal(expectedComment, comments[0]) assert.NotNil(span, "span not found") cfg.ExpectTags["sql.query_type"] = "Commit" diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 173bc3c372..3fee860472 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -184,10 +184,17 @@ func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { } func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { - writer, ok := carrier.(tracer.TextMapWriter) - if !ok { + switch c := carrier.(type) { + case tracer.QueryCommentInjector: + return t.injectQueryComments(context, c) + case tracer.TextMapWriter: + return t.injectTextMap(context, c) + default: return tracer.ErrInvalidCarrier } +} + +func (t *mocktracer) injectTextMap(context ddtrace.SpanContext, writer tracer.TextMapWriter) error { ctx, ok := context.(*spanContext) if !ok || ctx.traceID == 0 || ctx.spanID == 0 { return tracer.ErrInvalidSpanContext @@ -203,3 +210,21 @@ func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) er }) return nil } + +func (t *mocktracer) injectQueryComments(context ddtrace.SpanContext, injector tracer.QueryCommentInjector) error { + ctx, ok := context.(*spanContext) + samplingPriority := 0 + if ok { + samplingPriority = ctx.samplingPriority() + } + injector.Set(tracer.ServiceVersionSQLCommentKey, "v-test") + injector.Set(tracer.ServiceEnvironmentSQLCommentKey, "test-env") + injector.Set(tracer.ServiceNameSQLCommentKey, "test-service") + injector.SetDynamicTag(tracer.TraceIDSQLCommentKey, "test-trace-id") + injector.SetDynamicTag(tracer.SpanIDSQLCommentKey, "test-span-id") + injector.SetDynamicTag(tracer.SamplingPrioritySQLCommentKey, strconv.Itoa(samplingPriority)) + // Save injected comments to assert the sql commenting behavior + commented, _ := injector.CommentQuery("") + t.injectedComments = append(t.injectedComments, commented) + return nil +} diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 03668b6755..abb0d43abc 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -34,9 +34,9 @@ const ( ServiceEnvironmentSQLCommentKey = "dde" ) -// QueryCommenter is a more specific interface implemented by carrier that implement the TextMapWriter +// QueryCommentInjector is a more specific interface implemented by carrier that implement the TextMapWriter // as well as CommentQuery and AddSpanID methods -type QueryCommenter interface { +type QueryCommentInjector interface { TextMapWriter SetDynamicTag(key, val string) CommentQuery(query string) (commented string, spanID uint64) @@ -61,15 +61,15 @@ func NewCommentPropagator(mode SQLCommentInjectionMode) *SQLCommentPropagator { func (p *SQLCommentPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - case QueryCommenter: + case QueryCommentInjector: return p.injectWithCommentCarrier(spanCtx, c) default: - // SQLCommentPropagator only handles QueryCommenter carriers + // SQLCommentPropagator only handles QueryCommentInjector carriers return nil } } -func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanContext, carrier QueryCommenter) error { +func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanContext, carrier QueryCommentInjector) error { if p.mode == CommentInjectionDisabled { return nil } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 71bc9c53f8..f0910cde4c 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -238,8 +238,8 @@ type propagator struct { func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - // QueryCommenter carriers are only supported by the SQLCommentPropagator - case QueryCommenter: + // QueryCommentInjector carriers are only supported by the SQLCommentPropagator + case QueryCommentInjector: return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) @@ -340,8 +340,8 @@ type propagatorB3 struct{} func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - // QueryCommenter carriers are only supported by the SQLCommentPropagator - case QueryCommenter: + // QueryCommentInjector carriers are only supported by the SQLCommentPropagator + case QueryCommentInjector: return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) From 19a749b970838605fc8113306e40596a4144b612 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 16:34:28 -0700 Subject: [PATCH 049/104] Add propagator configuration for sql comment injection mode --- ddtrace/tracer/option.go | 7 +++++-- ddtrace/tracer/sqlcomment.go | 8 ++++---- ddtrace/tracer/sqlcomment_test.go | 2 +- ddtrace/tracer/textmap.go | 12 +++++------- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 90dcd034d5..a47096d4d6 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -41,6 +41,9 @@ var ( // defaultMaxTagsHeaderLen specifies the default maximum length of the X-Datadog-Tags header value. defaultMaxTagsHeaderLen = 512 + + // defaultMaxTagsHeaderLen specifies the default sql comment injection mode. + defaultSQLCommentInjectionMode = CommentInjectionDisabled ) // config holds the tracer configuration. @@ -261,10 +264,10 @@ func newConfig(opts ...StartOption) *config { if c.transport == nil { c.transport = newHTTPTransport(c.agentAddr, c.httpClient) } - // TODO: add sql commenting mode to propagator config and add env variable to control the mode if c.propagator == nil { c.propagator = NewPropagator(&PropagatorConfig{ - MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), + MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), + SQLCommentInjectionMode: SQLCommentInjectionMode(internal.IntEnv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", int(defaultSQLCommentInjectionMode))), }) } if c.logger != nil { diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index abb0d43abc..c811077345 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -17,11 +17,11 @@ type SQLCommentInjectionMode int const ( // CommentInjectionDisabled represents the comment injection mode where all injection is disabled. - CommentInjectionDisabled SQLCommentInjectionMode = iota + CommentInjectionDisabled SQLCommentInjectionMode = 0 // StaticTagsSQLCommentInjection represents the comment injection mode where only static tags are injected. Static tags include values that are set once during the lifetime of an application: service name, env, version. - StaticTagsSQLCommentInjection + StaticTagsSQLCommentInjection SQLCommentInjectionMode = 1 // FullSQLCommentInjection represents the comment injection mode where both static and dynamic tags are injected. Dynamic tags include values like span id, trace id and sampling priority. - FullSQLCommentInjection + FullSQLCommentInjection SQLCommentInjectionMode = 2 ) // Values for sql comment keys @@ -55,7 +55,7 @@ func CommentWithDynamicTagsDiscarded(discard bool) SQLCommentCarrierOption { } } -func NewCommentPropagator(mode SQLCommentInjectionMode) *SQLCommentPropagator { +func NewSQLCommentPropagator(mode SQLCommentInjectionMode) *SQLCommentPropagator { return &SQLCommentPropagator{mode: mode} } diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 881776a338..51037e7908 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -56,7 +56,7 @@ func TestQueryTextCarrier(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - propagator := NewCommentPropagator(tc.mode) + propagator := NewSQLCommentPropagator(tc.mode) tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0"), WithPropagator(propagator)) root := tracer.StartSpan("db.call", WithSpanID(10), ServiceName("whiskey-db")).(*span) diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index f0910cde4c..39d644256f 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -120,6 +120,8 @@ type PropagatorConfig struct { // See https://github.com/openzipkin/b3-propagation B3 bool + // SQLCommentInjectionMode specifies the sql comment injection mode that will be used + // to inject tags. It defaults to CommentInjectionDisabled. SQLCommentInjectionMode SQLCommentInjectionMode } @@ -174,6 +176,9 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { if cfg.B3 { list = append(list, &propagatorB3{}) } + if cfg.SQLCommentInjectionMode > CommentInjectionDisabled { + list = append(list, NewSQLCommentPropagator(cfg.SQLCommentInjectionMode)) + } for _, v := range strings.Split(ps, ",") { switch strings.ToLower(v) { case "datadog": @@ -183,12 +188,6 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { // propagatorB3 hasn't already been added, add a new one. list = append(list, &propagatorB3{}) } - // TODO: read the sql commenting mode from env variable and/or config - case "sqlcommenter": - if !cfg.B3 { - // propagatorB3 hasn't already been added, add a new one. - list = append(list, &propagatorB3{}) - } default: log.Warn("unrecognized propagator: %s\n", v) } @@ -243,7 +242,6 @@ func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) er return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) - default: return ErrInvalidCarrier } From e9801686c853eb69487af2e8c8d9edfe4da204ff Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 22:10:21 -0700 Subject: [PATCH 050/104] Add godoc and better sql comment propagator tests --- contrib/database/sql/conn.go | 2 +- ddtrace/mocktracer/mocktracer.go | 4 +- ddtrace/tracer/sqlcomment.go | 122 ++++++++++++++++++------------ ddtrace/tracer/sqlcomment_test.go | 103 ++++++++++++++++--------- ddtrace/tracer/textmap.go | 8 +- 5 files changed, 150 insertions(+), 89 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 3b59dd4473..21d601479d 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -213,7 +213,7 @@ func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string spanContext = span.Context() } - sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.CommentWithDynamicTagsDiscarded(discardDynamicTags)) + sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.SQLCommentWithDynamicTagsDiscarded(discardDynamicTags)) err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { // this should never happen diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 3fee860472..694957f9f0 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -185,7 +185,7 @@ func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - case tracer.QueryCommentInjector: + case tracer.QueryCommentCarrier: return t.injectQueryComments(context, c) case tracer.TextMapWriter: return t.injectTextMap(context, c) @@ -211,7 +211,7 @@ func (t *mocktracer) injectTextMap(context ddtrace.SpanContext, writer tracer.Te return nil } -func (t *mocktracer) injectQueryComments(context ddtrace.SpanContext, injector tracer.QueryCommentInjector) error { +func (t *mocktracer) injectQueryComments(context ddtrace.SpanContext, injector tracer.QueryCommentCarrier) error { ctx, ok := context.(*spanContext) samplingPriority := 0 if ok { diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index c811077345..3168440cd0 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -24,7 +24,7 @@ const ( FullSQLCommentInjection SQLCommentInjectionMode = 2 ) -// Values for sql comment keys +// Key names for SQL comment tags. const ( SamplingPrioritySQLCommentKey = "ddsp" TraceIDSQLCommentKey = "ddtid" @@ -34,46 +34,65 @@ const ( ServiceEnvironmentSQLCommentKey = "dde" ) -// QueryCommentInjector is a more specific interface implemented by carrier that implement the TextMapWriter -// as well as CommentQuery and AddSpanID methods -type QueryCommentInjector interface { +// QueryCommentCarrier is a more specific interface implemented by carriers that implement the TextMapWriter +// as well as CommentQuery, AddSpanID and SetDynamicTag methods. It is compatible with Datadog's SQLCommentPropagator. +// Note that Datadog's TextMapPropagator is compatible with QueryCommentCarriers but only in the fact that it +// ignores it without returning errors. +type QueryCommentCarrier interface { TextMapWriter + + // SetDynamicTag sets the given dynamic key/value pair. This method is a variation on TextMapWriter's Set method + // that exists for dynamic tag values. SetDynamicTag(key, val string) + + // CommentQuery returns the given query with any injected tags prepended in the form of a sqlcommenter-formatted + // SQL comment. It also returns a non-zero span ID if a new span ID was generated as part of the inject operation. + // See https://google.github.io/sqlcommenter/spec for the full sqlcommenter spec. CommentQuery(query string) (commented string, spanID uint64) + + // AddSpanID adds a new span ID to the carrier. This happens if no existing trace is found when the + // injection happens. AddSpanID(spanID uint64) } // SQLCommentPropagator implements the Propagator interface to inject tags -// in sql comments +// in sql comments. It is only compatible with QueryCommentCarrier implementations +// but allows other carriers to flow through without returning errors. type SQLCommentPropagator struct { mode SQLCommentInjectionMode } -func CommentWithDynamicTagsDiscarded(discard bool) SQLCommentCarrierOption { +// SQLCommentWithDynamicTagsDiscarded enables control discarding dynamic tags on a SQLCommentCarrier. +// Its main purpose is to allow discarding dynamic tags per SQL operation when they aren't relevant +// (i.e. Prepared statements). +func SQLCommentWithDynamicTagsDiscarded(discard bool) SQLCommentCarrierOption { return func(c *SQLCommentCarrierConfig) { c.discardDynamicTags = discard } } +// NewSQLCommentPropagator returns a new SQLCommentPropagator with the given injection mode func NewSQLCommentPropagator(mode SQLCommentInjectionMode) *SQLCommentPropagator { return &SQLCommentPropagator{mode: mode} } +// Inject injects the span context in the given carrier. Note that it is only compatible +// with QueryCommentCarriers and no-ops if the carrier is of any other type. func (p *SQLCommentPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - case QueryCommentInjector: + case QueryCommentCarrier: return p.injectWithCommentCarrier(spanCtx, c) default: - // SQLCommentPropagator only handles QueryCommentInjector carriers + // SQLCommentPropagator only handles QueryCommentCarrier carriers but lets any other carrier + // flow through without returning errors return nil } } -func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanContext, carrier QueryCommentInjector) error { +func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanContext, carrier QueryCommentCarrier) error { if p.mode == CommentInjectionDisabled { return nil } - if p.mode == StaticTagsSQLCommentInjection || p.mode == FullSQLCommentInjection { ctx, ok := spanCtx.(*spanContext) var env, pversion string @@ -97,18 +116,20 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont } if p.mode == FullSQLCommentInjection { samplingPriority := 0 - ctx, _ := spanCtx.(*spanContext) - if sp, ok := ctx.samplingPriority(); ok { - samplingPriority = sp - } var traceID, spanID uint64 - if ctx.TraceID() > 0 { - traceID = ctx.TraceID() - } - if ctx.SpanID() > 0 { - spanID = ctx.SpanID() - } + ctx, ok := spanCtx.(*spanContext) + if ok { + if sp, ok := ctx.samplingPriority(); ok { + samplingPriority = sp + } + if ctx.TraceID() > 0 { + traceID = ctx.TraceID() + } + if ctx.SpanID() > 0 { + spanID = ctx.SpanID() + } + } if spanID == 0 { spanID = random.Uint64() carrier.AddSpanID(spanID) @@ -123,6 +144,7 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont return nil } +// Extract is not implemented for the SQLCommentPropagator. func (p *SQLCommentPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { return nil, fmt.Errorf("not implemented") } @@ -132,16 +154,18 @@ type SQLCommentCarrierConfig struct { discardDynamicTags bool } +// SQLCommentCarrierOption represents a function that can be provided as a parameter to NewSQLCommentCarrier. type SQLCommentCarrierOption func(c *SQLCommentCarrierConfig) -// SQLCommentCarrier holds tags to be serialized as a SQL Comment +// SQLCommentCarrier implements QueryCommentCarrier by holding tags, configuration +// and a potential new span id to generate a SQL comment injected in queries. type SQLCommentCarrier struct { tags map[string]string cfg SQLCommentCarrierConfig spanID uint64 } -// NewSQLCommentCarrier returns a new SQLCommentCarrier +// NewSQLCommentCarrier returns a new SQLCommentCarrier. func NewSQLCommentCarrier(opts ...SQLCommentCarrierOption) (s *SQLCommentCarrier) { s = new(SQLCommentCarrier) for _, apply := range opts { @@ -151,7 +175,8 @@ func NewSQLCommentCarrier(opts ...SQLCommentCarrierOption) (s *SQLCommentCarrier return s } -// Set implements TextMapWriter. +// Set implements TextMapWriter. In the context of SQL comment injection, this method +// is used for static tags only. See SetDynamicTag for dynamic tags. func (c *SQLCommentCarrier) Set(key, val string) { if c.tags == nil { c.tags = make(map[string]string) @@ -159,6 +184,18 @@ func (c *SQLCommentCarrier) Set(key, val string) { c.tags[key] = val } +// ForeachKey implements TextMapReader. +func (c SQLCommentCarrier) ForeachKey(handler func(key, val string) error) error { + for k, v := range c.tags { + if err := handler(k, v); err != nil { + return err + } + } + return nil +} + +// SetDynamicTag implements QueryCommentCarrier. This method is used to inject dynamic tags only +// (i.e. span id, trace id, sampling priority). See Set for static tags. func (c *SQLCommentCarrier) SetDynamicTag(key, val string) { if c.cfg.discardDynamicTags { return @@ -169,29 +206,15 @@ func (c *SQLCommentCarrier) SetDynamicTag(key, val string) { c.tags[key] = val } +// AddSpanID implements QueryCommentCarrier and is used to save a span id generated during SQL comment injection +// in the case where there is no active trace. func (c *SQLCommentCarrier) AddSpanID(spanID uint64) { c.spanID = spanID } -func (c *SQLCommentCarrier) SpanID() uint64 { - return c.spanID -} - -func commentWithTags(tags map[string]string) (comment string) { - if len(tags) == 0 { - return "" - } - serializedTags := make([]string, 0, len(tags)) - for k, v := range tags { - serializedTags = append(serializedTags, serializeTag(k, v)) - } - sort.Strings(serializedTags) - comment = strings.Join(serializedTags, ",") - return fmt.Sprintf("/*%s*/", comment) -} - // CommentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a -// prepended SQL comment +// prepended SQL comment. The format of the comment follows the sqlcommenter spec. +// See https://google.github.io/sqlcommenter/spec/ for more details. func (c *SQLCommentCarrier) CommentQuery(query string) (commented string, spanID uint64) { comment := commentWithTags(c.tags) if comment == "" { @@ -203,14 +226,17 @@ func (c *SQLCommentCarrier) CommentQuery(query string) (commented string, spanID return fmt.Sprintf("%s %s", comment, query), c.spanID } -// ForeachKey implements TextMapReader. -func (c SQLCommentCarrier) ForeachKey(handler func(key, val string) error) error { - for k, v := range c.tags { - if err := handler(k, v); err != nil { - return err - } +func commentWithTags(tags map[string]string) (comment string) { + if len(tags) == 0 { + return "" } - return nil + serializedTags := make([]string, 0, len(tags)) + for k, v := range tags { + serializedTags = append(serializedTags, serializeTag(k, v)) + } + sort.Strings(serializedTags) + comment = strings.Join(serializedTags, ",") + return fmt.Sprintf("/*%s*/", comment) } func serializeTag(key string, value string) (serialized string) { diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 51037e7908..0ba72c36e6 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -1,6 +1,9 @@ package tracer import ( + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "strconv" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -9,48 +12,77 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" ) -func TestQueryTextCarrier(t *testing.T) { +func TestSQLCommentPropagator(t *testing.T) { + prepareSpanContextWithSpanID := func(tracer *tracer) ddtrace.SpanContext { + root := tracer.StartSpan("db.call", WithSpanID(10), ServiceName("whiskey-db")).(*span) + root.SetTag(ext.SamplingPriority, 2) + return root.Context() + } + testCases := []struct { - name string - query string - mode SQLCommentInjectionMode - carrierOpts []SQLCommentCarrierOption - commented string + name string + query string + mode SQLCommentInjectionMode + carrierOpts []SQLCommentCarrierOption + prepareSpanContext func(*tracer) ddtrace.SpanContext + expectedQuery string + expectedSpanIDGen bool }{ { - name: "all tags injected", + name: "all tags injected", + query: "SELECT * from FOO", + mode: FullSQLCommentInjection, + carrierOpts: nil, + prepareSpanContext: prepareSpanContextWithSpanID, + expectedQuery: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", + expectedSpanIDGen: false, + }, + { + name: "no existing trace", query: "SELECT * from FOO", mode: FullSQLCommentInjection, carrierOpts: nil, - commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", + prepareSpanContext: func(tracer *tracer) ddtrace.SpanContext { + return nil + }, + expectedQuery: "/*ddsid='',ddsn='whiskey-service',ddsp='0',ddtid=''*/ SELECT * from FOO", + expectedSpanIDGen: true, }, { - name: "empty query, all tags injected", - query: "", - mode: FullSQLCommentInjection, - carrierOpts: nil, - commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/", + name: "empty query, all tags injected", + query: "", + mode: FullSQLCommentInjection, + carrierOpts: nil, + prepareSpanContext: prepareSpanContextWithSpanID, + expectedQuery: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/", + expectedSpanIDGen: false, }, { - name: "query with existing comment", - query: "SELECT * from FOO -- test query", - mode: FullSQLCommentInjection, - carrierOpts: nil, - commented: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", + name: "query with existing comment", + query: "SELECT * from FOO -- test query", + mode: FullSQLCommentInjection, + carrierOpts: nil, + prepareSpanContext: prepareSpanContextWithSpanID, + expectedQuery: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", + expectedSpanIDGen: false, }, { - name: "discard dynamic tags", - query: "SELECT * from FOO", - mode: FullSQLCommentInjection, - carrierOpts: []SQLCommentCarrierOption{CommentWithDynamicTagsDiscarded(true)}, - commented: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", + name: "discard dynamic tags", + query: "SELECT * from FOO", + mode: FullSQLCommentInjection, + carrierOpts: []SQLCommentCarrierOption{SQLCommentWithDynamicTagsDiscarded(true)}, + prepareSpanContext: prepareSpanContextWithSpanID, + expectedQuery: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", + expectedSpanIDGen: false, }, { - name: "static tags only mode", - query: "SELECT * from FOO", - mode: StaticTagsSQLCommentInjection, - carrierOpts: nil, - commented: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", + name: "static tags only mode", + query: "SELECT * from FOO", + mode: StaticTagsSQLCommentInjection, + carrierOpts: nil, + prepareSpanContext: prepareSpanContextWithSpanID, + expectedQuery: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", + expectedSpanIDGen: false, }, } @@ -59,17 +91,20 @@ func TestQueryTextCarrier(t *testing.T) { propagator := NewSQLCommentPropagator(tc.mode) tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0"), WithPropagator(propagator)) - root := tracer.StartSpan("db.call", WithSpanID(10), ServiceName("whiskey-db")).(*span) - root.SetTag(ext.SamplingPriority, 2) - ctx := root.Context() - + ctx := tc.prepareSpanContext(tracer) carrier := NewSQLCommentCarrier(tc.carrierOpts...) err := tracer.Inject(ctx, carrier) require.NoError(t, err) commented, spanID := carrier.CommentQuery(tc.query) - assert.Equal(t, tc.commented, commented) - assert.Equal(t, uint64(0), spanID) + if tc.expectedSpanIDGen { + assert.Greater(t, spanID, uint64(0)) + expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(spanID, 10)) + assert.Equal(t, expected, commented) + } else { + assert.Equal(t, uint64(0), spanID) + assert.Equal(t, tc.expectedQuery, commented) + } }) } } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 39d644256f..b61629191e 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -237,8 +237,8 @@ type propagator struct { func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - // QueryCommentInjector carriers are only supported by the SQLCommentPropagator - case QueryCommentInjector: + // QueryCommentCarrier carriers are only supported by the SQLCommentPropagator + case QueryCommentCarrier: return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) @@ -338,8 +338,8 @@ type propagatorB3 struct{} func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - // QueryCommentInjector carriers are only supported by the SQLCommentPropagator - case QueryCommentInjector: + // QueryCommentCarrier carriers are only supported by the SQLCommentPropagator + case QueryCommentCarrier: return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) From 1b08ad0208becf173976cdcece9dc3c7a996437f Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 22:14:21 -0700 Subject: [PATCH 051/104] Formatting / Styling Fixes --- contrib/database/sql/conn.go | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 21d601479d..dd4258ca4e 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -60,8 +60,8 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) - stmt, err := connPrepareCtx.PrepareContext(ctx, commentedQuery) + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) if spanID > 0 { tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) } else { @@ -73,8 +73,8 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) - stmt, err = tc.Prepare(commentedQuery) + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + stmt, err = tc.Prepare(cquery) if spanID > 0 { tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) } else { @@ -90,8 +90,8 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) - r, err := execContext.ExecContext(ctx, commentedQuery, args) + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + r, err := execContext.ExecContext(ctx, cquery, args) if spanID > 0 { tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) } else { @@ -109,8 +109,8 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) - r, err = execer.Exec(commentedQuery, dargs) + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + r, err = execer.Exec(cquery, dargs) if spanID > 0 { tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) } else { @@ -138,8 +138,8 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, false) - rows, err := queryerContext.QueryContext(ctx, commentedQuery, args) + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + rows, err := queryerContext.QueryContext(ctx, cquery, args) if spanID > 0 { tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) } else { @@ -157,8 +157,8 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - commentedQuery, spanID := tc.withSQLCommentsInjected(ctx, query, true) - rows, err = queryer.Query(commentedQuery, dargs) + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + rows, err = queryer.Query(cquery, dargs) if spanID > 0 { tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) } else { @@ -207,24 +207,22 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // withSQLCommentsInjected will return the query with sql comments injected according to the comment injection mode along // with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating // the span following the traced database call. -func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (commentedQuery string, injectedSpanID uint64) { +func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (cquery string, injectedSpanID uint64) { var spanContext ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() } - sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.SQLCommentWithDynamicTagsDiscarded(discardDynamicTags)) err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { // this should never happen log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } - return sqlCommentCarrier.CommentQuery(query) } // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, startSpanOptions ...ddtrace.StartSpanOption) { +func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, spanOpts ...ddtrace.StartSpanOption) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -236,7 +234,7 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri return } name := fmt.Sprintf("%s.query", tp.driverName) - opts := append(startSpanOptions, + opts := append(spanOpts, tracer.ServiceName(tp.cfg.serviceName), tracer.SpanType(ext.SpanTypeSQL), tracer.StartTime(startTime), From 3d973d53336294bb6d5cf0fe04a1ace9a4030bc7 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 22:15:18 -0700 Subject: [PATCH 052/104] Reorganize imports --- contrib/database/sql/sql_test.go | 7 ++++--- contrib/internal/sqltest/sqltest.go | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 5ccecf7a89..9022305927 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -16,13 +16,14 @@ import ( "testing" "time" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" ) // tableName holds the SQL table that these tests will be run against. It must be unique cross-repo. diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 0877d53ef1..93b0a344bb 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -12,11 +12,12 @@ import ( "log" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // Prepare sets up a table with the given name in both the MySQL and Postgres databases and returns From d70dd5d0b43c53766d17a56e83b85b5e21f13030 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 22:20:17 -0700 Subject: [PATCH 053/104] Remove obsolete code --- contrib/internal/sqltest/sqltest.go | 54 ++++------------------------- ddtrace/ddtrace.go | 25 ------------- 2 files changed, 6 insertions(+), 73 deletions(-) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 93b0a344bb..30823f3aea 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -86,7 +86,6 @@ func testConnect(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -108,7 +107,6 @@ func testPing(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -241,7 +239,6 @@ func testBeginRollback(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -319,39 +316,6 @@ func testExec(cfg *Config) func(*testing.T) { } } -func assertInjectedComments(t *testing.T, cfg *Config, discardDynamicTags bool) { - c := cfg.mockTracer.InjectedComments() - carrier := tracer.SQLCommentCarrier{} - for k, v := range expectedInjectedTags(cfg, discardDynamicTags) { - carrier.Set(k, v) - } - - if !cfg.ExpectTagInjection.StaticTags && !cfg.ExpectTagInjection.DynamicTags { - assert.Len(t, c, 0) - } else { - require.Len(t, c, 1) - commented, spanID := carrier.CommentQuery("") - assert.Equal(t, commented, c[0]) - assert.Greater(t, spanID, 0) - } -} - -func expectedInjectedTags(cfg *Config, discardDynamicTags bool) map[string]string { - tags := make(map[string]string) - // Prepare statements should never have dynamic tags injected so we only check if static tags are expected - if cfg.ExpectTagInjection.StaticTags { - tags[tracer.ServiceNameSQLCommentKey] = "test-service" - tags[tracer.ServiceEnvironmentSQLCommentKey] = "test-env" - tags[tracer.ServiceVersionSQLCommentKey] = "v-test" - } - if cfg.ExpectTagInjection.DynamicTags && !discardDynamicTags { - tags[tracer.SamplingPrioritySQLCommentKey] = "0" - tags[tracer.TraceIDSQLCommentKey] = "test-trace-id" - tags[tracer.SpanIDSQLCommentKey] = "test-span-id" - } - return tags -} - func verifyConnectSpan(span mocktracer.Span, assert *assert.Assertions, cfg *Config) { assert.Equal(cfg.ExpectName, span.OperationName()) cfg.ExpectTags["sql.query_type"] = "Connect" @@ -360,19 +324,13 @@ func verifyConnectSpan(span mocktracer.Span, assert *assert.Assertions, cfg *Con } } -// TagInjectionExpectation holds expectations relating to tag injection -type TagInjectionExpectation struct { - StaticTags bool - DynamicTags bool -} - // Config holds the test configuration. type Config struct { *sql.DB - mockTracer mocktracer.Tracer - DriverName string - TableName string - ExpectName string - ExpectTags map[string]interface{} - ExpectTagInjection TagInjectionExpectation + mockTracer mocktracer.Tracer + DriverName string + TableName string + ExpectName string + ExpectTags map[string]interface{} + ExpectTags map[string]interface{} } diff --git a/ddtrace/ddtrace.go b/ddtrace/ddtrace.go index 092e03d1e2..06a1732fc1 100644 --- a/ddtrace/ddtrace.go +++ b/ddtrace/ddtrace.go @@ -135,28 +135,3 @@ type Logger interface { // Log prints the given message. Log(msg string) } - -// InjectionConfig holds the configuration for injection a span into a carrier. It is usually passed -// around by reference to one or more InjectionOption functions which shape it into its -// final form. -type InjectionConfig struct { - // TraceIDKey defines the key to use to inject the trade id. The trace id is only injected if this value. - // is not empty - TraceIDKey string - // SpanIDKey defines the key to use to inject the span id. The span id is only injected if this value. - // is not empty - SpanIDKey string - // SamplingPriorityKey defines the key to use to inject the sampling priority. The sampling priority is only - // injected if this value is not empty. - SamplingPriorityKey string - // ServiceNameKey defines the key to use to inject the service name. The service name is only. - // injected if this value is not empty - ServiceNameKey string - // EnvKey defines the key to use to inject the environment. The environment is only injected if this value is not. - // empty - EnvKey string - // ParentVersionKey defines the key to use to inject the version. The version is only injected if this value is not empty. - ParentVersionKey string - // SpanID defines the span id value to use for injection. If not present, the span id value from the SpanContext is used - SpanID uint64 -} From 25131c03582424447144b3d7cde225c1c069b076 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 22:21:45 -0700 Subject: [PATCH 054/104] More reorganization of imports --- ddtrace/tracer/sqlcomment_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 0ba72c36e6..f96c772b68 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -1,15 +1,15 @@ package tracer import ( - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "strconv" "strings" "testing" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" ) func TestSQLCommentPropagator(t *testing.T) { From b1c9579a0a9721e94d86a2735096765ff3baeddc Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 25 May 2022 22:32:09 -0700 Subject: [PATCH 055/104] Remove accidental duplicated line --- contrib/internal/sqltest/sqltest.go | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 30823f3aea..893a680231 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -332,5 +332,4 @@ type Config struct { TableName string ExpectName string ExpectTags map[string]interface{} - ExpectTags map[string]interface{} } From ecacc6dd43b4541f442e216d18e34a89802eb42e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 10:13:53 -0700 Subject: [PATCH 056/104] Add missing copyright headers --- ddtrace/tracer/sqlcomment.go | 5 +++++ ddtrace/tracer/sqlcomment_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 3168440cd0..a2636d3a89 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -1,3 +1,8 @@ +// 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 tracer import ( diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index f96c772b68..689b098ad0 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -1,3 +1,8 @@ +// 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 tracer import ( From ab995ec0102f4f91af7e22c8c4ef540bde5a8515 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 12:05:57 -0700 Subject: [PATCH 057/104] Make SQLCommentPropagator be a no-op on Extract --- ddtrace/tracer/sqlcomment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index a2636d3a89..adaf754284 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -149,9 +149,9 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont return nil } -// Extract is not implemented for the SQLCommentPropagator. +// Extract is a no-op for the SQLCommentPropagator. func (p *SQLCommentPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { - return nil, fmt.Errorf("not implemented") + return nil, nil } // SQLCommentCarrierConfig holds configuration for a SQLCommentCarrier From 32a0a31d578741ed02b7c6b2ab1380bdc2a5e4f4 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 14:42:53 -0700 Subject: [PATCH 058/104] Fix getPropagators not including SQLCommentPropagator --- ddtrace/tracer/sqlcomment_test.go | 4 ++-- ddtrace/tracer/textmap.go | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 689b098ad0..cb4d511b8e 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -93,8 +93,8 @@ func TestSQLCommentPropagator(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - propagator := NewSQLCommentPropagator(tc.mode) - tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0"), WithPropagator(propagator)) + p := NewPropagator(&PropagatorConfig{SQLCommentInjectionMode: tc.mode}) + tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0"), WithPropagator(p)) ctx := tc.prepareSpanContext(tracer) carrier := NewSQLCommentCarrier(tc.carrierOpts...) diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index b61629191e..493c22de77 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -169,6 +169,9 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { if cfg.B3 { defaultPs = append(defaultPs, &propagatorB3{}) } + if cfg.SQLCommentInjectionMode > CommentInjectionDisabled { + defaultPs = append(defaultPs, NewSQLCommentPropagator(cfg.SQLCommentInjectionMode)) + } if ps == "" { return defaultPs } From 0f4c463b87e0c31e403acf0849584c1b50e80cbb Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 14:45:10 -0700 Subject: [PATCH 059/104] Revert comma removal --- contrib/internal/sqltest/sqltest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 893a680231..a31180e696 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -131,7 +131,7 @@ func testQuery(cfg *Config) func(*testing.T) { expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans - //connect, prepare and query + //connect, prepare, and query assert.Len(spans, 3) span := spans[1] cfg.ExpectTags["sql.query_type"] = "Prepare" From 28c1b593cb73e77136082dc771ab054665ab22f7 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 26 May 2022 14:44:23 -0700 Subject: [PATCH 060/104] Update ddtrace/tracer/option.go Co-authored-by: Andrew Glaude --- ddtrace/tracer/option.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index a47096d4d6..6d27d727a9 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -42,7 +42,7 @@ var ( // defaultMaxTagsHeaderLen specifies the default maximum length of the X-Datadog-Tags header value. defaultMaxTagsHeaderLen = 512 - // defaultMaxTagsHeaderLen specifies the default sql comment injection mode. + // defaultSQLCommentInjectionMode specifies the default sql comment injection mode. defaultSQLCommentInjectionMode = CommentInjectionDisabled ) From 423a065fe86795b70100997758aafc7c528cd938 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 15:04:46 -0700 Subject: [PATCH 061/104] Make spancontext.meta thread safe --- ddtrace/tracer/spancontext.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ddtrace/tracer/spancontext.go b/ddtrace/tracer/spancontext.go index a001f34d89..f19bc7e021 100644 --- a/ddtrace/tracer/spancontext.go +++ b/ddtrace/tracer/spancontext.go @@ -128,6 +128,8 @@ func (c *spanContext) baggageItem(key string) string { } func (c *spanContext) meta(key string) (val string, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() val, ok = c.span.Meta[key] return val, ok } From 182b92ad10ffe7419098c04efcd5109271eae4f5 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 15:05:00 -0700 Subject: [PATCH 062/104] Update comment around setting of ParentVersion --- ddtrace/tracer/tracer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index 96b3ad175f..c6ee1b8ee7 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -426,8 +426,8 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt if t.config.universalVersion || (!t.config.universalVersion && span.Service == t.config.serviceName) { span.setMeta(ext.Version, t.config.version) } - // If the span has a different service than the global service, attribute and set the config version as the - // parent version + // For SQL spans which have a different database-inferred service name, set the service version as + // the parent version to avoid confusion with the database service's version if span.Service != t.config.serviceName { span.setMeta(ext.ParentVersion, t.config.version) } From 165f6e570b171d19af688cdcdb9060844ed0ceb8 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 18:22:50 -0700 Subject: [PATCH 063/104] Fix sql comment span id being the parent span id instead of the SQL span's. --- contrib/database/sql/conn.go | 4 ++++ ddtrace/ext/tags.go | 7 +------ ddtrace/tracer/sqlcomment.go | 22 +++++++++------------- ddtrace/tracer/sqlcomment_test.go | 26 +++++++++++--------------- ddtrace/tracer/tracer.go | 5 ----- 5 files changed, 25 insertions(+), 39 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index dd4258ca4e..6e0a7b84b3 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -208,6 +208,10 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating // the span following the traced database call. func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (cquery string, injectedSpanID uint64) { + // The sql span only gets created after the call to the database because we need to be able to skip spans + // when a driver returns driver.ErrSkip. In order to work with those constraints, the parent span is used + // during SQL comment injection and a new span ID is generated for the sql span and used later when/if the span + // gets created. var spanContext ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() diff --git a/ddtrace/ext/tags.go b/ddtrace/ext/tags.go index 8c627a8380..6cb72ffb37 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -52,12 +52,7 @@ const ( // Version is a tag that specifies the current application version. Version = "version" - - // ParentVersion is a tag that specifies the parent's application version - // (i.e. for database service spans, this would be the version of the running application rather than - // the database version). - ParentVersion = "parent.version" - + // ResourceName defines the Resource name for the Span. ResourceName = "resource.name" diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index adaf754284..f815a0a73c 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -98,15 +98,18 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont if p.mode == CommentInjectionDisabled { return nil } + spanID := random.Uint64() + carrier.AddSpanID(spanID) + if p.mode == StaticTagsSQLCommentInjection || p.mode == FullSQLCommentInjection { ctx, ok := spanCtx.(*spanContext) - var env, pversion string + var env, version string if ok { if e, ok := ctx.meta(ext.Environment); ok { env = e } - if version, ok := ctx.meta(ext.ParentVersion); ok { - pversion = version + if v, ok := ctx.meta(ext.Version); ok { + version = v } } if globalconfig.ServiceName() != "" { @@ -115,14 +118,14 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont if env != "" { carrier.Set(ServiceEnvironmentSQLCommentKey, env) } - if pversion != "" { - carrier.Set(ServiceVersionSQLCommentKey, pversion) + if version != "" { + carrier.Set(ServiceVersionSQLCommentKey, version) } } if p.mode == FullSQLCommentInjection { samplingPriority := 0 - var traceID, spanID uint64 + var traceID uint64 ctx, ok := spanCtx.(*spanContext) if ok { if sp, ok := ctx.samplingPriority(); ok { @@ -131,13 +134,6 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont if ctx.TraceID() > 0 { traceID = ctx.TraceID() } - if ctx.SpanID() > 0 { - spanID = ctx.SpanID() - } - } - if spanID == 0 { - spanID = random.Uint64() - carrier.AddSpanID(spanID) } if traceID == 0 { traceID = spanID diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index cb4d511b8e..cabdd0ead5 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -19,7 +19,7 @@ import ( func TestSQLCommentPropagator(t *testing.T) { prepareSpanContextWithSpanID := func(tracer *tracer) ddtrace.SpanContext { - root := tracer.StartSpan("db.call", WithSpanID(10), ServiceName("whiskey-db")).(*span) + root := tracer.StartSpan("service.calling.db", WithSpanID(10)).(*span) root.SetTag(ext.SamplingPriority, 2) return root.Context() } @@ -39,8 +39,8 @@ func TestSQLCommentPropagator(t *testing.T) { mode: FullSQLCommentInjection, carrierOpts: nil, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", - expectedSpanIDGen: false, + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", + expectedSpanIDGen: true, }, { name: "no existing trace", @@ -59,8 +59,8 @@ func TestSQLCommentPropagator(t *testing.T) { mode: FullSQLCommentInjection, carrierOpts: nil, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/", - expectedSpanIDGen: false, + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/", + expectedSpanIDGen: true, }, { name: "query with existing comment", @@ -68,8 +68,8 @@ func TestSQLCommentPropagator(t *testing.T) { mode: FullSQLCommentInjection, carrierOpts: nil, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='10',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", - expectedSpanIDGen: false, + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", + expectedSpanIDGen: true, }, { name: "discard dynamic tags", @@ -102,14 +102,10 @@ func TestSQLCommentPropagator(t *testing.T) { require.NoError(t, err) commented, spanID := carrier.CommentQuery(tc.query) - if tc.expectedSpanIDGen { - assert.Greater(t, spanID, uint64(0)) - expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(spanID, 10)) - assert.Equal(t, expected, commented) - } else { - assert.Equal(t, uint64(0), spanID) - assert.Equal(t, tc.expectedQuery, commented) - } + + assert.Greater(t, spanID, uint64(0)) + expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(spanID, 10)) + assert.Equal(t, expected, commented) }) } } diff --git a/ddtrace/tracer/tracer.go b/ddtrace/tracer/tracer.go index c6ee1b8ee7..ee32b00efb 100644 --- a/ddtrace/tracer/tracer.go +++ b/ddtrace/tracer/tracer.go @@ -426,11 +426,6 @@ func (t *tracer) StartSpan(operationName string, options ...ddtrace.StartSpanOpt if t.config.universalVersion || (!t.config.universalVersion && span.Service == t.config.serviceName) { span.setMeta(ext.Version, t.config.version) } - // For SQL spans which have a different database-inferred service name, set the service version as - // the parent version to avoid confusion with the database service's version - if span.Service != t.config.serviceName { - span.setMeta(ext.ParentVersion, t.config.version) - } } if t.config.env != "" { span.setMeta(ext.Environment, t.config.env) From 98730b92d7f2078b6ac7ffebadbf1d0c91d61baa Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 26 May 2022 19:07:02 -0700 Subject: [PATCH 064/104] Fix linting error --- ddtrace/ext/tags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/ext/tags.go b/ddtrace/ext/tags.go index 6cb72ffb37..767bd4ea7c 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -52,7 +52,7 @@ const ( // Version is a tag that specifies the current application version. Version = "version" - + // ResourceName defines the Resource name for the Span. ResourceName = "resource.name" From 53f7bfffddea4df7fa4402e7e1ffa1887f94fb82 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 31 May 2022 08:10:47 -0700 Subject: [PATCH 065/104] Update comments to reflect latest implementation --- contrib/database/sql/conn.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 6e0a7b84b3..bcdb6fef12 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -209,8 +209,8 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // the span following the traced database call. func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (cquery string, injectedSpanID uint64) { // The sql span only gets created after the call to the database because we need to be able to skip spans - // when a driver returns driver.ErrSkip. In order to work with those constraints, the parent span is used - // during SQL comment injection and a new span ID is generated for the sql span and used later when/if the span + // when a driver returns driver.ErrSkip. In order to work with those constraints, a new span id is generated and + // used during SQL comment injection and returned for the sql span to be used later when/if the span // gets created. var spanContext ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { @@ -219,8 +219,8 @@ func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.SQLCommentWithDynamicTagsDiscarded(discardDynamicTags)) err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { - // this should never happen - log.Warn("contrib/database/sql: failed to inject query comments: %v", err) + // This should happen only if the SQLCommentPropagator is not set via the sql comment injection mode. + log.Warn("contrib/database/sql: failed to inject query comments. Make sure you've set up SQLCommentInjectionMode on the propagator configuration: %v", err) } return sqlCommentCarrier.CommentQuery(query) } From 940cca65452aac6107246121b476b0af9c4f9c51 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 31 May 2022 08:28:29 -0700 Subject: [PATCH 066/104] Update condition on sql comment injection mode --- ddtrace/tracer/textmap.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 493c22de77..f0347d0ae7 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -169,7 +169,7 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { if cfg.B3 { defaultPs = append(defaultPs, &propagatorB3{}) } - if cfg.SQLCommentInjectionMode > CommentInjectionDisabled { + if cfg.SQLCommentInjectionMode != CommentInjectionDisabled { defaultPs = append(defaultPs, NewSQLCommentPropagator(cfg.SQLCommentInjectionMode)) } if ps == "" { @@ -179,7 +179,7 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { if cfg.B3 { list = append(list, &propagatorB3{}) } - if cfg.SQLCommentInjectionMode > CommentInjectionDisabled { + if cfg.SQLCommentInjectionMode != CommentInjectionDisabled { list = append(list, NewSQLCommentPropagator(cfg.SQLCommentInjectionMode)) } for _, v := range strings.Split(ps, ",") { From 83db4a1274265c2f8f8c285779f5d625d71daba4 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 2 Jun 2022 11:18:44 -0700 Subject: [PATCH 067/104] Update ddtrace/tracer/sqlcomment.go Co-authored-by: Andrew Glaude --- ddtrace/tracer/sqlcomment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index f815a0a73c..3d4f80d143 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -150,7 +150,7 @@ func (p *SQLCommentPropagator) Extract(carrier interface{}) (ddtrace.SpanContext return nil, nil } -// SQLCommentCarrierConfig holds configuration for a SQLCommentCarrier +// SQLCommentCarrierConfig holds configuration for a SQLCommentCarrier. type SQLCommentCarrierConfig struct { discardDynamicTags bool } From c7aeb0e4504c402c35a578b5743011f04ff2b85c Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 2 Jun 2022 11:19:05 -0700 Subject: [PATCH 068/104] Update ddtrace/tracer/sqlcomment.go Co-authored-by: Andrew Glaude --- ddtrace/tracer/sqlcomment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 3d4f80d143..9b55fc2876 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -196,7 +196,7 @@ func (c SQLCommentCarrier) ForeachKey(handler func(key, val string) error) error } // SetDynamicTag implements QueryCommentCarrier. This method is used to inject dynamic tags only -// (i.e. span id, trace id, sampling priority). See Set for static tags. +// (e.g. span id, trace id, sampling priority). See Set for static tags. func (c *SQLCommentCarrier) SetDynamicTag(key, val string) { if c.cfg.discardDynamicTags { return From 0cbb92dd0c4ad47e5d928bf4645d97a41d8fd68d Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 2 Jun 2022 11:25:29 -0700 Subject: [PATCH 069/104] Address last PR feedback --- contrib/database/sql/conn.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index bcdb6fef12..42e1e34548 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -59,8 +59,8 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() + cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) if spanID > 0 { tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) @@ -73,7 +73,6 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) stmt, err = tc.Prepare(cquery) if spanID > 0 { tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) @@ -126,12 +125,8 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { start := time.Now() if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) - tc.tryTrace(ctx, queryTypePing, "", start, err) - if err != nil { - return err - } } - + tc.tryTrace(ctx, queryTypePing, "", start, err) return err } From 733dd7ba3a22f342a5dac6b257e2700781ed2729 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 09:14:53 -0700 Subject: [PATCH 070/104] Remove custom propagator --- contrib/database/sql/conn.go | 61 ++--- contrib/database/sql/option.go | 20 +- contrib/database/sql/sql_test.go | 4 + contrib/internal/sqltest/sqltest.go | 56 +++-- ddtrace/mocktracer/mocktracer.go | 42 +--- ddtrace/tracer/option.go | 12 +- ddtrace/tracer/sqlcomment.go | 338 +++++++++++++++------------- ddtrace/tracer/sqlcomment_test.go | 47 ++-- ddtrace/tracer/textmap.go | 27 +-- 9 files changed, 307 insertions(+), 300 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 42e1e34548..30b8f259ff 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -59,14 +59,10 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + cquery, spanID := tc.injectComments(ctx, query, true) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) - if spanID > 0 { - tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) - } else { - tc.tryTrace(ctx, queryTypePrepare, query, start, err) - } + tc.tryTrace(ctx, queryTypePrepare, cquery, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } @@ -74,11 +70,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } stmt, err = tc.Prepare(cquery) - if spanID > 0 { - tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) - } else { - tc.tryTrace(ctx, queryTypePrepare, query, start, err) - } + tc.tryTrace(ctx, queryTypePrepare, cquery, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } @@ -89,13 +81,9 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + cquery, spanID := tc.injectComments(ctx, query, false) r, err := execContext.ExecContext(ctx, cquery, args) - if spanID > 0 { - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) - } else { - tc.tryTrace(ctx, queryTypeExec, query, start, err) - } + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -108,13 +96,9 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + cquery, spanID := tc.injectComments(ctx, query, false) r, err = execer.Exec(cquery, dargs) - if spanID > 0 { - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) - } else { - tc.tryTrace(ctx, queryTypeExec, query, start, err) - } + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) return r, err } return nil, driver.ErrSkip @@ -133,13 +117,9 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, false) + cquery, spanID := tc.injectComments(ctx, query, false) rows, err := queryerContext.QueryContext(ctx, cquery, args) - if spanID > 0 { - tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) - } else { - tc.tryTrace(ctx, queryTypeQuery, query, start, err) - } + tc.tryTrace(ctx, queryTypeQuery, cquery, start, err, tracer.WithSpanID(spanID)) return rows, err } if queryer, ok := tc.Conn.(driver.Queryer); ok { @@ -152,13 +132,9 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - cquery, spanID := tc.withSQLCommentsInjected(ctx, query, true) + cquery, spanID := tc.injectComments(ctx, query, true) rows, err = queryer.Query(cquery, dargs) - if spanID > 0 { - tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) - } else { - tc.tryTrace(ctx, queryTypeQuery, query, start, err) - } + tc.tryTrace(ctx, queryTypeQuery, cquery, start, err, tracer.WithSpanID(spanID)) return rows, err } return nil, driver.ErrSkip @@ -199,10 +175,10 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { return context.WithValue(ctx, spanTagsKey, tags) } -// withSQLCommentsInjected will return the query with sql comments injected according to the comment injection mode along +// injectComments returns the query with sql comments injected according to the comment injection mode along // with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating // the span following the traced database call. -func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string, discardDynamicTags bool) (cquery string, injectedSpanID uint64) { +func (tp *traceParams) injectComments(ctx context.Context, query string, discardTracingTags bool) (cquery string, spanID uint64) { // The sql span only gets created after the call to the database because we need to be able to skip spans // when a driver returns driver.ErrSkip. In order to work with those constraints, a new span id is generated and // used during SQL comment injection and returned for the sql span to be used later when/if the span @@ -211,13 +187,20 @@ func (tp *traceParams) withSQLCommentsInjected(ctx context.Context, query string if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() } - sqlCommentCarrier := tracer.NewSQLCommentCarrier(tracer.SQLCommentWithDynamicTagsDiscarded(discardDynamicTags)) + sqlCommentCarrier := tracer.NewSQLCommentCarrier(query, resolveInjectionMode(tp.cfg.commentInjectionMode, discardTracingTags)) err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { // This should happen only if the SQLCommentPropagator is not set via the sql comment injection mode. log.Warn("contrib/database/sql: failed to inject query comments. Make sure you've set up SQLCommentInjectionMode on the propagator configuration: %v", err) } - return sqlCommentCarrier.CommentQuery(query) + return sqlCommentCarrier.Query, sqlCommentCarrier.SpanID +} + +func resolveInjectionMode(mode tracer.SQLCommentInjectionMode, discardTracingTags bool) tracer.SQLCommentInjectionMode { + if discardTracingTags && mode == tracer.FullSQLCommentInjection { + mode = tracer.ServiceTagsInjection + } + return mode } // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 47bd4208bd..cabf85d827 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -6,16 +6,18 @@ package sql import ( + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "gopkg.in/DataDog/dd-trace-go.v1/internal" ) type config struct { - serviceName string - analyticsRate float64 - dsn string - childSpansOnly bool + serviceName string + analyticsRate float64 + dsn string + childSpansOnly bool + commentInjectionMode tracer.SQLCommentInjectionMode } // Option represents an option that can be passed to Register, Open or OpenDB. @@ -34,6 +36,7 @@ func defaults(cfg *config) { } else { cfg.analyticsRate = math.NaN() } + cfg.commentInjectionMode = tracer.SQLCommentInjectionMode(internal.IntEnv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", int(tracer.CommentInjectionDisabled))) } // WithServiceName sets the given service name when registering a driver, @@ -83,3 +86,12 @@ func WithChildSpansOnly() Option { cfg.childSpansOnly = true } } + +// WithCommentInjection 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. Use WithStaticTagsCommentInjection if this is a concern. +func WithCommentInjection(mode tracer.SQLCommentInjectionMode) Option { + return func(cfg *config) { + cfg.commentInjectionMode = mode + } +} diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 9022305927..3a622d629c 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -10,9 +10,11 @@ import ( "database/sql/driver" "errors" "fmt" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "log" "math" "os" + "strconv" "testing" "time" @@ -35,6 +37,8 @@ func TestMain(m *testing.M) { fmt.Println("--- SKIP: to enable integration test, set the INTEGRATION environment variable") os.Exit(0) } + os.Setenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", strconv.Itoa(int(tracer.FullSQLCommentInjection))) + defer os.Unsetenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE") defer sqltest.Prepare(tableName)() os.Exit(m.Run()) } diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index a31180e696..760eaa6f64 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -10,6 +10,7 @@ import ( "database/sql" "fmt" "log" + "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" @@ -86,7 +87,7 @@ func testConnect(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assert.Len(cfg.mockTracer.InjectedComments(), 0) + //assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -107,7 +108,7 @@ func testPing(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assert.Len(cfg.mockTracer.InjectedComments(), 0) + //assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -154,12 +155,38 @@ func testQuery(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } - comments := cfg.mockTracer.InjectedComments() - require.Len(t, comments, 1) - assert.Equal(expectedComment, comments[0]) + assertInjectedComment(t, querySpan, expectedComment) } } +func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expected string) { + q, ok := querySpan.Tag(ext.ResourceName).(string) + require.True(t, ok, "tag %s should be a string but was %v", ext.ResourceName, q) + c, err := findSQLComment(q) + require.NoError(t, err) + assert.Equal(t, expected, c) +} + +func findSQLComment(query string) (comment string, err error) { + start := strings.Index(query, "/*") + if start == -1 { + return "", nil + } + end := strings.Index(query[start:], "*/") + if end == -1 { + return "", nil + } + c := query[start : end+2] + spacesTrimmed := strings.TrimSpace(c) + if !strings.HasPrefix(spacesTrimmed, "/*") { + return "", fmt.Errorf("comments not in the sqlcommenter format, expected to start with '/*'") + } + if !strings.HasSuffix(spacesTrimmed, "*/") { + return "", fmt.Errorf("comments not in the sqlcommenter format, expected to end with '*/'") + } + return c, nil +} + func testStatement(cfg *Config) func(*testing.T) { query := "INSERT INTO %s(name) VALUES(%s)" switch cfg.DriverName { @@ -187,10 +214,11 @@ func testStatement(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + assertInjectedComments(t, span) - comments := cfg.mockTracer.InjectedComments() - require.Len(t, comments, 1) - assert.Equal("/*dde='test-env',ddsn='test-service',ddsv='v-test'*/", comments[0]) + //comments := cfg.mockTracer.InjectedComments() + //require.Len(t, comments, 1) + //assert.Equal("/*dde='test-env',ddsn='test-service',ddsv='v-test'*/", comments[0]) cfg.mockTracer.Reset() _, err2 := stmt.Exec("New York") @@ -239,7 +267,7 @@ func testBeginRollback(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assert.Len(cfg.mockTracer.InjectedComments(), 0) + //assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -264,7 +292,7 @@ func testExec(cfg *Config) func(*testing.T) { parent.Finish() // flush children spans := cfg.mockTracer.FinishedSpans() - expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" + //expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared exec so there are 2 extra spans for the exec: //prepare, exec, and then a close @@ -283,7 +311,7 @@ func testExec(cfg *Config) func(*testing.T) { } // Since SQLServer runs execute statements by doing a prepare, the expected comment // excludes dynamic tags which can only be injected on non-prepared statements - expectedComment = "/*dde='test-env',ddsn='test-service',ddsv='v-test'*/" + //expectedComment = "/*dde='test-env',ddsn='test-service',ddsv='v-test'*/" } else { assert.Len(spans, 5) } @@ -304,9 +332,9 @@ func testExec(cfg *Config) func(*testing.T) { span = s } } - comments := cfg.mockTracer.InjectedComments() - require.Len(t, comments, 1) - assert.Equal(expectedComment, comments[0]) + //comments := cfg.mockTracer.InjectedComments() + //require.Len(t, comments, 1) + //assert.Equal(expectedComment, comments[0]) assert.NotNil(span, "span not found") cfg.ExpectTags["sql.query_type"] = "Commit" diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 694957f9f0..fc9db0193c 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -33,9 +33,6 @@ type Tracer interface { // FinishedSpans returns the set of finished spans. FinishedSpans() []Span - // InjectedComments returns the set of sql comments injected on queries. - InjectedComments() []string - // Reset resets the spans and services recorded in the tracer. This is // especially useful when running tests in a loop, where a clean start // is desired for FinishedSpans calls. @@ -58,10 +55,9 @@ func Start() Tracer { } type mocktracer struct { - sync.RWMutex // guards below spans - finishedSpans []Span - injectedComments []string - openSpans map[uint64]Span + sync.RWMutex // guards below spans + finishedSpans []Span + openSpans map[uint64]Span } func newMockTracer() *mocktracer { @@ -106,19 +102,12 @@ func (t *mocktracer) FinishedSpans() []Span { return t.finishedSpans } -func (t *mocktracer) InjectedComments() []string { - t.RLock() - defer t.RUnlock() - return t.injectedComments -} - func (t *mocktracer) Reset() { t.Lock() defer t.Unlock() for k := range t.openSpans { delete(t.openSpans, k) } - t.injectedComments = nil t.finishedSpans = nil } @@ -140,6 +129,9 @@ const ( ) func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { + if sc, ok := carrier.(*tracer.SQLCommentCarrier); ok { + return sc.Extract() + } reader, ok := carrier.(tracer.TextMapReader) if !ok { return nil, tracer.ErrInvalidCarrier @@ -185,8 +177,8 @@ func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - case tracer.QueryCommentCarrier: - return t.injectQueryComments(context, c) + case *tracer.SQLCommentCarrier: + return c.Inject(context) case tracer.TextMapWriter: return t.injectTextMap(context, c) default: @@ -210,21 +202,3 @@ func (t *mocktracer) injectTextMap(context ddtrace.SpanContext, writer tracer.Te }) return nil } - -func (t *mocktracer) injectQueryComments(context ddtrace.SpanContext, injector tracer.QueryCommentCarrier) error { - ctx, ok := context.(*spanContext) - samplingPriority := 0 - if ok { - samplingPriority = ctx.samplingPriority() - } - injector.Set(tracer.ServiceVersionSQLCommentKey, "v-test") - injector.Set(tracer.ServiceEnvironmentSQLCommentKey, "test-env") - injector.Set(tracer.ServiceNameSQLCommentKey, "test-service") - injector.SetDynamicTag(tracer.TraceIDSQLCommentKey, "test-trace-id") - injector.SetDynamicTag(tracer.SpanIDSQLCommentKey, "test-span-id") - injector.SetDynamicTag(tracer.SamplingPrioritySQLCommentKey, strconv.Itoa(samplingPriority)) - // Save injected comments to assert the sql commenting behavior - commented, _ := injector.CommentQuery("") - t.injectedComments = append(t.injectedComments, commented) - return nil -} diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 6d27d727a9..b93327346b 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -264,11 +264,13 @@ func newConfig(opts ...StartOption) *config { if c.transport == nil { c.transport = newHTTPTransport(c.agentAddr, c.httpClient) } - if c.propagator == nil { - c.propagator = NewPropagator(&PropagatorConfig{ - MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), - SQLCommentInjectionMode: SQLCommentInjectionMode(internal.IntEnv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", int(defaultSQLCommentInjectionMode))), - }) + pcfg := &PropagatorConfig{ + MaxTagsHeaderLen: internal.IntEnv("DD_TRACE_TAGS_PROPAGATION_MAX_LENGTH", defaultMaxTagsHeaderLen), + } + if c.propagator != nil { + c.propagator = NewPropagator(pcfg, c.propagator) + } else { + c.propagator = NewPropagator(pcfg) } if c.logger != nil { log.UseLogger(c.logger) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 9b55fc2876..b3b02aacfd 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -7,14 +7,14 @@ package tracer import ( "fmt" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" "net/url" "sort" "strconv" "strings" - - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" ) // SQLCommentInjectionMode represents the mode of sql comment injection. @@ -23,9 +23,9 @@ type SQLCommentInjectionMode int const ( // CommentInjectionDisabled represents the comment injection mode where all injection is disabled. CommentInjectionDisabled SQLCommentInjectionMode = 0 - // StaticTagsSQLCommentInjection represents the comment injection mode where only static tags are injected. Static tags include values that are set once during the lifetime of an application: service name, env, version. - StaticTagsSQLCommentInjection SQLCommentInjectionMode = 1 - // FullSQLCommentInjection represents the comment injection mode where both static and dynamic tags are injected. Dynamic tags include values like span id, trace id and sampling priority. + // ServiceTagsInjection represents the comment injection mode where only service tags (name, env, version) are injected. + ServiceTagsInjection SQLCommentInjectionMode = 1 + // FullSQLCommentInjection represents the comment injection mode where both service tags and tracing tags. Tracing tags include span id, trace id and sampling priority. FullSQLCommentInjection SQLCommentInjectionMode = 2 ) @@ -39,69 +39,27 @@ const ( ServiceEnvironmentSQLCommentKey = "dde" ) -// QueryCommentCarrier is a more specific interface implemented by carriers that implement the TextMapWriter -// as well as CommentQuery, AddSpanID and SetDynamicTag methods. It is compatible with Datadog's SQLCommentPropagator. -// Note that Datadog's TextMapPropagator is compatible with QueryCommentCarriers but only in the fact that it -// ignores it without returning errors. -type QueryCommentCarrier interface { - TextMapWriter - - // SetDynamicTag sets the given dynamic key/value pair. This method is a variation on TextMapWriter's Set method - // that exists for dynamic tag values. - SetDynamicTag(key, val string) - - // CommentQuery returns the given query with any injected tags prepended in the form of a sqlcommenter-formatted - // SQL comment. It also returns a non-zero span ID if a new span ID was generated as part of the inject operation. - // See https://google.github.io/sqlcommenter/spec for the full sqlcommenter spec. - CommentQuery(query string) (commented string, spanID uint64) - - // AddSpanID adds a new span ID to the carrier. This happens if no existing trace is found when the - // injection happens. - AddSpanID(spanID uint64) -} - -// SQLCommentPropagator implements the Propagator interface to inject tags -// in sql comments. It is only compatible with QueryCommentCarrier implementations -// but allows other carriers to flow through without returning errors. -type SQLCommentPropagator struct { - mode SQLCommentInjectionMode -} - -// SQLCommentWithDynamicTagsDiscarded enables control discarding dynamic tags on a SQLCommentCarrier. -// Its main purpose is to allow discarding dynamic tags per SQL operation when they aren't relevant -// (i.e. Prepared statements). -func SQLCommentWithDynamicTagsDiscarded(discard bool) SQLCommentCarrierOption { - return func(c *SQLCommentCarrierConfig) { - c.discardDynamicTags = discard - } -} - -// NewSQLCommentPropagator returns a new SQLCommentPropagator with the given injection mode -func NewSQLCommentPropagator(mode SQLCommentInjectionMode) *SQLCommentPropagator { - return &SQLCommentPropagator{mode: mode} +type SQLCommentCarrier struct { + Query string + Mode SQLCommentInjectionMode + SpanID uint64 } -// Inject injects the span context in the given carrier. Note that it is only compatible -// with QueryCommentCarriers and no-ops if the carrier is of any other type. -func (p *SQLCommentPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { - switch c := carrier.(type) { - case QueryCommentCarrier: - return p.injectWithCommentCarrier(spanCtx, c) - default: - // SQLCommentPropagator only handles QueryCommentCarrier carriers but lets any other carrier - // flow through without returning errors - return nil - } +func NewSQLCommentCarrier(query string, mode SQLCommentInjectionMode) (s *SQLCommentCarrier) { + s = new(SQLCommentCarrier) + s.Mode = mode + s.Query = query + return s } -func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanContext, carrier QueryCommentCarrier) error { - if p.mode == CommentInjectionDisabled { +func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { + c.SpanID = random.Uint64() + if c.Mode == CommentInjectionDisabled { return nil } - spanID := random.Uint64() - carrier.AddSpanID(spanID) - if p.mode == StaticTagsSQLCommentInjection || p.mode == FullSQLCommentInjection { + tags := make(map[string]string) + if c.Mode == ServiceTagsInjection || c.Mode == FullSQLCommentInjection { ctx, ok := spanCtx.(*spanContext) var env, version string if ok { @@ -113,16 +71,16 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont } } if globalconfig.ServiceName() != "" { - carrier.Set(ServiceNameSQLCommentKey, globalconfig.ServiceName()) + tags[ServiceNameSQLCommentKey] = globalconfig.ServiceName() } if env != "" { - carrier.Set(ServiceEnvironmentSQLCommentKey, env) + tags[ServiceEnvironmentSQLCommentKey] = env } if version != "" { - carrier.Set(ServiceVersionSQLCommentKey, version) + tags[ServiceVersionSQLCommentKey] = version } } - if p.mode == FullSQLCommentInjection { + if c.Mode == FullSQLCommentInjection { samplingPriority := 0 var traceID uint64 @@ -136,98 +94,32 @@ func (p *SQLCommentPropagator) injectWithCommentCarrier(spanCtx ddtrace.SpanCont } } if traceID == 0 { - traceID = spanID + traceID = c.SpanID } - carrier.SetDynamicTag(TraceIDSQLCommentKey, strconv.FormatUint(traceID, 10)) - carrier.SetDynamicTag(SpanIDSQLCommentKey, strconv.FormatUint(spanID, 10)) - carrier.SetDynamicTag(SamplingPrioritySQLCommentKey, strconv.Itoa(samplingPriority)) + tags[TraceIDSQLCommentKey] = strconv.FormatUint(traceID, 10) + tags[SpanIDSQLCommentKey] = strconv.FormatUint(c.SpanID, 10) + tags[SamplingPrioritySQLCommentKey] = strconv.Itoa(samplingPriority) } - return nil -} -// Extract is a no-op for the SQLCommentPropagator. -func (p *SQLCommentPropagator) Extract(carrier interface{}) (ddtrace.SpanContext, error) { - return nil, nil -} - -// SQLCommentCarrierConfig holds configuration for a SQLCommentCarrier. -type SQLCommentCarrierConfig struct { - discardDynamicTags bool -} - -// SQLCommentCarrierOption represents a function that can be provided as a parameter to NewSQLCommentCarrier. -type SQLCommentCarrierOption func(c *SQLCommentCarrierConfig) - -// SQLCommentCarrier implements QueryCommentCarrier by holding tags, configuration -// and a potential new span id to generate a SQL comment injected in queries. -type SQLCommentCarrier struct { - tags map[string]string - cfg SQLCommentCarrierConfig - spanID uint64 -} - -// NewSQLCommentCarrier returns a new SQLCommentCarrier. -func NewSQLCommentCarrier(opts ...SQLCommentCarrierOption) (s *SQLCommentCarrier) { - s = new(SQLCommentCarrier) - for _, apply := range opts { - apply(&s.cfg) - } - - return s -} - -// Set implements TextMapWriter. In the context of SQL comment injection, this method -// is used for static tags only. See SetDynamicTag for dynamic tags. -func (c *SQLCommentCarrier) Set(key, val string) { - if c.tags == nil { - c.tags = make(map[string]string) - } - c.tags[key] = val -} - -// ForeachKey implements TextMapReader. -func (c SQLCommentCarrier) ForeachKey(handler func(key, val string) error) error { - for k, v := range c.tags { - if err := handler(k, v); err != nil { - return err - } - } + c.Query = commentQuery(c.Query, tags) return nil } -// SetDynamicTag implements QueryCommentCarrier. This method is used to inject dynamic tags only -// (e.g. span id, trace id, sampling priority). See Set for static tags. -func (c *SQLCommentCarrier) SetDynamicTag(key, val string) { - if c.cfg.discardDynamicTags { - return - } - if c.tags == nil { - c.tags = make(map[string]string) - } - c.tags[key] = val -} - -// AddSpanID implements QueryCommentCarrier and is used to save a span id generated during SQL comment injection -// in the case where there is no active trace. -func (c *SQLCommentCarrier) AddSpanID(spanID uint64) { - c.spanID = spanID -} - -// CommentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a +// commentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a // prepended SQL comment. The format of the comment follows the sqlcommenter spec. // See https://google.github.io/sqlcommenter/spec/ for more details. -func (c *SQLCommentCarrier) CommentQuery(query string) (commented string, spanID uint64) { - comment := commentWithTags(c.tags) - if comment == "" { - return query, c.spanID +func commentQuery(query string, tags map[string]string) string { + c := serializeTags(tags) + if c == "" { + return query } if query == "" { - return comment, c.spanID + return c } - return fmt.Sprintf("%s %s", comment, query), c.spanID + return fmt.Sprintf("%s %s", c, query) } -func commentWithTags(tags map[string]string) (comment string) { +func serializeTags(tags map[string]string) (comment string) { if len(tags) == 0 { return "" } @@ -248,18 +140,19 @@ func serializeTag(key string, value string) (serialized string) { } func serializeKey(key string) (encoded string) { - urlEncoded := url.PathEscape(key) - escapedMeta := escapeMetaChars(urlEncoded) - - return escapedMeta + enc := urlEncode(key) + return escapeMetaChars(enc) } -func serializeValue(value string) (encoded string) { - urlEncoded := url.PathEscape(value) - escapedMeta := escapeMetaChars(urlEncoded) - escaped := escapeSQL(escapedMeta) +func serializeValue(val string) (encoded string) { + enc := urlEncode(val) + escapedMeta := escapeMetaChars(enc) + return escapeSQL(escapedMeta) +} - return escaped +func urlEncode(val string) string { + e := url.QueryEscape(val) + return strings.Replace(e, "+", "%20", -1) } func escapeSQL(value string) (escaped string) { @@ -269,3 +162,138 @@ func escapeSQL(value string) (escaped string) { func escapeMetaChars(value string) (escaped string) { return strings.ReplaceAll(value, "'", "\\'") } + +// Extract parses the first sql comment found in the query text and returns the span context with values +// extracted from tags extracted for the sql comment. +func (c *SQLCommentCarrier) Extract() (ddtrace.SpanContext, error) { + cmt, err := findSQLComment(c.Query) + if err != nil { + return nil, err + } + if cmt == "" { + return nil, nil + } + tags, err := extractCommentTags(cmt) + if err != nil { + return nil, fmt.Errorf("unable to extract tags from comment [%s]: %w", cmt, err) + } + var spid uint64 + if v := tags[SpanIDSQLCommentKey]; v != "" { + spid, err = strconv.ParseUint(v, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse span id [%s]: %w", v, err) + } + } + var tid uint64 + if v := tags[TraceIDSQLCommentKey]; v != "" { + tid, err = strconv.ParseUint(v, 10, 64) + if err != nil { + return nil, fmt.Errorf("unable to parse trace id [%s]: %w", v, err) + } + } + svc := tags[ServiceNameSQLCommentKey] + ctx := newSpanContext(&span{ + Service: svc, + Meta: map[string]string{ + ext.Version: tags[ServiceVersionSQLCommentKey], + ext.Environment: tags[ServiceEnvironmentSQLCommentKey], + }, + SpanID: spid, + TraceID: tid, + }, nil) + if v := tags[SamplingPrioritySQLCommentKey]; v != "" { + p, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + ctx.trace = newTrace() + ctx.trace.setSamplingPriority(svc, p, samplernames.Default, 1) + } + return ctx, nil +} + +func findSQLComment(query string) (comment string, err error) { + start := strings.Index(query, "/*") + if start == -1 { + return "", nil + } + end := strings.Index(query[start:], "*/") + if end == -1 { + return "", nil + } + c := query[start : end+2] + spacesTrimmed := strings.TrimSpace(c) + if !strings.HasPrefix(spacesTrimmed, "/*") { + return "", fmt.Errorf("comments not in the sqlcommenter format, expected to start with '/*'") + } + if !strings.HasSuffix(spacesTrimmed, "*/") { + return "", fmt.Errorf("comments not in the sqlcommenter format, expected to end with '*/'") + } + c = strings.TrimLeft(c, "/*") + c = strings.TrimRight(c, "*/") + return strings.TrimSpace(c), nil +} + +func extractCommentTags(comment string) (keyValues map[string]string, err error) { + keyValues = make(map[string]string) + if err != nil { + return nil, err + } + if comment == "" { + return keyValues, nil + } + tagList := strings.Split(comment, ",") + for _, t := range tagList { + k, v, err := extractKeyValue(t) + if err != nil { + return nil, err + } else { + keyValues[k] = v + } + } + return keyValues, nil +} + +func extractKeyValue(tag string) (key string, val string, err error) { + parts := strings.SplitN(tag, "=", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("tag format invalid, expected 'key=value' but got %s", tag) + } + key, err = extractKey(parts[0]) + if err != nil { + return "", "", err + } + val, err = extractValue(parts[1]) + if err != nil { + return "", "", err + } + return key, val, nil +} + +func extractKey(keyVal string) (key string, err error) { + unescaped := unescapeMetaCharacters(keyVal) + decoded, err := url.PathUnescape(unescaped) + if err != nil { + return "", fmt.Errorf("failed to url unescape key: %w", err) + } + + return decoded, nil +} + +func extractValue(rawValue string) (value string, err error) { + trimmedLeft := strings.TrimLeft(rawValue, "'") + trimmed := strings.TrimRight(trimmedLeft, "'") + + unescaped := unescapeMetaCharacters(trimmed) + decoded, err := url.PathUnescape(unescaped) + + if err != nil { + return "", fmt.Errorf("failed to url unescape value: %w", err) + } + + return decoded, nil +} + +func unescapeMetaCharacters(val string) (unescaped string) { + return strings.ReplaceAll(val, "\\'", "'") +} diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index cabdd0ead5..7d566f6dd2 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -28,7 +28,6 @@ func TestSQLCommentPropagator(t *testing.T) { name string query string mode SQLCommentInjectionMode - carrierOpts []SQLCommentCarrierOption prepareSpanContext func(*tracer) ddtrace.SpanContext expectedQuery string expectedSpanIDGen bool @@ -37,75 +36,59 @@ func TestSQLCommentPropagator(t *testing.T) { name: "all tags injected", query: "SELECT * from FOO", mode: FullSQLCommentInjection, - carrierOpts: nil, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", expectedSpanIDGen: true, }, { - name: "no existing trace", - query: "SELECT * from FOO", - mode: FullSQLCommentInjection, - carrierOpts: nil, + name: "no existing trace", + query: "SELECT * from FOO", + mode: FullSQLCommentInjection, prepareSpanContext: func(tracer *tracer) ddtrace.SpanContext { return nil }, - expectedQuery: "/*ddsid='',ddsn='whiskey-service',ddsp='0',ddtid=''*/ SELECT * from FOO", + expectedQuery: "/*ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='0',ddtid=''*/ SELECT * from FOO", expectedSpanIDGen: true, }, { name: "empty query, all tags injected", query: "", mode: FullSQLCommentInjection, - carrierOpts: nil, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/", + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/", expectedSpanIDGen: true, }, { name: "query with existing comment", query: "SELECT * from FOO -- test query", mode: FullSQLCommentInjection, - carrierOpts: nil, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", expectedSpanIDGen: true, }, - { - name: "discard dynamic tags", - query: "SELECT * from FOO", - mode: FullSQLCommentInjection, - carrierOpts: []SQLCommentCarrierOption{SQLCommentWithDynamicTagsDiscarded(true)}, - prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", - expectedSpanIDGen: false, - }, { name: "static tags only mode", query: "SELECT * from FOO", - mode: StaticTagsSQLCommentInjection, - carrierOpts: nil, + mode: ServiceTagsInjection, prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsn='whiskey-service',ddsv='1.0.0'*/ SELECT * from FOO", + expectedQuery: "/*dde='test-env',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsv='1.0.0'*/ SELECT * from FOO", expectedSpanIDGen: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - p := NewPropagator(&PropagatorConfig{SQLCommentInjectionMode: tc.mode}) - tracer := newTracer(WithService("whiskey-service"), WithEnv("test-env"), WithServiceVersion("1.0.0"), WithPropagator(p)) + // the test service name includes all RFC3986 reserved characters to make sure all of them are url encoded + // as per the sqlcommenter spec + tracer := newTracer(WithService("whiskey-service !#$%&'()*+,/:;=?@[]"), WithEnv("test-env"), WithServiceVersion("1.0.0")) ctx := tc.prepareSpanContext(tracer) - carrier := NewSQLCommentCarrier(tc.carrierOpts...) + carrier := NewSQLCommentCarrier(tc.query, tc.mode) err := tracer.Inject(ctx, carrier) require.NoError(t, err) - commented, spanID := carrier.CommentQuery(tc.query) - - assert.Greater(t, spanID, uint64(0)) - expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(spanID, 10)) - assert.Equal(t, expected, commented) + expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(carrier.SpanID, 10)) + assert.Equal(t, expected, carrier.Query) }) } } diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index f0347d0ae7..70cbecd07a 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -119,16 +119,12 @@ type PropagatorConfig struct { // B3 specifies if B3 headers should be added for trace propagation. // See https://github.com/openzipkin/b3-propagation B3 bool - - // SQLCommentInjectionMode specifies the sql comment injection mode that will be used - // to inject tags. It defaults to CommentInjectionDisabled. - SQLCommentInjectionMode SQLCommentInjectionMode } // NewPropagator returns a new propagator which uses TextMap to inject // and extract values. It propagates trace and span IDs and baggage. // To use the defaults, nil may be provided in place of the config. -func NewPropagator(cfg *PropagatorConfig) Propagator { +func NewPropagator(cfg *PropagatorConfig, propagators ...Propagator) Propagator { if cfg == nil { cfg = new(PropagatorConfig) } @@ -144,6 +140,12 @@ func NewPropagator(cfg *PropagatorConfig) Propagator { if cfg.PriorityHeader == "" { cfg.PriorityHeader = DefaultPriorityHeader } + if len(propagators) > 0 { + return &chainedPropagator{ + injectors: propagators, + extractors: propagators, + } + } return &chainedPropagator{ injectors: getPropagators(cfg, headerPropagationStyleInject), extractors: getPropagators(cfg, headerPropagationStyleExtract), @@ -169,9 +171,6 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { if cfg.B3 { defaultPs = append(defaultPs, &propagatorB3{}) } - if cfg.SQLCommentInjectionMode != CommentInjectionDisabled { - defaultPs = append(defaultPs, NewSQLCommentPropagator(cfg.SQLCommentInjectionMode)) - } if ps == "" { return defaultPs } @@ -179,9 +178,6 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { if cfg.B3 { list = append(list, &propagatorB3{}) } - if cfg.SQLCommentInjectionMode != CommentInjectionDisabled { - list = append(list, NewSQLCommentPropagator(cfg.SQLCommentInjectionMode)) - } for _, v := range strings.Split(ps, ",") { switch strings.ToLower(v) { case "datadog": @@ -206,6 +202,9 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { // out of the current process. The implementation propagates the // TraceID and the current active SpanID, as well as the Span baggage. func (p *chainedPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { + if c, ok := carrier.(*SQLCommentCarrier); ok { + return c.Inject(spanCtx) + } for _, v := range p.injectors { err := v.Inject(spanCtx, carrier) if err != nil { @@ -240,9 +239,6 @@ type propagator struct { func (p *propagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - // QueryCommentCarrier carriers are only supported by the SQLCommentPropagator - case QueryCommentCarrier: - return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) default: @@ -341,9 +337,6 @@ type propagatorB3 struct{} func (p *propagatorB3) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - // QueryCommentCarrier carriers are only supported by the SQLCommentPropagator - case QueryCommentCarrier: - return nil case TextMapWriter: return p.injectTextMap(spanCtx, c) default: From ab4480988942f66811891e648ae7f21efe3b414e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 10:16:06 -0700 Subject: [PATCH 071/104] WIP: Removed propagator in favor or handling in chainedPropagator --- contrib/database/sql/conn.go | 4 +- contrib/internal/sqltest/sqltest.go | 130 ++++++++++++++++++++++++--- ddtrace/tracer/sqlcomment.go | 135 +--------------------------- 3 files changed, 123 insertions(+), 146 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 30b8f259ff..88fdee2d71 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -67,7 +67,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return nil, err } - return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil + return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: cquery}, nil } stmt, err = tc.Prepare(cquery) tc.tryTrace(ctx, queryTypePrepare, cquery, start, err, tracer.WithSpanID(spanID)) @@ -75,7 +75,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr return nil, err } - return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil + return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: cquery}, nil } func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 760eaa6f64..983817d869 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -9,7 +9,10 @@ import ( "context" "database/sql" "fmt" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "log" + "net/url" + "os" "strings" "testing" @@ -47,10 +50,14 @@ func Prepare(tableName string) func() { } mssql.Exec(queryDrop) mssql.Exec(queryCreate) + svc := globalconfig.ServiceName() + globalconfig.SetServiceName("test-service") return func() { mysql.Exec(queryDrop) postgres.Exec(queryDrop) mssql.Exec(queryDrop) + globalconfig.SetServiceName(svc) + defer os.Unsetenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE") } } @@ -129,7 +136,12 @@ func testQuery(cfg *Config) func(*testing.T) { spans := cfg.mockTracer.FinishedSpans() var querySpan mocktracer.Span - expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" + expectedCommentTags := map[string]tagExpectation{ + "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, + "ddsp": {MustBeSet: true, ExpectedValue: "0"}, + "ddsid": {MustBeSet: true}, + "ddtid": {MustBeSet: true}, + } if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans //connect, prepare, and query @@ -143,7 +155,12 @@ func testQuery(cfg *Config) func(*testing.T) { querySpan = spans[2] // Since SQLServer runs execute statements by doing a prepare first, the expected comment // excludes dynamic tags which can only be injected on non-prepared statements - expectedComment = "/*dde='test-env',ddsn='test-service',ddsv='v-test'*/" + expectedCommentTags = map[string]tagExpectation{ + "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, + "ddsp": {MustBeSet: false}, + "ddsid": {MustBeSet: false}, + "ddtid": {MustBeSet: false}, + } } else { assert.Len(spans, 2) querySpan = spans[1] @@ -155,16 +172,39 @@ func testQuery(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComment(t, querySpan, expectedComment) + assertInjectedComment(t, querySpan, expectedCommentTags) } } -func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expected string) { +type tagExpectation struct { + MustBeSet bool + ExpectedValue string +} + +func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expectedTags map[string]tagExpectation) { q, ok := querySpan.Tag(ext.ResourceName).(string) require.True(t, ok, "tag %s should be a string but was %v", ext.ResourceName, q) - c, err := findSQLComment(q) + tags, err := extractTags(q) require.NoError(t, err) - assert.Equal(t, expected, c) + for k, e := range expectedTags { + if e.MustBeSet { + assert.NotZerof(t, tags[k], "Value must be set on tag %s", k) + } else { + assert.Zero(t, tags[k], "Value should not be set on tag %s", k) + } + if e.ExpectedValue != "" { + assert.Equal(t, e.ExpectedValue, tags[k], "Value mismatch on tag %s", k) + } + } +} + +func extractTags(query string) (map[string]string, error) { + c, err := findSQLComment(query) + if err != nil { + return nil, err + } + + return extractCommentTags(c) } func findSQLComment(query string) (comment string, err error) { @@ -184,7 +224,73 @@ func findSQLComment(query string) (comment string, err error) { if !strings.HasSuffix(spacesTrimmed, "*/") { return "", fmt.Errorf("comments not in the sqlcommenter format, expected to end with '*/'") } - return c, nil + c = strings.TrimLeft(c, "/*") + c = strings.TrimRight(c, "*/") + return strings.TrimSpace(c), nil +} + +func extractCommentTags(comment string) (keyValues map[string]string, err error) { + keyValues = make(map[string]string) + if err != nil { + return nil, err + } + if comment == "" { + return keyValues, nil + } + tagList := strings.Split(comment, ",") + for _, t := range tagList { + k, v, err := extractKeyValue(t) + if err != nil { + return nil, err + } else { + keyValues[k] = v + } + } + return keyValues, nil +} + +func extractKeyValue(tag string) (key string, val string, err error) { + parts := strings.SplitN(tag, "=", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("tag format invalid, expected 'key=value' but got %s", tag) + } + key, err = extractKey(parts[0]) + if err != nil { + return "", "", err + } + val, err = extractValue(parts[1]) + if err != nil { + return "", "", err + } + return key, val, nil +} + +func extractKey(keyVal string) (key string, err error) { + unescaped := unescapeMetaCharacters(keyVal) + decoded, err := url.PathUnescape(unescaped) + if err != nil { + return "", fmt.Errorf("failed to url unescape key: %w", err) + } + + return decoded, nil +} + +func extractValue(rawValue string) (value string, err error) { + trimmedLeft := strings.TrimLeft(rawValue, "'") + trimmed := strings.TrimRight(trimmedLeft, "'") + + unescaped := unescapeMetaCharacters(trimmed) + decoded, err := url.PathUnescape(unescaped) + + if err != nil { + return "", fmt.Errorf("failed to url unescape value: %w", err) + } + + return decoded, nil +} + +func unescapeMetaCharacters(val string) (unescaped string) { + return strings.ReplaceAll(val, "\\'", "'") } func testStatement(cfg *Config) func(*testing.T) { @@ -214,11 +320,13 @@ func testStatement(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComments(t, span) - //comments := cfg.mockTracer.InjectedComments() - //require.Len(t, comments, 1) - //assert.Equal("/*dde='test-env',ddsn='test-service',ddsv='v-test'*/", comments[0]) + assertInjectedComment(t, span, map[string]tagExpectation{ + "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, + "ddsp": {MustBeSet: false}, + "ddsid": {MustBeSet: false}, + "ddtid": {MustBeSet: false}, + }) cfg.mockTracer.Reset() _, err2 := stmt.Exec("New York") diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index b3b02aacfd..dc47f4e944 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -10,7 +10,6 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" - "gopkg.in/DataDog/dd-trace-go.v1/internal/samplernames" "net/url" "sort" "strconv" @@ -163,137 +162,7 @@ func escapeMetaChars(value string) (escaped string) { return strings.ReplaceAll(value, "'", "\\'") } -// Extract parses the first sql comment found in the query text and returns the span context with values -// extracted from tags extracted for the sql comment. +// Extract is not implemented on SQLCommentCarrier func (c *SQLCommentCarrier) Extract() (ddtrace.SpanContext, error) { - cmt, err := findSQLComment(c.Query) - if err != nil { - return nil, err - } - if cmt == "" { - return nil, nil - } - tags, err := extractCommentTags(cmt) - if err != nil { - return nil, fmt.Errorf("unable to extract tags from comment [%s]: %w", cmt, err) - } - var spid uint64 - if v := tags[SpanIDSQLCommentKey]; v != "" { - spid, err = strconv.ParseUint(v, 10, 64) - if err != nil { - return nil, fmt.Errorf("unable to parse span id [%s]: %w", v, err) - } - } - var tid uint64 - if v := tags[TraceIDSQLCommentKey]; v != "" { - tid, err = strconv.ParseUint(v, 10, 64) - if err != nil { - return nil, fmt.Errorf("unable to parse trace id [%s]: %w", v, err) - } - } - svc := tags[ServiceNameSQLCommentKey] - ctx := newSpanContext(&span{ - Service: svc, - Meta: map[string]string{ - ext.Version: tags[ServiceVersionSQLCommentKey], - ext.Environment: tags[ServiceEnvironmentSQLCommentKey], - }, - SpanID: spid, - TraceID: tid, - }, nil) - if v := tags[SamplingPrioritySQLCommentKey]; v != "" { - p, err := strconv.Atoi(v) - if err != nil { - return nil, err - } - ctx.trace = newTrace() - ctx.trace.setSamplingPriority(svc, p, samplernames.Default, 1) - } - return ctx, nil -} - -func findSQLComment(query string) (comment string, err error) { - start := strings.Index(query, "/*") - if start == -1 { - return "", nil - } - end := strings.Index(query[start:], "*/") - if end == -1 { - return "", nil - } - c := query[start : end+2] - spacesTrimmed := strings.TrimSpace(c) - if !strings.HasPrefix(spacesTrimmed, "/*") { - return "", fmt.Errorf("comments not in the sqlcommenter format, expected to start with '/*'") - } - if !strings.HasSuffix(spacesTrimmed, "*/") { - return "", fmt.Errorf("comments not in the sqlcommenter format, expected to end with '*/'") - } - c = strings.TrimLeft(c, "/*") - c = strings.TrimRight(c, "*/") - return strings.TrimSpace(c), nil -} - -func extractCommentTags(comment string) (keyValues map[string]string, err error) { - keyValues = make(map[string]string) - if err != nil { - return nil, err - } - if comment == "" { - return keyValues, nil - } - tagList := strings.Split(comment, ",") - for _, t := range tagList { - k, v, err := extractKeyValue(t) - if err != nil { - return nil, err - } else { - keyValues[k] = v - } - } - return keyValues, nil -} - -func extractKeyValue(tag string) (key string, val string, err error) { - parts := strings.SplitN(tag, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("tag format invalid, expected 'key=value' but got %s", tag) - } - key, err = extractKey(parts[0]) - if err != nil { - return "", "", err - } - val, err = extractValue(parts[1]) - if err != nil { - return "", "", err - } - return key, val, nil -} - -func extractKey(keyVal string) (key string, err error) { - unescaped := unescapeMetaCharacters(keyVal) - decoded, err := url.PathUnescape(unescaped) - if err != nil { - return "", fmt.Errorf("failed to url unescape key: %w", err) - } - - return decoded, nil -} - -func extractValue(rawValue string) (value string, err error) { - trimmedLeft := strings.TrimLeft(rawValue, "'") - trimmed := strings.TrimRight(trimmedLeft, "'") - - unescaped := unescapeMetaCharacters(trimmed) - decoded, err := url.PathUnescape(unescaped) - - if err != nil { - return "", fmt.Errorf("failed to url unescape value: %w", err) - } - - return decoded, nil -} - -func unescapeMetaCharacters(val string) (unescaped string) { - return strings.ReplaceAll(val, "\\'", "'") + return nil, nil } From 7d71b8194a25930f1f6687a69f637b989d1dcf7d Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 15:54:04 -0700 Subject: [PATCH 072/104] Fix linting errors --- contrib/database/sql/conn.go | 4 ++-- contrib/database/sql/option.go | 2 +- contrib/database/sql/sql_test.go | 2 +- contrib/internal/sqltest/sqltest.go | 31 ++++++++++++++++++----------- ddtrace/tracer/sqlcomment.go | 24 +++++++++++++--------- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 88fdee2d71..d0662f9ada 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -83,7 +83,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv if execContext, ok := tc.Conn.(driver.ExecerContext); ok { cquery, spanID := tc.injectComments(ctx, query, false) r, err := execContext.ExecContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeExec, cquery, start, err, tracer.WithSpanID(spanID)) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -98,7 +98,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv } cquery, spanID := tc.injectComments(ctx, query, false) r, err = execer.Exec(cquery, dargs) - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeExec, cquery, start, err, tracer.WithSpanID(spanID)) return r, err } return nil, driver.ErrSkip diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index cabf85d827..b8a4c39cde 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -6,9 +6,9 @@ package sql import ( - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal" ) diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 3a622d629c..7166d7370f 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -10,7 +10,6 @@ import ( "database/sql/driver" "errors" "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "log" "math" "os" @@ -21,6 +20,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 983817d869..92da4e0127 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -9,7 +9,6 @@ import ( "context" "database/sql" "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "log" "net/url" "os" @@ -19,6 +18,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -242,9 +242,8 @@ func extractCommentTags(comment string) (keyValues map[string]string, err error) k, v, err := extractKeyValue(t) if err != nil { return nil, err - } else { - keyValues[k] = v } + keyValues[k] = v } return keyValues, nil } @@ -375,7 +374,6 @@ func testBeginRollback(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - //assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -400,7 +398,12 @@ func testExec(cfg *Config) func(*testing.T) { parent.Finish() // flush children spans := cfg.mockTracer.FinishedSpans() - //expectedComment := "/*dde='test-env',ddsid='test-span-id',ddsn='test-service',ddsp='0',ddsv='v-test',ddtid='test-trace-id'*/" + expectedCommentTags := map[string]tagExpectation{ + "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, + "ddsp": {MustBeSet: true, ExpectedValue: "0"}, + "ddsid": {MustBeSet: true}, + "ddtid": {MustBeSet: true}, + } if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared exec so there are 2 extra spans for the exec: //prepare, exec, and then a close @@ -417,32 +420,36 @@ func testExec(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - // Since SQLServer runs execute statements by doing a prepare, the expected comment + // Since SQLServer runs execute statements by doing a prepare first, the expected comment // excludes dynamic tags which can only be injected on non-prepared statements - //expectedComment = "/*dde='test-env',ddsn='test-service',ddsv='v-test'*/" + expectedCommentTags = map[string]tagExpectation{ + "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, + "ddsp": {MustBeSet: false}, + "ddsid": {MustBeSet: false}, + "ddtid": {MustBeSet: false}, + } } else { assert.Len(spans, 5) } var span mocktracer.Span for _, s := range spans { - if s.OperationName() == cfg.ExpectName && s.Tag(ext.ResourceName) == query { + rn, _ := s.Tag(ext.ResourceName).(string) + if s.OperationName() == cfg.ExpectName && strings.HasSuffix(rn, query) { span = s } } - assert.NotNil(span, "span not found") + require.NotNil(t, span, "span not found") cfg.ExpectTags["sql.query_type"] = "Exec" for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } + assertInjectedComment(t, span, expectedCommentTags) for _, s := range spans { if s.OperationName() == cfg.ExpectName && s.Tag(ext.ResourceName) == "Commit" { span = s } } - //comments := cfg.mockTracer.InjectedComments() - //require.Len(t, comments, 1) - //assert.Equal(expectedComment, comments[0]) assert.NotNil(span, "span not found") cfg.ExpectTags["sql.query_type"] = "Commit" diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index dc47f4e944..d2b2d308ce 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -7,13 +7,14 @@ package tracer import ( "fmt" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "net/url" "sort" "strconv" "strings" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" ) // SQLCommentInjectionMode represents the mode of sql comment injection. @@ -38,21 +39,26 @@ const ( ServiceEnvironmentSQLCommentKey = "dde" ) +// SQLCommentCarrier is a carrier implementation that injects a span context in a SQL query in the form +// of a sqlcommenter formatted comment prepended to the original query text. +// See https://google.github.io/sqlcommenter/spec/ for more details. type SQLCommentCarrier struct { Query string Mode SQLCommentInjectionMode SpanID uint64 } -func NewSQLCommentCarrier(query string, mode SQLCommentInjectionMode) (s *SQLCommentCarrier) { - s = new(SQLCommentCarrier) - s.Mode = mode - s.Query = query - return s +// NewSQLCommentCarrier returns a new instance of a SQLCommentCarrier +func NewSQLCommentCarrier(query string, mode SQLCommentInjectionMode) (c *SQLCommentCarrier) { + c = new(SQLCommentCarrier) + c.Mode = mode + c.Query = query + c.SpanID = random.Uint64() + return c } +// Inject injects a span context in the carrier's query. func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { - c.SpanID = random.Uint64() if c.Mode == CommentInjectionDisabled { return nil } From 363ab4cad15f67b6433c6f7e639a6209759416c8 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 16:09:22 -0700 Subject: [PATCH 073/104] Fix gorm tests --- contrib/database/sql/sql_test.go | 11 +++-------- contrib/internal/sqltest/sqltest.go | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 7166d7370f..5ccecf7a89 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -13,19 +13,16 @@ import ( "log" "math" "os" - "strconv" "testing" "time" - "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - mssql "github.com/denisenkom/go-mssqldb" "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" ) // tableName holds the SQL table that these tests will be run against. It must be unique cross-repo. @@ -37,8 +34,6 @@ func TestMain(m *testing.M) { fmt.Println("--- SKIP: to enable integration test, set the INTEGRATION environment variable") os.Exit(0) } - os.Setenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", strconv.Itoa(int(tracer.FullSQLCommentInjection))) - defer os.Unsetenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE") defer sqltest.Prepare(tableName)() os.Exit(m.Run()) } diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 92da4e0127..222b4decc4 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -12,6 +12,7 @@ import ( "log" "net/url" "os" + "strconv" "strings" "testing" @@ -50,6 +51,7 @@ func Prepare(tableName string) func() { } mssql.Exec(queryDrop) mssql.Exec(queryCreate) + os.Setenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", strconv.Itoa(int(tracer.FullSQLCommentInjection))) svc := globalconfig.ServiceName() globalconfig.SetServiceName("test-service") return func() { From a07504f18d9ca79e6f510c5c446cc16c29ae9948 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 16:13:51 -0700 Subject: [PATCH 074/104] Fix wrong import order --- contrib/database/sql/sql_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 5ccecf7a89..10e0ea5df0 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -20,6 +20,7 @@ import ( "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" From 4d7aba050c1f5676af05134681006759504ad512 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 16:20:36 -0700 Subject: [PATCH 075/104] Gofmt --- contrib/database/sql/sql_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/database/sql/sql_test.go b/contrib/database/sql/sql_test.go index 10e0ea5df0..f4dc52d3b0 100644 --- a/contrib/database/sql/sql_test.go +++ b/contrib/database/sql/sql_test.go @@ -20,7 +20,7 @@ import ( "github.com/go-sql-driver/mysql" "github.com/lib/pq" "github.com/stretchr/testify/assert" - + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" From 7d3fbb8d4752e35ec3382fba3eb219c7acec19e5 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 16:27:06 -0700 Subject: [PATCH 076/104] Update log statement --- contrib/database/sql/conn.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index d0662f9ada..d25a91df30 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -176,8 +176,8 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { } // injectComments returns the query with sql comments injected according to the comment injection mode along -// with a span id injected into sql comments. If a span ID is returned, the caller should make sure to use it when creating -// the span following the traced database call. +// with a span id injected into sql comments. The returned span id should be used when the sql span is created +// following the traced database call. func (tp *traceParams) injectComments(ctx context.Context, query string, discardTracingTags bool) (cquery string, spanID uint64) { // The sql span only gets created after the call to the database because we need to be able to skip spans // when a driver returns driver.ErrSkip. In order to work with those constraints, a new span id is generated and @@ -190,8 +190,8 @@ func (tp *traceParams) injectComments(ctx context.Context, query string, discard sqlCommentCarrier := tracer.NewSQLCommentCarrier(query, resolveInjectionMode(tp.cfg.commentInjectionMode, discardTracingTags)) err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { - // This should happen only if the SQLCommentPropagator is not set via the sql comment injection mode. - log.Warn("contrib/database/sql: failed to inject query comments. Make sure you've set up SQLCommentInjectionMode on the propagator configuration: %v", err) + // this should never happen + log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } return sqlCommentCarrier.Query, sqlCommentCarrier.SpanID } From f8bc0801d9ebd34c5d91b7395a7bc8e49ae46b5f Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 16:35:24 -0700 Subject: [PATCH 077/104] Misc cleanup --- contrib/internal/sqltest/sqltest.go | 24 +++++++++++------------- ddtrace/tracer/option.go | 3 --- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 222b4decc4..cb9c920e09 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -96,7 +96,6 @@ func testConnect(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - //assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -117,7 +116,6 @@ func testPing(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - //assert.Len(cfg.mockTracer.InjectedComments(), 0) } } @@ -186,7 +184,7 @@ type tagExpectation struct { func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expectedTags map[string]tagExpectation) { q, ok := querySpan.Tag(ext.ResourceName).(string) require.True(t, ok, "tag %s should be a string but was %v", ext.ResourceName, q) - tags, err := extractTags(q) + tags, err := extractQueryTags(q) require.NoError(t, err) for k, e := range expectedTags { if e.MustBeSet { @@ -200,7 +198,7 @@ func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expectedTags } } -func extractTags(query string) (map[string]string, error) { +func extractQueryTags(query string) (map[string]string, error) { c, err := findSQLComment(query) if err != nil { return nil, err @@ -239,8 +237,8 @@ func extractCommentTags(comment string) (keyValues map[string]string, err error) if comment == "" { return keyValues, nil } - tagList := strings.Split(comment, ",") - for _, t := range tagList { + tags := strings.Split(comment, ",") + for _, t := range tags { k, v, err := extractKeyValue(t) if err != nil { return nil, err @@ -250,30 +248,30 @@ func extractCommentTags(comment string) (keyValues map[string]string, err error) return keyValues, nil } -func extractKeyValue(tag string) (key string, val string, err error) { +func extractKeyValue(tag string) (k string, v string, err error) { parts := strings.SplitN(tag, "=", 2) if len(parts) != 2 { return "", "", fmt.Errorf("tag format invalid, expected 'key=value' but got %s", tag) } - key, err = extractKey(parts[0]) + k, err = extractKey(parts[0]) if err != nil { return "", "", err } - val, err = extractValue(parts[1]) + v, err = extractValue(parts[1]) if err != nil { return "", "", err } - return key, val, nil + return k, v, nil } -func extractKey(keyVal string) (key string, err error) { +func extractKey(keyVal string) (k string, err error) { unescaped := unescapeMetaCharacters(keyVal) - decoded, err := url.PathUnescape(unescaped) + dec, err := url.PathUnescape(unescaped) if err != nil { return "", fmt.Errorf("failed to url unescape key: %w", err) } - return decoded, nil + return dec, nil } func extractValue(rawValue string) (value string, err error) { diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index b93327346b..a89ae19112 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -41,9 +41,6 @@ var ( // defaultMaxTagsHeaderLen specifies the default maximum length of the X-Datadog-Tags header value. defaultMaxTagsHeaderLen = 512 - - // defaultSQLCommentInjectionMode specifies the default sql comment injection mode. - defaultSQLCommentInjectionMode = CommentInjectionDisabled ) // config holds the tracer configuration. From 83bf1ae82402f1affd0e16a2391a150b903e17ad Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Tue, 7 Jun 2022 16:48:41 -0700 Subject: [PATCH 078/104] Simplify tests --- contrib/internal/sqltest/sqltest.go | 133 +++------------------------- 1 file changed, 12 insertions(+), 121 deletions(-) diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index cb9c920e09..936a6bafde 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -10,8 +10,8 @@ import ( "database/sql" "fmt" "log" - "net/url" "os" + "regexp" "strconv" "strings" "testing" @@ -136,12 +136,7 @@ func testQuery(cfg *Config) func(*testing.T) { spans := cfg.mockTracer.FinishedSpans() var querySpan mocktracer.Span - expectedCommentTags := map[string]tagExpectation{ - "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, - "ddsp": {MustBeSet: true, ExpectedValue: "0"}, - "ddsid": {MustBeSet: true}, - "ddtid": {MustBeSet: true}, - } + expectedComment := regexp.MustCompile("/\\*ddsid='[0-9]+',ddsn='test-service',ddsp='0',ddtid='[0-9]+'\\*/") if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans //connect, prepare, and query @@ -155,12 +150,7 @@ func testQuery(cfg *Config) func(*testing.T) { querySpan = spans[2] // Since SQLServer runs execute statements by doing a prepare first, the expected comment // excludes dynamic tags which can only be injected on non-prepared statements - expectedCommentTags = map[string]tagExpectation{ - "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, - "ddsp": {MustBeSet: false}, - "ddsid": {MustBeSet: false}, - "ddtid": {MustBeSet: false}, - } + expectedComment = regexp.MustCompile("/\\*ddsn='test-service'\\*/") } else { assert.Len(spans, 2) querySpan = spans[1] @@ -172,7 +162,7 @@ func testQuery(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComment(t, querySpan, expectedCommentTags) + assertInjectedComment(t, querySpan, expectedComment) } } @@ -181,30 +171,12 @@ type tagExpectation struct { ExpectedValue string } -func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expectedTags map[string]tagExpectation) { +func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expectedComment *regexp.Regexp) { q, ok := querySpan.Tag(ext.ResourceName).(string) require.True(t, ok, "tag %s should be a string but was %v", ext.ResourceName, q) - tags, err := extractQueryTags(q) + c, err := findSQLComment(q) require.NoError(t, err) - for k, e := range expectedTags { - if e.MustBeSet { - assert.NotZerof(t, tags[k], "Value must be set on tag %s", k) - } else { - assert.Zero(t, tags[k], "Value should not be set on tag %s", k) - } - if e.ExpectedValue != "" { - assert.Equal(t, e.ExpectedValue, tags[k], "Value mismatch on tag %s", k) - } - } -} - -func extractQueryTags(query string) (map[string]string, error) { - c, err := findSQLComment(query) - if err != nil { - return nil, err - } - - return extractCommentTags(c) + assert.Regexp(t, expectedComment, c) } func findSQLComment(query string) (comment string, err error) { @@ -224,72 +196,7 @@ func findSQLComment(query string) (comment string, err error) { if !strings.HasSuffix(spacesTrimmed, "*/") { return "", fmt.Errorf("comments not in the sqlcommenter format, expected to end with '*/'") } - c = strings.TrimLeft(c, "/*") - c = strings.TrimRight(c, "*/") - return strings.TrimSpace(c), nil -} - -func extractCommentTags(comment string) (keyValues map[string]string, err error) { - keyValues = make(map[string]string) - if err != nil { - return nil, err - } - if comment == "" { - return keyValues, nil - } - tags := strings.Split(comment, ",") - for _, t := range tags { - k, v, err := extractKeyValue(t) - if err != nil { - return nil, err - } - keyValues[k] = v - } - return keyValues, nil -} - -func extractKeyValue(tag string) (k string, v string, err error) { - parts := strings.SplitN(tag, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("tag format invalid, expected 'key=value' but got %s", tag) - } - k, err = extractKey(parts[0]) - if err != nil { - return "", "", err - } - v, err = extractValue(parts[1]) - if err != nil { - return "", "", err - } - return k, v, nil -} - -func extractKey(keyVal string) (k string, err error) { - unescaped := unescapeMetaCharacters(keyVal) - dec, err := url.PathUnescape(unescaped) - if err != nil { - return "", fmt.Errorf("failed to url unescape key: %w", err) - } - - return dec, nil -} - -func extractValue(rawValue string) (value string, err error) { - trimmedLeft := strings.TrimLeft(rawValue, "'") - trimmed := strings.TrimRight(trimmedLeft, "'") - - unescaped := unescapeMetaCharacters(trimmed) - decoded, err := url.PathUnescape(unescaped) - - if err != nil { - return "", fmt.Errorf("failed to url unescape value: %w", err) - } - - return decoded, nil -} - -func unescapeMetaCharacters(val string) (unescaped string) { - return strings.ReplaceAll(val, "\\'", "'") + return c, nil } func testStatement(cfg *Config) func(*testing.T) { @@ -319,13 +226,7 @@ func testStatement(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - - assertInjectedComment(t, span, map[string]tagExpectation{ - "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, - "ddsp": {MustBeSet: false}, - "ddsid": {MustBeSet: false}, - "ddtid": {MustBeSet: false}, - }) + assertInjectedComment(t, span, regexp.MustCompile("/\\*ddsn='test-service'\\*/")) cfg.mockTracer.Reset() _, err2 := stmt.Exec("New York") @@ -398,12 +299,7 @@ func testExec(cfg *Config) func(*testing.T) { parent.Finish() // flush children spans := cfg.mockTracer.FinishedSpans() - expectedCommentTags := map[string]tagExpectation{ - "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, - "ddsp": {MustBeSet: true, ExpectedValue: "0"}, - "ddsid": {MustBeSet: true}, - "ddtid": {MustBeSet: true}, - } + expectedComment := regexp.MustCompile("/\\*ddsid='[0-9]+',ddsn='test-service',ddsp='0',ddtid='[0-9]+'\\*/") if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared exec so there are 2 extra spans for the exec: //prepare, exec, and then a close @@ -422,12 +318,7 @@ func testExec(cfg *Config) func(*testing.T) { } // Since SQLServer runs execute statements by doing a prepare first, the expected comment // excludes dynamic tags which can only be injected on non-prepared statements - expectedCommentTags = map[string]tagExpectation{ - "ddsn": {MustBeSet: true, ExpectedValue: "test-service"}, - "ddsp": {MustBeSet: false}, - "ddsid": {MustBeSet: false}, - "ddtid": {MustBeSet: false}, - } + expectedComment = regexp.MustCompile("/\\*ddsn='test-service'\\*/") } else { assert.Len(spans, 5) } @@ -444,7 +335,7 @@ func testExec(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComment(t, span, expectedCommentTags) + assertInjectedComment(t, span, expectedComment) for _, s := range spans { if s.OperationName() == cfg.ExpectName && s.Tag(ext.ResourceName) == "Commit" { span = s From 6726b3d37ef9431d716d45b82b3e557b3f6cce8c Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Wed, 8 Jun 2022 07:49:09 -0700 Subject: [PATCH 079/104] Update contrib/database/sql/conn.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index d25a91df30..67fbe7d953 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -178,7 +178,7 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // injectComments returns the query with sql comments injected according to the comment injection mode along // with a span id injected into sql comments. The returned span id should be used when the sql span is created // following the traced database call. -func (tp *traceParams) injectComments(ctx context.Context, query string, discardTracingTags bool) (cquery string, spanID uint64) { +func injectComments(ctx context.Context, query string, discardTracingTags bool) (cquery string, spanID uint64) { // The sql span only gets created after the call to the database because we need to be able to skip spans // when a driver returns driver.ErrSkip. In order to work with those constraints, a new span id is generated and // used during SQL comment injection and returned for the sql span to be used later when/if the span From b6b62e76d6c30dab4f670d9762fdab4e36646887 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Wed, 8 Jun 2022 07:49:24 -0700 Subject: [PATCH 080/104] Update contrib/database/sql/conn.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 67fbe7d953..dd184df782 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -183,7 +183,7 @@ func injectComments(ctx context.Context, query string, discardTracingTags bool) // when a driver returns driver.ErrSkip. In order to work with those constraints, a new span id is generated and // used during SQL comment injection and returned for the sql span to be used later when/if the span // gets created. - var spanContext ddtrace.SpanContext + var spanctx ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() } From 799339330c766f2e0039769f758ed2dbb9dcc687 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Wed, 8 Jun 2022 07:49:36 -0700 Subject: [PATCH 081/104] Update contrib/database/sql/conn.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index dd184df782..47363f6a4c 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -187,7 +187,7 @@ func injectComments(ctx context.Context, query string, discardTracingTags bool) if span, ok := tracer.SpanFromContext(ctx); ok { spanContext = span.Context() } - sqlCommentCarrier := tracer.NewSQLCommentCarrier(query, resolveInjectionMode(tp.cfg.commentInjectionMode, discardTracingTags)) + carrier := tracer.NewSQLCommentCarrier(query, resolveInjectionMode(tp.cfg.commentInjectionMode, discardTracingTags)) err := tracer.Inject(spanContext, sqlCommentCarrier) if err != nil { // this should never happen From 738c44683e7c95c447ec65926aa14418d11aad2e Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 8 Jun 2022 21:33:03 -0700 Subject: [PATCH 082/104] Change sql comment testing strategy --- contrib/database/sql/conn.go | 23 ++- contrib/database/sql/injection_test.go | 167 ++++++++++++++++++++ contrib/database/sql/internal/mockdriver.go | 138 ++++++++++++++++ contrib/database/sql/sql.go | 3 + contrib/internal/sqltest/sqltest.go | 64 +------- ddtrace/mocktracer/mocktracer.go | 5 - ddtrace/tracer/textmap.go | 3 - 7 files changed, 323 insertions(+), 80 deletions(-) create mode 100644 contrib/database/sql/injection_test.go create mode 100644 contrib/database/sql/internal/mockdriver.go diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 47363f6a4c..92f8d57e07 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -59,14 +59,13 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() - cquery, spanID := tc.injectComments(ctx, query, true) + cquery, spanID := injectComments(ctx, query, resolveInjectionMode(tc.cfg.commentInjectionMode, true)) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) tc.tryTrace(ctx, queryTypePrepare, cquery, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } - return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: cquery}, nil } stmt, err = tc.Prepare(cquery) @@ -81,7 +80,7 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { start := time.Now() if execContext, ok := tc.Conn.(driver.ExecerContext); ok { - cquery, spanID := tc.injectComments(ctx, query, false) + cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err := execContext.ExecContext(ctx, cquery, args) tc.tryTrace(ctx, queryTypeExec, cquery, start, err, tracer.WithSpanID(spanID)) return r, err @@ -96,7 +95,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv return nil, ctx.Err() default: } - cquery, spanID := tc.injectComments(ctx, query, false) + cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err = execer.Exec(cquery, dargs) tc.tryTrace(ctx, queryTypeExec, cquery, start, err, tracer.WithSpanID(spanID)) return r, err @@ -117,7 +116,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) { start := time.Now() if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { - cquery, spanID := tc.injectComments(ctx, query, false) + cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err := queryerContext.QueryContext(ctx, cquery, args) tc.tryTrace(ctx, queryTypeQuery, cquery, start, err, tracer.WithSpanID(spanID)) return rows, err @@ -132,7 +131,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri return nil, ctx.Err() default: } - cquery, spanID := tc.injectComments(ctx, query, true) + cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err = queryer.Query(cquery, dargs) tc.tryTrace(ctx, queryTypeQuery, cquery, start, err, tracer.WithSpanID(spanID)) return rows, err @@ -178,22 +177,22 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { // injectComments returns the query with sql comments injected according to the comment injection mode along // with a span id injected into sql comments. The returned span id should be used when the sql span is created // following the traced database call. -func injectComments(ctx context.Context, query string, discardTracingTags bool) (cquery string, spanID uint64) { +func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInjectionMode) (cquery string, spanID uint64) { // The sql span only gets created after the call to the database because we need to be able to skip spans // when a driver returns driver.ErrSkip. In order to work with those constraints, a new span id is generated and // used during SQL comment injection and returned for the sql span to be used later when/if the span // gets created. - var spanctx ddtrace.SpanContext + var spanCtx ddtrace.SpanContext if span, ok := tracer.SpanFromContext(ctx); ok { - spanContext = span.Context() + spanCtx = span.Context() } - carrier := tracer.NewSQLCommentCarrier(query, resolveInjectionMode(tp.cfg.commentInjectionMode, discardTracingTags)) - err := tracer.Inject(spanContext, sqlCommentCarrier) + carrier := tracer.NewSQLCommentCarrier(query, mode) + err := carrier.Inject(spanCtx) if err != nil { // this should never happen log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } - return sqlCommentCarrier.Query, sqlCommentCarrier.SpanID + return carrier.Query, carrier.SpanID } func resolveInjectionMode(mode tracer.SQLCommentInjectionMode, discardTracingTags bool) tracer.SQLCommentInjectionMode { diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go new file mode 100644 index 0000000000..0a3dbfc5cd --- /dev/null +++ b/contrib/database/sql/injection_test.go @@ -0,0 +1,167 @@ +// 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 ( + "context" + "database/sql" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +func TestCommentInjection(t *testing.T) { + testCases := []struct { + name string + opts []RegisterOption + callDB func(ctx context.Context, db *sql.DB) error + expectedPreparedStmts []string + expectedExecutedStmts []*regexp.Regexp + }{ + { + name: "prepared statement with default mode (disabled)", + opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedPreparedStmts: []string{"SELECT 1 from DUAL"}, + }, + { + name: "prepared statement in explicitly disabled mode", + opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedPreparedStmts: []string{"SELECT 1 from DUAL"}, + }, + { + name: "prepared statement in service tags only mode", + opts: []RegisterOption{WithCommentInjection(tracer.ServiceTagsInjection)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedPreparedStmts: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, + }, + { + name: "prepared statement in full mode", + opts: []RegisterOption{WithCommentInjection(tracer.FullSQLCommentInjection)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedPreparedStmts: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, + }, + { + name: "query in default mode (disabled)", + opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + }, + { + name: "query in explicitly disabled mode", + opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + }, + { + name: "query in service tags only mode", + opts: []RegisterOption{WithCommentInjection(tracer.ServiceTagsInjection)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, + }, + { + name: "query in full mode", + opts: []RegisterOption{WithCommentInjection(tracer.FullSQLCommentInjection)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, + }, + { + name: "exec in default mode (disabled)", + opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + }, + { + name: "exec in explicitly disabled mode", + opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + }, + { + name: "exec in service tags only mode", + opts: []RegisterOption{WithCommentInjection(tracer.ServiceTagsInjection)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, + }, + { + name: "exec in full mode", + opts: []RegisterOption{WithCommentInjection(tracer.FullSQLCommentInjection)}, + callDB: func(ctx context.Context, db *sql.DB) error { + _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") + return err + }, + expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + tracer.Start(tracer.WithService("test-service"), tracer.WithEnv("test-env"), tracer.WithServiceVersion("1.0.0")) + defer tracer.Stop() + + d := internal.NewMockDriver() + Register("test", d, tc.opts...) + defer unregister("test") + + db, err := Open("test", "dn") + require.NoError(t, err) + + s, ctx := tracer.StartSpanFromContext(context.Background(), "test.call", tracer.WithSpanID(1)) + err = tc.callDB(ctx, db) + s.Finish() + + require.NoError(t, err) + require.Len(t, d.PreparedStmts, len(tc.expectedPreparedStmts)) + for i, e := range tc.expectedPreparedStmts { + assert.Equal(t, e, d.PreparedStmts[i]) + } + + require.Len(t, d.ExecutedQueries, len(tc.expectedExecutedStmts)) + for i, e := range tc.expectedExecutedStmts { + assert.Regexp(t, e, d.ExecutedQueries[i]) + } + }) + } +} diff --git a/contrib/database/sql/internal/mockdriver.go b/contrib/database/sql/internal/mockdriver.go new file mode 100644 index 0000000000..667501a9fe --- /dev/null +++ b/contrib/database/sql/internal/mockdriver.go @@ -0,0 +1,138 @@ +// 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 internal // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/sqltest" + +import ( + "context" + "database/sql/driver" + "io" +) + +type MockDriver struct { + PreparedStmts []string + ExecutedQueries []string +} + +func NewMockDriver() (d *MockDriver) { + return &MockDriver{} +} + +func (d *MockDriver) Open(name string) (driver.Conn, error) { + return &MockConn{driver: d}, nil +} + +func (d *MockDriver) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + d.ExecutedQueries = append(d.ExecutedQueries, query) + return &rows{}, nil +} + +func (d *MockDriver) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + d.PreparedStmts = append(d.PreparedStmts, query) + return &MockStmt{stmt: query, driver: d}, nil +} + +func (d *MockDriver) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + return &MockTx{driver: d}, nil +} + +type MockConn struct { + driver *MockDriver +} + +// Prepare returns a prepared statement, bound to this connection. +func (m *MockConn) Prepare(query string) (driver.Stmt, error) { + m.driver.PreparedStmts = append(m.driver.PreparedStmts, query) + return &MockStmt{stmt: query, driver: m.driver}, nil +} + +func (m *MockConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + m.driver.ExecutedQueries = append(m.driver.ExecutedQueries, query) + return &rows{}, nil +} + +func (m *MockConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + m.driver.ExecutedQueries = append(m.driver.ExecutedQueries, query) + return &mockResult{}, nil +} + +func (m *MockConn) Close() (err error) { + return nil +} + +func (m *MockConn) Begin() (driver.Tx, error) { + return &MockTx{driver: m.driver}, nil +} + +type rows struct { +} + +func (r *rows) Columns() []string { + return []string{} +} + +func (r *rows) Close() error { + return nil +} + +func (r *rows) Next(dest []driver.Value) error { + return io.EOF +} + +type MockTx struct { + driver *MockDriver +} + +func (t *MockTx) Commit() error { + return nil +} + +func (t *MockTx) Rollback() error { + return nil +} + +type MockStmt struct { + stmt string + driver *MockDriver +} + +func (s *MockStmt) Close() error { + return nil +} + +func (s *MockStmt) NumInput() int { + return 0 +} + +func (s *MockStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + return &mockResult{}, nil +} + +func (s *MockStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + return &rows{}, nil +} + +func (s *MockStmt) Exec(args []driver.Value) (driver.Result, error) { + s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + return &mockResult{}, nil +} + +func (s *MockStmt) Query(args []driver.Value) (driver.Rows, error) { + s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + return &rows{}, nil +} + +type mockResult struct { +} + +func (r *mockResult) LastInsertId() (int64, error) { + return 0, nil +} + +func (r *mockResult) RowsAffected() (int64, error) { + return 0, nil +} diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 2383f4a499..3349735962 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -186,6 +186,9 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { if math.IsNaN(cfg.analyticsRate) { cfg.analyticsRate = rc.analyticsRate } + if cfg.commentInjectionMode == 0 { + cfg.commentInjectionMode = rc.commentInjectionMode + } cfg.childSpansOnly = rc.childSpansOnly tc := &tracedConnector{ connector: c, diff --git a/contrib/internal/sqltest/sqltest.go b/contrib/internal/sqltest/sqltest.go index 936a6bafde..35dbd1ddd0 100644 --- a/contrib/internal/sqltest/sqltest.go +++ b/contrib/internal/sqltest/sqltest.go @@ -10,19 +10,13 @@ import ( "database/sql" "fmt" "log" - "os" - "regexp" - "strconv" - "strings" "testing" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" - "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // Prepare sets up a table with the given name in both the MySQL and Postgres databases and returns @@ -51,15 +45,10 @@ func Prepare(tableName string) func() { } mssql.Exec(queryDrop) mssql.Exec(queryCreate) - os.Setenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", strconv.Itoa(int(tracer.FullSQLCommentInjection))) - svc := globalconfig.ServiceName() - globalconfig.SetServiceName("test-service") return func() { mysql.Exec(queryDrop) postgres.Exec(queryDrop) mssql.Exec(queryDrop) - globalconfig.SetServiceName(svc) - defer os.Unsetenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE") } } @@ -106,7 +95,7 @@ func testPing(cfg *Config) func(*testing.T) { err := cfg.DB.Ping() assert.Nil(err) spans := cfg.mockTracer.FinishedSpans() - require.Len(t, spans, 2) + assert.Len(spans, 2) verifyConnectSpan(spans[0], assert, cfg) @@ -136,7 +125,6 @@ func testQuery(cfg *Config) func(*testing.T) { spans := cfg.mockTracer.FinishedSpans() var querySpan mocktracer.Span - expectedComment := regexp.MustCompile("/\\*ddsid='[0-9]+',ddsn='test-service',ddsp='0',ddtid='[0-9]+'\\*/") if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared queries so there are 3 spans //connect, prepare, and query @@ -148,9 +136,7 @@ func testQuery(cfg *Config) func(*testing.T) { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } querySpan = spans[2] - // Since SQLServer runs execute statements by doing a prepare first, the expected comment - // excludes dynamic tags which can only be injected on non-prepared statements - expectedComment = regexp.MustCompile("/\\*ddsn='test-service'\\*/") + } else { assert.Len(spans, 2) querySpan = spans[1] @@ -162,41 +148,7 @@ func testQuery(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, querySpan.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComment(t, querySpan, expectedComment) - } -} - -type tagExpectation struct { - MustBeSet bool - ExpectedValue string -} - -func assertInjectedComment(t *testing.T, querySpan mocktracer.Span, expectedComment *regexp.Regexp) { - q, ok := querySpan.Tag(ext.ResourceName).(string) - require.True(t, ok, "tag %s should be a string but was %v", ext.ResourceName, q) - c, err := findSQLComment(q) - require.NoError(t, err) - assert.Regexp(t, expectedComment, c) -} - -func findSQLComment(query string) (comment string, err error) { - start := strings.Index(query, "/*") - if start == -1 { - return "", nil - } - end := strings.Index(query[start:], "*/") - if end == -1 { - return "", nil - } - c := query[start : end+2] - spacesTrimmed := strings.TrimSpace(c) - if !strings.HasPrefix(spacesTrimmed, "/*") { - return "", fmt.Errorf("comments not in the sqlcommenter format, expected to start with '/*'") } - if !strings.HasSuffix(spacesTrimmed, "*/") { - return "", fmt.Errorf("comments not in the sqlcommenter format, expected to end with '*/'") - } - return c, nil } func testStatement(cfg *Config) func(*testing.T) { @@ -226,7 +178,6 @@ func testStatement(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComment(t, span, regexp.MustCompile("/\\*ddsn='test-service'\\*/")) cfg.mockTracer.Reset() _, err2 := stmt.Exec("New York") @@ -299,7 +250,6 @@ func testExec(cfg *Config) func(*testing.T) { parent.Finish() // flush children spans := cfg.mockTracer.FinishedSpans() - expectedComment := regexp.MustCompile("/\\*ddsid='[0-9]+',ddsn='test-service',ddsp='0',ddtid='[0-9]+'\\*/") if cfg.DriverName == "sqlserver" { //The mssql driver doesn't support non-prepared exec so there are 2 extra spans for the exec: //prepare, exec, and then a close @@ -316,32 +266,26 @@ func testExec(cfg *Config) func(*testing.T) { for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - // Since SQLServer runs execute statements by doing a prepare first, the expected comment - // excludes dynamic tags which can only be injected on non-prepared statements - expectedComment = regexp.MustCompile("/\\*ddsn='test-service'\\*/") } else { assert.Len(spans, 5) } var span mocktracer.Span for _, s := range spans { - rn, _ := s.Tag(ext.ResourceName).(string) - if s.OperationName() == cfg.ExpectName && strings.HasSuffix(rn, query) { + if s.OperationName() == cfg.ExpectName && s.Tag(ext.ResourceName) == query { span = s } } - require.NotNil(t, span, "span not found") + assert.NotNil(span, "span not found") cfg.ExpectTags["sql.query_type"] = "Exec" for k, v := range cfg.ExpectTags { assert.Equal(v, span.Tag(k), "Value mismatch on tag %s", k) } - assertInjectedComment(t, span, expectedComment) for _, s := range spans { if s.OperationName() == cfg.ExpectName && s.Tag(ext.ResourceName) == "Commit" { span = s } } - assert.NotNil(span, "span not found") cfg.ExpectTags["sql.query_type"] = "Commit" for k, v := range cfg.ExpectTags { diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index fc9db0193c..caef0a49d5 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -129,9 +129,6 @@ const ( ) func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { - if sc, ok := carrier.(*tracer.SQLCommentCarrier); ok { - return sc.Extract() - } reader, ok := carrier.(tracer.TextMapReader) if !ok { return nil, tracer.ErrInvalidCarrier @@ -177,8 +174,6 @@ func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { switch c := carrier.(type) { - case *tracer.SQLCommentCarrier: - return c.Inject(context) case tracer.TextMapWriter: return t.injectTextMap(context, c) default: diff --git a/ddtrace/tracer/textmap.go b/ddtrace/tracer/textmap.go index 70cbecd07a..c762c32803 100644 --- a/ddtrace/tracer/textmap.go +++ b/ddtrace/tracer/textmap.go @@ -202,9 +202,6 @@ func getPropagators(cfg *PropagatorConfig, env string) []Propagator { // out of the current process. The implementation propagates the // TraceID and the current active SpanID, as well as the Span baggage. func (p *chainedPropagator) Inject(spanCtx ddtrace.SpanContext, carrier interface{}) error { - if c, ok := carrier.(*SQLCommentCarrier); ok { - return c.Inject(spanCtx) - } for _, v := range p.injectors { err := v.Inject(spanCtx, carrier) if err != nil { From e22347a28d6e9b497b237e1ae3b9084e7bb23c29 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 8 Jun 2022 22:00:53 -0700 Subject: [PATCH 083/104] Inline sqlcommenter implementation --- contrib/database/sql/conn.go | 22 +++++++++++----------- contrib/database/sql/injection_test.go | 24 ++++++++++++------------ contrib/database/sql/option.go | 6 +++--- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 92f8d57e07..fc0d5fb956 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -62,19 +62,19 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr cquery, spanID := injectComments(ctx, query, resolveInjectionMode(tc.cfg.commentInjectionMode, true)) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) - tc.tryTrace(ctx, queryTypePrepare, cquery, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } - return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: cquery}, nil + return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } stmt, err = tc.Prepare(cquery) - tc.tryTrace(ctx, queryTypePrepare, cquery, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } - return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: cquery}, nil + return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (r driver.Result, err error) { @@ -82,7 +82,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv if execContext, ok := tc.Conn.(driver.ExecerContext); ok { cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err := execContext.ExecContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeExec, cquery, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -97,7 +97,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv } cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err = execer.Exec(cquery, dargs) - tc.tryTrace(ctx, queryTypeExec, cquery, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) return r, err } return nil, driver.ErrSkip @@ -118,7 +118,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err := queryerContext.QueryContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeQuery, cquery, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) return rows, err } if queryer, ok := tc.Conn.(driver.Queryer); ok { @@ -133,7 +133,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri } cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err = queryer.Query(cquery, dargs) - tc.tryTrace(ctx, queryTypeQuery, cquery, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) return rows, err } return nil, driver.ErrSkip @@ -186,7 +186,7 @@ func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInj if span, ok := tracer.SpanFromContext(ctx); ok { spanCtx = span.Context() } - carrier := tracer.NewSQLCommentCarrier(query, mode) + carrier := tracer.SQLCommentCarrier{Query: query, Mode: mode} err := carrier.Inject(spanCtx) if err != nil { // this should never happen @@ -196,8 +196,8 @@ func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInj } func resolveInjectionMode(mode tracer.SQLCommentInjectionMode, discardTracingTags bool) tracer.SQLCommentInjectionMode { - if discardTracingTags && mode == tracer.FullSQLCommentInjection { - mode = tracer.ServiceTagsInjection + if discardTracingTags && mode == tracer.SQLInjectionModeFull { + mode = tracer.SQLInjectionModeService } return mode } diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go index 0a3dbfc5cd..0778986fbf 100644 --- a/contrib/database/sql/injection_test.go +++ b/contrib/database/sql/injection_test.go @@ -28,7 +28,7 @@ func TestCommentInjection(t *testing.T) { }{ { name: "prepared statement with default mode (disabled)", - opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err @@ -37,7 +37,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "prepared statement in explicitly disabled mode", - opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err @@ -46,7 +46,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "prepared statement in service tags only mode", - opts: []RegisterOption{WithCommentInjection(tracer.ServiceTagsInjection)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeService)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err @@ -55,7 +55,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "prepared statement in full mode", - opts: []RegisterOption{WithCommentInjection(tracer.FullSQLCommentInjection)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeFull)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err @@ -64,7 +64,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "query in default mode (disabled)", - opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err @@ -73,7 +73,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "query in explicitly disabled mode", - opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err @@ -82,7 +82,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "query in service tags only mode", - opts: []RegisterOption{WithCommentInjection(tracer.ServiceTagsInjection)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeService)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err @@ -91,7 +91,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "query in full mode", - opts: []RegisterOption{WithCommentInjection(tracer.FullSQLCommentInjection)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeFull)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err @@ -100,7 +100,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "exec in default mode (disabled)", - opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err @@ -109,7 +109,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "exec in explicitly disabled mode", - opts: []RegisterOption{WithCommentInjection(tracer.CommentInjectionDisabled)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err @@ -118,7 +118,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "exec in service tags only mode", - opts: []RegisterOption{WithCommentInjection(tracer.ServiceTagsInjection)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeService)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err @@ -127,7 +127,7 @@ func TestCommentInjection(t *testing.T) { }, { name: "exec in full mode", - opts: []RegisterOption{WithCommentInjection(tracer.FullSQLCommentInjection)}, + opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeFull)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index b8a4c39cde..1861549c61 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -36,7 +36,7 @@ func defaults(cfg *config) { } else { cfg.analyticsRate = math.NaN() } - cfg.commentInjectionMode = tracer.SQLCommentInjectionMode(internal.IntEnv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", int(tracer.CommentInjectionDisabled))) + cfg.commentInjectionMode = tracer.SQLCommentInjectionMode(internal.IntEnv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", int(tracer.SQLInjectionDisabled))) } // WithServiceName sets the given service name when registering a driver, @@ -87,10 +87,10 @@ func WithChildSpansOnly() Option { } } -// WithCommentInjection enables injection of tags as sql comments on traced queries. +// 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. Use WithStaticTagsCommentInjection if this is a concern. -func WithCommentInjection(mode tracer.SQLCommentInjectionMode) Option { +func WithSQLCommentInjection(mode tracer.SQLCommentInjectionMode) Option { return func(cfg *config) { cfg.commentInjectionMode = mode } From 3576b5614451b3c04c62d67bd4d9b5d711f9f115 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 8 Jun 2022 22:04:32 -0700 Subject: [PATCH 084/104] Fix mode handling bug --- ddtrace/tracer/sqlcomment.go | 151 +++++++++++------------------- ddtrace/tracer/sqlcomment_test.go | 12 +-- 2 files changed, 62 insertions(+), 101 deletions(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index d2b2d308ce..bf01ede422 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -21,22 +21,22 @@ import ( type SQLCommentInjectionMode int const ( - // CommentInjectionDisabled represents the comment injection mode where all injection is disabled. - CommentInjectionDisabled SQLCommentInjectionMode = 0 - // ServiceTagsInjection represents the comment injection mode where only service tags (name, env, version) are injected. - ServiceTagsInjection SQLCommentInjectionMode = 1 - // FullSQLCommentInjection represents the comment injection mode where both service tags and tracing tags. Tracing tags include span id, trace id and sampling priority. - FullSQLCommentInjection SQLCommentInjectionMode = 2 + // SQLInjectionDisabled represents the comment injection mode where all injection is disabled. + SQLInjectionDisabled SQLCommentInjectionMode = 0 + // SQLInjectionModeService represents the comment injection mode where only service tags (name, env, version) are injected. + SQLInjectionModeService SQLCommentInjectionMode = 1 + // SQLInjectionModeFull represents the comment injection mode where both service tags and tracing tags. Tracing tags include span id, trace id and sampling priority. + SQLInjectionModeFull SQLCommentInjectionMode = 2 ) // Key names for SQL comment tags. const ( - SamplingPrioritySQLCommentKey = "ddsp" - TraceIDSQLCommentKey = "ddtid" - SpanIDSQLCommentKey = "ddsid" - ServiceNameSQLCommentKey = "ddsn" - ServiceVersionSQLCommentKey = "ddsv" - ServiceEnvironmentSQLCommentKey = "dde" + sqlCommentKeySamplingPriority = "ddsp" + sqlCommentTraceID = "ddtid" + sqlCommentSpanID = "ddsid" + sqlCommentService = "ddsn" + sqlCommentVersion = "ddsv" + sqlCommentEnv = "dde" ) // SQLCommentCarrier is a carrier implementation that injects a span context in a SQL query in the form @@ -48,23 +48,33 @@ type SQLCommentCarrier struct { SpanID uint64 } -// NewSQLCommentCarrier returns a new instance of a SQLCommentCarrier -func NewSQLCommentCarrier(query string, mode SQLCommentInjectionMode) (c *SQLCommentCarrier) { - c = new(SQLCommentCarrier) - c.Mode = mode - c.Query = query - c.SpanID = random.Uint64() - return c -} - // Inject injects a span context in the carrier's query. func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { - if c.Mode == CommentInjectionDisabled { - return nil - } - + c.SpanID = random.Uint64() tags := make(map[string]string) - if c.Mode == ServiceTagsInjection || c.Mode == FullSQLCommentInjection { + switch c.Mode { + case SQLInjectionDisabled: + return nil + case SQLInjectionModeFull: + samplingPriority := 0 + var traceID uint64 + ctx, ok := spanCtx.(*spanContext) + if ok { + if sp, ok := ctx.samplingPriority(); ok { + samplingPriority = sp + } + if ctx.TraceID() > 0 { + traceID = ctx.TraceID() + } + } + if traceID == 0 { + traceID = c.SpanID + } + tags[sqlCommentTraceID] = strconv.FormatUint(traceID, 10) + tags[sqlCommentSpanID] = strconv.FormatUint(c.SpanID, 10) + tags[sqlCommentKeySamplingPriority] = strconv.Itoa(samplingPriority) + fallthrough + case SQLInjectionModeService: ctx, ok := spanCtx.(*spanContext) var env, version string if ok { @@ -76,36 +86,15 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { } } if globalconfig.ServiceName() != "" { - tags[ServiceNameSQLCommentKey] = globalconfig.ServiceName() + tags[sqlCommentService] = globalconfig.ServiceName() } if env != "" { - tags[ServiceEnvironmentSQLCommentKey] = env + tags[sqlCommentEnv] = env } if version != "" { - tags[ServiceVersionSQLCommentKey] = version - } - } - if c.Mode == FullSQLCommentInjection { - samplingPriority := 0 - - var traceID uint64 - ctx, ok := spanCtx.(*spanContext) - if ok { - if sp, ok := ctx.samplingPriority(); ok { - samplingPriority = sp - } - if ctx.TraceID() > 0 { - traceID = ctx.TraceID() - } + tags[sqlCommentVersion] = version } - if traceID == 0 { - traceID = c.SpanID - } - tags[TraceIDSQLCommentKey] = strconv.FormatUint(traceID, 10) - tags[SpanIDSQLCommentKey] = strconv.FormatUint(c.SpanID, 10) - tags[SamplingPrioritySQLCommentKey] = strconv.Itoa(samplingPriority) } - c.Query = commentQuery(c.Query, tags) return nil } @@ -114,58 +103,30 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { // prepended SQL comment. The format of the comment follows the sqlcommenter spec. // See https://google.github.io/sqlcommenter/spec/ for more details. func commentQuery(query string, tags map[string]string) string { - c := serializeTags(tags) - if c == "" { - return query - } - if query == "" { - return c - } - return fmt.Sprintf("%s %s", c, query) -} - -func serializeTags(tags map[string]string) (comment string) { if len(tags) == 0 { return "" } serializedTags := make([]string, 0, len(tags)) for k, v := range tags { - serializedTags = append(serializedTags, serializeTag(k, v)) + eKey := url.QueryEscape(k) + eKey = strings.Replace(eKey, "+", "%20", -1) + sKey := strings.ReplaceAll(eKey, "'", "\\'") + eVal := url.QueryEscape(v) + eVal = strings.Replace(eVal, "+", "%20", -1) + escVal := strings.ReplaceAll(eVal, "'", "\\'") + sValue := fmt.Sprintf("'%s'", escVal) + serializedTags = append(serializedTags, fmt.Sprintf("%s=%s", sKey, sValue)) } sort.Strings(serializedTags) - comment = strings.Join(serializedTags, ",") - return fmt.Sprintf("/*%s*/", comment) -} - -func serializeTag(key string, value string) (serialized string) { - sKey := serializeKey(key) - sValue := serializeValue(value) - - return fmt.Sprintf("%s=%s", sKey, sValue) -} - -func serializeKey(key string) (encoded string) { - enc := urlEncode(key) - return escapeMetaChars(enc) -} - -func serializeValue(val string) (encoded string) { - enc := urlEncode(val) - escapedMeta := escapeMetaChars(enc) - return escapeSQL(escapedMeta) -} - -func urlEncode(val string) string { - e := url.QueryEscape(val) - return strings.Replace(e, "+", "%20", -1) -} - -func escapeSQL(value string) (escaped string) { - return fmt.Sprintf("'%s'", value) -} - -func escapeMetaChars(value string) (escaped string) { - return strings.ReplaceAll(value, "'", "\\'") + sTags := strings.Join(serializedTags, ",") + cmt := fmt.Sprintf("/*%s*/", sTags) + if cmt == "" { + return query + } + if query == "" { + return cmt + } + return fmt.Sprintf("%s %s", cmt, query) } // Extract is not implemented on SQLCommentCarrier diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 7d566f6dd2..8a59dfd349 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -35,7 +35,7 @@ func TestSQLCommentPropagator(t *testing.T) { { name: "all tags injected", query: "SELECT * from FOO", - mode: FullSQLCommentInjection, + mode: SQLInjectionModeFull, prepareSpanContext: prepareSpanContextWithSpanID, expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", expectedSpanIDGen: true, @@ -43,7 +43,7 @@ func TestSQLCommentPropagator(t *testing.T) { { name: "no existing trace", query: "SELECT * from FOO", - mode: FullSQLCommentInjection, + mode: SQLInjectionModeFull, prepareSpanContext: func(tracer *tracer) ddtrace.SpanContext { return nil }, @@ -53,7 +53,7 @@ func TestSQLCommentPropagator(t *testing.T) { { name: "empty query, all tags injected", query: "", - mode: FullSQLCommentInjection, + mode: SQLInjectionModeFull, prepareSpanContext: prepareSpanContextWithSpanID, expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/", expectedSpanIDGen: true, @@ -61,7 +61,7 @@ func TestSQLCommentPropagator(t *testing.T) { { name: "query with existing comment", query: "SELECT * from FOO -- test query", - mode: FullSQLCommentInjection, + mode: SQLInjectionModeFull, prepareSpanContext: prepareSpanContextWithSpanID, expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", expectedSpanIDGen: true, @@ -69,7 +69,7 @@ func TestSQLCommentPropagator(t *testing.T) { { name: "static tags only mode", query: "SELECT * from FOO", - mode: ServiceTagsInjection, + mode: SQLInjectionModeService, prepareSpanContext: prepareSpanContextWithSpanID, expectedQuery: "/*dde='test-env',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsv='1.0.0'*/ SELECT * from FOO", expectedSpanIDGen: false, @@ -83,7 +83,7 @@ func TestSQLCommentPropagator(t *testing.T) { tracer := newTracer(WithService("whiskey-service !#$%&'()*+,/:;=?@[]"), WithEnv("test-env"), WithServiceVersion("1.0.0")) ctx := tc.prepareSpanContext(tracer) - carrier := NewSQLCommentCarrier(tc.query, tc.mode) + carrier := SQLCommentCarrier{Query: tc.query, Mode: tc.mode} err := tracer.Inject(ctx, carrier) require.NoError(t, err) From 39b6a532198eaad78c08977fb8947e13aae251a9 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 8 Jun 2022 22:17:39 -0700 Subject: [PATCH 085/104] Add godoc to mockdriver --- contrib/database/sql/internal/mockdriver.go | 77 +++++++++++---------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/contrib/database/sql/internal/mockdriver.go b/contrib/database/sql/internal/mockdriver.go index 667501a9fe..77cbc180fb 100644 --- a/contrib/database/sql/internal/mockdriver.go +++ b/contrib/database/sql/internal/mockdriver.go @@ -11,117 +11,116 @@ import ( "io" ) +// MockDriver implements a mock driver that captures and stores prepared and executed statements type MockDriver struct { PreparedStmts []string ExecutedQueries []string } -func NewMockDriver() (d *MockDriver) { - return &MockDriver{} -} - +// Open implements the Conn interface func (d *MockDriver) Open(name string) (driver.Conn, error) { - return &MockConn{driver: d}, nil -} - -func (d *MockDriver) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - d.ExecutedQueries = append(d.ExecutedQueries, query) - return &rows{}, nil -} - -func (d *MockDriver) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { - d.PreparedStmts = append(d.PreparedStmts, query) - return &MockStmt{stmt: query, driver: d}, nil -} - -func (d *MockDriver) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { - return &MockTx{driver: d}, nil + return &mockConn{driver: d}, nil } -type MockConn struct { +type mockConn struct { driver *MockDriver } -// Prepare returns a prepared statement, bound to this connection. -func (m *MockConn) Prepare(query string) (driver.Stmt, error) { +// Prepare implements the driver.Conn interface +func (m *mockConn) Prepare(query string) (driver.Stmt, error) { m.driver.PreparedStmts = append(m.driver.PreparedStmts, query) - return &MockStmt{stmt: query, driver: m.driver}, nil + return &mockStmt{stmt: query, driver: m.driver}, nil } -func (m *MockConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { +// QueryContext implements the QueryerContext interface +func (m *mockConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { m.driver.ExecutedQueries = append(m.driver.ExecutedQueries, query) return &rows{}, nil } -func (m *MockConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { +// ExecContext implements the ExecerContext interface +func (m *mockConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { m.driver.ExecutedQueries = append(m.driver.ExecutedQueries, query) return &mockResult{}, nil } -func (m *MockConn) Close() (err error) { +// Close implements the Conn interface +func (m *mockConn) Close() (err error) { return nil } -func (m *MockConn) Begin() (driver.Tx, error) { - return &MockTx{driver: m.driver}, nil +// Begin implements the Conn interface +func (m *mockConn) Begin() (driver.Tx, error) { + return &mockTx{driver: m.driver}, nil } type rows struct { } +// Columns implements the Rows interface func (r *rows) Columns() []string { return []string{} } +// Close implements the Rows interface func (r *rows) Close() error { return nil } +// Next implements the Rows interface func (r *rows) Next(dest []driver.Value) error { return io.EOF } -type MockTx struct { +type mockTx struct { driver *MockDriver } -func (t *MockTx) Commit() error { +// Commit implements the Tx interface +func (t *mockTx) Commit() error { return nil } -func (t *MockTx) Rollback() error { +// Rollback implements the Tx interface +func (t *mockTx) Rollback() error { return nil } -type MockStmt struct { +type mockStmt struct { stmt string driver *MockDriver } -func (s *MockStmt) Close() error { +// Close implements the Stmt interface +func (s *mockStmt) Close() error { return nil } -func (s *MockStmt) NumInput() int { +// NumInput implements the Stmt interface +func (s *mockStmt) NumInput() int { return 0 } -func (s *MockStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { +// Exec implements the Stmt interface +func (s *mockStmt) Exec(args []driver.Value) (driver.Result, error) { s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) return &mockResult{}, nil } -func (s *MockStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { +// Query implements the Stmt interface +func (s *mockStmt) Query(args []driver.Value) (driver.Rows, error) { s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) return &rows{}, nil } -func (s *MockStmt) Exec(args []driver.Value) (driver.Result, error) { +// ExecContext implements the StmtExecContext interface +func (s *mockStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) return &mockResult{}, nil } -func (s *MockStmt) Query(args []driver.Value) (driver.Rows, error) { +// QueryContext implements the StmtQueryContext interface +func (s *mockStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) return &rows{}, nil } @@ -129,10 +128,12 @@ func (s *MockStmt) Query(args []driver.Value) (driver.Rows, error) { type mockResult struct { } +// LastInsertId implements the Result interface func (r *mockResult) LastInsertId() (int64, error) { return 0, nil } +// RowsAffected implements the Result interface func (r *mockResult) RowsAffected() (int64, error) { return 0, nil } From d3605f072fc6a08768358f75a9edb93b352ff0cf Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Wed, 8 Jun 2022 22:26:38 -0700 Subject: [PATCH 086/104] Fix tests --- contrib/database/sql/injection_test.go | 2 +- ddtrace/tracer/sqlcomment_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go index 0778986fbf..22a9706f86 100644 --- a/contrib/database/sql/injection_test.go +++ b/contrib/database/sql/injection_test.go @@ -141,7 +141,7 @@ func TestCommentInjection(t *testing.T) { tracer.Start(tracer.WithService("test-service"), tracer.WithEnv("test-env"), tracer.WithServiceVersion("1.0.0")) defer tracer.Stop() - d := internal.NewMockDriver() + d := &internal.MockDriver{} Register("test", d, tc.opts...) defer unregister("test") diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 8a59dfd349..c78bd045e2 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -84,7 +84,7 @@ func TestSQLCommentPropagator(t *testing.T) { ctx := tc.prepareSpanContext(tracer) carrier := SQLCommentCarrier{Query: tc.query, Mode: tc.mode} - err := tracer.Inject(ctx, carrier) + err := carrier.Inject(ctx) require.NoError(t, err) expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(carrier.SpanID, 10)) From 452d8901af943c068ce485371ddfebe75be92b1d Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 9 Jun 2022 08:06:59 -0700 Subject: [PATCH 087/104] Update contrib/database/sql/conn.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/conn.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index fc0d5fb956..6ea847c1d7 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -59,7 +59,12 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt driver.Stmt, err error) { start := time.Now() - cquery, spanID := injectComments(ctx, query, resolveInjectionMode(tc.cfg.commentInjectionMode, true)) + mode := tc.cfg.commentInjectionMode + if mode == tracer.SQLInjectionModeFull { + // no context other than service in prepared statements + mode = tracer.SQLInjectionModeService + } + cquery, spanID := injectComments(ctx, query, mode) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) From 8415c84014da7d64cc846eca32cd7d63b449d58b Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 9 Jun 2022 08:07:12 -0700 Subject: [PATCH 088/104] Update contrib/database/sql/conn.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/conn.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 6ea847c1d7..8e9535a7f3 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -192,8 +192,7 @@ func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInj spanCtx = span.Context() } carrier := tracer.SQLCommentCarrier{Query: query, Mode: mode} - err := carrier.Inject(spanCtx) - if err != nil { + if err := carrier.Inject(spanCtx); err != nil { // this should never happen log.Warn("contrib/database/sql: failed to inject query comments: %v", err) } From 7e742dc2cc3a1705435e3ba0a0e0d536d8b5b7f6 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 9 Jun 2022 08:07:51 -0700 Subject: [PATCH 089/104] Update ddtrace/tracer/sqlcomment.go Co-authored-by: Gabriel Aszalos --- ddtrace/tracer/sqlcomment.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index bf01ede422..94fb68170e 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -118,8 +118,7 @@ func commentQuery(query string, tags map[string]string) string { serializedTags = append(serializedTags, fmt.Sprintf("%s=%s", sKey, sValue)) } sort.Strings(serializedTags) - sTags := strings.Join(serializedTags, ",") - cmt := fmt.Sprintf("/*%s*/", sTags) + cmt := fmt.Sprintf("/*%s*/", strings.Join(serializedTags, ",")) if cmt == "" { return query } From bd54cb373594fed02104be9da25302018845fadd Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 9 Jun 2022 10:45:56 -0700 Subject: [PATCH 090/104] Use span id instead of allowing options --- contrib/database/sql/conn.go | 25 +++++++++++++------------ contrib/database/sql/sql.go | 2 +- contrib/database/sql/stmt.go | 10 +++++----- contrib/database/sql/tx.go | 4 ++-- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 8e9535a7f3..c7d2b6c587 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -43,14 +43,14 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - tc.tryTrace(ctx, queryTypeBegin, "", start, err) + tc.tryTrace(ctx, queryTypeBegin, "", start, err, 0) if err != nil { return nil, err } return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - tc.tryTrace(ctx, queryTypeBegin, "", start, err) + tc.tryTrace(ctx, queryTypeBegin, "", start, err, 0) if err != nil { return nil, err } @@ -67,14 +67,14 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr cquery, spanID := injectComments(ctx, query, mode) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) - tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypePrepare, query, start, err, spanID) if err != nil { return nil, err } return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } stmt, err = tc.Prepare(cquery) - tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypePrepare, query, start, err, spanID) if err != nil { return nil, err } @@ -87,7 +87,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv if execContext, ok := tc.Conn.(driver.ExecerContext); ok { cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err := execContext.ExecContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeExec, query, start, err, spanID) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -102,7 +102,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv } cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err = execer.Exec(cquery, dargs) - tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeExec, query, start, err, spanID) return r, err } return nil, driver.ErrSkip @@ -114,7 +114,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - tc.tryTrace(ctx, queryTypePing, "", start, err) + tc.tryTrace(ctx, queryTypePing, "", start, err, 0) return err } @@ -123,7 +123,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err := queryerContext.QueryContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeQuery, query, start, err, spanID) return rows, err } if queryer, ok := tc.Conn.(driver.Queryer); ok { @@ -138,7 +138,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri } cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err = queryer.Query(cquery, dargs) - tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) + tc.tryTrace(ctx, queryTypeQuery, query, start, err, spanID) return rows, err } return nil, driver.ErrSkip @@ -207,7 +207,7 @@ func resolveInjectionMode(mode tracer.SQLCommentInjectionMode, discardTracingTag } // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, spanOpts ...ddtrace.StartSpanOption) { +func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, spanID uint64) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -219,11 +219,12 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri return } name := fmt.Sprintf("%s.query", tp.driverName) - opts := append(spanOpts, + opts := []ddtrace.StartSpanOption{ tracer.ServiceName(tp.cfg.serviceName), tracer.SpanType(ext.SpanTypeSQL), tracer.StartTime(startTime), - ) + tracer.WithSpanID(spanID), + } if !math.IsNaN(tp.cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, tp.cfg.analyticsRate)) } diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 3349735962..e48aad0551 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -141,7 +141,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - tp.tryTrace(ctx, queryTypeConnect, "", start, err) + tp.tryTrace(ctx, queryTypeConnect, "", start, err, 0) if err != nil { return nil, err } diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index a1f419d1ac..a8e8c22096 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -26,7 +26,7 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - s.tryTrace(s.ctx, queryTypeClose, "", start, err) + s.tryTrace(s.ctx, queryTypeClose, "", start, err, 0) return err } @@ -35,7 +35,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { res, err := stmtExecContext.ExecContext(ctx, args) - s.tryTrace(ctx, queryTypeExec, s.query, start, err) + s.tryTrace(ctx, queryTypeExec, s.query, start, err, 0) return res, err } dargs, err := namedValueToValue(args) @@ -48,7 +48,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) default: } res, err = s.Exec(dargs) - s.tryTrace(ctx, queryTypeExec, s.query, start, err) + s.tryTrace(ctx, queryTypeExec, s.query, start, err, 0) return res, err } @@ -57,7 +57,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { rows, err := stmtQueryContext.QueryContext(ctx, args) - s.tryTrace(ctx, queryTypeQuery, s.query, start, err) + s.tryTrace(ctx, queryTypeQuery, s.query, start, err, 0) return rows, err } dargs, err := namedValueToValue(args) @@ -70,7 +70,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) default: } rows, err = s.Query(dargs) - s.tryTrace(ctx, queryTypeQuery, s.query, start, err) + s.tryTrace(ctx, queryTypeQuery, s.query, start, err, 0) return rows, err } diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index 66d4a040f9..c8f23870d5 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -24,7 +24,7 @@ type tracedTx struct { func (t *tracedTx) Commit() (err error) { start := time.Now() err = t.Tx.Commit() - t.tryTrace(t.ctx, queryTypeCommit, "", start, err) + t.tryTrace(t.ctx, queryTypeCommit, "", start, err, 0) return err } @@ -32,6 +32,6 @@ func (t *tracedTx) Commit() (err error) { func (t *tracedTx) Rollback() (err error) { start := time.Now() err = t.Tx.Rollback() - t.tryTrace(t.ctx, queryTypeRollback, "", start, err) + t.tryTrace(t.ctx, queryTypeRollback, "", start, err, 0) return err } From b351637619cb43ee7f4bc5da70b15f0c113326a2 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 9 Jun 2022 14:11:09 -0700 Subject: [PATCH 091/104] Address some but not all comments --- contrib/database/sql/conn.go | 7 -- contrib/database/sql/injection_test.go | 24 +++---- contrib/database/sql/sql.go | 3 +- ddtrace/mocktracer/mocktracer.go | 20 ++++-- ddtrace/tracer/sqlcomment.go | 42 ++++++------ ddtrace/tracer/sqlcomment_test.go | 88 +++++++++++++------------- 6 files changed, 94 insertions(+), 90 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index c7d2b6c587..15597c1ce5 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -199,13 +199,6 @@ func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInj return carrier.Query, carrier.SpanID } -func resolveInjectionMode(mode tracer.SQLCommentInjectionMode, discardTracingTags bool) tracer.SQLCommentInjectionMode { - if discardTracingTags && mode == tracer.SQLInjectionModeFull { - mode = tracer.SQLInjectionModeService - } - return mode -} - // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, spanID uint64) { if err == driver.ErrSkip { diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go index 22a9706f86..2dde69c41a 100644 --- a/contrib/database/sql/injection_test.go +++ b/contrib/database/sql/injection_test.go @@ -27,7 +27,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts []*regexp.Regexp }{ { - name: "prepared statement with default mode (disabled)", + name: "prepare", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") @@ -36,7 +36,7 @@ func TestCommentInjection(t *testing.T) { expectedPreparedStmts: []string{"SELECT 1 from DUAL"}, }, { - name: "prepared statement in explicitly disabled mode", + name: "prepare-disabled", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") @@ -45,7 +45,7 @@ func TestCommentInjection(t *testing.T) { expectedPreparedStmts: []string{"SELECT 1 from DUAL"}, }, { - name: "prepared statement in service tags only mode", + name: "prepare-service", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeService)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") @@ -54,7 +54,7 @@ func TestCommentInjection(t *testing.T) { expectedPreparedStmts: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, }, { - name: "prepared statement in full mode", + name: "prepare-full", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeFull)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") @@ -63,7 +63,7 @@ func TestCommentInjection(t *testing.T) { expectedPreparedStmts: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, }, { - name: "query in default mode (disabled)", + name: "query", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") @@ -72,7 +72,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { - name: "query in explicitly disabled mode", + name: "query-disabled", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") @@ -81,7 +81,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { - name: "query in service tags only mode", + name: "query-service", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeService)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") @@ -90,7 +90,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, }, { - name: "query in full mode", + name: "query-full", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeFull)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") @@ -99,7 +99,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, }, { - name: "exec in default mode (disabled)", + name: "exec", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") @@ -108,7 +108,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { - name: "exec in explicitly disabled mode", + name: "exec-disabled", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionDisabled)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") @@ -117,7 +117,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { - name: "exec in service tags only mode", + name: "exec-service", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeService)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") @@ -126,7 +126,7 @@ func TestCommentInjection(t *testing.T) { expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, }, { - name: "exec in full mode", + name: "exec-full", opts: []RegisterOption{WithSQLCommentInjection(tracer.SQLInjectionModeFull)}, callDB: func(ctx context.Context, db *sql.DB) error { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index e48aad0551..f0b20949a3 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -26,6 +26,7 @@ import ( "time" "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -186,7 +187,7 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { if math.IsNaN(cfg.analyticsRate) { cfg.analyticsRate = rc.analyticsRate } - if cfg.commentInjectionMode == 0 { + if cfg.commentInjectionMode == tracer.SQLInjectionDisabled { cfg.commentInjectionMode = rc.commentInjectionMode } cfg.childSpansOnly = rc.childSpansOnly diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index caef0a49d5..32ba681e5f 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -173,12 +173,24 @@ func (t *mocktracer) Extract(carrier interface{}) (ddtrace.SpanContext, error) { } func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) error { - switch c := carrier.(type) { - case tracer.TextMapWriter: - return t.injectTextMap(context, c) - default: + writer, ok := carrier.(tracer.TextMapWriter) + if !ok { return tracer.ErrInvalidCarrier } + ctx, ok := context.(*spanContext) + if !ok || ctx.traceID == 0 || ctx.spanID == 0 { + return tracer.ErrInvalidSpanContext + } + writer.Set(traceHeader, strconv.FormatUint(ctx.traceID, 10)) + writer.Set(spanHeader, strconv.FormatUint(ctx.spanID, 10)) + if ctx.hasSamplingPriority() { + writer.Set(priorityHeader, strconv.Itoa(ctx.priority)) + } + ctx.ForeachBaggageItem(func(k, v string) bool { + writer.Set(baggagePrefix+k, v) + return true + }) + return nil } func (t *mocktracer) injectTextMap(context ddtrace.SpanContext, writer tracer.TextMapWriter) error { diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 94fb68170e..c27b57c9ec 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -8,7 +8,6 @@ package tracer import ( "fmt" "net/url" - "sort" "strconv" "strings" @@ -17,7 +16,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" ) -// SQLCommentInjectionMode represents the mode of sql comment injection. +// SQLCommentInjectionMode represents the mode of SQL comment injection. type SQLCommentInjectionMode int const ( @@ -56,16 +55,15 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { case SQLInjectionDisabled: return nil case SQLInjectionModeFull: - samplingPriority := 0 - var traceID uint64 - ctx, ok := spanCtx.(*spanContext) - if ok { + var ( + samplingPriority int + traceID uint64 + ) + if ctx, ok := spanCtx.(*spanContext); ok { if sp, ok := ctx.samplingPriority(); ok { samplingPriority = sp } - if ctx.TraceID() > 0 { - traceID = ctx.TraceID() - } + traceID = ctx.TraceID() } if traceID == 0 { traceID = c.SpanID @@ -75,9 +73,8 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { tags[sqlCommentKeySamplingPriority] = strconv.Itoa(samplingPriority) fallthrough case SQLInjectionModeService: - ctx, ok := spanCtx.(*spanContext) var env, version string - if ok { + if ctx, ok := spanCtx.(*spanContext); ok { if e, ok := ctx.meta(ext.Environment); ok { env = e } @@ -107,17 +104,20 @@ func commentQuery(query string, tags map[string]string) string { return "" } serializedTags := make([]string, 0, len(tags)) - for k, v := range tags { - eKey := url.QueryEscape(k) - eKey = strings.Replace(eKey, "+", "%20", -1) - sKey := strings.ReplaceAll(eKey, "'", "\\'") - eVal := url.QueryEscape(v) - eVal = strings.Replace(eVal, "+", "%20", -1) - escVal := strings.ReplaceAll(eVal, "'", "\\'") - sValue := fmt.Sprintf("'%s'", escVal) - serializedTags = append(serializedTags, fmt.Sprintf("%s=%s", sKey, sValue)) + // the sqlcommenter specification dictates that tags should be sorted. Since we know all injected keys, + // we skip a sorting operation by specifying the order of keys statically + orderedKeys := []string{sqlCommentEnv, sqlCommentSpanID, sqlCommentService, sqlCommentKeySamplingPriority, sqlCommentVersion, sqlCommentTraceID} + for _, k := range orderedKeys { + if v, ok := tags[k]; ok { + // we need to URL-encode all + characters and escape single quotes + // https://google.github.io/sqlcommenter/spec/ + key := strings.Replace(url.QueryEscape(k), "+", "%20", -1) + key = strings.ReplaceAll(key, "'", "\\'") + val := strings.Replace(url.QueryEscape(v), "+", "%20", -1) + val = strings.ReplaceAll(val, "'", "\\'") + serializedTags = append(serializedTags, fmt.Sprintf("%s='%s'", key, val)) + } } - sort.Strings(serializedTags) cmt := fmt.Sprintf("/*%s*/", strings.Join(serializedTags, ",")) if cmt == "" { return query diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index c78bd045e2..8073653af7 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -17,62 +17,53 @@ import ( "github.com/stretchr/testify/require" ) -func TestSQLCommentPropagator(t *testing.T) { - prepareSpanContextWithSpanID := func(tracer *tracer) ddtrace.SpanContext { - root := tracer.StartSpan("service.calling.db", WithSpanID(10)).(*span) - root.SetTag(ext.SamplingPriority, 2) - return root.Context() - } - +func TestSQLCommentCarrier(t *testing.T) { testCases := []struct { - name string - query string - mode SQLCommentInjectionMode - prepareSpanContext func(*tracer) ddtrace.SpanContext - expectedQuery string - expectedSpanIDGen bool + name string + query string + mode SQLCommentInjectionMode + injectSpan bool + expectedQuery string + expectedSpanIDGen bool }{ { - name: "all tags injected", - query: "SELECT * from FOO", - mode: SQLInjectionModeFull, - prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", - expectedSpanIDGen: true, + name: "all tags injected", + query: "SELECT * from FOO", + mode: SQLInjectionModeFull, + injectSpan: true, + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO", + expectedSpanIDGen: true, }, { - name: "no existing trace", - query: "SELECT * from FOO", - mode: SQLInjectionModeFull, - prepareSpanContext: func(tracer *tracer) ddtrace.SpanContext { - return nil - }, + name: "no existing trace", + query: "SELECT * from FOO", + mode: SQLInjectionModeFull, expectedQuery: "/*ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='0',ddtid=''*/ SELECT * from FOO", expectedSpanIDGen: true, }, { - name: "empty query, all tags injected", - query: "", - mode: SQLInjectionModeFull, - prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/", - expectedSpanIDGen: true, + name: "empty query, all tags injected", + query: "", + mode: SQLInjectionModeFull, + injectSpan: true, + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/", + expectedSpanIDGen: true, }, { - name: "query with existing comment", - query: "SELECT * from FOO -- test query", - mode: SQLInjectionModeFull, - prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", - expectedSpanIDGen: true, + name: "query with existing comment", + query: "SELECT * from FOO -- test query", + mode: SQLInjectionModeFull, + injectSpan: true, + expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", + expectedSpanIDGen: true, }, { - name: "static tags only mode", - query: "SELECT * from FOO", - mode: SQLInjectionModeService, - prepareSpanContext: prepareSpanContextWithSpanID, - expectedQuery: "/*dde='test-env',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsv='1.0.0'*/ SELECT * from FOO", - expectedSpanIDGen: false, + name: "static tags only mode", + query: "SELECT * from FOO", + mode: SQLInjectionModeService, + injectSpan: true, + expectedQuery: "/*dde='test-env',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsv='1.0.0'*/ SELECT * from FOO", + expectedSpanIDGen: false, }, } @@ -81,10 +72,17 @@ func TestSQLCommentPropagator(t *testing.T) { // the test service name includes all RFC3986 reserved characters to make sure all of them are url encoded // as per the sqlcommenter spec tracer := newTracer(WithService("whiskey-service !#$%&'()*+,/:;=?@[]"), WithEnv("test-env"), WithServiceVersion("1.0.0")) + defer tracer.Stop() + + var spanCtx ddtrace.SpanContext + if tc.injectSpan { + root := tracer.StartSpan("service.calling.db", WithSpanID(10)).(*span) + root.SetTag(ext.SamplingPriority, 2) + spanCtx = root.Context() + } - ctx := tc.prepareSpanContext(tracer) carrier := SQLCommentCarrier{Query: tc.query, Mode: tc.mode} - err := carrier.Inject(ctx) + err := carrier.Inject(spanCtx) require.NoError(t, err) expected := strings.ReplaceAll(tc.expectedQuery, "", strconv.FormatUint(carrier.SpanID, 10)) From ffc7af42f0d11b0ea44dbee867c7a3a6dafd2724 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 9 Jun 2022 10:48:59 -0700 Subject: [PATCH 092/104] Update contrib/database/sql/internal/mockdriver.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/internal/mockdriver.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/database/sql/internal/mockdriver.go b/contrib/database/sql/internal/mockdriver.go index 77cbc180fb..5ec32e03a7 100644 --- a/contrib/database/sql/internal/mockdriver.go +++ b/contrib/database/sql/internal/mockdriver.go @@ -54,8 +54,7 @@ func (m *mockConn) Begin() (driver.Tx, error) { return &mockTx{driver: m.driver}, nil } -type rows struct { -} +type rows struct {} // Columns implements the Rows interface func (r *rows) Columns() []string { From 42f27e914649a8de41e5444e282cdc5495d08d55 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 9 Jun 2022 10:49:29 -0700 Subject: [PATCH 093/104] Update contrib/database/sql/internal/mockdriver.go Co-authored-by: Gabriel Aszalos --- contrib/database/sql/internal/mockdriver.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/database/sql/internal/mockdriver.go b/contrib/database/sql/internal/mockdriver.go index 5ec32e03a7..9ff7b0fb43 100644 --- a/contrib/database/sql/internal/mockdriver.go +++ b/contrib/database/sql/internal/mockdriver.go @@ -124,8 +124,7 @@ func (s *mockStmt) QueryContext(ctx context.Context, args []driver.NamedValue) ( return &rows{}, nil } -type mockResult struct { -} +type mockResult struct {} // LastInsertId implements the Result interface func (r *mockResult) LastInsertId() (int64, error) { From 2a0dbed855b311aeb8882d973de8469e38c147d9 Mon Sep 17 00:00:00 2001 From: Alexandre Normand Date: Thu, 9 Jun 2022 11:49:36 -0700 Subject: [PATCH 094/104] Update ddtrace/tracer/sqlcomment.go Co-authored-by: Gabriel Aszalos --- ddtrace/tracer/sqlcomment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index c27b57c9ec..70deb8c65a 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -47,7 +47,7 @@ type SQLCommentCarrier struct { SpanID uint64 } -// Inject injects a span context in the carrier's query. +// Inject injects a span context in the carrier's Query field as a comment. func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { c.SpanID = random.Uint64() tags := make(map[string]string) From cb5a1b874f4db8993b828c9757fa6347bfb63a63 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 9 Jun 2022 14:13:43 -0700 Subject: [PATCH 095/104] Rename sqlcomment test cases --- ddtrace/tracer/sqlcomment_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 8073653af7..1358ad54b7 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -27,7 +27,7 @@ func TestSQLCommentCarrier(t *testing.T) { expectedSpanIDGen bool }{ { - name: "all tags injected", + name: "default", query: "SELECT * from FOO", mode: SQLInjectionModeFull, injectSpan: true, @@ -35,14 +35,22 @@ func TestSQLCommentCarrier(t *testing.T) { expectedSpanIDGen: true, }, { - name: "no existing trace", + name: "service", + query: "SELECT * from FOO", + mode: SQLInjectionModeService, + injectSpan: true, + expectedQuery: "/*dde='test-env',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsv='1.0.0'*/ SELECT * from FOO", + expectedSpanIDGen: false, + }, + { + name: "no-trace", query: "SELECT * from FOO", mode: SQLInjectionModeFull, expectedQuery: "/*ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='0',ddtid=''*/ SELECT * from FOO", expectedSpanIDGen: true, }, { - name: "empty query, all tags injected", + name: "no-query", query: "", mode: SQLInjectionModeFull, injectSpan: true, @@ -50,21 +58,13 @@ func TestSQLCommentCarrier(t *testing.T) { expectedSpanIDGen: true, }, { - name: "query with existing comment", + name: "commented", query: "SELECT * from FOO -- test query", mode: SQLInjectionModeFull, injectSpan: true, expectedQuery: "/*dde='test-env',ddsid='',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsp='2',ddsv='1.0.0',ddtid='10'*/ SELECT * from FOO -- test query", expectedSpanIDGen: true, }, - { - name: "static tags only mode", - query: "SELECT * from FOO", - mode: SQLInjectionModeService, - injectSpan: true, - expectedQuery: "/*dde='test-env',ddsn='whiskey-service%20%21%23%24%25%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D',ddsv='1.0.0'*/ SELECT * from FOO", - expectedSpanIDGen: false, - }, } for _, tc := range testCases { From 8a2d5d4a366b4afb3206425dfdda1b3e6d8872ab Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 9 Jun 2022 14:27:03 -0700 Subject: [PATCH 096/104] Gofmt mockdriver.go --- contrib/database/sql/internal/mockdriver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/database/sql/internal/mockdriver.go b/contrib/database/sql/internal/mockdriver.go index 9ff7b0fb43..a19f6f00e4 100644 --- a/contrib/database/sql/internal/mockdriver.go +++ b/contrib/database/sql/internal/mockdriver.go @@ -54,7 +54,7 @@ func (m *mockConn) Begin() (driver.Tx, error) { return &mockTx{driver: m.driver}, nil } -type rows struct {} +type rows struct{} // Columns implements the Rows interface func (r *rows) Columns() []string { @@ -124,7 +124,7 @@ func (s *mockStmt) QueryContext(ctx context.Context, args []driver.NamedValue) ( return &rows{}, nil } -type mockResult struct {} +type mockResult struct{} // LastInsertId implements the Result interface func (r *mockResult) LastInsertId() (int64, error) { From 04b59cac7464fe22d6edef40f8b1413f3dc80c37 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 9 Jun 2022 14:36:41 -0700 Subject: [PATCH 097/104] More Renaming --- contrib/database/sql/injection_test.go | 42 +++++++++++++------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go index 2dde69c41a..b52522e2f4 100644 --- a/contrib/database/sql/injection_test.go +++ b/contrib/database/sql/injection_test.go @@ -20,11 +20,11 @@ import ( func TestCommentInjection(t *testing.T) { testCases := []struct { - name string - opts []RegisterOption - callDB func(ctx context.Context, db *sql.DB) error - expectedPreparedStmts []string - expectedExecutedStmts []*regexp.Regexp + name string + opts []RegisterOption + callDB func(ctx context.Context, db *sql.DB) error + prepared []string + executed []*regexp.Regexp }{ { name: "prepare", @@ -33,7 +33,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err }, - expectedPreparedStmts: []string{"SELECT 1 from DUAL"}, + prepared: []string{"SELECT 1 from DUAL"}, }, { name: "prepare-disabled", @@ -42,7 +42,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err }, - expectedPreparedStmts: []string{"SELECT 1 from DUAL"}, + prepared: []string{"SELECT 1 from DUAL"}, }, { name: "prepare-service", @@ -51,7 +51,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err }, - expectedPreparedStmts: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, + prepared: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, }, { name: "prepare-full", @@ -60,7 +60,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.PrepareContext(ctx, "SELECT 1 from DUAL") return err }, - expectedPreparedStmts: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, + prepared: []string{"/*dde='test-env',ddsn='test-service',ddsv='1.0.0'*/ SELECT 1 from DUAL"}, }, { name: "query", @@ -69,7 +69,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { name: "query-disabled", @@ -78,7 +78,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { name: "query-service", @@ -87,7 +87,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, }, { name: "query-full", @@ -96,7 +96,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, }, { name: "exec", @@ -105,7 +105,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { name: "exec-disabled", @@ -114,7 +114,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("SELECT 1 from DUAL")}, }, { name: "exec-service", @@ -123,7 +123,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsn='test-service',ddsv='1.0.0'\\*/ SELECT 1 from DUAL")}, }, { name: "exec-full", @@ -132,7 +132,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err }, - expectedExecutedStmts: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, }, } @@ -153,13 +153,13 @@ func TestCommentInjection(t *testing.T) { s.Finish() require.NoError(t, err) - require.Len(t, d.PreparedStmts, len(tc.expectedPreparedStmts)) - for i, e := range tc.expectedPreparedStmts { + require.Len(t, d.PreparedStmts, len(tc.prepared)) + for i, e := range tc.prepared { assert.Equal(t, e, d.PreparedStmts[i]) } - require.Len(t, d.ExecutedQueries, len(tc.expectedExecutedStmts)) - for i, e := range tc.expectedExecutedStmts { + require.Len(t, d.ExecutedQueries, len(tc.executed)) + for i, e := range tc.executed { assert.Regexp(t, e, d.ExecutedQueries[i]) } }) From 3cfb8d0b6a3428eb8f47582137a31d7d1e63afee Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Thu, 9 Jun 2022 16:13:19 -0700 Subject: [PATCH 098/104] Add Benchmark and Include an Optimized Implementation of CommentQuery --- ddtrace/tracer/sqlcomment.go | 38 ++++++++++++++++++++----------- ddtrace/tracer/sqlcomment_test.go | 15 ++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 70deb8c65a..81e91dfae4 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -6,8 +6,6 @@ package tracer import ( - "fmt" - "net/url" "strconv" "strings" @@ -96,6 +94,9 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { return nil } +var keyReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D") +var valueReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D", "'", "\\'") + // commentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a // prepended SQL comment. The format of the comment follows the sqlcommenter spec. // See https://google.github.io/sqlcommenter/spec/ for more details. @@ -103,29 +104,40 @@ func commentQuery(query string, tags map[string]string) string { if len(tags) == 0 { return "" } - serializedTags := make([]string, 0, len(tags)) + b := strings.Builder{} // the sqlcommenter specification dictates that tags should be sorted. Since we know all injected keys, // we skip a sorting operation by specifying the order of keys statically orderedKeys := []string{sqlCommentEnv, sqlCommentSpanID, sqlCommentService, sqlCommentKeySamplingPriority, sqlCommentVersion, sqlCommentTraceID} + first := true for _, k := range orderedKeys { if v, ok := tags[k]; ok { - // we need to URL-encode all + characters and escape single quotes + // we need to URL-encode both keys and values and escape single quotes in values // https://google.github.io/sqlcommenter/spec/ - key := strings.Replace(url.QueryEscape(k), "+", "%20", -1) - key = strings.ReplaceAll(key, "'", "\\'") - val := strings.Replace(url.QueryEscape(v), "+", "%20", -1) - val = strings.ReplaceAll(val, "'", "\\'") - serializedTags = append(serializedTags, fmt.Sprintf("%s='%s'", key, val)) + key := keyReplacer.Replace(k) + val := valueReplacer.Replace(v) + if first { + b.WriteString("/*") + } else { + b.WriteRune(',') + } + b.WriteString(key) + b.WriteRune('=') + b.WriteRune('\'') + b.WriteString(val) + b.WriteRune('\'') + first = false } } - cmt := fmt.Sprintf("/*%s*/", strings.Join(serializedTags, ",")) - if cmt == "" { + if b.Len() == 0 { return query } + b.WriteString("*/") if query == "" { - return cmt + return b.String() } - return fmt.Sprintf("%s %s", cmt, query) + b.WriteRune(' ') + b.WriteString(query) + return b.String() } // Extract is not implemented on SQLCommentCarrier diff --git a/ddtrace/tracer/sqlcomment_test.go b/ddtrace/tracer/sqlcomment_test.go index 1358ad54b7..449e11c0ee 100644 --- a/ddtrace/tracer/sqlcomment_test.go +++ b/ddtrace/tracer/sqlcomment_test.go @@ -90,3 +90,18 @@ func TestSQLCommentCarrier(t *testing.T) { }) } } + +func BenchmarkSQLCommentSerialization(b *testing.B) { + t := map[string]string{ + sqlCommentEnv: "test-env", + sqlCommentTraceID: "0123456789", + sqlCommentSpanID: "9876543210", + sqlCommentVersion: "1.0.0", + sqlCommentService: "test-svc", + } + + b.ReportAllocs() + for n := 0; n < b.N; n++ { + commentQuery("SELECT 1 from DUAL", t) + } +} From 037c0f7ffa31902e09ed5266f29e7278fb3e77d6 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 10 Jun 2022 07:46:16 -0700 Subject: [PATCH 099/104] Revert "Use span id instead of allowing options" This reverts commit ea84e8c03c060832c8a1528e6f2798843753079c. --- contrib/database/sql/conn.go | 25 ++++++++++++------------- contrib/database/sql/sql.go | 2 +- contrib/database/sql/stmt.go | 10 +++++----- contrib/database/sql/tx.go | 4 ++-- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 15597c1ce5..8c6d74fac2 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -43,14 +43,14 @@ func (tc *tracedConn) BeginTx(ctx context.Context, opts driver.TxOptions) (tx dr start := time.Now() if connBeginTx, ok := tc.Conn.(driver.ConnBeginTx); ok { tx, err = connBeginTx.BeginTx(ctx, opts) - tc.tryTrace(ctx, queryTypeBegin, "", start, err, 0) + tc.tryTrace(ctx, queryTypeBegin, "", start, err) if err != nil { return nil, err } return &tracedTx{tx, tc.traceParams, ctx}, nil } tx, err = tc.Conn.Begin() - tc.tryTrace(ctx, queryTypeBegin, "", start, err, 0) + tc.tryTrace(ctx, queryTypeBegin, "", start, err) if err != nil { return nil, err } @@ -67,14 +67,14 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr cquery, spanID := injectComments(ctx, query, mode) if connPrepareCtx, ok := tc.Conn.(driver.ConnPrepareContext); ok { stmt, err := connPrepareCtx.PrepareContext(ctx, cquery) - tc.tryTrace(ctx, queryTypePrepare, query, start, err, spanID) + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } stmt, err = tc.Prepare(cquery) - tc.tryTrace(ctx, queryTypePrepare, query, start, err, spanID) + tc.tryTrace(ctx, queryTypePrepare, query, start, err, tracer.WithSpanID(spanID)) if err != nil { return nil, err } @@ -87,7 +87,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv if execContext, ok := tc.Conn.(driver.ExecerContext); ok { cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err := execContext.ExecContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeExec, query, start, err, spanID) + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) return r, err } if execer, ok := tc.Conn.(driver.Execer); ok { @@ -102,7 +102,7 @@ func (tc *tracedConn) ExecContext(ctx context.Context, query string, args []driv } cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) r, err = execer.Exec(cquery, dargs) - tc.tryTrace(ctx, queryTypeExec, query, start, err, spanID) + tc.tryTrace(ctx, queryTypeExec, query, start, err, tracer.WithSpanID(spanID)) return r, err } return nil, driver.ErrSkip @@ -114,7 +114,7 @@ func (tc *tracedConn) Ping(ctx context.Context) (err error) { if pinger, ok := tc.Conn.(driver.Pinger); ok { err = pinger.Ping(ctx) } - tc.tryTrace(ctx, queryTypePing, "", start, err, 0) + tc.tryTrace(ctx, queryTypePing, "", start, err) return err } @@ -123,7 +123,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri if queryerContext, ok := tc.Conn.(driver.QueryerContext); ok { cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err := queryerContext.QueryContext(ctx, cquery, args) - tc.tryTrace(ctx, queryTypeQuery, query, start, err, spanID) + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) return rows, err } if queryer, ok := tc.Conn.(driver.Queryer); ok { @@ -138,7 +138,7 @@ func (tc *tracedConn) QueryContext(ctx context.Context, query string, args []dri } cquery, spanID := injectComments(ctx, query, tc.cfg.commentInjectionMode) rows, err = queryer.Query(cquery, dargs) - tc.tryTrace(ctx, queryTypeQuery, query, start, err, spanID) + tc.tryTrace(ctx, queryTypeQuery, query, start, err, tracer.WithSpanID(spanID)) return rows, err } return nil, driver.ErrSkip @@ -200,7 +200,7 @@ func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInj } // tryTrace will create a span using the given arguments, but will act as a no-op when err is driver.ErrSkip. -func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, spanID uint64) { +func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query string, startTime time.Time, err error, spanOpts ...ddtrace.StartSpanOption) { if err == driver.ErrSkip { // Not a user error: driver is telling sql package that an // optional interface method is not implemented. There is @@ -212,12 +212,11 @@ func (tp *traceParams) tryTrace(ctx context.Context, qtype queryType, query stri return } name := fmt.Sprintf("%s.query", tp.driverName) - opts := []ddtrace.StartSpanOption{ + opts := append(spanOpts, tracer.ServiceName(tp.cfg.serviceName), tracer.SpanType(ext.SpanTypeSQL), tracer.StartTime(startTime), - tracer.WithSpanID(spanID), - } + ) if !math.IsNaN(tp.cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, tp.cfg.analyticsRate)) } diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index f0b20949a3..857d768093 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -142,7 +142,7 @@ func (t *tracedConnector) Connect(ctx context.Context) (driver.Conn, error) { } start := time.Now() conn, err := t.connector.Connect(ctx) - tp.tryTrace(ctx, queryTypeConnect, "", start, err, 0) + tp.tryTrace(ctx, queryTypeConnect, "", start, err) if err != nil { return nil, err } diff --git a/contrib/database/sql/stmt.go b/contrib/database/sql/stmt.go index a8e8c22096..a1f419d1ac 100644 --- a/contrib/database/sql/stmt.go +++ b/contrib/database/sql/stmt.go @@ -26,7 +26,7 @@ type tracedStmt struct { func (s *tracedStmt) Close() (err error) { start := time.Now() err = s.Stmt.Close() - s.tryTrace(s.ctx, queryTypeClose, "", start, err, 0) + s.tryTrace(s.ctx, queryTypeClose, "", start, err) return err } @@ -35,7 +35,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) start := time.Now() if stmtExecContext, ok := s.Stmt.(driver.StmtExecContext); ok { res, err := stmtExecContext.ExecContext(ctx, args) - s.tryTrace(ctx, queryTypeExec, s.query, start, err, 0) + s.tryTrace(ctx, queryTypeExec, s.query, start, err) return res, err } dargs, err := namedValueToValue(args) @@ -48,7 +48,7 @@ func (s *tracedStmt) ExecContext(ctx context.Context, args []driver.NamedValue) default: } res, err = s.Exec(dargs) - s.tryTrace(ctx, queryTypeExec, s.query, start, err, 0) + s.tryTrace(ctx, queryTypeExec, s.query, start, err) return res, err } @@ -57,7 +57,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) start := time.Now() if stmtQueryContext, ok := s.Stmt.(driver.StmtQueryContext); ok { rows, err := stmtQueryContext.QueryContext(ctx, args) - s.tryTrace(ctx, queryTypeQuery, s.query, start, err, 0) + s.tryTrace(ctx, queryTypeQuery, s.query, start, err) return rows, err } dargs, err := namedValueToValue(args) @@ -70,7 +70,7 @@ func (s *tracedStmt) QueryContext(ctx context.Context, args []driver.NamedValue) default: } rows, err = s.Query(dargs) - s.tryTrace(ctx, queryTypeQuery, s.query, start, err, 0) + s.tryTrace(ctx, queryTypeQuery, s.query, start, err) return rows, err } diff --git a/contrib/database/sql/tx.go b/contrib/database/sql/tx.go index c8f23870d5..66d4a040f9 100644 --- a/contrib/database/sql/tx.go +++ b/contrib/database/sql/tx.go @@ -24,7 +24,7 @@ type tracedTx struct { func (t *tracedTx) Commit() (err error) { start := time.Now() err = t.Tx.Commit() - t.tryTrace(t.ctx, queryTypeCommit, "", start, err, 0) + t.tryTrace(t.ctx, queryTypeCommit, "", start, err) return err } @@ -32,6 +32,6 @@ func (t *tracedTx) Commit() (err error) { func (t *tracedTx) Rollback() (err error) { start := time.Now() err = t.Tx.Rollback() - t.tryTrace(t.ctx, queryTypeRollback, "", start, err, 0) + t.tryTrace(t.ctx, queryTypeRollback, "", start, err) return err } From 685ee1fc3d4d89c2d4afb09e3f2743780058d8df Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 10 Jun 2022 08:27:48 -0700 Subject: [PATCH 100/104] Address some more comments --- contrib/database/sql/conn.go | 1 - contrib/database/sql/injection_test.go | 8 ++++---- contrib/database/sql/internal/mockdriver.go | 18 +++++++++--------- ddtrace/mocktracer/mocktracer.go | 17 ----------------- ddtrace/tracer/sqlcomment.go | 6 ++++-- 5 files changed, 17 insertions(+), 33 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index 8c6d74fac2..b5e43ff61d 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -78,7 +78,6 @@ func (tc *tracedConn) PrepareContext(ctx context.Context, query string) (stmt dr if err != nil { return nil, err } - return &tracedStmt{Stmt: stmt, traceParams: tc.traceParams, ctx: ctx, query: query}, nil } diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go index b52522e2f4..74f1bc5427 100644 --- a/contrib/database/sql/injection_test.go +++ b/contrib/database/sql/injection_test.go @@ -153,14 +153,14 @@ func TestCommentInjection(t *testing.T) { s.Finish() require.NoError(t, err) - require.Len(t, d.PreparedStmts, len(tc.prepared)) + require.Len(t, d.Prepared, len(tc.prepared)) for i, e := range tc.prepared { - assert.Equal(t, e, d.PreparedStmts[i]) + assert.Equal(t, e, d.Prepared[i]) } - require.Len(t, d.ExecutedQueries, len(tc.executed)) + require.Len(t, d.Executed, len(tc.executed)) for i, e := range tc.executed { - assert.Regexp(t, e, d.ExecutedQueries[i]) + assert.Regexp(t, e, d.Executed[i]) } }) } diff --git a/contrib/database/sql/internal/mockdriver.go b/contrib/database/sql/internal/mockdriver.go index a19f6f00e4..40f5428486 100644 --- a/contrib/database/sql/internal/mockdriver.go +++ b/contrib/database/sql/internal/mockdriver.go @@ -13,8 +13,8 @@ import ( // MockDriver implements a mock driver that captures and stores prepared and executed statements type MockDriver struct { - PreparedStmts []string - ExecutedQueries []string + Prepared []string + Executed []string } // Open implements the Conn interface @@ -28,19 +28,19 @@ type mockConn struct { // Prepare implements the driver.Conn interface func (m *mockConn) Prepare(query string) (driver.Stmt, error) { - m.driver.PreparedStmts = append(m.driver.PreparedStmts, query) + m.driver.Prepared = append(m.driver.Prepared, query) return &mockStmt{stmt: query, driver: m.driver}, nil } // QueryContext implements the QueryerContext interface func (m *mockConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - m.driver.ExecutedQueries = append(m.driver.ExecutedQueries, query) + m.driver.Executed = append(m.driver.Executed, query) return &rows{}, nil } // ExecContext implements the ExecerContext interface func (m *mockConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { - m.driver.ExecutedQueries = append(m.driver.ExecutedQueries, query) + m.driver.Executed = append(m.driver.Executed, query) return &mockResult{}, nil } @@ -102,25 +102,25 @@ func (s *mockStmt) NumInput() int { // Exec implements the Stmt interface func (s *mockStmt) Exec(args []driver.Value) (driver.Result, error) { - s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + s.driver.Executed = append(s.driver.Executed, s.stmt) return &mockResult{}, nil } // Query implements the Stmt interface func (s *mockStmt) Query(args []driver.Value) (driver.Rows, error) { - s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + s.driver.Executed = append(s.driver.Executed, s.stmt) return &rows{}, nil } // ExecContext implements the StmtExecContext interface func (s *mockStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { - s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + s.driver.Executed = append(s.driver.Executed, s.stmt) return &mockResult{}, nil } // QueryContext implements the StmtQueryContext interface func (s *mockStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { - s.driver.ExecutedQueries = append(s.driver.ExecutedQueries, s.stmt) + s.driver.Executed = append(s.driver.Executed, s.stmt) return &rows{}, nil } diff --git a/ddtrace/mocktracer/mocktracer.go b/ddtrace/mocktracer/mocktracer.go index 32ba681e5f..bcdb6953e9 100644 --- a/ddtrace/mocktracer/mocktracer.go +++ b/ddtrace/mocktracer/mocktracer.go @@ -192,20 +192,3 @@ func (t *mocktracer) Inject(context ddtrace.SpanContext, carrier interface{}) er }) return nil } - -func (t *mocktracer) injectTextMap(context ddtrace.SpanContext, writer tracer.TextMapWriter) error { - ctx, ok := context.(*spanContext) - if !ok || ctx.traceID == 0 || ctx.spanID == 0 { - return tracer.ErrInvalidSpanContext - } - writer.Set(traceHeader, strconv.FormatUint(ctx.traceID, 10)) - writer.Set(spanHeader, strconv.FormatUint(ctx.spanID, 10)) - if ctx.hasSamplingPriority() { - writer.Set(priorityHeader, strconv.Itoa(ctx.priority)) - } - ctx.ForeachBaggageItem(func(k, v string) bool { - writer.Set(baggagePrefix+k, v) - return true - }) - return nil -} diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 81e91dfae4..fec454d7a5 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -94,8 +94,10 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { return nil } -var keyReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D") -var valueReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D", "'", "\\'") +var ( + keyReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D") + valueReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D", "'", "\\'") +) // commentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a // prepended SQL comment. The format of the comment follows the sqlcommenter spec. From d39e3fb28d469e13e7eec277293687f555d65491 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 10 Jun 2022 11:28:03 -0700 Subject: [PATCH 101/104] Tweak test to increase confidence that sql comment span ids are new --- contrib/database/sql/injection_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/database/sql/injection_test.go b/contrib/database/sql/injection_test.go index 74f1bc5427..2d980d2e9e 100644 --- a/contrib/database/sql/injection_test.go +++ b/contrib/database/sql/injection_test.go @@ -96,7 +96,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.QueryContext(ctx, "SELECT 1 from DUAL") return err }, - executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='\\d+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, }, { name: "exec", @@ -132,7 +132,7 @@ func TestCommentInjection(t *testing.T) { _, err := db.ExecContext(ctx, "SELECT 1 from DUAL") return err }, - executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='[0-9]+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, + executed: []*regexp.Regexp{regexp.MustCompile("/\\*dde='test-env',ddsid='\\d+',ddsn='test-service',ddsp='1',ddsv='1.0.0',ddtid='1'\\*/ SELECT 1 from DUAL")}, }, } @@ -161,6 +161,8 @@ func TestCommentInjection(t *testing.T) { require.Len(t, d.Executed, len(tc.executed)) for i, e := range tc.executed { assert.Regexp(t, e, d.Executed[i]) + // the injected span ID should not be the parent's span ID + assert.NotContains(t, d.Executed[i], "ddsid='1'") } }) } From 6b53e19779a93963c8ba83c6729d1cbe0695f803 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 10 Jun 2022 11:42:55 -0700 Subject: [PATCH 102/104] Address a few more --- contrib/database/sql/conn.go | 4 ++-- ddtrace/tracer/sqlcomment.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/database/sql/conn.go b/contrib/database/sql/conn.go index b5e43ff61d..f2c98c57f8 100644 --- a/contrib/database/sql/conn.go +++ b/contrib/database/sql/conn.go @@ -178,8 +178,8 @@ func WithSpanTags(ctx context.Context, tags map[string]string) context.Context { return context.WithValue(ctx, spanTagsKey, tags) } -// injectComments returns the query with sql comments injected according to the comment injection mode along -// with a span id injected into sql comments. The returned span id should be used when the sql span is created +// injectComments returns the query with SQL comments injected according to the comment injection mode along +// with a span ID injected into SQL comments. The returned span ID should be used when the SQL span is created // following the traced database call. func injectComments(ctx context.Context, query string, mode tracer.SQLCommentInjectionMode) (cquery string, spanID uint64) { // The sql span only gets created after the call to the database because we need to be able to skip spans diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index fec454d7a5..97cc7dc303 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -106,7 +106,7 @@ func commentQuery(query string, tags map[string]string) string { if len(tags) == 0 { return "" } - b := strings.Builder{} + var b strings.Builder // the sqlcommenter specification dictates that tags should be sorted. Since we know all injected keys, // we skip a sorting operation by specifying the order of keys statically orderedKeys := []string{sqlCommentEnv, sqlCommentSpanID, sqlCommentService, sqlCommentKeySamplingPriority, sqlCommentVersion, sqlCommentTraceID} From b92c0ef80686bedf0aeda64271bf5e734fdce751 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 10 Jun 2022 12:10:59 -0700 Subject: [PATCH 103/104] Make SQLCommentInjection mode a string --- contrib/database/sql/option.go | 3 ++- contrib/database/sql/sql.go | 4 ++-- ddtrace/tracer/sqlcomment.go | 12 ++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/contrib/database/sql/option.go b/contrib/database/sql/option.go index 1861549c61..ca887772f1 100644 --- a/contrib/database/sql/option.go +++ b/contrib/database/sql/option.go @@ -7,6 +7,7 @@ package sql import ( "math" + "os" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal" @@ -36,7 +37,7 @@ func defaults(cfg *config) { } else { cfg.analyticsRate = math.NaN() } - cfg.commentInjectionMode = tracer.SQLCommentInjectionMode(internal.IntEnv("DD_TRACE_SQL_COMMENT_INJECTION_MODE", int(tracer.SQLInjectionDisabled))) + cfg.commentInjectionMode = tracer.SQLCommentInjectionMode(os.Getenv("DD_TRACE_SQL_COMMENT_INJECTION_MODE")) } // WithServiceName sets the given service name when registering a driver, diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 857d768093..1c23d22858 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -21,12 +21,12 @@ import ( "database/sql" "database/sql/driver" "errors" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "reflect" "time" "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -187,7 +187,7 @@ func OpenDB(c driver.Connector, opts ...Option) *sql.DB { if math.IsNaN(cfg.analyticsRate) { cfg.analyticsRate = rc.analyticsRate } - if cfg.commentInjectionMode == tracer.SQLInjectionDisabled { + if cfg.commentInjectionMode == tracer.SQLInjectionUndefined { cfg.commentInjectionMode = rc.commentInjectionMode } cfg.childSpansOnly = rc.childSpansOnly diff --git a/ddtrace/tracer/sqlcomment.go b/ddtrace/tracer/sqlcomment.go index 97cc7dc303..05cd0263da 100644 --- a/ddtrace/tracer/sqlcomment.go +++ b/ddtrace/tracer/sqlcomment.go @@ -15,15 +15,17 @@ import ( ) // SQLCommentInjectionMode represents the mode of SQL comment injection. -type SQLCommentInjectionMode int +type SQLCommentInjectionMode string const ( + // SQLInjectionUndefined represents the comment injection mode is not set. This is the same as SQLInjectionDisabled. + SQLInjectionUndefined SQLCommentInjectionMode = "" // SQLInjectionDisabled represents the comment injection mode where all injection is disabled. - SQLInjectionDisabled SQLCommentInjectionMode = 0 + SQLInjectionDisabled SQLCommentInjectionMode = "disabled" // SQLInjectionModeService represents the comment injection mode where only service tags (name, env, version) are injected. - SQLInjectionModeService SQLCommentInjectionMode = 1 + SQLInjectionModeService SQLCommentInjectionMode = "service" // SQLInjectionModeFull represents the comment injection mode where both service tags and tracing tags. Tracing tags include span id, trace id and sampling priority. - SQLInjectionModeFull SQLCommentInjectionMode = 2 + SQLInjectionModeFull SQLCommentInjectionMode = "full" ) // Key names for SQL comment tags. @@ -50,6 +52,8 @@ func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error { c.SpanID = random.Uint64() tags := make(map[string]string) switch c.Mode { + case SQLInjectionUndefined: + fallthrough case SQLInjectionDisabled: return nil case SQLInjectionModeFull: From 3e87cecce7961f1194aabbcde0dc8360bede2740 Mon Sep 17 00:00:00 2001 From: Alex Normand Date: Fri, 10 Jun 2022 12:55:48 -0700 Subject: [PATCH 104/104] Reorder imports --- contrib/database/sql/sql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/database/sql/sql.go b/contrib/database/sql/sql.go index 1c23d22858..0383d4021c 100644 --- a/contrib/database/sql/sql.go +++ b/contrib/database/sql/sql.go @@ -21,12 +21,12 @@ import ( "database/sql" "database/sql/driver" "errors" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "math" "reflect" "time" "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql/internal" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" )