Skip to content

Commit 5d8a735

Browse files
fix(sentry): improve AV error diagnostics and logging (#1146)
* fix(call): stringify console logs for Sentry breadcrumb capture Sentry's breadcrumb capture doesn't serialize nested objects from console.log - it just shows [Object] or [Array]. This makes the diagnostic logging useless for debugging AV issues. Changed all AV diagnostic console.log calls to use JSON.stringify: - Desired subscription state logging in Call.jsx - Status check logging in Call.jsx - Participant updated logging in eventLogger.js - AV issue reporting in FixAV.jsx The pretty-printed JSON (null, 2) ensures: 1. Sentry breadcrumbs capture full data as readable strings 2. Local console output remains human-readable for debugging Fixes #1142 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(sentry): use normalizeDepth config instead of JSON.stringify Replace JSON.stringify logging with Sentry normalizeDepth configuration to properly capture nested objects in breadcrumbs and error reports. Changes: - Add normalizeDepth: 6 to Sentry.init() to handle 4-6 level objects - Revert JSON.stringify calls in Call.jsx, eventLogger.js, and FixAV.jsx - Add module-level ref for desired subscription state tracking - Include desiredSubscriptions in FixAV error reports for diagnosing mismatches between layout-computed and actual subscription states This approach is cleaner and preserves object structure in Sentry UI, making it easier to diagnose AV issues. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(sentry): improve media error diagnostics with Daily event and permissions Capture additional context when camera/mic errors occur to aid diagnosis: - Extract and log Daily's error type (not-found, permissions, in-use, etc.) - Preserve the full Daily event payload in Sentry reports - Query browser Permissions API to capture camera/mic permission state This helps distinguish between permission denials, hardware issues, and in-use conflicts when debugging user media errors. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(sentry): enhance AV error reports with device, network, and summary info Add comprehensive diagnostic data to help debug audio/video issues: FixAV.jsx: - Add track off/blocked reasons to identify why audio/video isn't working - Add audioDevices info (current mic/camera, available audio outputs) - Add networkStats from Daily for quality diagnosis - Add audioContextState to detect browser autoplay blocking - Add summary field for easy log scanning UserMediaError.jsx: - Add summary field with error type, permissions, and device counts Example summaries: - "[AV Issue] User reported "cant-hear" with 1 remote participant(s), audioContext=suspended" - "[Media Error] camera-error (in-use): cam=granted, mic=granted, 1 cam, 2 mic" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 7c4de55 commit 5d8a735

3 files changed

Lines changed: 93 additions & 4 deletions

File tree

client/src/call/FixAV.jsx

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function useFixAV() {
2828
);
2929
}, []);
3030

31-
const handleSubmitFix = useCallback(() => {
31+
const handleSubmitFix = useCallback(async () => {
3232
if (selectedIssues.length === 0) return;
3333

3434
// Capture current state for debugging
@@ -39,10 +39,14 @@ export function useFixAV() {
3939
audio: {
4040
subscribed: p.tracks?.audio?.subscribed,
4141
state: p.tracks?.audio?.state,
42+
off: p.tracks?.audio?.off, // reason track is off (e.g., "user", "bandwidth")
43+
blocked: p.tracks?.audio?.blocked, // browser blocked playback
4244
},
4345
video: {
4446
subscribed: p.tracks?.video?.subscribed,
4547
state: p.tracks?.video?.state,
48+
off: p.tracks?.video?.off,
49+
blocked: p.tracks?.video?.blocked,
4650
},
4751
}));
4852

@@ -53,16 +57,68 @@ export function useFixAV() {
5357
latestDesiredSubscriptions.current || new Map()
5458
);
5559

60+
// Get audio device info (input from Daily, output from browser)
61+
let audioDevices = null;
62+
try {
63+
const inputDevices = callObject?.getInputDevices?.();
64+
const browserDevices = await navigator.mediaDevices?.enumerateDevices();
65+
const audioOutputs = browserDevices?.filter(
66+
(d) => d.kind === "audiooutput"
67+
);
68+
audioDevices = {
69+
currentMic: inputDevices?.mic || null,
70+
currentCamera: inputDevices?.camera || null,
71+
audioOutputCount: audioOutputs?.length || 0,
72+
audioOutputs: audioOutputs?.map((d) => ({
73+
label: d.label || "Unknown",
74+
idSuffix: d.deviceId?.slice(-6) || "unknown",
75+
})),
76+
};
77+
} catch (err) {
78+
audioDevices = { error: err?.message || String(err) };
79+
}
80+
81+
// Get network stats for quality diagnosis
82+
let networkStats = null;
83+
try {
84+
networkStats = await callObject?.getNetworkStats?.();
85+
} catch (err) {
86+
networkStats = { error: err?.message || String(err) };
87+
}
88+
89+
// Check AudioContext state (suspended = autoplay blocked)
90+
let audioContextState = "unknown";
91+
try {
92+
const AudioContextClass =
93+
window.AudioContext || window.webkitAudioContext;
94+
if (AudioContextClass) {
95+
const ctx = new AudioContextClass();
96+
audioContextState = ctx.state;
97+
ctx.close().catch(() => {});
98+
}
99+
} catch (err) {
100+
audioContextState = `error: ${err?.message || String(err)}`;
101+
}
102+
103+
// Build summary for easy scanning
104+
const remoteCount = participantSummary.filter((p) => !p.local).length;
105+
const issueList = selectedIssues.join(", ");
106+
const summary = `User reported "${issueList}" with ${remoteCount} remote participant(s), audioContext=${audioContextState}`;
107+
56108
const reportData = {
109+
summary,
57110
userReportedIssues: selectedIssues,
58111
participants: participantSummary,
59112
desiredSubscriptions,
60113
meetingState: callObject?.meetingState?.(),
61114
localSessionId,
115+
audioDevices,
116+
networkStats,
117+
audioContextState,
62118
};
63119

64120
// Log to console (appears in Sentry breadcrumbs)
65-
console.log("[AV Issue] User reported problem:", reportData);
121+
console.log("[AV Issue]", summary, reportData);
66122

67123
// Send to Sentry
68124
if (Sentry?.captureMessage) {

client/src/call/UserMediaError.jsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,28 @@ export function UserMediaError({ error }) {
6464
const details = {
6565
type: error?.type,
6666
message: error?.message,
67+
dailyErrorType: error?.dailyErrorType, // e.g., "not-found", "permissions", "in-use"
6768
audioOk,
6869
videoOk,
69-
raw: error,
70+
dailyEvent: error?.dailyEvent, // Full Daily event for diagnosis
7071
};
7172

73+
// Check browser permissions state for additional context
74+
try {
75+
if (navigator.permissions) {
76+
const [camPerm, micPerm] = await Promise.all([
77+
navigator.permissions.query({ name: "camera" }).catch(() => null),
78+
navigator.permissions.query({ name: "microphone" }).catch(() => null),
79+
]);
80+
details.permissions = {
81+
camera: camPerm?.state || "unknown", // "granted", "denied", "prompt"
82+
microphone: micPerm?.state || "unknown",
83+
};
84+
}
85+
} catch (permErr) {
86+
details.permissionsError = permErr?.message || String(permErr);
87+
}
88+
7289
try {
7390
if (navigator?.mediaDevices?.enumerateDevices) {
7491
const devices = await navigator.mediaDevices.enumerateDevices();
@@ -99,7 +116,17 @@ export function UserMediaError({ error }) {
99116
console.warn("Failed to enumerate media devices", err);
100117
}
101118

102-
console.error("User media error", details);
119+
// Build summary for easy scanning
120+
const permStatus = details.permissions
121+
? `cam=${details.permissions.camera}, mic=${details.permissions.microphone}`
122+
: "permissions unknown";
123+
const deviceCount = details.deviceSurvey
124+
? `${details.deviceSurvey.cameraCount} cam, ${details.deviceSurvey.micCount} mic`
125+
: "devices unknown";
126+
const summary = `${error?.type || "unknown"} error (${error?.dailyErrorType || "no daily type"}): ${permStatus}, ${deviceCount}`;
127+
details.summary = summary;
128+
129+
console.error("[Media Error]", summary, details);
103130
if (Sentry?.captureMessage) {
104131
Sentry.captureMessage("User media error", {
105132
level: "error",

client/src/call/VideoCall.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,15 @@ export function VideoCall({
249249
? ev.errorMsg
250250
: ev?.errorMsg?.message || ev?.error?.message;
251251

252+
// Extract Daily's error type (e.g., "not-found", "permissions", "in-use", "constraints")
253+
const dailyErrorType =
254+
ev?.error?.type || ev?.errorMsg?.type || ev?.type;
255+
252256
setDeviceError({
253257
type,
254258
message: rawMessage || null,
259+
dailyErrorType: dailyErrorType || null,
260+
dailyEvent: ev, // Preserve the full Daily event for diagnosis
255261
});
256262
};
257263

0 commit comments

Comments
 (0)