feat: add -v/-vv verbosity flags and trace-level hex dumps

Add clap CLI argument parsing with three log-level controls:
  -q/--quiet  errors only
  (default)   info — connections and value changes
  -v          debug — every packet's cmd/src/flags logged on TX and RX
  -vv         trace — full 32-byte hex dump of every packet on the wire

RUST_LOG env var continues to work as a fine-grained override for any
of the above levels.

Also expand the TX log line to include src and flags fields to match
the RX line, making the two symmetric.
This commit is contained in:
2026-04-10 14:56:12 -04:00
parent e83bc02563
commit 635a223fd5
3 changed files with 73 additions and 11 deletions
+6 -5
View File
@@ -10,8 +10,9 @@ name = "qmk-host"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
hidapi = "2" # Cross-platform Raw HID access hidapi = "2" # Cross-platform Raw HID access
anyhow = "1" # Ergonomic error handling anyhow = "1" # Ergonomic error handling
log = "0.4" # Logging facade log = "0.4" # Logging facade
env_logger = "0.11" # Logger implementation (RUST_LOG env var) env_logger = "0.11" # Logger implementation (RUST_LOG env var)
ctrlc = "3" # Ctrl+C / SIGINT handler ctrlc = "3" # Ctrl+C / SIGINT handler
clap = { version = "4", features = ["derive"] } # CLI argument parsing
+9 -3
View File
@@ -15,7 +15,7 @@ use std::time::Duration;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use hidapi::HidApi; use hidapi::HidApi;
use log::{debug, error, info, warn}; use log::{debug, error, info, trace, warn};
use crate::protocol::{Packet, PACKET_SIZE, USAGE_ID, USAGE_PAGE}; use crate::protocol::{Packet, PACKET_SIZE, USAGE_ID, USAGE_PAGE};
@@ -122,10 +122,14 @@ fn io_loop(
// --- Drain the outgoing write queue (non-blocking) ----------------- // --- Drain the outgoing write queue (non-blocking) -----------------
while let Ok(pkt) = rx.try_recv() { while let Ok(pkt) = rx.try_recv() {
write_buf[1..].copy_from_slice(&pkt.0); write_buf[1..].copy_from_slice(&pkt.0);
trace!("[kbd {:02x}] TX {:02x?}", device_id, &write_buf[1..]);
if let Err(e) = device.write(&write_buf) { if let Err(e) = device.write(&write_buf) {
error!("[kbd {:02x}] write error: {}", device_id, e); error!("[kbd {:02x}] write error: {}", device_id, e);
} else { } else {
debug!("[kbd {:02x}] sent cmd=0x{:02x}", device_id, pkt.cmd()); debug!(
"[kbd {:02x}] TX cmd=0x{:02x} src=0x{:02x} flags=0x{:02x}",
device_id, pkt.cmd(), pkt.src(), pkt.flags()
);
} }
} }
@@ -136,11 +140,13 @@ fn io_loop(
} }
Ok(_n) => { Ok(_n) => {
let pkt = Packet(read_buf); let pkt = Packet(read_buf);
trace!("[kbd {:02x}] RX {:02x?}", device_id, &read_buf[..]);
if pkt.is_custom() { if pkt.is_custom() {
debug!( debug!(
"[kbd {:02x}] received cmd=0x{:02x} flags=0x{:02x}", "[kbd {:02x}] RX cmd=0x{:02x} src=0x{:02x} flags=0x{:02x}",
device_id, device_id,
pkt.cmd(), pkt.cmd(),
pkt.src(),
pkt.flags() pkt.flags()
); );
if to_broker.send((device_id, pkt)).is_err() { if to_broker.send((device_id, pkt)).is_err() {
+58 -3
View File
@@ -16,7 +16,11 @@
//! # Usage //! # Usage
//! //!
//! ``` //! ```
//! RUST_LOG=debug qmk-host //! qmk-host # info-level output (connections, value changes)
//! qmk-host -v # debug-level: log every packet cmd/src/flags
//! qmk-host -vv # trace-level: full 32-byte hex dump of every packet
//! qmk-host -q # quiet: errors only
//! RUST_LOG=debug qmk-host # env var still works as an override
//! ``` //! ```
//! //!
//! # Permissions //! # Permissions
@@ -36,6 +40,7 @@ use std::sync::mpsc;
use std::thread; use std::thread;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clap::Parser;
use log::{error, info, warn}; use log::{error, info, warn};
mod app_monitor; mod app_monitor;
@@ -47,9 +52,59 @@ mod volume;
use protocol::{DEV_HOST, Packet}; use protocol::{DEV_HOST, Packet};
// ---------------------------------------------------------------------------
// CLI
// ---------------------------------------------------------------------------
/// Raw HID bridge for QMK keyboards.
///
/// Syncs layer state between keyboards and delivers host data (volume,
/// brightness) via the QMK raw-HID interface.
#[derive(Parser)]
#[command(name = "qmk-host", version)]
struct Args {
/// Increase log verbosity.
///
/// Pass once (-v) for debug output (every packet's command, source and
/// flags). Pass twice (-vv) for trace output (full 32-byte hex dump of
/// every packet sent and received). The RUST_LOG environment variable
/// still overrides this flag when set.
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
/// Suppress all output except errors.
#[arg(short, long)]
quiet: bool,
}
impl Args {
/// Resolve the effective log-level filter string from the CLI flags.
/// The caller passes this to `env_logger` as the default; `RUST_LOG`
/// will still override it if set.
fn log_level(&self) -> &'static str {
if self.quiet {
"error"
} else {
match self.verbose {
0 => "info",
1 => "debug",
_ => "trace",
}
}
}
}
// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------
fn main() -> Result<()> { fn main() -> Result<()> {
// Initialise logging. Set RUST_LOG=debug for verbose output. let args = Args::parse();
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or(args.log_level()),
)
.init();
info!("qmk-host starting"); info!("qmk-host starting");