jq expression
This commit is contained in:
219
Cargo.lock
generated
219
Cargo.lock
generated
@@ -2,6 +2,34 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -67,6 +95,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
@@ -129,6 +163,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chumsky"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.55"
|
||||
@@ -237,6 +280,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
@@ -360,12 +415,34 @@ dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hifijson"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a7763b98ba8a24f59e698bf9ab197e7676c640d6455d1580b4ce7dc560f0f0d"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
@@ -508,6 +585,16 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
@@ -520,6 +607,66 @@ version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "jaq-core"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6fda09ee08c84c81293fdf811d9ebaa87b327557b5391f290c926d728c2ddd4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"base64",
|
||||
"chrono",
|
||||
"hifijson",
|
||||
"jaq-interpret",
|
||||
"libm",
|
||||
"log",
|
||||
"regex",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaq-interpret"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fe95ec3c24af3fd9f3dd1091593f5e49b003a66c496a8aa39d764d0a06ae17b"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"dyn-clone",
|
||||
"hifijson",
|
||||
"indexmap",
|
||||
"jaq-syn",
|
||||
"once_cell",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaq-parse"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0346d7d3146cdda8acd929581f3d6626a332356c74d5c95aeaffaac2eb6dee82"
|
||||
dependencies = [
|
||||
"chumsky",
|
||||
"jaq-syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaq-std"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfbaa55578fd3b70433b594a370741e0c364e4afff92cc0099623fce87311bc1"
|
||||
dependencies = [
|
||||
"jaq-syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaq-syn"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba44fe4428c71304604261ecbae047ee9cfb60c4f1a6bd222ebbb31726d3948"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.85"
|
||||
@@ -542,6 +689,12 @@ version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
@@ -569,6 +722,12 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.1.1"
|
||||
@@ -799,6 +958,35 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
@@ -863,6 +1051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -885,6 +1074,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@@ -1182,6 +1384,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@@ -1285,6 +1493,11 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"futures-util",
|
||||
"jaq-core",
|
||||
"jaq-interpret",
|
||||
"jaq-parse",
|
||||
"jaq-std",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
@@ -1542,3 +1755,9 @@ dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
||||
|
||||
@@ -12,3 +12,8 @@ clap = { version = "4", features = ["derive"] }
|
||||
futures-util = "0.3"
|
||||
chrono = "0.4"
|
||||
url = "2"
|
||||
jaq-core = "1"
|
||||
jaq-interpret = "1"
|
||||
jaq-parse = "1"
|
||||
jaq-std = "1"
|
||||
serde_json = "1"
|
||||
|
||||
91
src/main.rs
91
src/main.rs
@@ -1,10 +1,11 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Local;
|
||||
use clap::Parser;
|
||||
use url::{form_urlencoded, Url};
|
||||
use futures_util::StreamExt;
|
||||
use jaq_interpret::{Ctx, Filter, FilterT, ParseCtx, RcIter, Val};
|
||||
use tokio::task::JoinSet;
|
||||
use tokio_tungstenite::{
|
||||
connect_async_with_config,
|
||||
@@ -15,6 +16,7 @@ use tokio_tungstenite::{
|
||||
},
|
||||
};
|
||||
use tracing::{debug, error, info, warn, Level};
|
||||
use url::{form_urlencoded, Url};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "websocket-debug")]
|
||||
@@ -35,6 +37,10 @@ struct Args {
|
||||
/// Query string parameters to add to all URLs (pre-encoded, e.g., "name=First%20Last&key=value")
|
||||
#[arg(short = 'q', long = "query-string-all")]
|
||||
query_string_all: Option<String>,
|
||||
|
||||
/// jq expression to evaluate on JSON text messages for logging (e.g., ".message" or ".data.id")
|
||||
#[arg(short = 'j', long = "jaq")]
|
||||
jaq: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -58,6 +64,37 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fs::create_dir_all(&session_dir)?;
|
||||
info!("Created session directory: {}", session_dir.display());
|
||||
|
||||
// Compile jaq filter if provided
|
||||
let jaq_filter: Option<Arc<Filter>> = if let Some(ref expr) = args.jaq {
|
||||
let mut defs = ParseCtx::new(Vec::new());
|
||||
defs.insert_natives(jaq_core::core());
|
||||
defs.insert_defs(jaq_std::std());
|
||||
|
||||
let (parsed, errs) = jaq_parse::parse(expr, jaq_parse::main());
|
||||
if !errs.is_empty() {
|
||||
let err_msgs: Vec<String> = errs.iter().map(|e| format!("{:?}", e)).collect();
|
||||
return Err(format!("Failed to parse jaq expression: {}", err_msgs.join(", ")).into());
|
||||
}
|
||||
|
||||
let parsed = parsed.ok_or("Failed to parse jaq expression")?;
|
||||
let filter = defs.compile(parsed);
|
||||
|
||||
if !defs.errs.is_empty() {
|
||||
return Err(
|
||||
format!(
|
||||
"Failed to compile jaq expression ({} error(s))",
|
||||
defs.errs.len()
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
info!("Using jaq filter: {}", expr);
|
||||
Some(Arc::new(filter))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Parse extra query params once if specified
|
||||
let extra_params: Vec<(String, String)> = args
|
||||
.query_string_all
|
||||
@@ -216,6 +253,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
for (letter, url, ws_stream) in connections {
|
||||
let session_dir = session_dir.clone();
|
||||
let jaq_filter = jaq_filter.clone();
|
||||
|
||||
join_set.spawn(async move {
|
||||
let (_, mut read) = ws_stream.split();
|
||||
@@ -226,10 +264,55 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(message) => {
|
||||
match message {
|
||||
Message::Text(text) => {
|
||||
let preview: String = text.chars().take(50).collect();
|
||||
let truncated = if text.len() > 50 { "..." } else { "" };
|
||||
info!("[{}:{}] Text: {}{}", letter, seq_num, preview, truncated);
|
||||
// Determine what to log based on jaq filter
|
||||
let log_content = if let Some(ref filter) = jaq_filter {
|
||||
match serde_json::from_str::<serde_json::Value>(&text) {
|
||||
Ok(json_val) => {
|
||||
let inputs = RcIter::new(core::iter::empty());
|
||||
let ctx = Ctx::new([], &inputs);
|
||||
let out =
|
||||
filter.run((ctx, Val::from(json_val)));
|
||||
let results: Vec<Result<Val, _>> = out.collect();
|
||||
|
||||
let mut output_parts = Vec::new();
|
||||
for result in results {
|
||||
match result {
|
||||
Ok(val) => {
|
||||
output_parts.push(val.to_string());
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"[{}:{}] jaq error: {}",
|
||||
letter, seq_num, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if output_parts.is_empty() {
|
||||
"(no output)".to_string()
|
||||
} else {
|
||||
output_parts.join(", ")
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"[{}:{}] JSON parse error: {}",
|
||||
letter, seq_num, e
|
||||
);
|
||||
let preview: String = text.chars().take(50).collect();
|
||||
let truncated = if text.len() > 50 { "..." } else { "" };
|
||||
format!("{}{}", preview, truncated)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let preview: String = text.chars().take(50).collect();
|
||||
let truncated = if text.len() > 50 { "..." } else { "" };
|
||||
format!("{}{}", preview, truncated)
|
||||
};
|
||||
|
||||
info!("[{}:{}] Text: {}", letter, seq_num, log_content);
|
||||
|
||||
// Always write full message to file
|
||||
let filename =
|
||||
session_dir.join(format!("{}{}.txt", letter, seq_num));
|
||||
if let Err(e) = fs::write(&filename, &text) {
|
||||
|
||||
Reference in New Issue
Block a user