> 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-zh/unity/add-features/metrics.md).

# SDK 事件日志记录

整理 SDK 在无需用户代码的情况下自动收集并发送到平台的运行时事件。重点不是我应该埋点什么， **而是已经在埋点什么**的文档。

### 如何开启

`Runtime/Helpers/AIT.PerformanceLogger.cs`中的 `AITPerformanceLogger`会 `[RuntimeInitializeOnLoadMethod(BeforeSceneLoad)]`会自动初始化。无需安装或调用。

发送仅在 **WebGL 构建中** 发生。在 Unity Editor 和其他平台中， `SendLog`会在进入后立即返回，因此既不会创建事件，也不会发送出去。因为桥接只存在于 WebGL 构建中。

所有事件的 `log_type`是 `unity_runtime`，事件类型则用 `log_name`区分。

### 事件类别

| log\_name                 | 触发条件                                                              | 速率限制                     |
| ------------------------- | ----------------------------------------------------------------- | ------------------------ |
| `unity_scene_transition`  | `SceneManager.sceneLoaded` / `sceneUnloaded`                      | 无                        |
| `unity_first_interactive` | 原始首个场景加载完成                                                        | 每个会话 1 次                 |
| `unity_low_memory`        | `Application.lowMemory`                                           | 每 30 秒 1 次               |
| `unity_error`             | `Application.logMessageReceived` (Error/Exception/Assert)         | 每 60 秒 10 次 + 去重         |
| `unity_lifecycle`         | `AITVisibilityHelper.OnVisibilityChanged`, `Application.quitting` | focus\_changed：每 5 秒 1 次 |
| `unity_frame_stall`       | `Time.unscaledDeltaTime` > 500ms                                  | 每 60 秒 5 次               |
| `unity_screen_change`     | `Screen.width`/`height`/`orientation` 检测到变化                       | 每 2 秒 1 次                |
| `unity_gc_collection`     | `GC.CollectionCount(0)` 检测到变化                                     | 每 60 秒 5 次               |
| `unity_timescale_change`  | `Time.timeScale` 检测到变化                                            | 每 5 秒 1 次                |

通过轮询检测的项（`frame_stall`, `screen_change`, `gc_collection`, `timescale_change`）由专用的 `AITPerformanceLoggerMonitor` GameObject 的 `Update`中每帧检查。该对象因 `HideAndDontSave` + `、DontDestroyOnLoad`而不会显示在 Hierarchy 中，并会在场景切换时存活。

> **注意**：焦点事件不是来自 `Application.focusChanged`，而是来自 SDK 自身的 `AITVisibilityHelper`。在 WebGL 中，这是因为浏览器标签页可见性才是真正的信号。

### 各事件参数

所有事件都包含以下公共参数。

| 参数                     | 说明                    |
| ---------------------- | --------------------- |
| `event_type`           | 同一 `log_name` 中区分细分类型 |
| `time_since_start_sec` | 应用启动后的经过时间（保留 1 位小数）  |

`unity_first_interactive`仅此例外， `time_since_start_sec` 改用 `time_since_start_ms`。

#### 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 %}

| 参数                    | 说明                                   | event\_type     |
| --------------------- | ------------------------------------ | --------------- |
| `event_type`          | `scene_loaded` 或 `scene_unloaded`    | 总计              |
| `scene_name`          | 场景名称                                 | 总计              |
| `scene_build_index`   | Build Settings 索引                    | 总计              |
| `load_mode`           | `Single` 或 `Additive`                | 仅 scene\_loaded |
| `previous_scene`      | 之前加载的场景名称                            | 仅 scene\_loaded |
| `total_loaded_scenes` | 当前已加载的场景数（`SceneManager.sceneCount`) | 总计              |

#### unity\_first\_interactive

这是衡量原始首个场景加载完成时点、也就是游戏实际可操作的瞬间的事件。每个会话只发送一次。

{% code collapsedlinecount="10" %}

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

{% endcode %}

触发判定有两个规则。

* **`AITProxyBoot`开头的场景会被跳过。** 因为 SDK 注入的代理启动场景并不是游戏原本的首个场景。
* **无论是否激活，都会在最初的目标场景中确定“首次”。** 即使关闭日志记录，该场景中的标记也会被固定，因此之后加载的场景不会被事后报告为 first。

激活状态会在构建时先通过 jslib 查询一次模板中写入的值并缓存。查询失败时 **视为已激活**。

> **注意**：启动首个场景的加载发生在 first-paint 之前，因此在没有额外优化的构建中，这个值几乎会与 first-paint 时间一致。若两项指标的间隔变大，则说明首个场景变重了。

#### 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 %}

| 参数            | 说明                                               |
| ------------- | ------------------------------------------------ |
| `event_type`  | `error`, `exception`, `assert`                   |
| `message`     | 错误消息（截断为 500 字）                                  |
| `stack_trace` | 堆栈跟踪（截断为 200 字）                                  |
| `log_type`    | Unity `LogType` (`Error`, `Exception`, `Assert`) |

去重是 **在 60 秒窗口内按消息哈希**进行的。相同消息在窗口内重复出现时，只发送第一条；窗口过去后哈希集合会清空并再次上报。即使堆栈跟踪不同，只要消息相同也视为相同。

#### 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`是会话期间加载的 **累计** 场景数， `unity_scene_transition`中的 `total_loaded_scenes`（与当前同时加载数不同）。

#### 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 %}

判定基准不是 Time.deltaTime，而是 Time.unscaledDeltaTime。将 Time.timeScale 设为 0 的暂停区间不会被算作卡顿。

#### 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 %}

当尺寸和方向同时变化时 `orientation_change` 只会发送一个事件。旋转通常会伴随尺寸变化，因此避免两个事件重叠发送。

#### 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 %}

检测 **只看 gen0 计数器变化** 。 `generation` generation 值是通过 gen1/gen2 累计计数推算出来的，因此无法准确指出本次采集实际属于哪一代。而 `gen*_total`是自进程启动以来的累计值，因此这些值更值得信赖。

#### 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 %}

### 在调试控制台中查看

开启调试控制台后，可通过屏幕左下角按钮打开控制台，并在 **指标** 标签中直接查看这些事件。会显示事件列表和按类别累计的计数，因此无需查看平台仪表盘也能立刻确认埋点是否在运行。

调试控制台在 Dev Server 配置文件中默认启用，在其他配置文件中也可通过 [构建配置](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)中的 `AIT_DEBUG_CONSOLE` 环境变量开启。

> **注意**：类别计数表会以子字符串方式匹配上方 8 个类别名称。 `unity_first_interactive`它不会命中其中任何一项，因此会在表格下方作为单独一行显示。不是遗漏，而是分类方式不同。

### 安全措施

| 项目        | 说明                                                                                      |
| --------- | --------------------------------------------------------------------------------------- |
| try-catch | 包裹所有处理器，确保日志失败不会让游戏停止                                                                   |
| 防止重入      | `_isSending` 作为守卫 `logMessageReceived` → `SendLog` → 警告日志 → `logMessageReceived` 阻止无限循环 |
| 限流        | 通过按类别固定上限防止过量发送                                                                         |
| 字符串截断     | 将错误消息·堆栈跟踪截断为固定长度                                                                       |

重入守卫尤其重要。 `SendLog`在 WebGL 以外环境中 `Debug.LogWarning`不使用它的原因也是一样——一旦记录警告，该警告又会再次 `logMessageReceived`通过它进入。

### 相关文档

* [构建配置](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles) — 打开和关闭调试控制台
* [Sentry 集成](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration) — 也将错误发送到 Sentry
* [API 使用模式](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/api-usage-patterns) — 直接发送事件的 Analytics API


---

# 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-zh/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.
