> 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

Summarizes the runtime events that the SDK automatically collects and sends to the platform without user code. This is not a document about what I should instrument, **what is already being instrumented**is a document that checks it.

### How is it enabled?

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

Transmission is **only in WebGL builds** happens. In 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.

All events' `log_type`is `unity_runtime`and the event type is `log_name`distinguished by.

### 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 every 30 seconds                   |
| `unity_error`             | `Application.logMessageReceived` (Error/Exception/Assert)         | 10 times per 60 seconds + deduplication |
| `unity_lifecycle`         | `AITVisibilityHelper.OnVisibilityChanged`, `Application.quitting` | focus\_changed: 1 time every 5 seconds  |
| `unity_frame_stall`       | `Time.unscaledDeltaTime` > 500ms                                  | 5 times per 60 seconds                  |
| `unity_screen_change`     | `Screen.width`/`height`/`orientation` change detected             | 1 time every 2 seconds                  |
| `unity_gc_collection`     | `GC.CollectionCount(0)` change detected                           | 5 times per 60 seconds                  |
| `unity_timescale_change`  | `Time.timeScale` change detected                                  | 1 time every 5 seconds                  |

Polled net (`frame_stall`, `screen_change`, `gc_collection`, `timescale_change`) is a dedicated `AITPerformanceLoggerMonitor` GameObject's `Update`checks every frame. This object `HideAndDontSave` + `DontDestroyOnLoad`so it is not visible in the Hierarchy and survives scene transitions.

> **Note**: focus events come from `Application.focusChanged`not the SDK's own `AITVisibilityHelper`because in WebGL, browser tab visibility is the real signal.

### Parameters by event

All events include the common parameters below.

| Parameter              | Description                                           |
| ---------------------- | ----------------------------------------------------- |
| `event_type`           | within the same `log_name` distinguish detailed types |
| `time_since_start_sec` | Elapsed time since app start (1 decimal place)        |

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

#### unity\_scene\_transition

{% code collapsedlinecount="10" %}

```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
}
```

{% endcode %}

| Parameter             | 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`                                        | scene\_loaded only |
| `previous_scene`      | Name of the previously loaded Scene                           | scene\_loaded only |
| `total_loaded_scenes` | Number of currently loaded Scenes (`SceneManager.sceneCount`) | All                |

#### unity\_first\_interactive

This event measures the moment when loading of the original first scene ends, that is, when the game actually becomes playable. It is sent only once per session.

{% code collapsedlinecount="10" %}

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

{% endcode %}

There are two rules for determining the trigger.

* **`AITProxyBoot`Scenes starting with** The proxy boot scene injected by the SDK is not the game's original first scene.
* **The "first" is determined in the earliest target scene regardless of whether it is active.** Even if logging is off, the flag is fixed in that scene, so scenes loaded later are not reported as first afterward.

Whether it is active is checked once via jslib for the value baked into the template at build time, and then cached. If the lookup fails, **considered active**it is considered active (fail-open).

> **Note**: The boot first scene load happens just before first-paint, so in builds without separate optimization, this value is almost the same as the first-paint time. If the gap between the two metrics widens, it is a sign that the first scene has become heavier.

#### unity\_low\_memory

{% code collapsedlinecount="10" %}

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

{% endcode %}

#### unity\_error

{% code collapsedlinecount="10" %}

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

{% endcode %}

| Parameter     | 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 message hash within a 60-second window**The same message repeated within the window sends only the first one, and once the window passes the hash set is cleared and reported again. Even if the stack trace differs, the same message is treated as the same.

#### unity\_lifecycle

{% code collapsedlinecount="10" %}

```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 }
```

{% endcode %}

`total_scenes_loaded`is the cumulative **scene count** loaded during the session, `unity_scene_transition`of `total_loaded_scenes`(current simultaneous loaded count), a different value.

#### unity\_frame\_stall

{% code collapsedlinecount="10" %}

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

{% endcode %}

The basis is Time.unscaledDeltaTime, not Time.deltaTime. Pause periods with Time.timeScale set to 0 are not captured as stalls.

#### unity\_screen\_change

{% code collapsedlinecount="10" %}

```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 }
```

{% endcode %}

If size and orientation change together, `orientation_change` only one is sent. Since rotation usually accompanies a size change, this prevents the two events from overlapping.

#### unity\_gc\_collection

{% code collapsedlinecount="10" %}

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

{% endcode %}

Detection is **only the gen0 counter change** is observed. `generation` These `gen*_total`are more reliable because they are cumulative since process start.

#### unity\_timescale\_change

{% code collapsedlinecount="10" %}

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

{% endcode %}

### Checking in the debug console

When the debug console is on, open the console with the button at the bottom left of the screen and **Metrics** you can view these events as-is in the tab. Because the event list and cumulative counts by category are shown, you can immediately verify whether instrumentation is running without looking at the platform dashboard.

The debug console is enabled by default in the Dev Server profile, and in other profiles too [build profile](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)of `AIT_DEBUG_CONSOLE` environment variable.

> **Note**: The count table by category matches the above 8 category names as substrings. `unity_first_interactive`does not match any of them, so it appears as a separate row at the bottom of the table. This is not missing data but a difference in classification.

### Safeguards

| Item               | Description                                                                                                            |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| try-catch          | Wrapping all handlers so logging failures do not stop the game                                                         |
| Reentry prevention | `_isSending` as a guard `logMessageReceived` → `SendLog` → warning log → `logMessageReceived` Infinite loop prevention |
| Rate limiting      | Prevents excessive sending with fixed per-category limits                                                              |
| String truncation  | Cuts error messages and stack traces at a fixed length                                                                 |

Reentry guard is especially important. `SendLog`In environments other than WebGL, `Debug.LogWarning`is not used for the same reason — if a warning is logged, that warning comes back through `logMessageReceived`again.

### Related documents

* [build profile](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles) — Turn the debug console on and off
* [Sentry integration](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration) — Send errors to Sentry too
* [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.
