PPI Media Observability

수업 중 게스트 카메라·마이크·소리 끊김 진단 로깅 추가 가이드

마지막 업데이트 2026-07-24

수업 중 게스트 카메라·마이크·소리 끊김 진단 로깅 추가 가이드: 입력: 1. 결론: “게스트 네트워크” 단일 라벨을 5개 원인 축으로 분해, 주요 처리 단계: 3. 권장 로그 파이프라인, 결과: 9. 단계적 적용 제안 흐름
동작 흐름 요약
  1. 입력: 1. 결론: “게스트 네트워크” 단일 라벨을 5개 원인 축으로 분해
  2. 주요 처리 단계: 3. 권장 로그 파이프라인
  3. 결과: 9. 단계적 적용 제안

현재 “아동 카메라/마이크 연결 끊김”, “진행자에게 소리 안 들림”, “아동에게 핑퐁이 소리 안 들림”이 게스트 네트워크 이슈로 묶여 분류되는 경우가 있다. 하지만 실제 게스트 네트워크가 양호한 세션도 있으므로, 네트워크·장치·브라우저 autoplay/출력 라우팅·OpenAI Realtime WebRTC·mediasoup SFU Producer/Consumer·호스트 재생 element를 분리 진단할 수 있도록 로깅을 보강한다.

작성일: 2026-07-24 분석 기준: 로컬 checkout 65e256f / develop 주의: git pull origin develop은 GitHub 인증 문제로 실패

1. 결론: “게스트 네트워크” 단일 라벨을 5개 원인 축으로 분해

P0

네트워크/소켓

Socket.io disconnect/reconnect, engine transport 변경, RTT, candidate pair, TURN/TCP fallback 여부를 수집한다. 단순 navigator.onLine만으로 판단하지 않는다.

P0

게스트 장치/트랙

getUserMedia 성공 후 실제 track readyState, muted, enabled, deviceId/groupId, onmute/onended를 카메라·마이크별로 남긴다.

P0

SFU 송수신

아동 Producer(camera-video/mic-audio/ai-audio)와 진행자 Consumer 생명주기를 producerId/consumerId 기준으로 연결한다.

P1

OpenAI Realtime

AI 세션 RTCPeerConnection/DataChannel과 AI audio element 재생 성공 여부를 분리한다. play().catch(() => {}) 같은 실패 삼킴을 없앤다.

P1

호스트 재생/렌더링

Consumer track은 live인데 host video/audio element가 pause/error/stall 상태인지, audio RMS가 0인지 별도 측정한다.

원칙

상관관계 ID

모든 로그에 roomId, peerId, socketId, producerId, consumerId, streamType, ts를 포함해 LogRocket과 Grafana를 한 줄로 조인할 수 있게 한다.

2. 현재 코드상 관찰된 기존 로그와 빈틈

영역현재 존재하는 로그/이벤트진단상 부족한 점근거 파일
게스트 SFU Producerguest-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 Consumerhost-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 callbackAI audio play() 실패가 삼켜지고, currentTime 증가·audio energy·output sinkId 성공/실패가 부족apps/web/entities/guest-session/model/use-ai-session.ts:1075-1133
레거시 P2P Guest MediauseWebRTCMonitoring으로 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

3. 권장 로그 파이프라인

01입장 전 환경 스냅샷UA, OS, device list, permission, network info, selected device
02getUserMedia 결과constraints, 성공/실패, track identity, settings/capabilities
03SFU Producer 송신transport state + producer lifecycle + outbound stats delta
04SFU Consumer 수신new-producer → consume → resume → track add → inbound stats
05재생/가청/가시성video frame, audio RMS, element play promise, sinkId, visibility
핵심 판정 규칙: 네트워크 이슈로 분류하려면 최소한 socket disconnect 또는 transport failed/disconnected, RTT/packetLoss 악화, inbound/outbound packets 정체가 같은 시간대에 같이 있어야 한다. 반대로 Producer/Consumer stats는 정상인데 element playback/RMS만 0이면 네트워크가 아니라 브라우저 재생·출력·element 문제로 분류한다.

4. 우선순위별 로깅 추가 포인트

P0-1

공통 mediaDiagnosticLogger 유틸 추가

브라우저 로그는 현재 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
}
P0-2

게스트 Producer outbound stats

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 정체 → 네트워크 후보
P0-3

진행자 Consumer inbound stats

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/Height
P0-4

서버 SFU lifecycle 구조화

ProducerManager/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
});
P1-1

AI audio element 재생 검증

use-ai-session.ts:1117-1133audioEl.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, visibilityState
P1-2

게스트 장치/권한/트랙 이벤트

use-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
P1-3

Socket.io 연결 원인 세분화

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(지원 브라우저만)
P2

호스트 UI element playback probe

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 상태

5. 분류 매트릭스: 어떤 로그 조합이면 네트워크가 아닌가

관찰 조합분류해석다음 확인
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/freezeframesEncoded, 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

6. 구현 체크리스트

7. 코드 기준 상세 작업 위치

파일/라인추가할 로그목적
apps/web/hooks/mediasoup/use-mediasoup-producer.ts:115-154send 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-550camera/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-260consume 요청/응답에 producerId, consumerId, streamType, resume latency, failure reasonProducer는 있는데 Consumer가 실패하는 케이스 분리
apps/web/hooks/mediasoup/use-mediasoup-consumer.ts:442-498recv transport RTT 루프에 consumer별 inbound stats delta 추가진행자 “소리 안 들림/화면 멈춤” 수신단 근거 확보
apps/web/entities/guest-session/model/use-ai-session.ts:1078-1097OpenAI pc stats snapshot, dataChannel state, failure/recovery durationOpenAI Realtime 실패와 SFU 실패 분리
apps/web/entities/guest-session/model/use-ai-session.ts:1117-1133AI audio play promise 결과, sinkId, currentTime 증가, audio element events“아동에게 핑퐁이 소리 안 들림”의 재생/출력 원인 추적
apps/web/hooks/use-guest-media-manager.ts:333-380getUserMedia constraints/result, track settings, startTrackMonitoring status payload 확장레거시 P2P 경로의 장치/트랙 이슈 분리
apps/web/socketSfu.ts:31-74room/peer 맥락이 포함된 connect_error/disconnect/reconnect log소켓 레벨 네트워크 문제와 앱 세션 상태 연결
apps/socket/src/mediasoup/transportManager.ts:68-84dtlsstatechange, close reason, direction, peerId, roomId, transportId 구조화서버에서 transport close/fail 원인 확인
apps/socket/src/mediasoup/producerManager.ts:57-69producer score/trace, appData.type, close reason, consumer count아동 Producer 자체 문제인지 판단
apps/socket/src/mediasoup/consumerManager.ts:93-115consumer score/producerclose/transportclose에 producerPeerId, streamType, roomId 추가진행자 Consumer만 닫히는 케이스 추적
apps/web/entities/monitor-session/model/use-monitor-session.ts:453-610media alert payload에 diagnosisSummary, sourceLayer, correlatedIds 추가알림에서 “네트워크 추정” 대신 근거 기반 분류 표시

8. 권장 이벤트 이름

클라이언트 LogRocket용

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

서버 Grafana/Loki용

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

9. 단계적 적용 제안

Step 1

비침습 로깅

공통 schema와 Producer/Consumer stats logging만 먼저 추가한다. 기능 동작을 바꾸지 않고 진단 데이터부터 확보한다.

Step 2

Monitor 진단 표시

transport-only alert가 아니라 “송신 정상/수신 정체/재생 정체” 같은 diagnosisSummary를 진행자 화면에 표시한다.

Step 3

자동 복구

로그로 분류 기준이 안정되면 특정 조건에서 producer re-produce, consumer re-consume, audio element 재생 재시도 등 복구 액션을 붙인다.

주의: 로깅 추가 전에는 “게스트 네트워크 문제”로 확정하지 말고 “네트워크/장치/SFU/재생 경로 미분리” 상태로 표기하는 것이 안전하다. 특히 좋은 네트워크에서도 iOS/Chrome 장치 track 중단, remote audio clone 무음, host output routing 문제가 발생할 수 있다.