Skip to content

Commit

Permalink
[hnysqlx] change db.caller to walk the stack until it finds a non-dat…
Browse files Browse the repository at this point in the history
…abase function name to allow additional nesting abstractions (#280)
  • Loading branch information
maplebed authored Oct 15, 2021
1 parent bdaa689 commit 0d25b78
Showing 1 changed file with 90 additions and 19 deletions.
109 changes: 90 additions & 19 deletions wrappers/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,46 +121,117 @@ func GetRequestProps(req *http.Request) map[string]interface{} {
return reqProps
}

// getCallersNames grabs the current call stack, skips up a few levels, then
// grabs as many function names as depth. Suggested use is something like 1, 2
// meaning "get my parent and its parent". skip=0 means the function calling
// this one.
func getCallersNames(skip, depth int) []string {
callers := make([]string, 0, depth)
var dbNames = map[string]interface{}{
"BindNamed": nil,
"Beginx": nil,
"BeginTxx": nil,
"Exec": nil,
"ExecContext": nil,
"Get": nil,
"GetContext": nil,
"MapperFunc": nil,
"MustBegin": nil,
"MustBeginTx": nil,
"MustExec": nil,
"MustExecContext": nil,
"NamedExec": nil,
"NamedExecContext": nil,
"NamedQuery": nil,
"NamedQueryContext": nil,
"Ping": nil,
"PingContext": nil,
"PrepareNamed": nil,
"PrepareNamedContext": nil,
"Preparex": nil,
"PreparexContext": nil,
"Query": nil,
"QueryContext": nil,
"QueryRow": nil,
"QueryRowContext": nil,
"Queryx": nil,
"QueryxContext": nil,
"QueryRowx": nil,
"QueryRowxContext": nil,
"Rebind": nil,
"Select": nil,
"SelectContext": nil,
"Close": nil,
"Driver": nil,
"SetConnMaxLifetime": nil,
"SetMaxIdleConns": nil,
"SetMaxOpenConns": nil,
// and now some function names from this instrumentation
"getNonDBCallerName": nil,
"sharedDBEvent": nil,
"BuildDBEvent": nil,
"BuildDBSpan": nil,
// and now some unnamed functions
"func1": nil,
}

var localNames = map[string]interface{}{
// and now some function names from this instrumentation
"getNonDBCallerName": nil,
"sharedDBEvent": nil,
"BuildDBEvent": nil,
"BuildDBSpan": nil,
// and now some unnamed functions
"func1": nil,
}

// getCallersNames grabs the current call stack, skips up out of runtime, then
// grabs as many function names as depth. It then walks up the tree until it
// finds a name that is not one of the official sqlx names or a name from this
// instrumentation. It uses that for the name of the span to indicate who is
// calling into the sqlx instrumentation.
func getCallersNames() (dbcall string, caller string) {
depth := 10 // how big a stack do we want to check
skip := 1 // how many steps do we jump up from here? skip runtime.

callerPcs := make([]uintptr, depth)
// add 2 to skip to account for runtime.Callers and getCallersNames
numCallers := runtime.Callers(skip+2, callerPcs)
// If there are no callers, the entire stacktrace is nil
if numCallers == 0 {
return callers
return
}
callersFrames := runtime.CallersFrames(callerPcs)
for i := 0; i < depth; i++ {
fr, more := callersFrames.Next()
// store the function's name
nameParts := strings.Split(fr.Function, ".")
callers = append(callers, nameParts[len(nameParts)-1])
caller = nameParts[len(nameParts)-1]
if _, ok := dbNames[caller]; ok {
// we've found the DB call, record the first one to ensure it's the lowest
// skip the names in this wrapper though
if _, ok := localNames[caller]; !ok {
if dbcall == "" {
dbcall = caller
}
}
} else {
// we've found a function name that's not a DB call, return it
return
}
if !more {
break
}
}
return callers
// well we didn't find one but let's return what we've got
return
}

func sharedDBEvent(bld *libhoney.Builder, query string, args ...interface{}) *libhoney.Event {
ev := bld.NewEvent()

// skip 2 - this one and the buildDB*, so we get the sqlx function and its parent
callerNames := getCallersNames(2, 2)
switch len(callerNames) {
case 2:
ev.AddField("db.call", callerNames[0])
ev.AddField("name", callerNames[0])
ev.AddField("db.caller", callerNames[1])
case 1:
ev.AddField("db.call", callerNames[0])
ev.AddField("name", callerNames[0])
default:
dbcall, dbcaller := getCallersNames()
ev.AddField("db.call", dbcall)
ev.AddField("db.caller", dbcaller)
ev.AddField("name", dbcaller)

// in case we got nothin, use a default for name. the db.* will be empty it's fine
if dbcaller == "" {
ev.AddField("name", "db")
}

Expand Down

0 comments on commit 0d25b78

Please sign in to comment.