> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/unity/add-features/metrics.md).

# SDK event logging

This documents the runtime events that the SDK automatically collects and sends to the platform without user code. Rather than what I need to instrument, **what is already being instrumented**is what this document explains.

### How is it enabled?

`Runtime/Helpers/AIT.PerformanceLogger.cs`the `AITPerformanceLogger`is `[RuntimeInitializeOnLoadMethod(BeforeSceneLoad)]`is initialized automatically. There is nothing to install or call.

Transmission **only in WebGL builds** occurs. In the Unity Editor and other platforms, `SendLog`returns immediately upon entry, so events are neither created nor sent. This is because the bridge exists only in WebGL builds.

For all events, `log_type`is `unity_runtime`and event types are `log_name`used to distinguish them.

### Event category

| log\_name                 | Trigger                                                           | Rate Limit                              |
| ------------------------- | ----------------------------------------------------------------- | --------------------------------------- |
| `unity_scene_transition`  | `SceneManager.sceneLoaded` / `sceneUnloaded`                      | None                                    |
| `unity_first_interactive` | Original first scene load complete                                | Once per session                        |
| `unity_low_memory`        | `Application.lowMemory`                                           | Once per 30 seconds                     |
| `unity_error`             | `Application.logMessageReceived` (Error/Exception/Assert)         | 10 times per 60 seconds + deduplication |
| `unity_lifecycle`         | `AITVisibilityHelper.OnVisibilityChanged`, `Application.quitting` | focus\_changed: once per 5 seconds      |
| `unity_frame_stall`       | `Time.unscaledDeltaTime` > 500ms                                  | 5 times per 60 seconds                  |
| `unity_screen_change`     | `Screen.width`/`height`/`orientation` Change detected             | Once per 2 seconds                      |
| `unity_gc_collection`     | `GC.CollectionCount(0)` Change detected                           | 5 times per 60 seconds                  |
| `unity_timescale_change`  | `Time.timeScale` Change detected                                  | Once per 5 seconds                      |

The four detected by polling (`frame_stall`, `screen_change`, `gc_collection`, `timescale_change`) are checked every frame in the dedicated `AITPerformanceLoggerMonitor` GameObject's `Update`. This object is `HideAndDontSave` + `DontDestroyOnLoad`so it does not appear in the Hierarchy and survives scene transitions.

> **Note**: Focus events come from `Application.focusChanged`rather than the SDK's own `AITVisibilityHelper`. In WebGL, browser tab visibility is the actual signal.

### Parameters by event

All events include the following common parameters.

| Parameters             | Description                                            |
| ---------------------- | ------------------------------------------------------ |
| `event_type`           | such as `log_name` distinguishes detailed types within |
| `time_since_start_sec` | Elapsed time since app startup (one decimal place)     |

`unity_first_interactive`As the only exception, `time_since_start_sec` instead `time_since_start_ms`is used.

#### unity\_scene\_transition

```json
{
    "event_type": "scene_loaded",
    "scene_name": "GameScene",
    "scene_build_index": 2,
    "load_mode": "Single",
    "previous_scene": "MainMenu",
    "total_loaded_scenes": 3,
    "time_since_start_sec": 12.5
}
```

| Parameters            | Description                                                   | event\_type        |
| --------------------- | ------------------------------------------------------------- | ------------------ |
| `event_type`          | `scene_loaded` or `scene_unloaded`                            | All                |
| `scene_name`          | Scene name                                                    | All                |
| `scene_build_index`   | Build Settings index                                          | All                |
| `load_mode`           | `Single` or `Additive`                                        | Only scene\_loaded |
| `previous_scene`      | Name of the scene loaded immediately before                   | Only scene\_loaded |
| `total_loaded_scenes` | Number of currently loaded scenes (`SceneManager.sceneCount`) | All                |

#### unity\_first\_interactive

This event measures the point when the original first scene has finished loading—that is, the moment when the game actually becomes controllable. It is sent only once per session.

```json
{
    "event_type": "first_interactive",
    "scene_name": "MainMenu",
    "scene_build_index": 0,
    "time_since_start_ms": 4820
}
```

There are two rules for determining when to fire.

* **`AITProxyBoot`Scenes beginning with are skipped.** This is because the proxy boot scene injected by the SDK is not the game's original first scene.
* **“First” is determined at the first target scene regardless of whether it is active.** Even if logging is disabled, the flag is fixed at that scene, so a scene loaded later is not reported as first after the fact.

Whether it is enabled is queried once via jslib using the value embedded in the template at build time, then cached. If the query fails, it is **considered enabled**(fail-open).

> **Note**: Since the initial boot scene load occurs immediately before first paint, this value is nearly the same as the first-paint time in builds without separate optimization. If the gap between the two metrics widens, it signals that the first scene has become heavier.

#### unity\_low\_memory

```json
{
    "event_type": "low_memory",
    "time_since_start_sec": 120.5
}
```

#### unity\_error

```json
{
    "event_type": "exception",
    "message": "NullReferenceException: ...",
    "stack_trace": "at GameManager.Update() ...",
    "log_type": "Exception",
    "time_since_start_sec": 45.2
}
```

| Parameters    | Description                                      |
| ------------- | ------------------------------------------------ |
| `event_type`  | `error`, `exception`, `assert`                   |
| `message`     | Error message (truncated at 500 characters)      |
| `stack_trace` | Stack trace (truncated at 200 characters)        |
| `log_type`    | Unity `LogType` (`Error`, `Exception`, `Assert`) |

Deduplication **is based on the message hash within a 60-second window**. If the same message repeats within the window, only the first occurrence is sent; once the window expires, the hash set is cleared and it is reported again. Messages are considered the same even if their stack traces differ.

#### unity\_lifecycle

```json
{ "event_type": "focus_changed", "has_focus": true, "time_since_start_sec": 120.5 }
{ "event_type": "quitting", "session_duration_sec": 300.5, "total_scenes_loaded": 5 }
```

`total_scenes_loaded`is the number of scenes loaded during the session, **cumulative** scene count, `unity_scene_transition`the `total_loaded_scenes`which differs from (the current number loaded simultaneously).

#### unity\_frame\_stall

```json
{
    "event_type": "frame_stall",
    "frame_duration_ms": 750,
    "threshold_ms": 500,
    "time_since_start_sec": 45.2
}
```

The basis is `Time.deltaTime`not `Time.unscaledDeltaTime`. `Time.timeScale`so paused periods, which set it to 0, are not detected as stalls.

#### unity\_screen\_change

```json
{ "event_type": "screen_resize", "width": 1920, "height": 1080, "previous_width": 1280, "previous_height": 720, "time_since_start_sec": 30.0 }
{ "event_type": "orientation_change", "width": 1080, "height": 1920, "orientation": "Portrait", "previous_orientation": "LandscapeLeft", "time_since_start_sec": 30.0 }
```

When size and orientation change together, `orientation_change` alone is sent. Since rotation usually accompanies a size change, this prevents the two events from being sent redundantly.

#### unity\_gc\_collection

```json
{
    "event_type": "gc_collection",
    "generation": 1,
    "gen0_total": 45,
    "gen1_total": 12,
    "gen2_total": 3,
    "time_since_start_sec": 60.0
}
```

Detection **only checks changes in the gen0 counter** . `generation` The value is estimated from the cumulative gen1/gen2 counts, so it does not accurately indicate which generation this collection actually belonged to. The `gen*_total`values are more reliable in that they are cumulative values since process startup.

#### unity\_timescale\_change

```json
{
    "event_type": "timescale_changed",
    "time_scale": 0.0,
    "previous_time_scale": 1.0,
    "time_since_start_sec": 15.0
}
```

### Checking in the debug console

When the debug console is enabled, open it using the button at the bottom left of the screen and **Metrics** you can view these events directly in the tab. The event list and cumulative counts by category are displayed, allowing you to immediately verify that instrumentation is running without checking the platform dashboard.

The debug console is enabled by default in the Dev Server profile, and in other profiles it can also be enabled with an [Build Profiles](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)the `AIT_DEBUG_CONSOLE` environment variable.

> **Note**: The per-category count table matches the eight category names above as substrings. `unity_first_interactive`does not match any of them and therefore appears as a separate row below the table. This is not an omission, but a difference in classification.

### Safeguards

| Item                  | Description                                                                                                            |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| try-catch             | Wraps all handlers so logging failures do not stop the game                                                            |
| Reentrancy prevention | `_isSending` With a guard, `logMessageReceived` → `SendLog` → warning log → `logMessageReceived` blocks infinite loops |
| Rate limiting         | Prevents excessive transmission with fixed limits per category                                                         |
| String truncation     | Truncates error messages and stack traces at fixed lengths                                                             |

The reentrancy guard is especially important. `SendLog`in non-WebGL environments `Debug.LogWarning`does not use for the same reason — if it leaves a warning, that warning enters again through `logMessageReceived`.

### Related documents

* [Build Profiles](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles) — Enable and disable the debug console
* [Sentry integration](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration) — Send errors to Sentry as well
* [API usage patterns](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/api-usage-patterns) — Analytics API for sending events directly


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/unity/add-features/metrics.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
