버그 리포트에 게임 상태 담기 — 무엇을 스냅샷할 것인가

환경·세션·순간·재현 단서 네 갈래로 나눈 게임 상태 분류, 비용 대비 가치 순위, 담으면 안 되는 것, 직렬화할 때 걸리는 함정까지 정리합니다.

  • unity
  • bug-report
  • performance

스크린샷만 있는 리포트가 재현으로 이어지지 않는 이유는 재현이 실패하는 이유에서 다뤘듯 조건이 통째로 유실되기 때문입니다. 화면에 찍힌 건 결과이고, 원인은 그 순간의 게임 상태에 있습니다. 이 글은 그 상태에서 무엇을 어디까지 담을지를 정리합니다.

네 갈래로 나눈다

모든 걸 다 담으려 하면 어디부터 손대야 할지 막막해집니다. 넷으로 쪼개면 각각이 답할 질문이 분명해집니다.

분류담는 값답하는 질문
환경기기 모델, OS, GPU, Unity 버전, 빌드 번호특정 기기·버전에서만 나는가
세션씬 이름, 플레이 시간, 진행도어디서, 얼마나 플레이한 뒤인가
순간FPS, 메모리, 최근 로그 N줄터지기 직전 상태는 어땠나
재현 단서최근 입력 이력, 랜덤 시드값어떤 조작을 어떤 순서로 했나

환경과 세션은 캡처 시점에 한 번 모으면 끝입니다. 순간은 값이 계속 바뀌므로 캡처 직전 최신값을 떠야 하고, 재현 단서는 애초에 게임 로직에서 계속 쌓아 두고 있어야 캡처 순간에 꺼내 쓸 수 있습니다. 이 차이를 놓치면 캡처 코드 자체는 멀쩡한데 정작 값이 비어 있는 상황이 생깁니다 — 예를 들어 입력 이력을 캡처하는 순간에만 수집하기 시작하면, 그 시점엔 이미 쌓여 있어야 할 이전 입력이 하나도 없습니다.

비용 대비 가치 순위

전부 같은 비용이 아닙니다. 무료에 가까운 것부터 붙이는 게 순서입니다.

  1. 환경 정보 — SystemInfo, Application.version 몇 줄로 끝. 비용 거의 없음, 가치는 즉시 나타남("이 기기에서만 난다"를 바로 걸러줍니다)
  2. 씬 이름·최근 로그 — 이미 최소 구현 가이드에 코드가 있습니다. 비용 낮음
  3. FPS·메모리 — 계산 몇 줄. 성능 관련 버그가 아니어도 "그 순간 프레임이 떨어지고 있었다" 같은 정황을 덤으로 얻습니다
  4. 프로젝트별 진행도 — 인벤토리 개수, 퀘스트 상태, 접속 서버. 프로젝트마다 무엇을 뽑을지 설계해야 해서 비용이 조금 올라갑니다
  5. 최근 입력 이력·시드값 — 재현에 가장 직접적으로 기여하지만, 입력을 계속 쌓아 두는 구조 자체를 새로 설계해야 해서 비용이 가장 높습니다

13번은 하루 안에 끝나는 작업이고 재현율에 즉시 반영됩니다. 45번은 프로젝트 로직에 손을 대야 해서 팀 상황에 따라 우선순위가 갈립니다. 예를 들어 QA 가 5명이고 주 2회 빌드를 받는 팀이라면 13번만 붙여도 "재현 안 됨"으로 닫히던 티켓의 상당수가 줄어드는 걸 바로 체감할 수 있고, 45번은 그 뒤에 프로젝트 로직과 맞춰 가며 설계해도 늦지 않습니다.

담으면 안 되는 것

스냅샷이 커질수록 담고 싶은 유혹도 커지지만, 세 가지는 선을 그어야 합니다.

  • 개인정보: 이메일, 실명, 결제 정보. 디바이스 식별자도 플랫폼 정책과 개인정보 규정부터 확인해야 하는 항목입니다.
  • 인증 토큰: 세션 토큰이나 API 키가 스냅샷에 섞여 들어가면 리포트 자체가 보안 사고가 됩니다. 직렬화 대상 목록에서 인증 관련 필드는 화이트리스트 방식으로 아예 제외해야 합니다.
  • 과도한 덤프: 씬 전체 오브젝트 트리나 전체 세이브 파일을 통째로 넣는 식입니다. 리포트 하나가 수 MB 를 넘어가면 전송 실패율이 오르고, 정작 봐야 할 값이 노이즈에 묻힙니다.

직렬화할 때 걸리는 함정

  • 메인 스레드 전용 API: Time, SystemInfo 계열 다수가 메인 스레드 전용입니다. 캡처 트리거가 워커 스레드(예: 예외 콜백)에서 걸리면 값을 못 읽고 예외가 납니다. 캡처 로직은 메인 스레드에서 실행되도록 큐잉하거나, 메인 스레드가 주기적으로 갱신해 둔 캐시값을 대신 읽어야 합니다.
  • JSON 크기: 리스트·딕셔너리를 그대로 직렬화하면 항목 수 제한 없이 커집니다. 최근 입력 이력처럼 계속 쌓이는 데이터는 캡처 시점에 최근 N개로 잘라서 담아야 합니다.
  • 순환 참조: GameObject 나 커스텀 클래스를 직렬화기에 그대로 넘기면 참조가 서로를 가리키다가 무한 루프에 빠지거나 예외가 납니다. 원본 객체가 아니라 값만 뽑아 평평한 DTO 로 옮긴 뒤 직렬화해야 합니다.
// 원본 객체를 직렬화하지 않는다 — 값만 평평하게 옮긴다
struct StateSnapshot
{
    public string scene;
    public float  fps;
    public long   memoryMB;
    public string[] recentInputs; // 원본 입력 큐가 아니라 최근 N개만 복사
}

정리

환경은 싸고 즉시 유용하고, 순간은 조금 더 손이 가고, 재현 단서는 가장 비싸지만 가장 결정적입니다. 순서대로 붙이면 어느 단계에서 멈춰도 손해가 아닙니다. 개인정보·인증 토큰은 애초에 수집 대상에서 빼고, 직렬화는 원본 객체가 아니라 값을 뽑은 DTO 로 합니다. 트리거·전송까지 포함한 전체 그림은 1편, 로그와 함께 붙이는 최소 코드는 최소 구현 가이드에 있습니다.

저희 Rekon은 이 스냅샷을 영상·로그와 같은 시간축에 자동으로 묶어 둡니다. 직접 설계하신다면 위 우선순위 그대로, 환경 정보부터 붙이시길 권합니다.