feat(garmin): all-JSON wrapper protocol and log stream
Error responses from wrapper.py now carry error_type and traceback in
the protocol itself, logged as attrs on the Go side's per-call record.
wrapper.py's free-text stderr debug prints become JSON log lines
({level,msg,...attrs}); client.go parses that stream and re-emits it
through the application logger (level-mapped, source=garmin-wrapper),
wrapping any non-JSON line instead of passing it through raw. The
wrapper also survives malformed request lines and unserializable
results with JSON responses instead of crashing with a raw traceback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -486,3 +486,43 @@ func TestSubprocessClient_RoundTrip_LogsFailedCallAtErrorLevel(t *testing.T) {
|
||||
t.Errorf("error field = %q, want it to mention \"boom\"", errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardWrapperStderr_ReemitsJSONAndWrapsFreeText(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
prev := slog.Default()
|
||||
slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})))
|
||||
defer slog.SetDefault(prev)
|
||||
|
||||
stderr := strings.NewReader(
|
||||
`{"level":"warn","msg":"startup tokenstore login failed","error":"boom"}` + "\n" +
|
||||
"Traceback (most recent call last): free text\n",
|
||||
)
|
||||
forwardWrapperStderr(stderr)
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(buf.String()), "\n")
|
||||
if len(lines) != 2 {
|
||||
t.Fatalf("expected 2 log lines, got %d: %q", len(lines), buf.String())
|
||||
}
|
||||
|
||||
var first map[string]any
|
||||
if err := json.Unmarshal([]byte(lines[0]), &first); err != nil {
|
||||
t.Fatalf("first line not JSON: %v", err)
|
||||
}
|
||||
if first["level"] != "WARN" || first["msg"] != "startup tokenstore login failed" {
|
||||
t.Errorf("first = %v, want the wrapper's level and msg preserved", first)
|
||||
}
|
||||
if first["error"] != "boom" || first["source"] != "garmin-wrapper" {
|
||||
t.Errorf("first = %v, want error attr preserved and source=garmin-wrapper", first)
|
||||
}
|
||||
|
||||
var second map[string]any
|
||||
if err := json.Unmarshal([]byte(lines[1]), &second); err != nil {
|
||||
t.Fatalf("second line not JSON: %v", err)
|
||||
}
|
||||
if second["msg"] != "garmin wrapper stderr" || second["level"] != "WARN" {
|
||||
t.Errorf("second = %v, want free text wrapped as a warn record", second)
|
||||
}
|
||||
if line, _ := second["line"].(string); !strings.Contains(line, "Traceback") {
|
||||
t.Errorf("second line attr = %q, want the raw text preserved", line)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user