diff --git a/README.md b/README.md index 4b3148b..ea36672 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Zipkin instrumentation SQL -A sql wrapper including Zipkin instrumentation +[![Build Status](https://travis-ci.com/jcchavezs/zipkin-instrumentation-sql.svg?branch=master)](https://travis-ci.com/jcchavezs/zipkin-instrumentation-sql) +[![Go Report Card](https://goreportcard.com/badge/github.com/jcchavezs/zipkin-instrumentation-sql)](https://goreportcard.com/report/github.com/jcchavezs/zipkin-instrumentation-sql) +[![GoDoc](https://godoc.org/github.com/jcchavezs/zipkin-instrumentation-sql?status.svg)](https://godoc.org/github.com/jcchavezs/zipkin-instrumentation-sql) +[![Sourcegraph](https://sourcegraph.com/github.com/jcchavezs/zipkin-instrumentation-sql/-/badge.svg)](https://sourcegraph.com/github.com/jcchavezs/zipkin-instrumentation-sql?badge) + +A SQL wrapper including Zipkin instrumentation ## Usage @@ -8,20 +13,67 @@ A sql wrapper including Zipkin instrumentation import ( _ "github.com/go-sql-driver/mysql" zipkinsql "github.com/jcchavezs/zipkin-instrumentation-sql" + zipkin "github.com/openzipkin/zipkin-go" ) var ( driverName string err error db *sql.DB + tracer *zipkin.Tracer ) // Register our zipkinsql wrapper for the provided MySQL driver. -driverName, err = zipkinsql.Register("mysql", zipkinsql.WithAllTraceOptions()) +driverName, err = zipkinsql.Register("mysql", tracer, zipkinsql.WithAllTraceOptions()) if err != nil { log.Fatalf("unable to register zipkin driver: %v\n", err) } -// Connect to a MySQL database using the ocsql driver wrapper. +// Connect to a MySQL database using the zipkinsql driver wrapper. db, err = sql.Open(driverName, "myDSN") -``` \ No newline at end of file +``` + +You can also wrap your own driver with zipkin instrumentation as follows: + +```go + +import ( + mysql "github.com/go-sql-driver/mysql" + "github.com/opencensus-integrations/zipkinsql" +) + +var ( + driver driver.Driver + err error + db *sql.DB + tracer *zipkin.Tracer +) + +// Explicitly wrap the MySQL driver with zipkinsql +driver = zipkinsql.Wrap(&mysql.MySQLDriver{}, tracer) + +// Register our zipkinsql wrapper as a database driver +sql.Register("zipkinsql-mysql", driver) + +// Connect to a MySQL database using the zipkinsql driver wrapper +db, err = sql.Open("zipkinsql-mysql", "myDSN") +``` + +## Usage of *Context methods + +Instrumentation is possible if the context is being passed downstream in methods. +This is not only for instrumentation purposes but also a [good practice](https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39) in go programming. `database/sql` package exposes already a set of methods that receive the context as first paramenter: + +- `*DB.Begin` -> `*DB.BeginTx` +- `*DB.Exec` -> `*DB.ExecContext` +- `*DB.Ping` -> `*DB.PingContext` +- `*DB.Prepare` -> `*DB.PrepareContext` +- `*DB.Query` -> `*DB.QueryContext` +- `*DB.QueryRow` -> `*DB.QueryRowContext` +- `*Stmt.Exec` -> `*Stmt.ExecContext` +- `*Stmt.Query` -> `*Stmt.QueryContext` +- `*Stmt.QueryRow` -> `*Stmt.QueryRowContext` +- `*Tx.Exec` -> `*Tx.ExecContext` +- `*Tx.Prepare` -> `*Tx.PrepareContext` +- `*Tx.Query` -> `*Tx.QueryContext` +- `*Tx.QueryRow` -> `*Tx.QueryRowContext` diff --git a/driver.go b/driver.go index 983a92f..4bbd17b 100644 --- a/driver.go +++ b/driver.go @@ -175,7 +175,7 @@ func (c zConn) ExecContext(ctx context.Context, query string, args []driver.Name span, _ := c.tracer.StartSpanFromContext(ctx, "sql/exec") defer span.Finish() - if c.options.Query { + if c.options.TagQuery { span.Tag("sql.query", query) } @@ -209,7 +209,7 @@ func (c zConn) QueryContext(ctx context.Context, query string, args []driver.Nam span, _ := c.tracer.StartSpanFromContext(ctx, "sql/exec") defer span.Finish() - if c.options.Query { + if c.options.TagQuery { span.Tag("sql.query", query) } @@ -293,7 +293,7 @@ type zResult struct { } func (r zResult) LastInsertId() (int64, error) { - if !r.options.LastInsertID { + if !r.options.LastInsertIDSpan { return r.driver.LastInsertId() } @@ -310,7 +310,7 @@ func (r zResult) LastInsertId() (int64, error) { func (r zResult) RowsAffected() (cnt int64, err error) { zipkin.SpanFromContext(r.ctx) - if r.options.RowsAffected && zipkin.SpanFromContext(r.ctx) != nil { + if r.options.RowsAffectedSpan && zipkin.SpanFromContext(r.ctx) != nil { span, _ := r.tracer.StartSpanFromContext(r.ctx, "sql/rows_affected") setSpanDefaultTags(span, r.options.DefaultTags) defer func() { @@ -359,7 +359,7 @@ func (s zStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res d span.Finish() }() - if s.options.Query { + if s.options.TagQuery { span.Tag("sql.query", s.query) } @@ -370,6 +370,13 @@ func (s zStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (res d if err != nil { return nil, err } + + if s.options.TagAffectedRows { + if affectedRows, err := res.RowsAffected(); err != nil { + span.Tag("sql.affected_rows", fmt.Sprintf("%d", affectedRows)) + } + } + res, err = zResult{driver: res, ctx: ctx, options: s.options}, nil return } @@ -385,7 +392,7 @@ func (s zStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows span.Finish() }() - if s.options.Query { + if s.options.TagQuery { span.Tag("sql.query", s.query) } @@ -402,6 +409,7 @@ func (s zStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (rows if err != nil { return nil, err } + rows, err = zRows{driver: rows, ctx: ctx, options: s.options}, nil return } diff --git a/options.go b/options.go index 733abc1..d48ec80 100644 --- a/options.go +++ b/options.go @@ -4,22 +4,24 @@ package zipkinsql type TraceOption func(o *TraceOptions) // TraceOptions holds configuration of our zipkinsql tracing middleware. -// By default all options are set to false intentionally when creating a wrapped -// driver and provide the most sensible default with both performance and -// security in mind. +// By default all boolean options are set to false intentionally when creating +// a wrapped driver and provide the most sensible default with both performance +// and security in mind. type TraceOptions struct { - // RowsAffected, if set to true, will enable the creation of spans on - // RowsAffected calls. - RowsAffected bool + // LastInsertIDSpan, if set to true, will enable the creation of spans on + // LastInsertId calls. + LastInsertIDSpan bool + + // RowsAffectedSpan, if set to true, will enable the creation of spans on + // RowsAffectedSpan calls. + RowsAffectedSpan bool - // Query, if set to true, will enable recording of sql queries in spans. + // TagQuery, if set to true, will enable recording of sql queries in spans. // Only allow this if it is safe to have queries recorded with respect to // security. - Query bool + TagQuery bool - // LastInsertID, if set to true, will enable the creation of spans on - // LastInsertId calls. - LastInsertID bool + TagAffectedRows bool // DefaultTags will be set to each span as default. DefaultTags map[string]string @@ -34,12 +36,13 @@ func WithAllTraceOptions() TraceOption { // AllTraceOptions has all tracing options enabled. var AllTraceOptions = TraceOptions{ - RowsAffected: true, - Query: true, - LastInsertID: true, + RowsAffectedSpan: true, + LastInsertIDSpan: true, + TagQuery: true, + TagAffectedRows: true, } -// WithOptions sets our ocsql tracing middleware options through a single +// WithOptions sets the zipkinsql tracing middleware options through a single // TraceOptions object. func WithOptions(options TraceOptions) TraceOption { return func(o *TraceOptions) { @@ -47,28 +50,36 @@ func WithOptions(options TraceOptions) TraceOption { } } -// WithRowsAffected if set to true, will enable the creation of spans on +// WithRowsAffectedSpan if set to true, will enable the creation of spans on // RowsAffected calls. -func WithRowsAffected(b bool) TraceOption { +func WithRowsAffectedSpan(b bool) TraceOption { return func(o *TraceOptions) { - o.RowsAffected = b + o.RowsAffectedSpan = b } } -// WithLastInsertID if set to true, will enable the creation of spans on +// WithLastInsertIDSpan if set to true, will enable the creation of spans on // LastInsertId calls. -func WithLastInsertID(b bool) TraceOption { +func WithLastInsertIDSpan(b bool) TraceOption { return func(o *TraceOptions) { - o.LastInsertID = b + o.LastInsertIDSpan = b } } -// WithQuery if set to true, will enable recording of sql queries in spans. +// WithTagQuery if set to true, will enable recording of SQL queries in spans. // Only allow this if it is safe to have queries recorded with respect to // security. -func WithQuery(b bool) TraceOption { +func WithTagQuery(b bool) TraceOption { + return func(o *TraceOptions) { + o.TagQuery = b + } +} + +// WithTagAffectedRows if set to true, will enable recording of the affected rows +// number in spans. +func WithTagAffectedRows(b bool) TraceOption { return func(o *TraceOptions) { - o.Query = b + o.TagAffectedRows = b } }