> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/advanced-topics/performance-and-optimization.md).

# 日志、指标和重试策略

按子系统配置 SDK 日志详细级别，将日志路由到自定义接收端，读取 RTVI 管道延迟指标，并调整连接重试策略。

Convai Unity SDK 随附的默认设置可在训练模拟、交互式体验和游戏中通用。当您需要诊断延迟、减少生产环境中的日志噪音、了解 AI 管道的时序，或调整重连行为时——本页涵盖可用的控制项。

**跳转到您需要的主题：**

* [日志级别配置](#log-level-configuration) — 减少正式构建中的控制台噪音
* [自定义日志接收器](#custom-log-sinks) — 将日志路由到文件或远程服务
* [RTVI 服务端调试指标](#rtvi-server-side-debug-metrics) — 测量每回合的 AI 管道延迟
* [重试策略](#retry-policy) — 为不稳定的网络调整重连行为

***

### 日志级别配置

#### 日志级别

| 级别      | 枚举值 | 何时使用             |
| ------- | --- | ---------------- |
| `关闭`    | `0` | 禁用所有 SDK 日志。     |
| `Error` | `1` | 正式构建——仅错误。       |
| `警告`    | `2` | 预发布——警告和错误。      |
| `信息`    | `3` | **默认。** 正常运行消息。  |
| `调试`    | `4` | 积极开发——详细的生命周期事件。 |
| `跟踪`    | `5` | 深入调试——高频内部状态。    |

这些值对应于 `Convai.Domain.Logging.LogLevel` 枚举。

{% hint style="warning" %}
`跟踪` 和 `调试` 这些级别会产生大量输出。SDK 调试日志在 Unity 编辑器和开发构建中处于启用状态。在发布构建中，它们会被编译器剥离，除非 `CONVAI_DEBUG_LOGGING` 已添加到脚本定义符号中。不要在发布版本中包含 `跟踪` 已启用。
{% endhint %}

#### 在检视器中配置

打开 **Project Settings → Convai** （或选择 `ConvaiSettings` 位于 `Assets/Resources/ConvaiSettings.asset`的资源 **日志** 部分：

| 字段       | 说明                             |
| -------- | ------------------------------ |
| `全局日志级别` | 所有 SDK 子系统的最低级别。               |
| `包含堆栈跟踪` | 为警告和错误消息附加堆栈跟踪。                |
| `彩色输出`   | 在 Unity 控制台中按颜色区分日志消息。         |
| `类别覆盖`   | 按子系统级别覆盖——添加条目以为特定类别设置更细粒度的级别。 |

#### 日志类别

| 类别           | 子系统                         |
| ------------ | --------------------------- |
| `SDK`        | 通用操作                        |
| `Character`  | 每个角色会话生命周期                  |
| `音频`         | 麦克风采集、播放、音频设备               |
| `UI`         | UI 组件生命周期                   |
| `REST`       | REST API 通信                 |
| `传输`         | WebRTC / WebSocket 连接层      |
| `事件`         | 内部事件总线                      |
| `Player`     | 玩家身份与输入                     |
| `Editor`     | 仅编辑器操作                      |
| `视觉`         | 摄像头捕获和视频发布                  |
| `启动`         | SDK 启动和初始化                  |
| `Transcript` | 转录处理与路由                     |
| `叙事`         | 叙事设计系统                      |
| `唇形同步`       | 唇形同步处理和 blendshape 播放       |
| `动画`         | 身体动画系统——图层、过渡和移动            |
| `凝视`         | 注视系统——目标定位、策略以及眼睛/头部/身体求解器  |
| `身体语言`       | 肢体语言系统——手势、姿势、呼吸和小动作        |
| `动作`         | 动作系统——哪些命令到达、哪些被丢弃，以及目标如何解析 |

这些值对应于 `Convai.Domain.Logging.LogCategory` 枚举。

#### 在运行时配置

```csharp
using Convai.Runtime;
using Convai.Domain.Logging;

// 设置全局级别——会立即应用于后续所有日志调用。
ConvaiSettings.Instance.SetGlobalLogLevel(LogLevel.Debug);

// 在不更改全局级别的情况下覆盖特定子系统。
ConvaiSettings.Instance.SetCategoryOverrides(new[]
{
    new LogLevelOverride(LogCategory.Audio,     LogLevel.Debug),
    new LogLevelOverride(LogCategory.Transport, LogLevel.Warning)
});
```

`LogLevelOverride` 是一个包含两个字段的结构体： `LogCategory Category` 和 `LogLevel Level`.

#### `CONVAI_DEBUG_LOGGING` 脚本定义

`ConvaiLogger.Debug(...)` 调用受三个 `[Conditional]` 属性： `UNITY_EDITOR`, `DEVELOPMENT_BUILD`，以及 `CONVAI_DEBUG_LOGGING`。编译器会在未定义这些符号的任何构建中将它们剥离——在生产发布构建中零开销。

要在发布构建中启用调试日志：添加 `CONVAI_DEBUG_LOGGING` 为 **Project Settings → Player → Scripting Define Symbols**。开发构建会自动启用它们，无需此定义。

***

### 自定义日志接收器

通过实现以下内容，将 SDK 日志输出路由到文件、远程日志服务或任何目标： `ILogSink` 并将其注册到 `ConvaiLogger`.

```csharp
// FileSink.cs
using System;
using System.IO;
using Convai.Domain.Logging;

public class FileSink : ILogSink
{
    private readonly StreamWriter _writer;

    public string Name      => "FileSink";
    public bool   IsEnabled { get; private set; } = true;

    public FileSink(string path)
        => _writer = new StreamWriter(path, append: true);

    public void Write(LogEntry entry)
        => _writer.WriteLine($"[{entry.Timestamp:HH:mm:ss.fff}] [{entry.Level}] [{entry.Category}] {entry.Message}");

    public void Flush()             => _writer.Flush();
    public void SetEnabled(bool en) => IsEnabled = en;
    public void Dispose()           => _writer?.Dispose();
}
```

在以下之后注册 `ConvaiLogger` 初始化完成后：

```csharp
using Convai.Runtime.Logging;

// 当 ConvaiLogger 就绪时会触发 OnInitializationCompleted——务必在此处注册 sink，
// 切勿提前注册，以免错过初始化窗口。
ConvaiLogger.OnInitializationCompleted += _ =>
{
    string logPath = Path.Combine(Application.persistentDataPath, "convai_session.log");
    ConvaiLogger.RegisterSink(new FileSink(logPath));
};
```

**Sink 生命周期：**

* `ILogSink` 扩展 `IDisposable` — 实现类必须提供一个 `Dispose()` 方法。
* 调用 `ConvaiLogger.UnregisterSink(sink)` 在释放该 sink 之前。
* 调用 `ConvaiLogger.FlushAllSinks()` 在应用退出时调用，以确保缓冲条目被写入。

***

### RTVI 服务端调试指标

RTVI（实时语音交互）是 Convai 的会话协议。当 `ConvaiRoomManager.Debug` 是 `true`，服务器会在每次 AI 回合后将管道指标流式发送到客户端，显示每个阶段（语音转文本、LLM、文本转语音等）耗时。当您需要确定哪个管道阶段是瓶颈时使用——例如，AI 响应慢是因为转录延迟还是 LLM 生成时间？

#### 启用调试指标

`ConvaiRoomManager.Debug` 是一个只读运行时属性。通过勾选 **调试** 上的 `ConvaiRoomManager` 组件在检视器中，进入播放模式之前。

{% hint style="warning" %}
调试指标会给每个回合增加少量开销。仅在性能分析会话中勾选此选项——不要在发布时启用它。
{% endhint %}

#### 读取指标

```csharp
// MetricsLogger.cs
using Convai.Runtime.Adapters.Networking;
using Convai.Infrastructure.Protocol.Messages;
using UnityEngine;

public class MetricsLogger : MonoBehaviour
{
    [SerializeField] private ConvaiRoomManager _roomManager;

    private void OnEnable()
        => _roomManager.OnRtvMetricsReceived += OnMetricsReceived;

    private void OnDisable()
        => _roomManager.OnRtvMetricsReceived -= OnMetricsReceived;

    private void OnMetricsReceived(RTVIMetricsPayload payload)
    {
        if (payload.Ttfb       != null) Debug.Log($"[Metrics] TTFB: {payload.Ttfb}");
        if (payload.Processing != null) Debug.Log($"[Metrics] Processing: {payload.Processing}");
        if (payload.Custom     != null) Debug.Log($"[Metrics] Custom: {payload.Custom}");
    }
}
```

#### 指标字段

| 字段           | 类型        | 说明                                                                        |
| ------------ | --------- | ------------------------------------------------------------------------- |
| `Ttfb`       | `JToken?` | 每个处理器的首字节时间（秒）。由以下项组成的数组 `{processor, value}` 条目。                         |
| `Processing` | `JToken?` | 本次回合每个处理器的总处理时长（秒）。                                                       |
| `Custom`     | `JToken?` | 提供方特定指标——格式因处理器而异（例如， `output_fps`, `blendshapes_received` 来自 NeuroSync）。 |

`Ttfb` 和 `Processing` 是原始 `JToken` 值（来自 `Newtonsoft.Json.Linq`，通过 Unity 中的 `com.unity.nuget.newtonsoft-json` 包）。将它们反序列化为 `payload.Ttfb.ToObject<List<MetricEntry>>()` 其中 `MetricEntry` 是一个与以下形状匹配的结构体： `{processor, value}` 并非每个回合都包含每种指标类型——访问前务必进行 null 检查。

***

### 重试策略

SDK 使用 `ExponentialBackoffPolicy` 来处理暂时性连接失败。默认行为：

| 尝试    | 重试前延迟 |
| ----- | ----- |
| 1（初始） | 0 秒   |
| 2     | 1 秒   |
| 3     | 2 秒   |
| 4     | 4 秒   |

在总共 4 次尝试后（1 次初始 + 3 次重试），操作将失败。该策略仅重试瞬时错误：超时、网络中断，以及 `OperationCanceledException`。非瞬时错误（身份验证失败、配置无效）会立即失败。

#### 何时自定义重试策略

默认策略（4 次尝试、指数退避，最长 4 秒）可覆盖大多数消费者网络环境。以下情况可考虑自定义策略：

* **部署在工业或嵌入式硬件上** 且已知连接会间歇中断——增加 `MaxAttempts` 和延迟步长。
* **自助终端或常开安装** — 更激进的重试（更多尝试、更长的最大延迟）可避免在短暂网络掉线后需要用户干预。
* **低延迟训练环境** 且网络可靠——减少 `MaxAttempts` 以便快速失败，并将错误显示到您自己的重连 UI，而不是静默重试。

如果您只需要增加尝试次数而不更改延迟曲线，请增加 `maxRetryAttempts` 在 `ConvaiBootstrapConfigSnapshot` — 无需自定义策略。

#### 自定义重试策略

实现 `IRetryPolicy` 并使用 `RetryExecutor` 来包装任何异步操作：

```csharp
// AggressiveRetryPolicy.cs
using System;
using Convai.Runtime.Core.Policies;

public class AggressiveRetryPolicy : IRetryPolicy
{
    public int MaxAttempts => 6;

    public TimeSpan GetDelay(int attempt) => attempt switch
    {
        0 => TimeSpan.Zero,
        1 => TimeSpan.FromSeconds(2),
        2 => TimeSpan.FromSeconds(5),
        3 => TimeSpan.FromSeconds(10),
        4 => TimeSpan.FromSeconds(20),
        _ => TimeSpan.FromSeconds(30)
    };

    public bool ShouldRetry(Exception exception, int attempt)
    {
        if (attempt >= MaxAttempts) return false;
        return exception is TimeoutException
            || exception is System.Net.WebException
            || exception is OperationCanceledException;
    }
}
```

```csharp
using Convai.Runtime.Core.Policies;

var executor = new RetryExecutor(new AggressiveRetryPolicy());

await executor.ExecuteAsync(async (attempt, ct) =>
{
    Debug.Log($"Connection attempt {attempt + 1}...");
    await someAsyncOperation(ct);
    return true;
}, cancellationToken);
```

***

### 生产检查清单

* [ ] `全局日志级别` 设置为 `警告` 或 `Error` 在生产 `ConvaiSettings` 资源中。
* [ ] `CONVAI_DEBUG_LOGGING` **不** 存在于发布构建的脚本定义符号中。
* [ ] `ConvaiRoomManager.Debug` 复选框 **未勾选** 在生产场景中。
* [ ] `maxRetryAttempts` 在 `ConvaiBootstrapConfigSnapshot` 适合您的目标网络（默认 `3` 适用于大多数消费者连接）。
* [ ] `connectionTimeoutSeconds` 考虑到您部署中最慢的目标设备/网络（默认 `30` 秒）。
* [ ] Custom `ILogSink` 实例会在应用退出时注销并释放。
* [ ] `ConvaiLogger.FlushAllSinks()` 在 `Application.quitting` 处理程序中调用，如果任何 sink 会缓冲输出。

***

### 故障排除

| 症状                             | 可能原因                                                          | 修复方法                                                                              |
| ------------------------------ | ------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| 发布构建中的控制台被 SDK 消息刷屏            | `CONVAI_DEBUG_LOGGING` 定义存在，或者 `GlobalLogLevel` 是 `调试` 或 `跟踪` | 移除脚本定义，并将全局级别设置为 `警告` 或 `Error`.                                                  |
| `OnRtvMetricsReceived` 从未触发    | `ConvaiRoomManager.Debug` 未勾选，或者角色尚未完成完整回合                    | 启用 Debug 复选框，并验证完整的 AI 回合已完成（用户说话，角色回应）。                                          |
| `Ttfb` / `Processing` 字段为 null | 服务器未为本回合包含这些指标类型                                              | 访问前先处理 null。并非每个回合都包含每种指标类型。                                                      |
| 连接在 4 次尝试后失败，不再继续重试            | 默认 `ExponentialBackoffPolicy` 已达到 `MaxAttempts = 4`           | 增加 `maxRetryAttempts` 在 `ConvaiBootstrapConfigSnapshot`，或者实现一个自定义 `IRetryPolicy`. |
| 自定义日志 sink 缺少早期启动消息            | Sink 在以下之后注册： `ConvaiLogger.Initialize()` 已触发                 | 通过以下方式注册： `OnInitializationCompleted` 回调——在初始化完成前切勿注册 sink。                       |

***

### 下一步

{% content-ref url="/pages/aed151614a7019028126be07590213a17602a2f2" %}
[故障排查](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/troubleshooting.md)
{% endcontent-ref %}

{% content-ref url="/pages/007b237f8ee6f8a26b307152ee716a33905cd6da" %}
[运行时模块系统](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/advanced-topics/extending-the-sdk.md)
{% endcontent-ref %}

{% content-ref url="/pages/c94f3576730a705ef8d7adec01d712a12612b542" %}
[实现自定义模块](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/advanced-topics/implement-a-custom-module.md)
{% endcontent-ref %}


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/advanced-topics/performance-and-optimization.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
