From e092164e701c25ed91301c8018a6c9f0c9e69edf Mon Sep 17 00:00:00 2001 From: jedarden Date: Thu, 23 Apr 2026 21:32:04 -0400 Subject: [PATCH] =?UTF-8?q?P7.5.b:=20flatten=20JSON=20event=20fields=20for?= =?UTF-8?q?=20=C2=A710=20schema=20compliance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `.flatten_event(true)` to tracing-subscriber JSON layers so event fields (message, index, duration_ms, node_count, estimated_hits, degraded) appear at the top level of each JSON log line, matching the flat schema specified in plan ยง10. Also add a proper unit test for SearchRequestBody Debug redaction (previously a placeholder) confirming that query strings and filter values are replaced with "[redacted]". Co-Authored-By: Claude Opus 4.7 --- crates/miroir-proxy/src/main.rs | 2 ++ crates/miroir-proxy/src/routes/search.rs | 32 +++++++++++++++++++ .../tests/p7_5_structured_logging.rs | 12 ++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/crates/miroir-proxy/src/main.rs b/crates/miroir-proxy/src/main.rs index 39a5c93..8b4b0f8 100644 --- a/crates/miroir-proxy/src/main.rs +++ b/crates/miroir-proxy/src/main.rs @@ -163,6 +163,7 @@ async fn main() -> anyhow::Result<()> { if let Some(otel_layer) = otel::init_otel_layer(&config) { let json_layer = tracing_subscriber::fmt::layer() .json() + .flatten_event(true) .with_target(true) .with_current_span(true) .with_span_list(false); @@ -175,6 +176,7 @@ async fn main() -> anyhow::Result<()> { } else { let json_layer = tracing_subscriber::fmt::layer() .json() + .flatten_event(true) .with_target(true) .with_current_span(true) .with_span_list(false); diff --git a/crates/miroir-proxy/src/routes/search.rs b/crates/miroir-proxy/src/routes/search.rs index 3de3d53..4071f0e 100644 --- a/crates/miroir-proxy/src/routes/search.rs +++ b/crates/miroir-proxy/src/routes/search.rs @@ -262,3 +262,35 @@ pub fn strip_internal_fields(hit: &mut Value, client_requested_score: bool) { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_search_request_debug_redaction() { + let body = SearchRequestBody { + q: Some("sensitive user query about private data".to_string()), + offset: Some(0), + limit: Some(20), + filter: Some(serde_json::json!({"email": "user@example.com"})), + facets: Some(vec!["category".to_string()]), + ranking_score: Some(false), + rest: serde_json::json!({}), + }; + let debug_output = format!("{:?}", body); + + assert!( + !debug_output.contains("sensitive"), + "Debug output should not contain raw query text" + ); + assert!( + !debug_output.contains("user@example.com"), + "Debug output should not contain filter values" + ); + assert!( + debug_output.contains("[redacted]"), + "Debug output should show [redacted] for sensitive fields" + ); + } +} diff --git a/crates/miroir-proxy/tests/p7_5_structured_logging.rs b/crates/miroir-proxy/tests/p7_5_structured_logging.rs index 4c2d5a0..a2f27a3 100644 --- a/crates/miroir-proxy/tests/p7_5_structured_logging.rs +++ b/crates/miroir-proxy/tests/p7_5_structured_logging.rs @@ -291,15 +291,9 @@ fn test_log_levels_correct() { #[test] fn test_search_request_debug_redaction() { - // This test verifies that the Debug impl for SearchRequestBody - // redacts the query string to prevent PII leaks in logs - // - // The actual struct is in routes/search.rs and has: - // field("q", &"[redacted]") - // field("filter", &"[redacted]") - // - // We verify the behavior through the integration test that - // actually makes search requests and checks logs. + // Verified by unit test in routes/search.rs (SearchRequestBody is private). + // That test confirms the Debug impl redacts `q` and `filter` fields. + // This placeholder keeps the integration test file's acceptance criteria visible. } // ---------------------------------------------------------------------------