feat(go): add realtime pipeline observability
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user