Unity 클라이언트 이벤트 로깅·성능 모니터링 시스템 구성
앱의 SDK 초기화와 이벤트 로깅은 하나의 UserTracking 클래스가 MonoBehaviour Awake에서 처리했고, SDK마다 대기 이벤트를 따로 다뤘다. 이를 SDK 라이프사이클 관리, 통합 이벤트 로깅, 성능 모니터링으로 나누어 다시 구성했다. FPS·메모리 판정 기준과 알림 정책은 별도 문서(“클라이언트 성능 모니터링의 slow/frozen frame 판정과 알림 정책”)에서 다룬다.
시스템 구성
섹션 제목: “시스템 구성”graph TB subgraph "Log System" LSM[LogSystemManager<br/>SDK 라이프사이클 관리] AEL[AppEventLogger<br/>통합 이벤트 로깅] end
subgraph "Monitoring System" MS[MonitoringSystem<br/>FPS/Memory 모니터링] end
subgraph "SDK Wrapper" FS[FirebaseSystem] AFS[AppsFlyerSystem] ES[EventSdkSystem] end
subgraph "External SDK" FB[Firebase Analytics] AF[AppsFlyer] EV[Event Collection SDK] end
LSM -.->|초기화| FS LSM -.->|초기화| AFS LSM -.->|초기화| ES LSM -->|관리| AEL
AEL -->|이벤트 전송| FS AEL -->|이벤트 전송| AFS AEL -->|이벤트 전송| ES
MS -->|이벤트 전송| AEL
FS --> FB AFS --> AF ES --> EV| 컴포넌트 | 역할 | 타입 |
|---|---|---|
| LogSystemManager | SDK 초기화 및 라이프사이클 관리 | MonoBehaviour |
| AppEventLogger | 앱 이벤트 통합 로깅, 오프라인 큐 | static class |
| MonitoringSystem | 성능 모니터링 (FPS, Memory) | Singleton |
| FirebaseSystem | Firebase Analytics 래퍼 | static class |
| AppsFlyerSystem | AppsFlyer 래퍼 | static class |
| EventSdkSystem | 이벤트 수집 SDK 래퍼 | static class |
폴더 구조는 다음과 같다. AppEventLogger는 partial class로 나눠 메인 로직, 진행 단계 기록, 데이터 타입을 분리했다.
Assets/.../Scripts/System/├── LogSystem/│ ├── LogSystemManager.cs # SDK 라이프사이클 관리│ ├── AppEventLogger/│ │ ├── AppEventLogger.cs # 메인 로직, 오프라인 큐│ │ ├── AppEventLogger.Progress.cs # RecordStepStart/End/Info│ │ ├── AppEventLogger.Data.cs # EventInfo, OfflineEvent, enum│ │ └── LogTarget.cs # LogTarget Flags enum│ └── SDK/│ ├── FirebaseSystem.cs│ ├── AppsFlyerSystem.cs│ └── EventSdkSystem.cs└── Monitoring/ └── MonitoringSystem.cs초기화 흐름
섹션 제목: “초기화 흐름”SDK 초기화는 BeforeSceneLoad 시점에 앱 시작 최초 1회 수행한다. MonoBehaviour 인스턴스와 모니터링은 AfterSceneLoad에서 만든다.
sequenceDiagram participant Unity participant LSM as LogSystemManager participant AEL as AppEventLogger participant FS as FirebaseSystem participant AFS as AppsFlyerSystem participant ES as EventSdkSystem participant MS as MonitoringSystem
rect rgb(240, 248, 255) Note over Unity,ES: BeforeSceneLoad (앱 시작 최초 1회) Unity->>LSM: InitializeSDKs() LSM->>AEL: Initialize() LSM->>FS: Initialize() LSM->>AFS: Initialize() LSM->>ES: Initialize() end
rect rgb(255, 248, 240) Note over Unity,MS: AfterSceneLoad Unity->>LSM: CreateInstance() Unity->>MS: AutoInitialize() end
rect rgb(240, 255, 240) Note over LSM,ES: Update Loop (SDK 초기화 완료까지) loop Until SDK Ready LSM->>FS: Tick() LSM->>ES: Tick() end FS-->>LSM: IsInitialized = true ES-->>LSM: IsInitialized = true Note over LSM: sdkInitCompleted = true<br/>Tick 호출 중단 end
rect rgb(255, 240, 255) Note over LSM,AEL: Runtime loop 매 3초 LSM->>AEL: TryFlushOfflineQueue() end loop 매 5초 LSM->>AEL: ProcessPendingEvents() end end비동기로 초기화되는 Firebase와 이벤트 수집 SDK는 LogSystemManager.Update에서 Tick()으로 완료 여부를 확인한다. 둘 다 초기화되면 sdkInitCompleted를 세우고 Tick 호출을 멈춘다. 이와 별개로 3초마다 오프라인 큐 flush, 5초마다 대기 이벤트 처리를 시도한다.
flowchart TD A[Update] --> B{sdkInitCompleted?}
B -->|No| C[FirebaseSystem.Tick] C --> D[EventSdkSystem.Tick] D --> E{Both Initialized?} E -->|Yes| F[sdkInitCompleted = true<br/>Tick 호출 중단] E -->|No| G[Continue]
B -->|Yes| H[Skip Tick]
A --> I{3초 경과?} I -->|Yes| J[TryFlushOfflineQueue]
A --> K{5초 경과?} K -->|Yes| L[ProcessPendingEvents]앱 진행 이벤트
섹션 제목: “앱 진행 이벤트”앱 시작부터 로비 진입까지, 게임 선택부터 퇴장까지의 각 단계를 start_*/end_* 쌍과 단독 info_* 이벤트로 기록한다.
sequenceDiagram participant User participant App participant Server as Config/API Server participant Analytics as Firebase/Event SDK
User->>App: 앱 실행 App->>Analytics: app_launch (Event SDK)
App->>Analytics: start_init_fb_sdk App->>Analytics: end_init_fb_sdk
App->>Analytics: start_load_bi_scene App->>Analytics: end_load_bi_scene Note over App,Analytics: 콜드 스타트 완료
App->>Analytics: start_load_intro_scene App->>Analytics: end_load_intro_scene
App->>Server: Config API 요청 App->>Analytics: start_download_config Server-->>App: Config 응답 (모니터링/이벤트 SDK 설정 포함) App->>Analytics: end_download_config Note over App: MonitoringSystem/EventSdkSystem 설정 적용
App->>Analytics: info_init_system
alt 버전 업데이트 필요 App->>Analytics: info_version_check App->>Analytics: info_update_popup App->>Analytics: info_update_action end
alt 점검/제재 App->>Analytics: info_maintenance_notice App->>Analytics: info_user_sanction end
App->>Analytics: start_login App->>Server: 로그인 요청 Server-->>App: 로그인 응답 App->>Analytics: end_login
App->>Analytics: start_connect_server App->>Server: 게임 서버 연결 Server-->>App: 연결 완료 App->>Analytics: end_connect_server
App->>Analytics: start_api_lobby_info App->>Server: 로비 정보 요청 Server-->>App: 로비 정보 응답 App->>Analytics: end_api_lobby_info
App->>Analytics: start_load_lobby_scene App->>Analytics: end_load_lobby_scene
App->>Analytics: info_lobby_ready Note over App,Analytics: 로비 진입 완료sequenceDiagram participant User participant App participant Server as Game Server participant Analytics as Firebase/Event SDK
User->>App: 게임 선택 App->>Analytics: info_select_game App->>Analytics: info_check_game_asset
App->>Analytics: start_api_join_room App->>Server: 방 입장 요청 Server-->>App: 방 정보 응답 App->>Analytics: end_api_join_room
alt 에셋 다운로드 필요 App->>Analytics: start_download_asset App->>Server: 에셋 번들 다운로드 Server-->>App: 에셋 데이터 App->>Analytics: end_download_asset end
App->>Analytics: start_load_game_scene App->>Analytics: end_load_game_scene
App->>Analytics: info_game_ready
Note over App,Analytics: 게임 플레이
User->>App: 게임 나가기 App->>Analytics: info_game_exit로그 시스템
섹션 제목: “로그 시스템”LogTarget
섹션 제목: “LogTarget”이벤트를 보낼 SDK를 Flags enum으로 고른다.
[Flags]public enum LogTarget{ None = 0, Firebase = 1 << 0, AppsFlyer = 1 << 1, EventSdk = 1 << 2,
FirebaseAndEventSdk = Firebase | EventSdk, All = Firebase | AppsFlyer | EventSdk,}이벤트 로깅 흐름
섹션 제목: “이벤트 로깅 흐름”flowchart TD A[LogEvent 호출] --> B{ShouldQueueEvent?}
B -->|네트워크 끊김| C[EnqueueOfflineEvent] B -->|SDK 미초기화| C B -->|정상| D[SendToSDK]
C --> E[offlineQueue 저장<br/>최대 20개, FIFO]
D --> F{LogTarget} F -->|Firebase| G[FirebaseSystem] F -->|AppsFlyer| H[AppsFlyerSystem] F -->|EventSdk| I[EventSdkSystem]
subgraph "오프라인 큐 Flush" J[3초마다 / 포그라운드 복귀] --> K[TryFlushOfflineQueue] K --> L{네트워크 + SDK Ready?} L -->|Yes| D L -->|No| M[대기] end오프라인 큐
섹션 제목: “오프라인 큐”네트워크가 끊겼거나 SDK가 아직 초기화되지 않았을 때 이벤트를 메모리에 임시 저장한다. 기존에는 SDK마다 따로 처리하던 대기 이벤트를 AppEventLogger.offlineQueue 하나로 합쳤다.
| 설정 | 값 | 설명 |
|---|---|---|
| MAX_OFFLINE_QUEUE_SIZE | 20 | 최대 보관 이벤트 수 (FIFO) |
| Flush 주기 | 3초 | 큐 전송 시도 주기 |
| Flush 트리거 | 포그라운드 복귀 | OnApplicationPause(false) |
| 앱 종료 시 | 유실 | 영구 저장 없음 |
private class OfflineEvent{ public string EventName; public Dictionary<string, object> Parameters; public LogTarget Target; public float EnqueueTime;}SDK 래퍼
섹션 제목: “SDK 래퍼”FirebaseSystem
섹션 제목: “FirebaseSystem”- PC 플랫폼은 Firebase SDK를 지원하지 않아 GA4 Measurement Protocol로 대체한다.
- 모바일은 Firebase SDK를 직접 쓴다.
CheckAndFixDependenciesAsync()로 비동기 초기화하고Tick()에서 완료를 처리한다.
stateDiagram-v2 [*] --> Initializing: Initialize()
Initializing --> PC_Skip: Platform == PC Initializing --> Pending: CheckAndFixDependenciesAsync()
PC_Skip --> Ready: OnInitializationComplete(true) Pending --> Ready: Tick() + Success Pending --> Failed: Tick() + ErrorFirebaseSystem.Initialize();FirebaseSystem.LogEvent(eventName, parameters);FirebaseSystem.SetUserId(userId);FirebaseSystem.SetUserProperty(key, value);FirebaseSystem.IsInitialized; // 초기화 완료 여부AppsFlyerSystem
섹션 제목: “AppsFlyerSystem”initSDK() + startSDK()로 동기 초기화되어 즉시 완료된다. 기존 UserTracking에서 구조만 분리했다.
EventSdkSystem
섹션 제목: “EventSdkSystem”내부 분석용 이벤트 수집 SDK를 감싼다.
InitializeAsync()로 비동기 초기화한다.- 앱의 현재 서버 환경 값을 읽어 SDK 환경으로 쓴다. qa·dev는 dev로 정규화한다.
- 슬롯 스핀, 테이블 게임 등 게임별 특화 이벤트를 제공한다.
stateDiagram-v2 [*] --> Initializing: Initialize() Initializing --> Pending: InitializeAsync(env) Pending --> Ready: Tick() + Success Pending --> Failed: Tick() + Error
state Initializing { [*] --> LoadEnv: AppEnvironment.Instance LoadEnv --> NormalizeEnv: qa/dev → dev }EventSdkSystem.Initialize();EventSdkSystem.LogCustomEvent(eventName, parameters);EventSdkSystem.LogAppLaunch();EventSdkSystem.LogAppExit(exitType);EventSdkSystem.LogSlotSpin(params);EventSdkSystem.SetUserId(userId);EventSdkSystem.IsInitialized; // 초기화 완료 여부모니터링 시스템
섹션 제목: “모니터링 시스템”MonitoringSystem은 FPS와 메모리 사용량을 매 프레임 확인하고, 임계값을 넘거나 OS lowMemory 콜백이 오면 AppEventLogger를 통해 Firebase와 이벤트 수집 SDK에 이벤트를 보낸다. 백그라운드에서 포그라운드로 돌아온 뒤 첫 프레임까지의 시간(웜 스타트)도 측정한다.
graph TB subgraph "MonitoringSystem" INIT[Initialize<br/>AfterSceneLoad 자동 호출] CONFIG[ApplyApiConfig<br/>CDN 설정 적용] UPDATE[Update Loop<br/>매 프레임] end
subgraph "Monitors" FPS[FPS Monitor] MEM[Memory Monitor] WARM[Warm Start<br/>포그라운드 복귀 시간] end
subgraph "Output" AEL[AppEventLogger<br/>→ Firebase + Event SDK] end
INIT --> CONFIG CONFIG --> UPDATE UPDATE --> FPS UPDATE --> MEM UPDATE --> WARM
FPS --> AEL MEM --> AEL WARM --> AEL- 씬 로드 직후와 포그라운드 복귀 직후에는 일시적인 FPS 저하를 무시하는 유예 기간을 둔다.
- 메모리는
SystemInfo.systemMemorySize(시스템 메모리) 대비Profiler.GetTotalReservedMemoryLong()(현재 예약 메모리) 비율로 판정하고, OS lowMemory 콜백도 따로 받는다. - 웜 스타트는
info_warm_start이벤트에warm_start_duration,pause_duration,current_scene을 담아 보낸다. - 임계값, 유예 시간, 샘플링 비율, 웜 스타트 측정 여부는 CDN에서 내려받은 설정을
ApplyApiConfig()로 적용한다.
사용법
섹션 제목: “사용법”기본 로깅
섹션 제목: “기본 로깅”// 모든 SDK에 로깅AppEventLogger.LogEvent("event_name", parameters);
// 특정 SDK에만 로깅AppEventLogger.LogEvent("event_name", parameters, LogTarget.Firebase);AppEventLogger.LogEvent("event_name", parameters, LogTarget.FirebaseAndEventSdk);
// 편의 메서드AppEventLogger.LogFirebaseOnly("event_name", parameters);AppEventLogger.LogEventSdkOnly("event_name", parameters);앱 진행 트래킹
섹션 제목: “앱 진행 트래킹”// 단계 시작 기록AppEventLogger.RecordStepStart(AppStartProgress.LoadIntroScene);
// 단계 완료 기록AppEventLogger.RecordStepEnd(AppStartProgress.LoadIntroScene);
// 정보 기록 (start/end 없이 단독)AppEventLogger.RecordStepInfo(AppStartProgress.SelectGame, additionalParams);User ID 설정
섹션 제목: “User ID 설정”// 모든 SDK에 User ID 설정AppEventLogger.SetUserIdToAllSDK(userId);모니터링
섹션 제목: “모니터링”// 씬 로드 알림 (SceneLoadManager에서 호출)MonitoringSystem.Instance.OnSceneLoaded(sceneName);
// API Config 적용 (설정 로드 후 호출)MonitoringSystem.Instance.ApplyApiConfig();마이그레이션
섹션 제목: “마이그레이션”// BeforeUserTracking.Instance.LogEventFirebase("event", params);UserTracking.Instance.LogEventAppsFlyer("event");
// AfterAppEventLogger.LogEvent("event", params, LogTarget.Firebase);AppsFlyerSystem.LogEvent("event");
// 또는 통합 호출AppEventLogger.LogEvent("event", params); // 모든 SDK에 전송| 항목 | Before | After |
|---|---|---|
| SDK 초기화 | UserTracking.Init() |
LogSystemManager.InitializeSDKs() |
| 초기화 시점 | MonoBehaviour Awake |
BeforeSceneLoad |
| Firebase 로깅 | UserTracking.LogEventFirebase() |
FirebaseSystem.LogEvent() |
| AppsFlyer 로깅 | UserTracking.LogEventAppsFlyer() |
AppsFlyerSystem.LogEvent() |
| 대기 이벤트 | 각 SDK별 별도 처리 | AppEventLogger.offlineQueue 통합 |