fixes
Build and Publish / Metadata (push) Successful in 2s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 4s

This commit is contained in:
mats committed 2026-09-20 09:52:49 +02:00
1 parent f30adf05af
commit 8824af8699
29 files changed
+1201 -115

No files matched your search

+133 -31
View File
@@ -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"),
+25
View File
@@ -46,6 +46,31 @@ func TestNormalizeTraceRedactsValuesAndCreatesStableSignatures(t *testing.T) {
}
}
func TestNormalizeTraceCarriesPeerAndEdgeContext(t *testing.T) {
data := []byte(`{"resourceSpans":[
{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"nginx"}}]},"scopeSpans":[{"spans":[
{"traceId":"trace-1","spanId":"edge-1","kind":2,"name":"GET /","startTimeUnixNano":"100","endTimeUnixNano":"200","attributes":[{"key":"net.peer.ip","value":{"stringValue":"10.10.0.1"}}]}
]}]},
{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"checkout"}}]},"scopeSpans":[{"spans":[
{"traceId":"trace-1","spanId":"app-1","parentSpanId":"edge-1","kind":1,"name":"checkout","startTimeUnixNano":"120","endTimeUnixNano":"180","attributes":[{"key":"rpc.system","value":{"stringValue":"grpc"}}]}
]}]}
]}`)
events, err := NormalizeEntry("traces", 99, data)
if err != nil || len(events) != 2 {
t.Fatalf("normalize failed: %v %+v", err, events)
}
if events[0].PeerIP != "10.10.0.1" || events[0].PeerIPSource != "net.peer.ip" {
t.Fatalf("edge peer was not preserved: %+v", events[0])
}
child := events[1]
if child.PeerIP != "" || child.EdgePeerIP != "10.10.0.1" || child.EdgeService != "nginx" || child.EdgeSpanID != "edge-1" {
t.Fatalf("unexpected inherited edge context: %+v", child)
}
if child.SpanID != "app-1" || child.ParentSpanID != "edge-1" || child.SpanKind != 1 {
t.Fatalf("missing span relationship fields: %+v", child)
}
}
func TestNormalizeLogUsesEventTimestampAndFrontendSource(t *testing.T) {
data := []byte(`{"resourceLogs":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"web"}}]},"scopeLogs":[{"logRecords":[{"timeUnixNano":"1230000000","severityNumber":17,"body":{"stringValue":"User 123 failed"},"attributes":[{"key":"source","value":{"stringValue":"frontend"}}]}]}]}]}`)
events, err := NormalizeEntry("logs", 99, data)