mreo ui
This commit is contained in:
1 parent
49f9bb3458
commit
7436966689
22 files changed
+989
-125
No files matched your search
@@ -0,0 +1,136 @@
|
||||
// Package httpattr collapses the many attribute spellings used for "which
|
||||
// HTTP endpoint was this" (OTel stable semconv, legacy semconv, nginx, Go
|
||||
// net/http, browser RUM) into one Endpoint, so spans from different
|
||||
// instrumentations can be compared and joined.
|
||||
package httpattr
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Endpoint is the normalized identity of one HTTP request.
|
||||
type Endpoint struct {
|
||||
Method string // upper-case, e.g. "GET"
|
||||
Host string // host without port, lower-case; "" if unknown
|
||||
Path string // concrete path, no query/fragment, no trailing slash
|
||||
Route string // instrumentation-provided template ("/orders/:id"), may be ""
|
||||
}
|
||||
|
||||
// Key is the join key between a client-side (RUM) request and a server-side
|
||||
// span: method plus the concrete path. Route is deliberately not part of it -
|
||||
// a browser only knows the concrete URL.
|
||||
func (e Endpoint) Key() string {
|
||||
if e.Path == "" {
|
||||
return ""
|
||||
}
|
||||
return e.Method + " " + e.Path
|
||||
}
|
||||
|
||||
// Lookup returns the first non-empty value among keys.
|
||||
type Lookup func(keys ...string) string
|
||||
|
||||
// Extract reads an Endpoint out of span/log attributes.
|
||||
//
|
||||
// Sources, in order of preference:
|
||||
//
|
||||
// method: http.request.method, http.method
|
||||
// path: url.path, http.target (path+query), url.full, http.url
|
||||
// host: server.address, http.host, net.host.name, url.full/http.url host
|
||||
// route: http.route
|
||||
//
|
||||
// nginx's OTel module emits the stable keys; older nginx/Go/Java agents emit
|
||||
// http.target/http.url; browsers only know url.full (absolute or relative).
|
||||
func Extract(get Lookup) Endpoint {
|
||||
e := Endpoint{
|
||||
Method: strings.ToUpper(get("http.request.method", "http.method")),
|
||||
Route: get("http.route"),
|
||||
}
|
||||
e.Host = stripPort(strings.ToLower(get("server.address", "http.host", "net.host.name", "server.host")))
|
||||
if p := get("url.path"); p != "" {
|
||||
e.Path = CleanPath(p)
|
||||
} else if t := get("http.target"); t != "" {
|
||||
e.Path = CleanPath(t)
|
||||
}
|
||||
if full := get("url.full", "http.url"); full != "" {
|
||||
host, path := SplitURL(full)
|
||||
if e.Path == "" {
|
||||
e.Path = path
|
||||
}
|
||||
if e.Host == "" {
|
||||
e.Host = host
|
||||
}
|
||||
}
|
||||
// Some instrumentations report the router mount ("/") as the route.
|
||||
if e.Route == "/" && e.Path != "" && e.Path != "/" {
|
||||
e.Route = ""
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// SplitURL splits an absolute or relative URL into (host without port, clean path).
|
||||
func SplitURL(raw string) (host, path string) {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", CleanPath(raw)
|
||||
}
|
||||
return stripPort(strings.ToLower(u.Host)), CleanPath(u.Path)
|
||||
}
|
||||
|
||||
// CleanPath drops query/fragment and a trailing slash, and guarantees a
|
||||
// leading slash. "" stays "".
|
||||
func CleanPath(p string) string {
|
||||
if i := strings.IndexAny(p, "?#"); i >= 0 {
|
||||
p = p[:i]
|
||||
}
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(p, "/") {
|
||||
p = "/" + p
|
||||
}
|
||||
if len(p) > 1 {
|
||||
p = strings.TrimRight(p, "/")
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// StripPrefix removes a reverse-proxy mount prefix (e.g. nginx
|
||||
// `location /api/ { proxy_pass http://backend/; }` makes the browser see
|
||||
// /api/orders while the backend sees /orders). The longest matching prefix wins.
|
||||
func StripPrefix(path string, prefixes []string) string {
|
||||
best := ""
|
||||
for _, p := range prefixes {
|
||||
p = CleanPath(p)
|
||||
if p == "" || p == "/" {
|
||||
continue
|
||||
}
|
||||
if (path == p || strings.HasPrefix(path, p+"/")) && len(p) > len(best) {
|
||||
best = p
|
||||
}
|
||||
}
|
||||
if best == "" {
|
||||
return path
|
||||
}
|
||||
if rest := path[len(best):]; rest != "" {
|
||||
return rest
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
func stripPort(host string) string {
|
||||
if strings.HasPrefix(host, "[") { // IPv6 literal
|
||||
if i := strings.Index(host, "]"); i > 0 {
|
||||
return host[:i+1]
|
||||
}
|
||||
return host
|
||||
}
|
||||
if i := strings.LastIndexByte(host, ':'); i >= 0 {
|
||||
return host[:i]
|
||||
}
|
||||
return host
|
||||
}
|
||||
Reference in new issue
Block a user