-
Notifications
You must be signed in to change notification settings - Fork 83
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
Support SELECT/INSERT/UPDATE/DELETE db operation + span names #1253
base: main
Are you sure you want to change the base?
Changes from all commits
2b37d78
57d8ecf
b8aa93b
c56a75b
df3735a
ef789ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http | |
- Support `google.golang.org/grpc` `1.68.0`. ([#1251](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1251)) | ||
- Support `golang.org/x/net` `0.31.0`. ([#1254](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1254)) | ||
- Support `go.opentelemetry.io/[email protected]`. ([#1302](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1302)) | ||
- 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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,8 @@ import ( | |
"os" | ||
"strconv" | ||
|
||
sql "github.com/xwb1989/sqlparser" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked through a couple sql parsing libraries, this isn't the most maintained... https://github.com/krasun/gosqlparser seems good but for some reason I don't think it supports |
||
|
||
"go.opentelemetry.io/collector/pdata/pcommon" | ||
"go.opentelemetry.io/collector/pdata/ptrace" | ||
semconv "go.opentelemetry.io/otel/semconv/v1.26.0" | ||
|
@@ -27,6 +29,9 @@ const ( | |
|
||
// IncludeDBStatementEnvVar is the environment variable to opt-in for sql query inclusion in the trace. | ||
IncludeDBStatementEnvVar = "OTEL_GO_AUTO_INCLUDE_DB_STATEMENT" | ||
|
||
// IncludeDBOperationEnvVar is the environment variable to opt-in for sql query operation in the trace. | ||
IncludeDBOperationEnvVar = "OTEL_GO_AUTO_INCLUDE_DB_OPERATION" | ||
MrAlias marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we call this |
||
) | ||
|
||
// New returns a new [probe.Probe]. | ||
|
@@ -97,6 +102,32 @@ func processFn(e *event) ptrace.SpanSlice { | |
span.Attributes().PutStr(string(semconv.DBQueryTextKey), query) | ||
} | ||
|
||
includeOperationVal := os.Getenv(IncludeDBOperationEnvVar) | ||
if includeOperationVal != "" { | ||
include, err := strconv.ParseBool(includeOperationVal) | ||
if err == nil && include { | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we try to build the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was originally doing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The table might be defined in the query statement though, right? The query parsing library should be able to get this, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that I could find, unfortunately :\ The first library I tried (https://github.com/krasun/gosqlparser) does have that, which is how I was building it at first. But that library breaks when it tries to parse a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should work, right? package main
import (
"fmt"
"github.com/xwb1989/sqlparser"
)
// Parse takes a SQL query string and returns the parsed query statement type
// and table name, or an error if parsing failed.
func Parse(query string) (string, string, error) {
stmt, err := sqlparser.Parse(query)
if err != nil {
return "", "", fmt.Errorf("failed to parse query: %w", err)
}
switch stmt := stmt.(type) {
case *sqlparser.Select:
return "SELECT", getTableName(stmt.From), nil
case *sqlparser.Update:
return "UPDATE", getTableName(stmt.TableExprs), nil
case *sqlparser.Insert:
return "INSERT", stmt.Table.Name.String(), nil
case *sqlparser.Delete:
return "DELETE", getTableName(stmt.TableExprs), nil
default:
return "UNKNOWN", "", fmt.Errorf("unsupported statement type")
}
}
// getTableName extracts the table name from a SQL node.
func getTableName(node sqlparser.SQLNode) string {
switch tableExpr := node.(type) {
case sqlparser.TableName:
return tableExpr.Name.String()
case sqlparser.TableExprs:
for _, expr := range tableExpr {
if tableName, ok := expr.(*sqlparser.AliasedTableExpr); ok {
if name, ok := tableName.Expr.(sqlparser.TableName); ok {
return name.Name.String()
}
}
}
}
return ""
}
func main() {
queries := []string{
"SELECT * FROM users WHERE id = 1",
"SELECT id, name FROM users WHERE id = 1",
"INSERT INTO orders (id, amount) VALUES (1, 100)",
"UPDATE products SET price = 19.99 WHERE id = 10",
"DELETE FROM sessions WHERE expired = true",
"CREATE TABLE logs (id INT, message TEXT)",
}
for _, query := range queries {
fmt.Println("Query: ", query)
statement, table, err := Parse(query)
if err != nil {
fmt.Println("Error:", err)
continue
}
fmt.Printf("Statement: %s, Table: %s\n", statement, table)
}
}
FWIW, looking at vitess, it seems like it should be able to support more statement types in the table lookup (i.e. table create). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using package main
import (
"fmt"
"vitess.io/vitess/go/vt/sqlparser"
)
// Parse takes a SQL query string and returns the parsed query statement type
// and table name(s), or an error if parsing failed.
func Parse(query string) (string, []string, error) {
p, err := sqlparser.New(sqlparser.Options{})
if err != nil {
return "", nil, fmt.Errorf("failed to create parser: %w", err)
}
stmt, err := p.Parse(query)
if err != nil {
return "", nil, fmt.Errorf("failed to parse query: %w", err)
}
var statementType string
var tables []string
switch stmt := stmt.(type) {
case *sqlparser.Select:
statementType = "SELECT"
tables = extractTables(stmt.From)
case *sqlparser.Update:
statementType = "UPDATE"
tables = extractTables(stmt.TableExprs)
case *sqlparser.Insert:
statementType = "INSERT"
tables = []string{stmt.Table.TableNameString()}
case *sqlparser.Delete:
statementType = "DELETE"
tables = extractTables(stmt.TableExprs)
case *sqlparser.CreateTable:
statementType = "CREATE TABLE"
tables = []string{stmt.Table.Name.String()}
case *sqlparser.AlterTable:
statementType = "ALTER TABLE"
tables = []string{stmt.Table.Name.String()}
case *sqlparser.DropTable:
statementType = "DROP TABLE"
for _, table := range stmt.FromTables {
tables = append(tables, table.Name.String())
}
case *sqlparser.CreateDatabase:
statementType = "CREATE DATABASE"
tables = []string{stmt.DBName.String()}
case *sqlparser.DropDatabase:
statementType = "DROP DATABASE"
tables = []string{stmt.DBName.String()}
case *sqlparser.TruncateTable:
statementType = "TRUNCATE TABLE"
tables = []string{stmt.Table.Name.String()}
default:
return "UNKNOWN", nil, fmt.Errorf("unsupported statement type")
}
return statementType, tables, nil
}
// extractTables extracts table names from a list of SQL nodes.
func extractTables(exprs sqlparser.TableExprs) []string {
var tables []string
for _, expr := range exprs {
switch tableExpr := expr.(type) {
case *sqlparser.AliasedTableExpr:
if name, ok := tableExpr.Expr.(sqlparser.TableName); ok {
tables = append(tables, name.Name.String())
}
}
}
return tables
}
func main() {
queries := []string{
"SELECT * FROM users WHERE id = 1",
"SELECT id, name FROM users WHERE id = 1",
"INSERT INTO users (id, name) VALUES (1, 'Alice')",
"UPDATE users SET name = 'Bob' WHERE id = 1",
"DELETE FROM users WHERE id = 1",
"CREATE TABLE users (id INT, name VARCHAR(100))",
"ALTER TABLE users ADD COLUMN age INT",
"DROP TABLE users",
"CREATE DATABASE test_db",
"DROP DATABASE test_db",
"TRUNCATE TABLE users",
}
for _, query := range queries {
fmt.Println("Query: ", query)
statement, tables, err := Parse(query)
if err != nil {
fmt.Printf("Error parsing query: %s\nQuery: %s\n\n", err, query)
continue
}
fmt.Printf("Statement: %s, Tables/DBs: %v\n", statement, tables)
}
}
|
||
} | ||
} | ||
} | ||
} | ||
|
||
return spans | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we instead use the code this was based on: https://github.com/vitessio/vitess/tree/main/go/vt/sqlparser
It seems like this was a fork of that repository1, but it hasn't been maintained or synced since that fork.
Would it be too large of a dependency to just rely on vitess directly?
Footnotes
https://github.com/xwb1989/sqlparser?tab=readme-ov-file#notice ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did think about using vitess directly, but it seemed like a lot more than what we needed. Since this is just an implementation detail, we could refactor it later if we want but I think this is the best option for right now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned when looking through all the changes that have been applied to the upstream code over the past 6 years, there's a considerable amount of changes. Notably:
Should we make our own fork of this pacakge?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we maybe get a better understanding of dependency size by looking at what the binary size is based on the possible dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll admit it looks like you've dug into it deeper than I did, hah. These are some great points so maybe that is the better approach. I'll try switching it to that