> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# 导出设备日志

## 概述

本篇实践介绍如何导出 Wuji Hand 2 的设备日志并保存为 JSONL 文件。应用场景包括收集故障现场信息、分析固件事件和提供问题排查材料。

对应的 Python 示例为 [5.export\_flash\_logs.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_hand_2/5.export_flash_logs.py)，C 示例为 [5\_export\_flash\_logs.c](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/c/wuji_hand_2/5_export_flash_logs.c)。 诊断与日志的接口定义见[诊断与日志](/docs/wuji-hand/latest/sdk-reference/diagnostics)，本页说明集成流程和运行时行为。

## 前置条件

### 硬件

| 项目 | 要求 |
| - | - |
| Wuji Hand 2 | 1 只 |
| 连接方式 | 以太网 |

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| Wuji SDK | v2026.8.31 或更高版本 |
| Python | 3.10 及以上版本 |
| C 工具链 | CMake 3.16 及以上版本，以及解压后的 SDK 包 |

## 数据流

```mermaid theme={null}
flowchart TD
    SCAN["扫描并筛选 Wuji Hand 2<br/>Python: scan() / C: wuji_scan()"]
    CONNECT["按序列号连接<br/>Python: connect() / C: wuji_connect()"]
    EXPORT["全量导出闪存日志<br/>Python: export_flash_logs()<br/>C: wuji_hand_2_export_flash_logs()"]
    WRITE["解码并写入新的 JSONL 文件<br/>返回 path 与 frames"]
    CHECK{"frames > 0？"}
    READ["读取 JSONL 首行并解析"]
    CLEAN["释放导出结果并断开设备"]

    SCAN --> CONNECT --> EXPORT --> WRITE --> CHECK
    CHECK -->|是| READ --> CLEAN
    CHECK -->|否| CLEAN
```

**每次调用都是全量导出并写新文件。** 没有「先查有多少条」这样的接口，也没有增量导出。

### 输出位置

`out_dir` 默认为 `None`，写入 SDK 的默认目录 `~/.wuji/logs`，也就是 SDK 自身日志所在的位置，文件名带 `flash_` 前缀：

```text theme={null}
~/.wuji/logs/flash_<SN>_<日期>_<时刻>.jsonl
```

传 `out_dir` 可以改到别处。

### 每行的字段

| 字段 | 含义 |
| - | - |
| `timestamp_ms` | 设备时间戳，毫秒 |
| `level` | 日志级别，如 `Info`、`Warn` |
| `target` | 产生日志的固件模块 |
| `message` | 日志正文 |

## 集成步骤

### 1. 发现并连接目标设备

Python 通过 `scan()` 筛选 `DeviceType.WujiHand2`，确认结果非空后调用 `connect(sn=...)`。C 先初始化 SDK，使用 `wuji_scan()` 筛选 `WUJI_DEVICE_TYPE_WUJI_HAND_2`，再以序列号调用 `wuji_connect()`。导出不需要使能电机。

```python theme={null}
manager = SdkManager.instance()
hands = [d for d in manager.scan() if d.device_type == DeviceType.WujiHand2]
if not hands:
    raise RuntimeError("No Wuji Hand 2 found")
hand = manager.connect(sn=hands[0].sn, device_name="wuji_hand_2")
```

### 2. 导出日志并取得结果

一次调用完成全量导出，同时返回文件路径和帧数。Python 传入 `None`、C 传入 `NULL` 时使用 SDK 默认目录。

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    result = hand.export_flash_logs(out_dir=None)
    print(result["path"], result["frames"])
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    WujiFlashLogExport result;
    WujiStatus st = wuji_hand_2_export_flash_logs(dev, NULL, &result);
    if (st == WUJI_STATUS_OK) {
        printf("%s: %u frames\n", result.path, result.frame_count);
        wuji_hand_2_flash_log_export_free(&result);
    }
    ```
  </Tab>
</Tabs>

C 仅在导出成功后读取和释放 `result`。失败时输出对象未被填写，应读取错误并进入设备清理路径。

### 3. 检查文件并断开连接

Python 示例在帧数非零时读取 JSONL 首行，确认内容可解析。C 示例输出文件路径和帧数，应用可按该路径继续读取文件。

```python theme={null}
if result["frames"]:
    with open(result["path"], encoding="utf-8") as stream:
        first = json.loads(stream.readline())
    print(first["level"], first["target"], first["message"])
```

Python 在 `finally` 中调用 `manager.disconnect_all()`。C 依次调用 `wuji_dev_disconnect(dev)`、`wuji_dev_release(dev)` 和 `wuji_shutdown()`。

## 运行与输出

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_hand_2/5.export_flash_logs.py
    python examples/python/wuji_hand_2/5.export_flash_logs.py --out /tmp/hand2-logs
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/hand2/5_export_flash_logs
    ```
  </Tab>
</Tabs>

### 实测数据

在右手 Wuji Hand 2 上实跑：

```text theme={null}
Connected: WH2KA00XXXXXXXXX
Decoded 1180 frames into ~/.wuji/logs/flash_WH2KA00XXXXXXXXX_2026-09-17_212326.jsonl
First entry: [Info] sboard_app: [B2] T:25 R:20 g:0 bc:0/0 rel:0/0 mgmt:25/20 to:5 crc:0% oe:0 ne:0 fe:5 dma:0/0
```

| 指标 | 实测 |
| - | - |
| 导出条数 | 1180 |
| 耗时 | 2.89 秒 |
| 文件行数 | 1180，与 `frames` 一致 |
| 返回字段 | `path`、`frames` |

导出内容的构成：

| 级别 | 条数 |
| - | - |
| `Info` | 1169 |
| `Warn` | 11 |

| `target` | 条数 |
| - | - |
| `sboard_app` | 1117 |
| `sboard_app::bus_manager` | 40 |
| `sboard_app::wuji_server` | 12 |
| `sboard_app::zenoh_server` | 6 |
| `sboard_app::wall_clock_persist` | 2 |

绝大多数是 `sboard_app` 的周期性状态行，形如 `[B2] T:25 R:20 g:0 bc:0/0 ...`，以及节点心跳 `nodes 4/5: [1:hb=0 2:hb=0 ...]`。排查问题时先按 `level` 过滤出 `Warn` 及以上，再按 `target` 定位模块。

## 注意事项

| 分支点 | 表现 | 处理 |
| - | - | - |
| 环形缓冲区为空 | `frames` 为 0，仍会写出文件 | 示例据此跳过读取首行 |
| 指定目录不存在 | 由 SDK 侧处理 | 传已存在的目录更稳妥 |
| 多台设备 | 示例用扫到的第一台 | 共用网络要改成指定 SN |
| 导出耗时 | 与日志条数相关，实测 1180 条约 2.9 秒 | 不要在实时循环里调用 |

## 扩展用法

### 过滤关注的内容

JSONL 每行独立，用 `jq` 直接筛：

```bash theme={null}
jq -c 'select(.level != "Info")' flash_*.jsonl
jq -c 'select(.target | startswith("sboard_app::bus_manager"))' flash_*.jsonl
```

### 导出到指定目录

```bash theme={null}
python examples/python/wuji_hand_2/5.export_flash_logs.py --out /tmp/hand2-logs
```

传 `out_dir` 参数即可。默认写进 `~/.wuji/logs`，会和 SDK 自身日志混在一起，靠 `flash_` 前缀区分。

### 与 SDK 主机日志的关系

两者视角不同。闪存日志是固件视角，记录总线、节点心跳、服务端状态。SDK 日志在同一目录下，记录主机侧的连接、订阅、发布行为。排查连接类问题时两边对照时间戳看。

### 不要在实时循环里调用

全量导出会拉取整个环形缓冲区，实测千余条约 3 秒。这是排查工具，不是监控手段。持续监控请订阅 `joint_diagnostics`。

## 相关内容

* [数据订阅：回调](/docs/wuji-hand/latest/sdk-practice/subscribe-callback)：实时诊断数据的获取方式
* [故障排查](/docs/wuji-sdk/troubleshooting/)：常见问题与日志定位


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.