Skip to content

ENGTAI-63552: adding filter eval for client spans #250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion examples/postgres-query/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ toolchain go1.24.2

replace github.com/hypertrace/goagent => ../..

replace github.com/hypertrace/goagent/instrumentation/hypertrace/github.com/jackc/hyperpgx => ../../instrumentation/opentelemetry/github.com/jackc/hyperpgx
replace github.com/hypertrace/goagent/instrumentation/hypertrace/github.com/jackc/hyperpgx => ../../instrumentation/hypertrace/github.com/jackc/hyperpgx
Copy link
Collaborator

@tim-mwangi tim-mwangi Jul 22, 2025

Choose a reason for hiding this comment

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

I need to check why we need this replace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

one replace was always there pointing to the opentelemetry one's go module. When I added options pattern I added it only to the hypertrace wrapper (for other instrumentations like the grpc one we only had it for our hypertrace wrapper). So for using it I switched the replace from the otel one to hypertrace. But stranger part was that replace was not working transitively, hence needed to add both.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hopefully it does not give me trouble during the migration :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, 🤞


replace github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx => ../../instrumentation/opentelemetry/github.com/jackc/hyperpgx

require github.com/hypertrace/goagent/instrumentation/hypertrace/github.com/jackc/hyperpgx v0.0.0-00010101000000-000000000000

Expand All @@ -21,6 +23,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hypertrace/agent-config/gen/go v0.0.0-20240523214336-1259231da906 // indirect
github.com/hypertrace/goagent v0.0.0-00010101000000-000000000000 // indirect
github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx v0.0.0-00010101000000-000000000000 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.8.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
Expand Down
39 changes: 39 additions & 0 deletions instrumentation/hypertrace/database/hypersql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,42 @@ sql.Register("ht-mysql", driver)
// Connect to a MySQL database using the hypersql driver wrapper
db, err = sql.Open("ht-mysql", "user:password@/dbname")
```

For adding a filter implementation to the instrumentation, there's an option to use hypersql.WithFilter to add filters to the instrumentation.
```go

import (
"github.com/go-sql-driver/mysql"
"github.com/hypertrace/goagent/instrumentation/hypertrace/database/hypersql"
"github.com/hypertrace/goagent/sdk/filter"
)

// Explicitly wrap the MySQL driver with hypersql
driver := hypersql.Wrap(&mysql.MySQLDriver{}, hypersql.WithFilter(filter.NoopFilter{}))

// Register our hypersql wrapper as a database driver
sql.Register("ht-mysql", driver)

// Connect to a MySQL database using the hypersql driver wrapper
db, err = sql.Open("ht-mysql", "user:password@/dbname")
```

OR

```go
import (
"database/sql"
"github.com/hypertrace/goagent/instrumentation/hypertrace/database/hypersql"
"github.com/hypertrace/goagent/sdk/filter"
)

// Register our hypersql wrapper for the provided MySQL driver.
driverName, err = hypersql.Register("mysql", hypersql.WithFilter(filter.NoopFilter{}))
if err != nil {
log.Fatalf("unable to register goagent driver: %v\n", err)
}

// Connect to a MySQL database using the hypersql driver wrapper.
db, err = sql.Open(driverName, "user:password@/dbname")

```
24 changes: 24 additions & 0 deletions instrumentation/hypertrace/database/hypersql/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package hypersql // import "github.com/hypertrace/goagent/instrumentation/hypertrace/database/hypersql"

import (
"github.com/hypertrace/goagent/sdk/filter"
sdkSQL "github.com/hypertrace/goagent/sdk/instrumentation/database/sql"
)

type options struct {
Filter filter.Filter
}

func (o *options) toSDKOptions() *sdkSQL.Options {
opts := (sdkSQL.Options)(*o)
return &opts
}

type Option func(o *options)

// WithFilter adds a filter to the GRPC option.
func WithFilter(f filter.Filter) Option {
return func(o *options) {
o.Filter = f
}
}
15 changes: 15 additions & 0 deletions instrumentation/hypertrace/database/hypersql/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package hypersql // import "github.com/hypertrace/goagent/instrumentation/hypertrace/database/hypersql"

import (
"testing"

"github.com/hypertrace/goagent/sdk/filter"
"github.com/stretchr/testify/assert"
)

func TestOptionsToSDK(t *testing.T) {
o := &options{
Filter: filter.NoopFilter{},
}
assert.Equal(t, filter.NoopFilter{}, o.toSDKOptions().Filter)
}
19 changes: 17 additions & 2 deletions instrumentation/hypertrace/database/hypersql/sql.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
package hypersql // import "github.com/hypertrace/goagent/instrumentation/hypertrace/database/hypersql"

import (
"database/sql/driver"
otelsql "github.com/hypertrace/goagent/instrumentation/opentelemetry/database/hypersql"
)

// Wrap takes a SQL driver and wraps it with Hypertrace instrumentation.
var Wrap = otelsql.Wrap
func Wrap(d driver.Driver, opts ...Option) driver.Driver {
o := &options{}
for _, opt := range opts {
opt(o)
}

return otelsql.Wrap(d, o.toSDKOptions())
}

// Register initializes and registers the hypersql wrapped database driver
// identified by its driverName. On success it
// returns the generated driverName to use when calling sql.Open.
var Register = otelsql.Register
func Register(driverName string, opts ...Option) (string, error) {
o := &options{}
for _, opt := range opts {
opt(o)
}
return otelsql.Register(driverName, o.toSDKOptions())

}
10 changes: 8 additions & 2 deletions instrumentation/hypertrace/github.com/jackc/hyperpgx/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ replace github.com/hypertrace/goagent => ../../../../..

replace github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx => ../../../../../instrumentation/opentelemetry/github.com/jackc/hyperpgx

require github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx v0.0.0-00010101000000-000000000000
require (
github.com/hypertrace/goagent v0.0.0-00010101000000-000000000000
github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx v0.0.0-00010101000000-000000000000
github.com/stretchr/testify v1.10.0
)

require (
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
Expand All @@ -20,7 +25,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hypertrace/agent-config/gen/go v0.0.0-20240523214336-1259231da906 // indirect
github.com/hypertrace/goagent v0.0.0-00010101000000-000000000000 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.8.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
Expand All @@ -30,6 +34,7 @@ require (
github.com/jackc/pgtype v1.7.0 // indirect
github.com/jackc/pgx/v4 v4.11.0 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
Expand Down Expand Up @@ -59,4 +64,5 @@ require (
google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
24 changes: 24 additions & 0 deletions instrumentation/hypertrace/github.com/jackc/hyperpgx/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package hyperpgx // import "github.com/hypertrace/goagent/instrumentation/hypertrace/github.com/jackc/hyperpgx"

import (
otelpgx "github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx"
"github.com/hypertrace/goagent/sdk/filter"
)

type options struct {
Filter filter.Filter
}

func (o *options) toSDKOptions() *otelpgx.Options {
opts := (otelpgx.Options)(*o)
return &opts
}

type Option func(o *options)

// WithFilter adds a filter to the GRPC option.
func WithFilter(f filter.Filter) Option {
return func(o *options) {
o.Filter = f
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package hyperpgx // import "github.com/hypertrace/goagent/instrumentation/hypertrace/github.com/jackc/hyperpgx"

import (
"testing"

"github.com/hypertrace/goagent/sdk/filter"
"github.com/stretchr/testify/assert"
)

func TestOptionsToSDK(t *testing.T) {
o := &options{
Filter: filter.NoopFilter{},
}
assert.Equal(t, filter.NoopFilter{}, o.toSDKOptions().Filter)
}
15 changes: 13 additions & 2 deletions instrumentation/hypertrace/github.com/jackc/hyperpgx/pgx.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
package hyperpgx // import "github.com/hypertrace/goagent/instrumentation/hypertrace/github.com/jackc/hyperpgx"

import otelpgx "github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx"
import (
"context"

var Connect = otelpgx.Connect
otelpgx "github.com/hypertrace/goagent/instrumentation/opentelemetry/github.com/jackc/hyperpgx"
)

func Connect(ctx context.Context, connString string, opts ...Option) (otelpgx.PGXConn, error) {
o := &options{}
for _, opt := range opts {
opt(o)
}

return otelpgx.Connect(ctx, connString, o.toSDKOptions())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ import (
// for use in a grpc.Dial call.
// Interceptor format will be replaced with the stats.Handler since instrumentation has moved to the stats.Handler.
// See: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/v1.36.0/instrumentation/google.golang.org/grpc/otelgrpc/example_test.go
func UnaryClientInterceptor() grpc.UnaryClientInterceptor {
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
o := &options{}
for _, opt := range opts {
opt(o)
}

return sdkgrpc.WrapUnaryClientInterceptor(
grpcunaryinterceptors.UnaryClientInterceptor(),
opentelemetry.SpanFromContext,
o.toSDKOptions(),
map[string]string{},
)
}
9 changes: 7 additions & 2 deletions instrumentation/hypertrace/net/hyperhttp/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import (

// NewTransport wraps the provided http.RoundTripper with one that
// starts a span and injects the span context into the outbound request headers.
func NewTransport(base http.RoundTripper) http.RoundTripper {
func NewTransport(base http.RoundTripper, opts ...Option) http.RoundTripper {
o := &options{}
for _, opt := range opts {
opt(o)
}

return otelhttp.NewTransport(
sdkhttp.WrapTransport(base, opentelemetry.SpanFromContext, map[string]string{}),
sdkhttp.WrapTransport(base, opentelemetry.SpanFromContext, o.toSDKOptions(), map[string]string{}),
)
}
9 changes: 4 additions & 5 deletions instrumentation/opentelemetry/database/hypersql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@ package hypersql // import "github.com/hypertrace/goagent/instrumentation/opente

import (
"database/sql/driver"

"github.com/hypertrace/goagent/instrumentation/opentelemetry"
sdkSQL "github.com/hypertrace/goagent/sdk/instrumentation/database/sql"
)

// Wrap takes a SQL driver and wraps it with Hypertrace instrumentation.
func Wrap(d driver.Driver) driver.Driver {
return sdkSQL.Wrap(d, opentelemetry.StartSpan)
func Wrap(d driver.Driver, options *sdkSQL.Options) driver.Driver {
return sdkSQL.Wrap(d, opentelemetry.StartSpan, options)
}

// Register initializes and registers the hypersql wrapped database driver
// identified by its driverName. On success it
// returns the generated driverName to use when calling hypersql.Open.
func Register(driverName string) (string, error) {
return sdkSQL.Register(driverName, opentelemetry.StartSpan)
func Register(driverName string, options *sdkSQL.Options) (string, error) {
return sdkSQL.Register(driverName, opentelemetry.StartSpan, options)
}
67 changes: 66 additions & 1 deletion instrumentation/opentelemetry/database/hypersql/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,27 @@ import (
"testing"

"github.com/hypertrace/goagent/instrumentation/opentelemetry/internal/tracetesting"
"github.com/hypertrace/goagent/sdk"
"github.com/hypertrace/goagent/sdk/filter/result"
sdkSQL "github.com/hypertrace/goagent/sdk/instrumentation/database/sql"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/assert"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
apitrace "go.opentelemetry.io/otel/trace"
)

type mockFilter struct {
evaluator func(span sdk.Span) result.FilterResult
}

func (f *mockFilter) Evaluate(span sdk.Span) result.FilterResult {
return f.evaluator(span)
}

func createDB(t *testing.T) (*sql.DB, func() []sdktrace.ReadOnlySpan) {
_, flusher := tracetesting.InitTracer()

driverName, err := Register("sqlite3")
driverName, err := Register("sqlite3", nil)
if err != nil {
t.Fatalf("unable to register driver")
}
Expand Down Expand Up @@ -187,3 +198,57 @@ func TestTxWithRollbackSuccess(t *testing.T) {

db.Close()
}

func TestFilter(t *testing.T) {
_, flusher := tracetesting.InitTracer()

driverName, err := Register("sqlite3", &sdkSQL.Options{
Filter: &mockFilter{
evaluator: func(span sdk.Span) result.FilterResult {
assert.Equal(t, span.GetAttributes().GetValue("span.kind"), "client")

span.SetAttribute("span.type", "nospan")
return result.FilterResult{}
},
},
})
if err != nil {
t.Fatalf("unable to register driver")
}

db, err := sql.Open(driverName, "file:test.db?cache=shared&mode=memory")
if err != nil {
t.Fatal(err)
}

rows, err := db.Query("SELECT 1 WHERE 1 = ?", 1)
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
defer rows.Close()

for rows.Next() {
var n int
if err = rows.Scan(&n); err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
}
if err = rows.Err(); err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}

spans := flusher()
assert.Equal(t, 1, len(spans))

span := spans[0]
assert.Equal(t, "db:query", span.Name())
assert.Equal(t, apitrace.SpanKindClient, span.SpanKind())

attrs := tracetesting.LookupAttributes(span.Attributes())
assert.Equal(t, "SELECT 1 WHERE 1 = ?", attrs.Get("db.statement").AsString())
assert.Equal(t, "sqlite", attrs.Get("db.system").AsString())
assert.False(t, attrs.Has("error"))
assert.Equal(t, "nospan", attrs.Get("span.type").AsString())

db.Close()
}
Loading
Loading