> ## 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 在闪存里维护一个日志环形缓冲区。`export_flash_logs()` 把整个环拉下来、逐帧解码，写成 JSONL 文件，每行一条日志。

设备侧出问题时这是第一手材料。它记录的是固件视角的事件，比主机侧 SDK 日志更接近故障现场。

对应的 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)。

诊断与日志的接口定义见[诊断与日志](https://docs.wuji.tech/docs/zh/wuji-hand/beta1/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
    FLASH["设备闪存<br/>日志环形缓冲区"]
    PULL["export_flash_logs()<br/>整体拉取，无增量查询"]
    DEC["逐帧解码"]
    FILE["JSONL 文件<br/>每行一条日志"]
    RET["返回 path 与 frames"]

    FLASH -->|"全量"| PULL
    PULL --> DEC
    DEC --> FILE
    FILE --> RET
```

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

### 输出位置

`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` | 日志正文 |

## 运行时分支

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

## 调用顺序

> [tab] Python
>
> ```python theme={null}
> import json
> from wuji_sdk import DeviceType, SdkManager
>
> manager = SdkManager.instance()
> hands = [d for d in manager.scan() if d.device_type == DeviceType.WujiHand2]
>
> try:
>     # 1. 连接。多设备环境指定 sn
>     hand = manager.connect(sn=hands[0].sn, device_name="wuji_hand_2")
>
>     # 2. 全量导出。out_dir=None 写入 ~/.wuji/logs
>     result = hand.export_flash_logs(out_dir=None)
>     print(f"Decoded {result['frames']} frames into {result['path']}")
>
>     # 3. 读首行确认内容
>     if result["frames"]:
>         with open(result["path"], "r", encoding="utf-8") as f:
>             first = json.loads(f.readline())
>         print(f"First entry: [{first['level']}] {first['target']}: {first['message']}")
> finally:
>     manager.disconnect_all()
> ```

> [tab] C
>
> ```c theme={null}
> #include "wuji_sdk.h"
>
> /* 1. 连接后直接导出 */
> char path[512] = {0};
> uint32_t frames = 0;
> WujiStatus st = wuji_hand_2_export_flash_logs(dev, NULL, path, sizeof(path), &frames);
> if (st == WUJI_STATUS_OK) {
>     printf("Decoded %u frames into %s\n", frames, path);
> } else {
>     fprintf(stderr, "export failed: %s\n", wuji_last_error());
> }
>
> /* 2. 按需读取 JSONL 文件 */
> wuji_dev_disconnect(dev);
> wuji_dev_release(dev);
> ```

返回值是一个字典，只有 `path` 与 `frames` 两个键。

## 运行与输出

> [tab] 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] C
>
> ```bash theme={null}
> ./build/hand2/5_export_flash_logs
> ```

### 实测数据

在右手 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` 定位模块。

## 调整与适配

### 过滤关注的内容

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

## 相关内容

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


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