feat(go): add realtime pipeline observability

This commit is contained in:
lingniu
2026-07-03 15:06:27 +08:00
parent 28c81611a1
commit b8299faefa
10 changed files with 636 additions and 6 deletions

View File

@@ -211,6 +211,7 @@ func (s *TCPServer) handleFrame(ctx context.Context, conn net.Conn, raw []byte,
ParseError: err.Error(),
}
env.EventID = env.StableEventID()
s.recordParseErrorMetric(err)
} else {
resolved, resolveErr := s.resolver.Resolve(frameCtx, env)
if resolveErr != nil {
@@ -220,8 +221,11 @@ func (s *TCPServer) handleFrame(ctx context.Context, conn net.Conn, raw []byte,
}
env.Parsed["identity"] = map[string]any{"resolved": false, "error": resolveErr.Error()}
env.ParseStatus = envelope.ParsePartial
s.recordIdentityMetric("error")
} else {
env = resolved
annotateIdentityUnresolved(&env)
s.recordIdentityMetric(identityStatus(env))
}
enrichConnectionPlatform(&env, state)
}
@@ -314,6 +318,26 @@ func (s *TCPServer) recordPublishMetric(kind string, status string) {
})
}
func (s *TCPServer) recordParseErrorMetric(err error) {
if s.metrics == nil {
return
}
s.metrics.IncCounter("vehicle_gateway_parse_errors_total", metrics.Labels{
"protocol": string(s.protocol.Protocol),
"reason": classifyError(err),
})
}
func (s *TCPServer) recordIdentityMetric(status string) {
if s.metrics == nil {
return
}
s.metrics.IncCounter("vehicle_gateway_identity_total", metrics.Labels{
"protocol": string(s.protocol.Protocol),
"status": status,
})
}
func (s *TCPServer) recordConnectionMetric(delta float64) {
if s.metrics == nil {
return
@@ -323,6 +347,50 @@ func (s *TCPServer) recordConnectionMetric(delta float64) {
}, delta)
}
func identityStatus(env envelope.FrameEnvelope) string {
if strings.TrimSpace(env.VIN) != "" {
return "resolved"
}
if env.ParseStatus == envelope.ParsePartial {
return "error"
}
return "unresolved"
}
func annotateIdentityUnresolved(env *envelope.FrameEnvelope) {
if env == nil || env.ParseStatus == envelope.ParseBadFrame || strings.TrimSpace(env.VIN) != "" {
return
}
if env.Parsed == nil {
env.Parsed = map[string]any{}
}
if _, exists := env.Parsed["identity"]; exists {
return
}
env.Parsed["identity"] = map[string]any{
"resolved": false,
"reason": "no_binding",
}
}
func classifyError(err error) string {
text := strings.ToLower(strings.TrimSpace(fmt.Sprint(err)))
switch {
case text == "":
return "unknown"
case strings.Contains(text, "bcc") || strings.Contains(text, "checksum"):
return "checksum"
case strings.Contains(text, "short") || strings.Contains(text, "truncated") || strings.Contains(text, "length"):
return "length"
case strings.Contains(text, "json"):
return "json"
case strings.Contains(text, "start"):
return "start_symbol"
default:
return "parse"
}
}
func (p TCPProtocol) String() string {
return fmt.Sprintf("%s@%s", p.Protocol, p.Addr)
}