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

# Exporting Device Logs

## Overview

The Wuji Hand 2 keeps a log ring buffer in flash. `export_flash_logs()` pulls the whole ring, decodes it frame by frame, and writes a JSONL file with one log entry per line.

When something goes wrong on the device, this is the primary evidence. It records events from the firmware's point of view, which sits closer to the fault than host-side SDK logs.

Matching examples: `examples/python/wuji_hand_2/5.export_flash_logs.py` and `examples/c/wuji_hand_2/5_export_flash_logs.c`.

For diagnostic and logging API definitions, see [Diagnostics and Logs](/en/docs/wuji-hand/latest/sdk-reference/diagnostics). This page covers only how to run it and what the measurements show.

This example only reads. It doesn't enable the motors, and the fingers don't move.

## Prerequisites

### Hardware

| Item | Requirement |
| - | - |
| Wuji Hand 2 | 1 |
| Connection | Ethernet |

### Software

| Item | Requirement |
| - | - |
| Operating system | Ubuntu 22.04 LTS or later |
| Wuji SDK | v2026.8.31 or later |
| Python | 3.10 or later |
| C toolchain | CMake 3.16 or later, plus the extracted SDK package |

## Data Flow

```mermaid theme={null}
flowchart TD
    FLASH["device flash<br/>log ring buffer"]
    PULL["export_flash_logs()<br/>pulls everything, no incremental query"]
    DEC["decode frame by frame"]
    FILE["JSONL file<br/>one entry per line"]
    RET["returns path and frames"]

    FLASH -->|"full contents"| PULL
    PULL --> DEC
    DEC --> FILE
    FILE --> RET
```

**Every call exports everything and writes a new file.** There's no API to ask how many entries exist first, and no incremental export.

### Where Output Goes

`out_dir` defaults to `None`, writing to the SDK's default directory `~/.wuji/logs` alongside the SDK's own logs, with a `flash_` prefix on the filename:

```text theme={null}
~/.wuji/logs/flash_<SN>_<date>_<time>.jsonl
```

Pass `out_dir` to write elsewhere.

### Fields per Line

| Field | Meaning |
| - | - |
| `timestamp_ms` | Device timestamp in milliseconds |
| `level` | Log level, such as `Info` or `Warn` |
| `target` | The firmware module that produced the entry |
| `message` | The log text |

## Runtime Handling

| Branch point | Behavior | What to do |
| - | - | - |
| Empty ring buffer | `frames` is 0 and a file is still written | The example uses that to skip reading the first line |
| Target directory doesn't exist | Handled on the SDK side | Passing an existing directory is safer |
| Several devices | The example uses the first it finds | On a shared network, name a serial number |
| Export duration | Scales with entry count, measured at 2.9 seconds for 1180 entries | Don't call it inside a real-time loop |

## Integration Steps

<Tabs>
  <Tab title="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. Connect. With several devices around, name an sn
        hand = manager.connect(sn=hands[0].sn, device_name="wuji_hand_2")

        # 2. Full export. out_dir=None writes to ~/.wuji/logs
        result = hand.export_flash_logs(out_dir=None)
        print(f"Decoded {result['frames']} frames into {result['path']}")

        # 3. Read the first line to confirm the contents
        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>

  <Tab title="C">
    ```c theme={null}
    #include "wuji_sdk.h"

    /* 1. Export straight after connecting */
    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. Read the JSONL file as needed */
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    ```
  </Tab>
</Tabs>

The return value is a dict with exactly two keys, `path` and `frames`.

## Running It

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

### Measurements

On a right-hand Wuji Hand 2:

```text theme={null}
Connected: WH2KA00XXXXXXXXX
Decoded 1180 frames into ~/.wuji/logs/flash_WH2KA00XXXXXXXXX_<date>_<time>.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
```

| Metric | Measured |
| - | - |
| Entries exported | 1180 |
| Duration | 2.89 seconds |
| Lines in the file | 1180, matching `frames` |
| Returned fields | `path`, `frames` |

What the export contained:

| Level | Count |
| - | - |
| `Info` | 1169 |
| `Warn` | 11 |

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

Most of it is periodic status lines from `sboard_app` shaped like `[B2] T:25 R:20 g:0 bc:0/0 ...`, plus node heartbeats such as `nodes 4/5: [1:hb=0 2:hb=0 ...]`. When investigating, filter by `level` for `Warn` and above first, then narrow by `target` to a module.

## Adapting It

### Filter for What You Care About

Each JSONL line stands alone, so `jq` filters directly:

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

### Export to a Specific Directory

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

Pass the `out_dir` argument. The default lands in `~/.wuji/logs` mixed in with the SDK's own logs, distinguished by the `flash_` prefix.

### How This Relates to Host-Side SDK Logs

The two have different vantage points. Flash logs are the firmware's view, recording the bus, node heartbeats, and server state. SDK logs sit in the same directory and record host-side connect, subscribe, and publish activity. For connection problems, compare timestamps across both.

### Don't Call It in a Real-Time Loop

A full export pulls the entire ring buffer, measured at about 3 seconds for a thousand-odd entries. This is an investigation tool, not a monitoring mechanism. For continuous monitoring, subscribe to `joint_diagnostics`.

## Related

* [Subscribing With Callbacks](/en/docs/wuji-hand/latest/sdk-practice/subscribe-callback): how to get live diagnostic data
* [Troubleshooting](/en/docs/wuji-sdk/troubleshooting/): common problems and how to locate them in logs


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