Skip to content

Conversation

@aldy505
Copy link
Contributor

@aldy505 aldy505 commented Oct 18, 2024

Queries insights tracing for Go.

closes #1128
closes GO-95

@codecov
Copy link

codecov bot commented Oct 19, 2024

Codecov Report

❌ Patch coverage is 83.75000% with 52 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.92%. Comparing base (c243873) to head (3695056).

Files with missing lines Patch % Lines
sentrysql/conn.go 82.66% 20 Missing and 6 partials ⚠️
sentrysql/stmt.go 85.86% 8 Missing and 5 partials ⚠️
sentrysql/driver.go 65.62% 10 Missing and 1 partial ⚠️
sentrysql/sentrysql.go 92.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #893      +/-   ##
==========================================
- Coverage   86.07%   85.92%   -0.15%     
==========================================
  Files          62       69       +7     
  Lines        6090     6410     +320     
==========================================
+ Hits         5242     5508     +266     
- Misses        634      674      +40     
- Partials      214      228      +14     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@aldy505
Copy link
Contributor Author

aldy505 commented Oct 19, 2024

I really don't like golangci-lint, it's so pedantic :(

@aldy505
Copy link
Contributor Author

aldy505 commented Oct 19, 2024

Should be good by now.

@aldy505 aldy505 marked this pull request as ready for review October 19, 2024 05:20
@aldy505 aldy505 requested a review from ribice October 19, 2024 05:20
@ribice
Copy link
Collaborator

ribice commented Oct 22, 2024

I'll test this locally myself this week and report back. We should also update the docs and main repository with this integration.

@ribice
Copy link
Collaborator

ribice commented Oct 25, 2024

You're missing examples in _examples. These should also include example on how to set the DSN.

@aldy505
Copy link
Contributor Author

aldy505 commented Oct 25, 2024

You're missing examples in _examples. These should also include example on how to set the DSN.

@ribice I've been occupied so much with work this week. Will find the time to work on this later.

@aldy505 aldy505 requested a review from giortzisg May 7, 2025 03:40
Comment on lines +131 to +142
// if WantSpan is nil, yet we got some spans, it should be an error
if tt.WantSpan == nil {
t.Errorf("Expecting no spans, but got %d spans: %v", len(gotSpans), gotSpans)
continue
}

// if WantSpan is not nil, we should have at least one span
if len(gotSpans) == 0 {
t.Errorf("Expecting at least one span, but got %d spans: %v", len(gotSpans), gotSpans)
continue
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not have tt.WantSpans as a []Span so that we can compare immediately with gotSpans? I'd say that it's not very readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some rounds with this, I can't get []Span comparison working (problems with google/go-cmp package). Is it okay if we backlog this somehow?

Comment on lines +27 to +33
func TestMain(m *testing.M) {
sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName("memory"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")), sentrysql.WithServerAddress("localhost", "5432")))
// sentrysql-legacy is used by `sentrysql_legacy_test.go`
sql.Register("sentrysql-legacy", sentrysql.NewSentrySQL(ldriver, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("legacydb")), sentrysql.WithDatabaseName("fake")))

os.Exit(m.Run())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is obfuscated from the other tests. Should probably be an init statement on each file that registers whatever we need only for the tests on the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the docs: If Register is called twice with the same name or if driver is nil, it panics.

I believe setting this here is the "right" way to do so, since it'll be run after any library's init invocation.

Comment on lines +154 to +169
var foundMatch = false
gotSpans := got[i]

var diffs []string
for _, gotSpan := range gotSpans {
if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" {
diffs = append(diffs, diff)
} else {
foundMatch = true
break
}
}

if !foundMatch {
t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n"))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing applies here. We should probably use a []Span for WantSpans

@cleptric cleptric requested review from cleptric and Copilot June 17, 2025 11:03
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds Sentry tracing integration to Go’s database/sql by wrapping drivers, connectors, connections, statements, and transactions to automatically start spans for SQL operations.

  • Introduce sentrySQLConfig and Option functions to attach metadata (db system, name, host, port).
  • Wrap driver.Driver, driver.Connector, driver.Conn, and driver.Stmt to emit spans on Exec/Query (including context versions).
  • Add parseDatabaseOperation helper to extract SQL operation names and tests for it.

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
sentrysql/tx.go Added sentryTx wrapper for driver.Tx (currently no spans).
sentrysql/stmt.go Implemented sentryStmt with Exec/Query and ExecContext/QueryContext tracing.
sentrysql/sentrysql.go Defined DatabaseSystem, config struct, and constructors (NewSentrySQL, NewSentrySQLConnector).
sentrysql/options.go Added Option functions to set database metadata.
sentrysql/operation.go Added parseDatabaseOperation to derive operation names.
sentrysql/operation_test.go Added tests for parseDatabaseOperation.
sentrysql/driver.go Wrapped driver.Driver and driver.Connector for injection.
sentrysql/conn.go Implemented sentryConn with span creation for Exec/Query/Ping/Tx.
sentrysql/go.mod Initialized module and dependencies.
sentrysql/example_test.go Added examples for NewSentrySQL and NewSentrySQLConnector.
_examples/sql/main.go End-to-end example demonstrating tracing of queries and transactions.
Comments suppressed due to low confidence (1)

sentrysql/stmt.go:75

  • There are no unit tests covering ExecContext and QueryContext in sentryStmt. Adding tests would verify span creation and fallback behavior.
func (s *sentryStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error)

return s.Exec(values)
}

parentSpan := sentry.SpanFromContext(s.ctx)
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In ExecContext, you’re calling SpanFromContext on s.ctx instead of the passed-in ctx. This can attach spans to a stale context. Use sentry.SpanFromContext(ctx) instead.

Suggested change
parentSpan := sentry.SpanFromContext(s.ctx)
parentSpan := sentry.SpanFromContext(ctx)

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. ctx provided from the function argument does not have sentry span context. SInce what matters is the context in which the statement was started.

return s.Query(values)
}

parentSpan := sentry.SpanFromContext(s.ctx)
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In QueryContext, you’re using s.ctx for the parent span lookup instead of the method’s ctx parameter. Update to sentry.SpanFromContext(ctx) to ensure the correct context is used.

Suggested change
parentSpan := sentry.SpanFromContext(s.ctx)
parentSpan := sentry.SpanFromContext(ctx)

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. ctx provided from the function argument does not have sentry span context. SInce what matters is the context in which the statement was started.

type sentryTx struct {
originalTx driver.Tx
ctx context.Context
config *sentrySQLConfig
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The config field in sentryTx is never used. Consider removing it or adding tracing for Commit/Rollback to leverage the config metadata.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracing on Commit / Rollback would be useless.

@aldy505
Copy link
Contributor Author

aldy505 commented Sep 25, 2025

@cleptric @giortzisg any updates?

@giortzisg
Copy link
Contributor

We're currently doing a big refactoring on the transport layer, will have a look after that.

return nil, err
}

return &sentryTx{originalTx: tx, ctx: s.ctx, config: s.config}, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Context Propagation Issue in Sentry Wrappers

In sentryConn.BeginTx and sentryStmt's ExecContext and QueryContext methods, the wrapper incorrectly uses the stored s.ctx field instead of the method's ctx parameter when the underlying driver supports context. This can lead to stale context propagation and incorrect Sentry span parenting.

Additional Locations (2)

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment is actually correct here, we should derive the ctx from the caller and if that is nil then fallback to the parent ctx.

return s.Query(values)
}

parentSpan := sentry.SpanFromContext(s.ctx)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Incorrect Span Parentage in Context-Aware Drivers

In both ExecContext and QueryContext, when the underlying driver supports context-aware interfaces, sentry.SpanFromContext uses s.ctx instead of the ctx parameter. Since s.ctx is intended for fallback scenarios, using it here can result in incorrect span parentage or missing spans in the tracing hierarchy.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor

@giortzisg giortzisg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep the tests as is for now. Added a few comments, let's amend them and bump the go version and we should be good to merge this.

return nil, err
}

return &sentryTx{originalTx: tx, ctx: s.ctx, config: s.config}, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment is actually correct here, we should derive the ctx from the caller and if that is nil then fallback to the parent ctx.

s.config.SetData(span, query)
defer span.Finish()

rows, err := queryerContext.QueryContext(ctx, query, args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should propagate the span context here.

Suggested change
rows, err := queryerContext.QueryContext(ctx, query, args)
rows, err := queryerContext.QueryContext(span.Context(), query, args)

s.config.SetData(span, query)
defer span.Finish()

rows, err := execerContext.ExecContext(ctx, query, args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rows, err := execerContext.ExecContext(ctx, query, args)
rows, err := execerContext.ExecContext(span.Context(), query, args)

s.config.SetData(span, s.query)
defer span.Finish()

result, err := stmtExecContext.ExecContext(ctx, args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result, err := stmtExecContext.ExecContext(ctx, args)
result, err := stmtExecContext.ExecContext(span.Context(), args)

s.ctx = ctx
return s.Query(values)
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Wrong context used for span in statement query

In QueryContext, when the original statement implements StmtQueryContext, the code retrieves the parent span from s.ctx instead of the ctx parameter. This causes span tracking to use a stale context rather than the current one provided by the caller, potentially breaking the span hierarchy and context propagation for prepared statement queries.

Fix in Cursor Fix in Web

return nil, err
}

return s.Query(values)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing context assignment in QueryContext fallback path

In sentryStmt.QueryContext, when the underlying driver doesn't implement StmtQueryContext and falls back to the non-context Query method, the code fails to assign the context to s.ctx before calling s.Query(values). This causes the context and any associated span information to be lost. The ExecContext method correctly does s.ctx = ctx before calling s.Exec(values) at line 85, but QueryContext is missing this assignment at line 123, resulting in inconsistent behavior where query tracing won't work for legacy drivers.

Fix in Cursor Fix in Web

if tt.WantSpan == nil {
t.Errorf("Expecting no spans, but got %d spans: %v", len(gotSpans), gotSpans)
continue
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Test falsely errors when WantSpan is nil

The test logic incorrectly reports an error when tt.WantSpan is nil. The comment states the intent is to error only when WantSpan is nil AND spans were received, but the condition only checks tt.WantSpan == nil without verifying len(gotSpans) > 0. This causes the test to always fail when no spans are expected, even if correctly zero spans were received.

Fix in Cursor Fix in Web

users = append(users, user)
}

return users, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing rows.Err() check after iteration loop

In the GetAllUsers function, after iterating over rows.Next(), there's no call to rows.Err() to check for errors that occurred during iteration. The rows.Next() method returns false both when iteration completes successfully and when an error occurs. Without checking rows.Err(), iteration errors could be silently ignored and the function would return incomplete results.

Fix in Cursor Fix in Web

if tt.WantSpan == nil {
t.Errorf("Expecting no spans, but got %d spans: %v", len(gotSpans), gotSpans)
continue
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Same test bug pattern in legacy test file

The test logic at line 136 has the same bug as in sentrysql_connector_test.go. It always errors when tt.WantSpan == nil without checking if len(gotSpans) > 0. This causes false test failures when no spans are correctly expected and received. The same pattern appears at multiple locations in this file (lines 254, 439, 568).

Fix in Cursor Fix in Web

if tt.WantSpan == nil {
t.Errorf("Expecting no spans, but got %d spans: %v", len(gotSpans), gotSpans)
continue
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Test incorrectly errors when expecting and receiving zero spans

The test validation logic is incorrect. The comment states "if WantSpan is nil, yet we got some spans, it should be an error", but the condition only checks if tt.WantSpan == nil without verifying that spans were actually received. When WantSpan is nil and correctly zero spans are received, the test incorrectly reports an error. The condition needs to be if tt.WantSpan == nil && len(gotSpans) > 0 to match the intended behavior described in the comment.

Additional Locations (1)

Fix in Cursor Fix in Web

users = append(users, user)
}

return users, nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Example code missing rows.Err() check after iteration

The GetAllUsers function is missing a rows.Err() check after the iteration loop. Per Go's database/sql best practices, rows.Err() must be checked after the loop to catch any errors that may have occurred during iteration. As written, users copying this example code could silently miss database errors that occur while fetching rows.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Issue type

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Database/SQL Integration

6 participants