네트워크/소켓
Socket.io disconnect/reconnect, engine transport 변경, RTT, candidate pair, TURN/TCP fallback 여부를 수집한다. 단순 navigator.onLine만으로 판단하지 않는다.
마지막 업데이트 2026-07-24
현재 “아동 카메라/마이크 연결 끊김”, “진행자에게 소리 안 들림”, “아동에게 핑퐁이 소리 안 들림”이 게스트 네트워크 이슈로 묶여 분류되는 경우가 있다. 하지만 실제 게스트 네트워크가 양호한 세션도 있으므로, 네트워크·장치·브라우저 autoplay/출력 라우팅·OpenAI Realtime WebRTC·mediasoup SFU Producer/Consumer·호스트 재생 element를 분리 진단할 수 있도록 로깅을 보강한다.
Socket.io disconnect/reconnect, engine transport 변경, RTT, candidate pair, TURN/TCP fallback 여부를 수집한다. 단순 navigator.onLine만으로 판단하지 않는다.
getUserMedia 성공 후 실제 track readyState, muted, enabled, deviceId/groupId, onmute/onended를 카메라·마이크별로 남긴다.
아동 Producer(camera-video/mic-audio/ai-audio)와 진행자 Consumer 생명주기를 producerId/consumerId 기준으로 연결한다.
AI 세션 RTCPeerConnection/DataChannel과 AI audio element 재생 성공 여부를 분리한다. play().catch(() => {}) 같은 실패 삼킴을 없앤다.
Consumer track은 live인데 host video/audio element가 pause/error/stall 상태인지, audio RMS가 0인지 별도 측정한다.
모든 로그에 roomId, peerId, socketId, producerId, consumerId, streamType, ts를 포함해 LogRocket과 Grafana를 한 줄로 조인할 수 있게 한다.
| 영역 | 현재 존재하는 로그/이벤트 | 진단상 부족한 점 | 근거 파일 |
|---|---|---|---|
| 게스트 SFU Producer | guest-transport-state 1초 emit, send transport state, RTT 일부, camera/mic/ai producer 생성/trackended/transportclose 로그 | producer별 outbound stats, bytes/packets 증가량, track mute 지속시간, clone track vs 원본 track 매핑, socketId가 빠짐 | apps/web/hooks/mediasoup/use-mediasoup-producer.ts:115-154, 284-338, 340-550 |
| 진행자 SFU Consumer | host-transport-state 1초 emit, consumer 생성/resume, streamType별 track add 로그 | consumer별 inbound stats, audio energy/RMS, video framesDecoded/freeze, element playback 상태, producerId-ConsumerId 조인이 부족 | apps/web/hooks/mediasoup/use-mediasoup-consumer.ts:136-148, 161-260, 287-350, 442-498 |
| OpenAI Realtime AI 세션 | RTCPeerConnection state와 dataChannel 상태 로그, failed/disconnected/recovered callback | AI audio play() 실패가 삼켜지고, currentTime 증가·audio energy·output sinkId 성공/실패가 부족 | apps/web/entities/guest-session/model/use-ai-session.ts:1075-1133 |
| 레거시 P2P Guest Media | useWebRTCMonitoring으로 track 상태, RTT/packetLoss 일부, video element pause/error/playing/ended 로그 | socket emit payload가 요약 상태 위주라 packetLoss/RTT/tracks 상세가 monitor/Grafana에서 충분히 보이지 않음 | apps/web/hooks/use-guest-media-manager.ts:249-380, apps/web/hooks/use-webrtc-monitoring.ts:252-472 |
| Socket/SFU 서버 | JOIN/LEAVE, duplicate guest cleanup, transport/producer/consumer created/closed 로그 | server-side mediasoup transport stats/producer score/consumer score가 debug 위주이고 roomId·type·producerId·consumerId 구조화가 약함 | apps/socket/src/sfu-socket/handlers/connection-handlers.ts:18-152, media-handlers.ts:10-126, mediasoup/*.ts |
| Monitor 알림 | media alert 이벤트를 P0/P1/P2로 표시, guest transport degraded 감지 | 알림은 원인 분류가 아니라 상태 표시다. “네트워크”인지 “장치/트랙/consumer/playback”인지 결정할 근거 필드가 부족 | apps/web/entities/monitor-session/model/use-monitor-session.ts:453-610 |
브라우저 로그는 현재 human-readable이라 LogRocket에서 검색은 가능하지만 구조화 분석이 어렵다. logger.info("MEDIA_DIAG", payload) 형태를 표준화하고, payload는 JSON-safe flat schema로 통일한다.
{
event: "producer_stats",
roomId, peerId, socketId,
role: "guest" | "host",
streamType: "camera-video" | "mic-audio" | "ai-audio",
transportId, producerId, consumerId,
connectionState, iceConnectionState,
ts: Date.now(),
seq
}use-mediasoup-producer.ts의 기존 1초 guest-transport-state 루프에 producer별 stats delta를 추가한다. video는 framesEncoded, qualityLimitationReason, audio는 audioLevel/totalAudioEnergy/bytesSent/packetsSent를 수집한다.
판정 예: - mic track live + packetsSent 증가 + host inbound 0 → SFU/Consumer 경로 의심 - mic track muted/onended + packetsSent 정체 → 장치/브라우저 track 문제 - RTT/packetLoss 악화 + all streams 정체 → 네트워크 후보
use-mediasoup-consumer.ts에서 consumer 생성 직후 consumer.id, producerId, streamType을 저장하고, 1~2초 주기로 inbound stats를 streamType별로 남긴다.
audio: packetsReceived, bytesReceived, packetsLost, jitter,
audioLevel, totalAudioEnergy, concealedSamples
video: framesDecoded, framesDropped, freezeCount,
totalFreezesDuration, frameWidth/HeightProducerManager/ConsumerManager/TransportManager 로그에 appData.type, producerPeerId, direction, close reason, dtls/ice selected tuple을 추가한다. 현재는 created/closed 중심이라 원인 분류가 어렵다.
logger.info("SFU_MEDIA_LIFECYCLE", {
action: "producer_created", roomId, peerId,
transportId, producerId, kind, streamType: appData?.type
});use-ai-session.ts:1117-1133의 audioEl.play().catch(() => {})는 실제 무음 원인을 숨긴다. play promise resolve/reject, currentTime 증가, playing/waiting/stalled/error, sinkId 지원 여부를 남긴다.
event: "ai_audio_playback_probe"
fields: playResolved, playErrorName, currentTime,
paused, muted, volume, sinkIdSupported,
requestedOutputDeviceId, visibilityStateuse-guest-media-manager.ts:333-367와 V2 로컬 미디어 hook에 getUserMedia 전후 로그를 추가한다. 장치 라벨은 권한 후에만 보일 수 있으므로 permission state 변화와 함께 남긴다.
device_snapshot: audioInputs/videoInputs count, selectedDeviceId hash, groupId hash, label hash, constraints, track.getSettings(), permission state, track.onmute/onunmute/onended timestamp
socketSfu.ts:31-74에 이미 reconnect 로그가 있으나 room/peer 맥락이 없다. join 이후에는 socket wrapper가 roomId, peerId, role을 보유해 disconnect/reconnect/connect_error에 같이 찍히도록 한다.
disconnect reason, transport name, previous transport, reconnect attempt, ping latency, navigator.onLine, effectiveType/downlink/rtt(지원 브라우저만)
Consumer track이 도착한 뒤 video/audio element가 실제 렌더링·가청 상태인지 남긴다. 진행자가 “소리 안 들림”을 신고할 때 Consumer stats는 정상인데 element만 muted/paused일 수 있다.
video: readyState, paused, videoWidth/Height,
requestVideoFrameCallback delta
host audio: HTMLAudioElement 상태 또는 WebAudio RMS,
output device/sink 상태| 관찰 조합 | 분류 | 해석 | 다음 확인 |
|---|---|---|---|
| socket/transport connected, guest outbound packets 증가, host inbound packets 증가, host audio RMS 0 | 호스트 재생/출력 | 네트워크가 아니라 audio element mute/volume/sink/OS 출력/브라우저 autoplay 가능성 | host playback probe, output device, tab mute |
| guest mic track muted/onended, outbound audio packets 정체, socket은 정상 | 게스트 장치/브라우저 | 마이크 권한·장치 전환·iOS/Chrome track 중단 가능성 | track event, getSettings, permission/devicechange |
| camera outbound framesEncoded 정체, mic outbound 정상 | 카메라/인코더 | 전체 네트워크 장애가 아니라 카메라 track 또는 video encoder/freeze | framesEncoded, qualityLimitationReason, track readyState |
| guest outbound 정상, server producer created 정상, host consume/resume 실패 | Consumer/SFU 수신 | 진행자 Consumer 또는 recv transport 문제 | consumerId, producerId, canConsume, resume response |
| RTT 급증/packetLoss 증가, socket reconnect, producer/consumer packets 모두 정체 | 네트워크 강한 후보 | 이 경우에만 “게스트 네트워크” 라벨을 1순위로 유지 | candidate pair, TURN/TCP fallback, 시간대 Grafana |
| AI RTCPeerConnection failed, SFU mic/camera 정상 | OpenAI Realtime 경로 | 진행자가 아동 음성은 듣지만 AI/프리셋/핑퐁이 상호작용만 깨질 수 있음 | dataChannel readyState, OpenAI pc stats, session events |
MEDIA_DIAG prefix와 event 필드를 고정한다.roomId, peerId, socketId, role을 포함한다.camera-video, mic-audio, ai-audio를 producer/consumer 양쪽에 포함한다.MEDIA_DIAG로 시작해 export JSON에서 필터링 가능하게 한다.event, roomId, streamType label-like 필드를 포함한다.| 파일/라인 | 추가할 로그 | 목적 |
|---|---|---|
apps/web/hooks/mediasoup/use-mediasoup-producer.ts:115-154 | send transport state 변화에 socketId, transportId, reconnectAttempt, durationSinceLastConnected 추가 | transport 이슈와 일시적 reconnect를 구분 |
apps/web/hooks/mediasoup/use-mediasoup-producer.ts:284-338 | 기존 RTT 루프에 producer별 outbound stats delta 추가 | 네트워크 문제인지 track/encoder 문제인지 분리 |
apps/web/hooks/mediasoup/use-mediasoup-producer.ts:340-550 | camera/mic/ai track 원본+clone id, onmute/onunmute/onended, producer id, streamType 구조화 | 장치 track 중단, iPad remote audio clone 이슈 추적 |
apps/web/hooks/mediasoup/use-mediasoup-consumer.ts:161-260 | consume 요청/응답에 producerId, consumerId, streamType, resume latency, failure reason | Producer는 있는데 Consumer가 실패하는 케이스 분리 |
apps/web/hooks/mediasoup/use-mediasoup-consumer.ts:442-498 | recv transport RTT 루프에 consumer별 inbound stats delta 추가 | 진행자 “소리 안 들림/화면 멈춤” 수신단 근거 확보 |
apps/web/entities/guest-session/model/use-ai-session.ts:1078-1097 | OpenAI pc stats snapshot, dataChannel state, failure/recovery duration | OpenAI Realtime 실패와 SFU 실패 분리 |
apps/web/entities/guest-session/model/use-ai-session.ts:1117-1133 | AI audio play promise 결과, sinkId, currentTime 증가, audio element events | “아동에게 핑퐁이 소리 안 들림”의 재생/출력 원인 추적 |
apps/web/hooks/use-guest-media-manager.ts:333-380 | getUserMedia constraints/result, track settings, startTrackMonitoring status payload 확장 | 레거시 P2P 경로의 장치/트랙 이슈 분리 |
apps/web/socketSfu.ts:31-74 | room/peer 맥락이 포함된 connect_error/disconnect/reconnect log | 소켓 레벨 네트워크 문제와 앱 세션 상태 연결 |
apps/socket/src/mediasoup/transportManager.ts:68-84 | dtlsstatechange, close reason, direction, peerId, roomId, transportId 구조화 | 서버에서 transport close/fail 원인 확인 |
apps/socket/src/mediasoup/producerManager.ts:57-69 | producer score/trace, appData.type, close reason, consumer count | 아동 Producer 자체 문제인지 판단 |
apps/socket/src/mediasoup/consumerManager.ts:93-115 | consumer score/producerclose/transportclose에 producerPeerId, streamType, roomId 추가 | 진행자 Consumer만 닫히는 케이스 추적 |
apps/web/entities/monitor-session/model/use-monitor-session.ts:453-610 | media alert payload에 diagnosisSummary, sourceLayer, correlatedIds 추가 | 알림에서 “네트워크 추정” 대신 근거 기반 분류 표시 |
MEDIA_DIAG device_snapshot MEDIA_DIAG get_user_media_result MEDIA_DIAG track_state_change MEDIA_DIAG sfu_transport_state MEDIA_DIAG producer_lifecycle MEDIA_DIAG producer_stats MEDIA_DIAG consumer_lifecycle MEDIA_DIAG consumer_stats MEDIA_DIAG element_playback_state MEDIA_DIAG ai_realtime_state MEDIA_DIAG socket_connection_state
SFU_MEDIA_LIFECYCLE transport_created|connected|closed|dtls_failed SFU_MEDIA_LIFECYCLE producer_created|closed|score SFU_MEDIA_LIFECYCLE consumer_created|resumed|closed|producer_closed|score SFU_ROOM_LIFECYCLE join|leave|duplicate_cleanup|disconnect_cleanup
공통 schema와 Producer/Consumer stats logging만 먼저 추가한다. 기능 동작을 바꾸지 않고 진단 데이터부터 확보한다.
transport-only alert가 아니라 “송신 정상/수신 정체/재생 정체” 같은 diagnosisSummary를 진행자 화면에 표시한다.
로그로 분류 기준이 안정되면 특정 조건에서 producer re-produce, consumer re-consume, audio element 재생 재시도 등 복구 액션을 붙인다.