libcsp reports diagnostics through two channels:
- Counters — per-error-path
uint8_tglobals that you snapshot on demand. - Print toggles — two
uint8_tswitches that enable per-packet and per-RDP-transition messages routed through libcsp'scsp_print_func.
The Rust debug module wraps both in a typed API. There is no Rust-side
message hook; messages flow through the C-level csp_print_func which by
default writes to stdout.
When you see output like:
Port 15 is already in use
it is coming from the C library itself via csp_print_func, not from
the Rust bindings.
CSP logging originates from several places:
- Counters — incremented on buffer exhaustion, connection overflow, routing failures and similar error paths.
- Per-packet trace — when enabled, one line per packet routed.
- RDP trace — when enabled, lines describing RDP state transitions.
- Architecture layer — OS-specific diagnostics emitted by
mutex/queue/thread primitives you provide via
CspArch. - Interface drivers — SocketCAN, USART, ZMQ, etc.
Add the debug feature in Cargo.toml:
[dependencies]
libcsp = { version = "2.1", features = ["debug"] }With the feature disabled the counter globals and print toggles still exist
in the C sources but the libcsp::debug module is not compiled in. Keep
debug off for flight builds to avoid the print overhead and strip the
module from your binary.
use libcsp::debug::{self, Counters, RdpTrace};
// Snapshot the current error counters.
let c: Counters = debug::counters();
println!(
"buffer_out={} conn_out={} conn_ovf={} conn_noroute={} \
inval_reply={} errno={} can_errno={} eth_errno={}",
c.buffer_out, c.conn_out, c.conn_ovf, c.conn_noroute,
c.inval_reply, c.errno, c.can_errno, c.eth_errno,
);
// Zero all counters (useful between test phases).
debug::reset_counters();
// Enable per-packet tracing. Each routed packet produces one print line.
debug::set_packet_trace(true);
// Tune RDP verbosity.
debug::set_rdp_trace(RdpTrace::Errors); // error paths only
debug::set_rdp_trace(RdpTrace::Protocol); // errors + every state transition
debug::set_rdp_trace(RdpTrace::Off); // silent| Field | Incremented when |
|---|---|
buffer_out |
csp_buffer_get returned NULL (pool exhausted) |
conn_out |
No free connection slot for an outbound connect |
conn_ovf |
A connection's RX queue overflowed |
conn_noroute |
Router could not find a route for the destination |
inval_reply |
A received reply did not match any pending request |
errno |
Generic libcsp error counter (last-resort) |
can_errno |
SocketCAN driver error |
eth_errno |
Ethernet driver error |
Each counter saturates at u8::MAX; reset periodically if you want
finer-grained long-running stats.
| Variant | Meaning |
|---|---|
RdpTrace::Off |
No RDP prints |
RdpTrace::Errors |
Log only RDP error paths (invalid ACKs, timeouts, …) |
RdpTrace::Protocol |
Log errors plus every state-machine transition |
Print output is emitted by libcsp's csp_print_func. The default
implementation writes to stdout via printf(). To redirect log messages to
a file, ring buffer, or over a network, override csp_print_func at the C
level — for example, by providing your own implementation and linking it
before libcsp's default:
// Linked into your binary alongside libcsp.
#include <stdarg.h>
#include <stdio.h>
int csp_print_func(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int n = my_log_vprintf(fmt, ap); // your transport: RTT, UART, syslog…
va_end(ap);
return n;
}This is the only supported way to capture libcsp's own messages — the bindings expose no Rust-side message hook.
# Cargo captures test output by default
cargo test
# Show output for passing tests too
cargo test -- --nocapture
# Single-threaded (required for CSP tests since libcsp uses globals)
cargo test -- --nocapture --test-threads=1cargo test 2>&1 | tee test.logWhen you implement [CspArch] for embedded systems, the trait methods are
the natural place to add low-level tracing of mutex/queue/thread
operations:
unsafe impl CspArch for MyArch {
fn queue_create(&self, length: usize, item_size: usize) -> *mut core::ffi::c_void {
defmt::debug!("queue_create len={} item={}", length, item_size);
// ...
core::ptr::null_mut()
}
// ...
}What it means: A socket tried to bind to a port that is already bound.
When you see it:
- Running tests in parallel
- Reusing a CSP node without proper cleanup
- Port binding before a previous socket was dropped
Example from tests:
test service::tests::test_dispatcher_basic ... ok
Port 15 is already in use
Is this a problem?
- If the test still passes, the library correctly rejected the duplicate binding (expected behaviour).
- If the test fails, there is a resource leak or improper cleanup.
Solution:
// Ensure sockets are dropped before reuse
{
let mut sock = Socket::new(0);
sock.bind(15)?;
// sock is dropped here, releasing port 15
}
// Now port 15 can be bound again
let mut sock2 = Socket::new(0);
sock2.bind(15)?; // Should succeedWith debug::set_rdp_trace(RdpTrace::Protocol) you will see lines tracing
the state machine, e.g.:
RDP: Connection opened
RDP: Sending SYN
RDP: Received ACK
Route: Adding route 10/5 via LOOP
Route: Packet forwarded to interface LOOP
These are useful for debugging route-table population and dispatch.
- Enable the
debugfeature. - Run tests with
--nocapture --test-threads=1for deterministic output. - Snapshot counters before and after a scenario to catch regressions.
- Disable the
debugfeature to drop the print-trace code. - Still consider reading counters from your telemetry task — the globals
are available regardless of the feature flag, but the typed wrapper lives
behind
debug. - Use release builds which strip debug symbols.
- Use unique ports (0-63 range for CSP).
- Ensure proper cleanup with RAII patterns.
- Run tests sequentially (
--test-threads=1) — CSP state is global.
| Issue | Cause | Solution |
|---|---|---|
| No log output | debug feature not enabled |
Add features = ["debug"] |
| Counters stuck at zero | Never triggered the error path, or reset_counters called unexpectedly |
Inspect paths; re-snapshot |
| "Port already in use" errors | Resource leak or parallel tests | Use unique ports, run tests sequentially |
| Garbled output | Multiple threads printing | Use --test-threads=1 or route prints through your own csp_print_func |
src/debug.rs— counter snapshot and trace-toggle APIsrc/arch/test_arch.rs— Test architecture implementationsrc/arch.rs— Architecture trait definitionbuild.rs— Build configuration for debug features
Note: CSP logging is designed for debugging, not production monitoring. For production systems, implement application-level telemetry using Rust logging frameworks (e.g. tracing, defmt) and expose the counter snapshot through it.