Add in-memory logging and traffic capture

- Add server-wide log buffer with configurable level (DEBUG/INFO/WARNING/ERROR)
- Add per-port traffic capture for TX/RX data
- Auto-enable traffic logging for spy:// URLs
- Add configure_logging and enable_traffic_log tools
- Add serial://log and serial://{port}/log resources
- Update docs for new logging features
This commit is contained in:
Ryan Malloy 2026-02-04 13:39:16 -07:00
parent c651dc4c5e
commit cf064d46a2
4 changed files with 478 additions and 19 deletions

View file

@ -130,6 +130,74 @@ Like the data resource, reading raw data consumes the bytes from the serial buff
---
## `serial://log`
Read the server-wide log buffer. Returns recent log entries as formatted text, useful for debugging server behavior and diagnosing issues.
**URI:** `serial://log`
**Example output:**
```
# mcserial Server Log (47 entries)
[2024-02-03T18:45:12] INFO server: Opened /dev/ttyUSB0 at 115200 baud
[2024-02-03T18:45:13] DEBUG /dev/ttyUSB0: TX 8 bytes
[2024-02-03T18:45:14] WARNING zmodem: Sanitized filename: "../etc/passwd" -> "etc_passwd"
[2024-02-03T18:45:15] ERROR server: Failed to open /dev/ttyUSB1: Permission denied
```
Log entries include:
- **Timestamp** in ISO-8601 format
- **Level**: DEBUG, INFO, WARNING, or ERROR
- **Source**: "server" for general events, or port name for port-specific events
- **Message**: Human-readable description of the event
Use `configure_logging()` to adjust the minimum log level and buffer size. The default level is INFO, which captures port open/close events and errors.
<Aside type="tip">
Setting the log level to DEBUG captures TX/RX byte counts for all read/write operations. This is useful for debugging communication issues without enabling full traffic logging.
</Aside>
---
## `serial://{port}/log`
Read the per-port traffic log (if enabled). Returns a history of TX/RX data in hex + ASCII format.
**URI pattern:** `serial://{port}/log`
**Example URIs:**
```
serial:///dev/ttyUSB0/log
serial://spy:///dev/ttyUSB0/log
```
**Example output:**
```
# Traffic Log: /dev/ttyUSB0 (23 entries)
[18:45:12.123] TX (8 bytes): 01 03 00 00 00 01 84 0a |........|
[18:45:12.189] RX (7 bytes): 01 03 02 00 64 b8 44 |....d.D|
[18:45:13.001] TX (8 bytes): 01 03 00 01 00 01 d5 ca |........|
```
Each entry shows:
- **Timestamp** with millisecond precision
- **Direction**: TX (transmitted) or RX (received)
- **Byte count**
- **Hex dump**: First 16 bytes in hexadecimal
- **ASCII preview**: Printable characters (non-printable shown as `.`)
<Aside type="note">
Traffic logging is not enabled by default. Enable it with `enable_traffic_log(port)` or by opening the port with `spy://` scheme.
</Aside>
<Aside type="caution">
Unlike the data resource, reading the traffic log does **not** consume any data. It's a read-only view of the capture buffer. The actual serial data remains in the serial buffer until read via tools or the data resource.
</Aside>
---
## URI Encoding
The `{port}` segment in resource URIs uses the device path directly. For paths containing special characters (like forward slashes in Linux device paths), the MCP client handles URL encoding:

View file

@ -7,7 +7,7 @@ sidebar:
import { Aside } from '@astrojs/starlight/components';
These 18 tools work in both RS-232 and RS-485 modes. They cover port discovery, connection management, reading and writing data, configuration, flow control, and diagnostics.
These 20 tools work in both RS-232 and RS-485 modes. They cover port discovery, connection management, reading and writing data, configuration, flow control, diagnostics, and logging.
<Aside type="note">
Examples use tool-call notation: `tool_name(param=value)`. These are MCP tool calls made by the assistant, not code you write directly. Boolean values use JSON convention (`true`/`false`).
@ -457,3 +457,75 @@ detect_baud_rate(port="/dev/ttyUSB0", baudrates=[9600, 115200, 57600])
# Longer wait time for slow devices
detect_baud_rate(port="/dev/ttyUSB0", timeout_per_rate=1.0)
```
---
## Logging
### configure_logging
Configure server-wide logging behavior. Controls the minimum log level and buffer size for the in-memory log accessible via `serial://log`.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `level` | `str` | `"INFO"` | Minimum log level: `DEBUG`, `INFO`, `WARNING`, or `ERROR`. |
| `max_entries` | `int` | `1000` | Maximum entries in the server log buffer (10-100000). |
| `clear` | `bool` | `False` | Clear existing log entries before applying new settings. |
**Returns:** Dict with `success`, `level`, `previous_level`, `max_entries`, `current_entries`, `cleared`, and `resource_uri`.
Log levels filter what gets captured:
- **DEBUG**: All events including TX/RX byte counts
- **INFO**: Port open/close, configuration changes (default)
- **WARNING**: Potential issues like sanitized filenames
- **ERROR**: Failed operations only
```
# Set to DEBUG for detailed operation logging
configure_logging(level="DEBUG")
# Reduce buffer size to save memory
configure_logging(max_entries=500)
# Clear logs and set to ERROR only
configure_logging(level="ERROR", clear=true)
```
<Aside type="tip">
Set level to `DEBUG` when troubleshooting communication issues. This captures TX/RX byte counts for all read/write operations without the overhead of full traffic logging.
</Aside>
### enable_traffic_log
Enable or disable per-port traffic capture. When enabled, all TX/RX data on the port is captured to an in-memory buffer accessible via `serial://{port}/log`.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `port` | `str` | required | Device path of the port to configure. |
| `enabled` | `bool` | `True` | Enable or disable traffic logging. |
| `max_entries` | `int` | `500` | Maximum traffic entries to keep (10-10000). |
| `clear` | `bool` | `False` | Clear existing traffic log entries. |
**Returns:** Dict with `success`, `port`, `traffic_log_enabled`, `max_entries`, `current_entries`, and `resource_uri`.
```
# Enable traffic logging on a port
enable_traffic_log(port="/dev/ttyUSB0")
# Enable with larger buffer for long sessions
enable_traffic_log(port="/dev/ttyUSB0", max_entries=2000)
# Clear and restart traffic capture
enable_traffic_log(port="/dev/ttyUSB0", clear=true)
# Disable traffic logging
enable_traffic_log(port="/dev/ttyUSB0", enabled=false)
```
<Aside type="note">
Traffic logging is automatically enabled when opening a port with the `spy://` URL scheme. The buffer is set to 1000 entries for spy connections.
</Aside>
<Aside type="caution">
Traffic capture stores raw bytes in memory. For high-throughput connections, use a reasonable `max_entries` limit to avoid excessive memory usage.
</Aside>

View file

@ -105,21 +105,33 @@ close_serial_port(port="loop://")
## spy://
Debug wrapper that logs all serial traffic to stderr while passing data through to the underlying port. Wraps a real device path.
Debug wrapper that captures all serial traffic to an in-memory buffer while passing data through to the underlying port. Wraps a real device path.
**Format:** `spy://device_path`
All bytes read and written are printed to stderr in a human-readable format. The underlying serial connection works exactly as if you opened the device directly.
When you open a port with `spy://`, traffic logging is automatically enabled with a 1000-entry buffer. All bytes read and written are captured and can be retrieved later via the `serial://{port}/log` resource.
**When to use:** Debugging communication issues, reverse-engineering protocols, or logging traffic for analysis.
**When to use:** Debugging communication issues, reverse-engineering protocols, multi-tasking (send commands, do other work, read traffic log later), or logging traffic for analysis.
```
# Wraps /dev/ttyUSB0 with traffic logging
# Wraps /dev/ttyUSB0 with automatic traffic capture
open_serial_port(port="spy:///dev/ttyUSB0", baudrate=9600)
# Send a command
write_serial(port="spy:///dev/ttyUSB0", data="AT\r\n")
# ... do other work while device responds ...
# Read captured traffic via resource
# serial://spy:///dev/ttyUSB0/log
```
<Aside type="tip">
The spy:// scheme is especially useful for MCP clients. Unlike stderr logging, the captured traffic is accessible via MCP resources, letting the model send commands, perform other tasks, and then review the traffic log later.
</Aside>
<Aside type="note">
The spy output goes to stderr of the mcserial server process. Check your server's stderr output to see the logged traffic.
Traffic capture stores raw bytes in memory. The default buffer of 1000 entries is suitable for most debugging sessions. For longer sessions or high-throughput connections, consider using `enable_traffic_log()` to set a custom buffer size.
</Aside>
---