feat(diagnostics): expand client log attribute registry (#649)

Adds the attribute keys the Apple and Android clients need to record
startup, network, auth, and playback behavior in diagnostics reports.

The four playback keys were already being emitted by the Apple client
but were never registered here, so they were silently dropped by this
collector and hard-rejected by the hosted one. Registering them makes
the canonical registry the superset it was always meant to be.

position_seconds is deliberately registered as position_ms (integer):
attrValueType has only string and integer, and a millisecond ordinal
avoids adding a float type for a single key. launch_type is a string
for the same reason, rather than a bool.

New keys:
  playback:  session_id, play_method, reason, position_ms
  lifecycle: phase, duration_ms, outcome, reason, launch_type
  network:   outcome, error_code, attempt

Purely additive; no existing key changes type or is removed.
TestAttrRegistryStaysInSync keeps the Go map and the JSON in lockstep.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Quick
2026-08-15 11:39:17 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent febf5c4a28
commit 5fdb5d73ac
2 changed files with 61 additions and 1 deletions
@@ -39,6 +39,22 @@
"audio_underruns": {
"type": "integer",
"description": "Audio underrun count."
},
"session_id": {
"type": "string",
"description": "Playback session identifier for correlation with operational logs."
},
"play_method": {
"type": "string",
"description": "Selected playback delivery method such as direct, remux, or transcode."
},
"reason": {
"type": "string",
"description": "Playback route, interruption, or lifecycle reason."
},
"position_ms": {
"type": "integer",
"description": "Current playback position in milliseconds."
}
},
"focus": {
@@ -67,12 +83,44 @@
"duration_ms": {
"type": "integer",
"description": "Request duration in milliseconds."
},
"outcome": {
"type": "string",
"description": "Request outcome such as success, http_error, transport_error, or cancelled."
},
"error_code": {
"type": "string",
"description": "Stable error classification for a failed request."
},
"attempt": {
"type": "integer",
"description": "Attempt ordinal for a retried request."
}
},
"lifecycle": {
"state": {
"type": "string",
"description": "App lifecycle state."
},
"phase": {
"type": "string",
"description": "Startup or lifecycle phase identifier."
},
"duration_ms": {
"type": "integer",
"description": "Elapsed duration for the phase in milliseconds."
},
"outcome": {
"type": "string",
"description": "Result of the phase, such as success, failure, or skipped."
},
"reason": {
"type": "string",
"description": "Classification for the phase outcome."
},
"launch_type": {
"type": "string",
"description": "Launch classification, such as cold or warm."
}
},
"crash": {
+13 -1
View File
@@ -259,6 +259,10 @@ var attrRegistry = map[string]map[string]attrValueType{
"bitrate_kbps": attrInteger,
"dropped_frames": attrInteger,
"audio_underruns": attrInteger,
"session_id": attrString,
"play_method": attrString,
"reason": attrString,
"position_ms": attrInteger,
},
"focus": {
"target": attrString,
@@ -269,9 +273,17 @@ var attrRegistry = map[string]map[string]attrValueType{
"path": attrString,
"status": attrInteger,
"duration_ms": attrInteger,
"outcome": attrString,
"error_code": attrString,
"attempt": attrInteger,
},
"lifecycle": {
"state": attrString,
"state": attrString,
"phase": attrString,
"duration_ms": attrInteger,
"outcome": attrString,
"reason": attrString,
"launch_type": attrString,
},
"crash": {
"fingerprint": attrString,