Skip to content

Commit

Permalink
Support SELECT/INSERT/UPDATE/DELETE db operation + span names
Browse files Browse the repository at this point in the history
  • Loading branch information
damemi committed Nov 8, 2024
1 parent 1838d09 commit 89e64c7
Show file tree
Hide file tree
Showing 8 changed files with 809 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http

- Support Go standard libraries for 1.22.9 and 1.23.3. ([#1250](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1250))
- Support `google.golang.org/grpc` `1.68.0`. ([#1251](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1251))
- Support `SELECT`, `INSERT`, `UPDATE`, and `DELETE` for database span names and `db.operation.name` attribute. ([#1253](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1253))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
go.opentelemetry.io/contrib/bridges/prometheus v0.56.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/collector/pdata v1.18.0 h1:/yg2rO2dxqDM2p6GutsMCxXN6sKlXwyIz/ZYyUPONBg=
Expand Down
22 changes: 22 additions & 0 deletions internal/pkg/instrumentation/bpf/database/sql/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"os"
"strconv"

sql "github.com/xwb1989/sqlparser"

"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/ptrace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
Expand Down Expand Up @@ -95,6 +97,26 @@ func processFn(e *event) ptrace.SpanSlice {
query := unix.ByteSliceToString(e.Query[:])
if query != "" {
span.Attributes().PutStr(string(semconv.DBQueryTextKey), query)

q, err := sql.Parse(query)
if err == nil {
operation := ""
switch q.(type) {
case *sql.Select:
operation = "SELECT"
case *sql.Update:
operation = "UPDATE"
case *sql.Insert:
operation = "INSERT"
case *sql.Delete:
operation = "DELETE"
}

if operation != "" {
span.Attributes().PutStr(string(semconv.DBOperationNameKey), operation)
span.SetName(operation)
}
}
}

return spans
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/instrumentation/bpf/database/sql/probe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ func TestProbeConvertEvent(t *testing.T) {
want := func() ptrace.SpanSlice {
spans := ptrace.NewSpanSlice()
span := spans.AppendEmpty()
span.SetName("DB")
span.SetName("SELECT")
span.SetKind(ptrace.SpanKindClient)
span.SetStartTimestamp(utils.BootOffsetToTimestamp(startOffset))
span.SetEndTimestamp(utils.BootOffsetToTimestamp(endOffset))
span.SetTraceID(pcommon.TraceID(traceID))
span.SetSpanID(pcommon.SpanID(spanID))
span.SetFlags(uint32(trace.FlagsSampled))
utils.Attributes(span.Attributes(), semconv.DBQueryText("SELECT * FROM foo"))
utils.Attributes(span.Attributes(), semconv.DBQueryText("SELECT * FROM foo"), semconv.DBOperationName("SELECT"))
return spans
}()
assert.Equal(t, want, got)
Expand Down
164 changes: 164 additions & 0 deletions internal/test/e2e/databasesql/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,118 @@ func (s *Server) queryDb(w http.ResponseWriter, req *http.Request) {
}
}

func (s *Server) insert(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "INSERT INTO contacts (first_name) VALUES ('Mike')")
if err != nil {
panic(err)
}

logger.Info("queryDb called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

func (s *Server) update(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "UPDATE contacts SET last_name = 'Santa' WHERE first_name = 'Mike'")
if err != nil {
panic(err)
}

logger.Info("queryDb called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

func (s *Server) delete(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "DELETE FROM contacts WHERE first_name = 'Mike'")
if err != nil {
panic(err)
}

logger.Info("queryDb called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

func (s *Server) drop(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()

conn, err := s.db.Conn(ctx)
if err != nil {
panic(err)
}

rows, err := conn.QueryContext(req.Context(), "DROP TABLE contacts")
if err != nil {
panic(err)
}

logger.Info("queryDb called")
for rows.Next() {
var id int
var firstName string
var lastName string
var email string
var phone string
err := rows.Scan(&id, &firstName, &lastName, &email, &phone)
if err != nil {
panic(err)
}
fmt.Fprintf(w, "ID: %d, firstName: %s, lastName: %s, email: %s, phone: %s\n", id, firstName, lastName, email, phone)
}
}

var logger *zap.Logger

func main() {
Expand All @@ -115,6 +227,10 @@ func main() {
s := NewServer()

http.HandleFunc("/query_db", s.queryDb)
http.HandleFunc("/insert", s.insert)
http.HandleFunc("/update", s.update)
http.HandleFunc("/delete", s.delete)
http.HandleFunc("/drop", s.drop)
go func() {
_ = http.ListenAndServe(":8080", nil)
}()
Expand All @@ -134,6 +250,54 @@ func main() {
logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/insert")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/update")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/delete")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

resp, err = http.Get("http://localhost:8080/drop")
if err != nil {
logger.Error("Error performing GET", zap.Error(err))
}
body, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Error reading http body", zap.Error(err))
}

logger.Info("Body:\n", zap.String("body", string(body[:])))
_ = resp.Body.Close()

// give time for auto-instrumentation to report signal
time.Sleep(5 * time.Second)
}
Loading

0 comments on commit 89e64c7

Please sign in to comment.