in xray/sql_context.go [306:409]
func newDBAttribute(ctx context.Context, driverName string, d driver.Driver, conn driver.Conn, dsn string, filtered bool) (*dbAttribute, error) {
var attr dbAttribute
// Detect if DSN is a URL or not, set appropriate attribute
urlDsn := dsn
if !strings.Contains(dsn, "//") {
urlDsn = "//" + urlDsn
}
// Here we're trying to detect things like `host:port/database` as a URL, which is pretty hard
// So we just assume that if it's got a scheme, a user, or a query that it's probably a URL
if u, err := url.Parse(urlDsn); err == nil && (u.Scheme != "" || u.User != nil || u.RawQuery != "" || strings.Contains(u.Path, "@")) {
// Check that this isn't in the form of user/pass@host:port/db, as that will shove the host into the path
if strings.Contains(u.Path, "@") {
u, err = url.Parse(fmt.Sprintf("%s//%s%%2F%s", u.Scheme, u.Host, u.Path[1:]))
if err != nil {
return nil, err
}
}
// Strip password from user:password pair in address
if u.User != nil {
uname := u.User.Username()
// Some drivers use "user/pass@host:port" instead of "user:pass@host:port"
// So we must manually attempt to chop off a potential password.
// But we can skip this if we already found the password.
if _, ok := u.User.Password(); !ok {
uname = strings.Split(uname, "/")[0]
}
u.User = url.User(uname)
}
// Strip password and X-Amz-Security-Token (present in RDS IAM authentication) from query parameters
q := u.Query()
q.Del("password")
q.Del("X-Amz-Security-Token")
u.RawQuery = q.Encode()
// In the case of known DSL sub segment name will be dbname@host
host, _, _ := net.SplitHostPort(u.Host)
if len(host) > 0 {
attr.host = "@" + host
} else {
attr.host = host
}
attr.url = u.String()
if !strings.Contains(dsn, "//") {
attr.url = attr.url[2:]
}
} else {
// We don't *think* it's a URL, so now we have to try our best to strip passwords from
// some unknown DSL. We attempt to detect whether it's space-delimited or semicolon-delimited
// then remove any keys with the name "password" or "pwd". This won't catch everything, but
// from surveying the current (Jan 2017) landscape of drivers it should catch most.
if filtered {
attr.connectionString = dsn
} else {
attr.connectionString = stripPasswords(dsn)
}
}
// Detect database type and use that to populate attributes
var detectors []func(ctx context.Context, conn driver.Conn, attr *dbAttribute) error
switch driverName {
case "postgres":
detectors = append(detectors, postgresDetector)
case "mysql":
detectors = append(detectors, mysqlDetector)
default:
detectors = append(detectors, postgresDetector, mysqlDetector, mssqlDetector, oracleDetector)
}
for _, detector := range detectors {
if detector(ctx, conn, &attr) == nil {
break
}
attr.databaseType = "Unknown"
attr.databaseVersion = "Unknown"
attr.user = "Unknown"
attr.dbname = "Unknown"
}
// There's no standard to get SQL driver version information
// So we invent an interface by which drivers can provide us this data
type versionedDriver interface {
Version() string
}
if vd, ok := d.(versionedDriver); ok {
attr.driverVersion = vd.Version()
} else {
t := reflect.TypeOf(d)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
attr.driverVersion = t.PkgPath()
}
if attrHook != nil {
attrHook(&attr)
}
return &attr, nil
}