fixes
This commit is contained in:
1 parent
f30adf05af
commit
8824af8699
29 files changed
+1201
-115
No files matched your search
@@ -12,9 +12,10 @@ import (
|
||||
|
||||
"git.tornberg.me/go-otel/internal/httpattr"
|
||||
"git.tornberg.me/go-otel/internal/issues"
|
||||
"git.tornberg.me/go-otel/internal/uaclass"
|
||||
)
|
||||
|
||||
const Version = "v1"
|
||||
const Version = "v2"
|
||||
|
||||
type Event struct {
|
||||
EventID string `json:"eventId"`
|
||||
@@ -29,6 +30,7 @@ type Event struct {
|
||||
Method string `json:"method,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
UserAgent string `json:"userAgent,omitempty"`
|
||||
UAClass string `json:"uaClass,omitempty"`
|
||||
OriginHost string `json:"originHost,omitempty"`
|
||||
RefererHost string `json:"refererHost,omitempty"`
|
||||
FetchSite string `json:"fetchSite,omitempty"`
|
||||
@@ -53,6 +55,15 @@ type Event struct {
|
||||
MessageNormalized string `json:"messageNormalized,omitempty"`
|
||||
StackSignature string `json:"stackSignature,omitempty"`
|
||||
TraceID string `json:"traceId,omitempty"`
|
||||
SpanID string `json:"spanId,omitempty"`
|
||||
ParentSpanID string `json:"parentSpanId,omitempty"`
|
||||
SpanKind int `json:"spanKind,omitempty"`
|
||||
PeerIP string `json:"peerIp,omitempty"`
|
||||
PeerIPSource string `json:"peerIpSource,omitempty"`
|
||||
EdgePeerIP string `json:"edgePeerIp,omitempty"`
|
||||
EdgePeerSource string `json:"edgePeerSource,omitempty"`
|
||||
EdgeService string `json:"edgeService,omitempty"`
|
||||
EdgeSpanID string `json:"edgeSpanId,omitempty"`
|
||||
IssueFingerprint string `json:"issueFingerprint,omitempty"`
|
||||
IsRetry bool `json:"isRetry"`
|
||||
IsTimeout bool `json:"isTimeout"`
|
||||
@@ -96,35 +107,67 @@ func normalizeTraces(ingest int64, envelope map[string]json.RawMessage) []Event
|
||||
} `json:"scopeSpans"`
|
||||
}
|
||||
_ = json.Unmarshal(envelope["resourceSpans"], &resources)
|
||||
result := []Event{}
|
||||
type traceSpan struct {
|
||||
resource object
|
||||
item span
|
||||
attrs []attribute
|
||||
service string
|
||||
}
|
||||
var spans []traceSpan
|
||||
for _, resource := range resources {
|
||||
for _, scope := range resource.ScopeSpans {
|
||||
for _, item := range scope.Spans {
|
||||
attrs := mergeAttrs(resource.Resource.Attributes, item.Attributes)
|
||||
status := intValue(item.Status.Code)
|
||||
statusCode := intAttr(attrs, "http.response.status_code", "http.status_code")
|
||||
route := normalizeHTTPRoute(attrs)
|
||||
message := item.Status.Message
|
||||
exceptionType, exceptionMessage, stack := exception(item.Events)
|
||||
if exceptionMessage != "" {
|
||||
message = exceptionMessage
|
||||
}
|
||||
if message == "" {
|
||||
message = item.Name
|
||||
}
|
||||
if exceptionType != "" && message != "" {
|
||||
message = exceptionType + ": " + message
|
||||
}
|
||||
isError := status == 2 || isErrorStatus(statusCode) || intAttr(attrs, "rpc.grpc.status_code") > 0 || exceptionType != ""
|
||||
e := baseEvent(ingest, attrs, "span", item.TraceID)
|
||||
e.IsError, e.StatusCode, e.StatusClass = isError, statusCode, statusClass(statusCode)
|
||||
e.DurationMS = durationMS(item.Start, item.End)
|
||||
e.Route, e.SpanName, e.ExceptionType, e.MessageNormalized, e.StackSignature = route, item.Name, exceptionType, normalizeMessage(message), firstFrame(stack)
|
||||
e.IsRetry, e.IsTimeout, e.IsCancellation = operationFlags(attrs, statusCode, intAttr(attrs, "rpc.grpc.status_code"))
|
||||
result = append(result, finish(e))
|
||||
spans = append(spans, traceSpan{
|
||||
resource: resource.Resource,
|
||||
item: item,
|
||||
attrs: attrs,
|
||||
service: defaultString(stringAttr(attrs, "service.name"), "unknown"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
contexts := make(map[string]spanContext, len(spans))
|
||||
for _, current := range spans {
|
||||
if current.item.SpanID == "" || current.item.TraceID == "" {
|
||||
continue
|
||||
}
|
||||
peer, peerSource := peerAddress(current.attrs)
|
||||
contexts[spanKey(current.item.TraceID, current.item.SpanID)] = spanContext{
|
||||
TraceID: current.item.TraceID, SpanID: current.item.SpanID,
|
||||
ParentSpanID: current.item.ParentSpanID, Service: current.service,
|
||||
PeerIP: peer, PeerSource: peerSource,
|
||||
Edge: isEdgeService(current.service),
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]Event, 0, len(spans))
|
||||
for _, current := range spans {
|
||||
item, attrs := current.item, current.attrs
|
||||
status := intValue(item.Status.Code)
|
||||
statusCode := intAttr(attrs, "http.response.status_code", "http.status_code")
|
||||
route := normalizeHTTPRoute(attrs)
|
||||
message := item.Status.Message
|
||||
exceptionType, exceptionMessage, stack := exception(item.Events)
|
||||
if exceptionMessage != "" {
|
||||
message = exceptionMessage
|
||||
}
|
||||
if message == "" {
|
||||
message = item.Name
|
||||
}
|
||||
if exceptionType != "" && message != "" {
|
||||
message = exceptionType + ": " + message
|
||||
}
|
||||
isError := status == 2 || isErrorStatus(statusCode) || intAttr(attrs, "rpc.grpc.status_code") > 0 || exceptionType != ""
|
||||
e := baseEvent(ingest, attrs, "span", item.TraceID)
|
||||
e.SpanID, e.ParentSpanID, e.SpanKind = item.SpanID, item.ParentSpanID, item.Kind
|
||||
applyTraceContext(&e, contexts[spanKey(item.TraceID, item.SpanID)], contexts)
|
||||
e.IsError, e.StatusCode, e.StatusClass = isError, statusCode, statusClass(statusCode)
|
||||
e.DurationMS = durationMS(item.Start, item.End)
|
||||
e.Route, e.SpanName, e.ExceptionType, e.MessageNormalized, e.StackSignature = route, item.Name, exceptionType, normalizeMessage(message), firstFrame(stack)
|
||||
e.IsRetry, e.IsTimeout, e.IsCancellation = operationFlags(attrs, statusCode, intAttr(attrs, "rpc.grpc.status_code"))
|
||||
result = append(result, finish(e))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -198,17 +241,71 @@ type value struct {
|
||||
BoolValue bool `json:"boolValue"`
|
||||
}
|
||||
type span struct {
|
||||
TraceID string `json:"traceId"`
|
||||
Name string `json:"name"`
|
||||
Start string `json:"startTimeUnixNano"`
|
||||
End string `json:"endTimeUnixNano"`
|
||||
Attributes []attribute `json:"attributes"`
|
||||
Events []spanEvent `json:"events"`
|
||||
Status struct {
|
||||
TraceID string `json:"traceId"`
|
||||
SpanID string `json:"spanId"`
|
||||
ParentSpanID string `json:"parentSpanId"`
|
||||
Name string `json:"name"`
|
||||
Kind int `json:"kind"`
|
||||
Start string `json:"startTimeUnixNano"`
|
||||
End string `json:"endTimeUnixNano"`
|
||||
Attributes []attribute `json:"attributes"`
|
||||
Events []spanEvent `json:"events"`
|
||||
Status struct {
|
||||
Code json.Number `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"status"`
|
||||
}
|
||||
|
||||
type spanContext struct {
|
||||
TraceID, SpanID, ParentSpanID string
|
||||
Service string
|
||||
PeerIP, PeerSource string
|
||||
Edge bool
|
||||
}
|
||||
|
||||
func spanKey(traceID, spanID string) string { return traceID + "\x00" + spanID }
|
||||
|
||||
// applyTraceContext keeps the immediate peer separate from the peer observed
|
||||
// at an edge ancestor. In particular, a firewall/NAT address is never
|
||||
// relabeled as the original client address.
|
||||
func applyTraceContext(e *Event, current spanContext, contexts map[string]spanContext) {
|
||||
e.PeerIP, e.PeerIPSource = current.PeerIP, current.PeerSource
|
||||
edge := current
|
||||
found := current.Edge && current.PeerIP != ""
|
||||
for hops := 0; !found && edge.ParentSpanID != "" && hops < 32; hops++ {
|
||||
parent, ok := contexts[spanKey(edge.TraceID, edge.ParentSpanID)]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
edge = parent
|
||||
if edge.Edge && edge.PeerIP != "" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
e.EdgePeerIP, e.EdgePeerSource = edge.PeerIP, edge.PeerSource
|
||||
e.EdgeService, e.EdgeSpanID = edge.Service, edge.SpanID
|
||||
}
|
||||
}
|
||||
|
||||
func peerAddress(attrs []attribute) (string, string) {
|
||||
for _, key := range []string{"net.peer.ip", "client.address", "net.sock.peer.addr"} {
|
||||
if value := stringAttr(attrs, key); value != "" {
|
||||
return value, key
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func isEdgeService(service string) bool {
|
||||
switch strings.ToLower(service) {
|
||||
case "nginx", "ingress-nginx", "envoy", "traefik", "haproxy", "gateway":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type spanEvent struct {
|
||||
Name string `json:"name"`
|
||||
Attributes []attribute `json:"attributes"`
|
||||
@@ -309,13 +406,18 @@ func baseEvent(timestamp int64, attrs []attribute, kind, traceID string) Event {
|
||||
source = "backend"
|
||||
}
|
||||
ep := httpattr.Extract(func(keys ...string) string { return stringAttr(attrs, keys...) })
|
||||
ua := stringAttr(attrs, "http.user_agent", "user_agent.original")
|
||||
uaClass := ""
|
||||
if ua != "" {
|
||||
uaClass = uaclass.Of(ua)
|
||||
}
|
||||
return Event{
|
||||
Timestamp: timestamp, NormalizerVersion: Version,
|
||||
Service: defaultString(stringAttr(attrs, "service.name"), "unknown"),
|
||||
Release: stringAttr(attrs, "service.release"),
|
||||
Environment: defaultString(stringAttr(attrs, "deployment.environment.name", "deployment.environment", "service.environment"), ""),
|
||||
Method: ep.Method, Host: ep.Host,
|
||||
UserAgent: stringAttr(attrs, "http.user_agent", "user_agent.original"),
|
||||
UserAgent: ua, UAClass: uaClass,
|
||||
OriginHost: attrHost(stringAttr(attrs, "http.request.header.origin", "http.origin", "origin")),
|
||||
RefererHost: attrHost(stringAttr(attrs, "http.request.header.referer", "http.referer")),
|
||||
FetchSite: stringAttr(attrs, "http.request.header.sec_fetch_site"),
|
||||
|
||||
Reference in new issue
Block a user