Skip to content

Commit 738e231

Browse files
apollo_infra: replace error JSON field with message in trace logs
1 parent f61b45f commit 738e231

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

crates/apollo_infra/src/trace_util.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::io::{Result as IoResult, Write};
12
use std::str::FromStr;
23

34
use time::macros::format_description;
@@ -9,6 +10,37 @@ use tracing_subscriber::fmt::time::UtcTime;
910
use tracing_subscriber::prelude::*;
1011
use tracing_subscriber::{fmt, reload, EnvFilter};
1112

13+
// Renames the "error" key to "message" in a JSON object, if present.
14+
// If "message" already exists, leaves the object unchanged.
15+
pub(crate) fn rename_error_to_message(buf: &[u8]) -> Option<Vec<u8>> {
16+
let mut obj: serde_json::Map<String, serde_json::Value> = serde_json::from_slice(buf).ok()?;
17+
if !obj.contains_key("message") {
18+
if let Some(v) = obj.remove("error") {
19+
obj.insert("message".into(), v);
20+
}
21+
}
22+
let mut out = serde_json::to_vec(&obj).ok()?;
23+
out.push(b'\n');
24+
Some(out)
25+
}
26+
27+
// Wraps any writer to rename the "error" JSON key to "message" in log output.
28+
// This is used because #[instrument(...,err)] emits errors in the "error" field.
29+
struct ErrorToMessageWriter<W>(W);
30+
31+
impl<W: Write> Write for ErrorToMessageWriter<W> {
32+
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
33+
let len = buf.len();
34+
let output = rename_error_to_message(buf).unwrap_or_else(|| buf.to_vec());
35+
self.0.write_all(&output)?;
36+
Ok(len)
37+
}
38+
39+
fn flush(&mut self) -> IoResult<()> {
40+
self.0.flush()
41+
}
42+
}
43+
1244
// Crates we always keep at INFO regardless of operator-supplied spec.
1345
const QUIET_LIBS: &[&str] = &[
1446
"alloy_provider",
@@ -47,6 +79,7 @@ pub async fn configure_tracing() -> ReloadHandle {
4779

4880
let fmt_layer = fmt::layer()
4981
.json()
82+
.map_writer(|w| move || ErrorToMessageWriter(w()))
5083
.with_timer(timer)
5184
.with_target(false) // No module name.
5285
// Instead, file name and line number.

crates/apollo_infra/src/trace_util_tests.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use tracing::metadata::LevelFilter;
22
use tracing_subscriber::{reload, EnvFilter};
33

4-
use crate::trace_util::{get_log_directives, set_log_level, ReloadHandle};
4+
use crate::trace_util::{get_log_directives, rename_error_to_message, set_log_level, ReloadHandle};
55

66
#[test]
77
fn log_level_directive_updates() {
@@ -16,3 +16,57 @@ fn log_level_directive_updates() {
1616
let directives = get_log_directives(&reload_handle).unwrap();
1717
assert_eq!(directives, "b=debug,a=info,info");
1818
}
19+
20+
#[test]
21+
fn rename_error_to_message_renames_error_key() {
22+
let input = br#"{"level":"ERROR","error":"something failed","file":"test.rs"}"#;
23+
let output = rename_error_to_message(input).unwrap();
24+
let output_str = String::from_utf8(output).unwrap();
25+
26+
assert!(output_str.contains(r#""message":"something failed""#), "got: {output_str}");
27+
assert!(!output_str.contains(r#""error""#), "got: {output_str}");
28+
}
29+
30+
#[test]
31+
fn rename_error_to_message_preserves_other_fields() {
32+
let input = br#"{"level":"INFO","status":"ok","count":42}"#;
33+
let output = rename_error_to_message(input).unwrap();
34+
let output_str = String::from_utf8(output).unwrap();
35+
36+
assert!(output_str.contains(r#""level":"INFO""#), "got: {output_str}");
37+
assert!(output_str.contains(r#""status":"ok""#), "got: {output_str}");
38+
assert!(output_str.contains(r#""count":42"#), "got: {output_str}");
39+
}
40+
41+
#[test]
42+
fn rename_error_to_message_returns_none_for_invalid_json() {
43+
let input = b"not valid json";
44+
assert!(rename_error_to_message(input).is_none());
45+
}
46+
47+
#[test]
48+
fn rename_error_to_message_only_renames_root_level_error() {
49+
// Nested "error" fields should NOT be renamed - only root level
50+
let input = br#"{"error":"root error","nested":{"error":"nested error"}}"#;
51+
let output = rename_error_to_message(input).unwrap();
52+
let parsed: serde_json::Value = serde_json::from_slice(&output).unwrap();
53+
54+
// Root "error" should be renamed to "message"
55+
assert_eq!(parsed["message"], "root error");
56+
assert!(parsed.get("error").is_none(), "root 'error' should be removed");
57+
58+
// Nested "error" should remain unchanged
59+
assert_eq!(parsed["nested"]["error"], "nested error");
60+
}
61+
62+
#[test]
63+
fn rename_error_to_message_preserves_existing_message_field() {
64+
// If both "error" and "message" exist, leave the object unchanged
65+
let input = br#"{"error":"the error","message":"original message"}"#;
66+
let output = rename_error_to_message(input).unwrap();
67+
let parsed: serde_json::Value = serde_json::from_slice(&output).unwrap();
68+
69+
// Both fields should remain unchanged
70+
assert_eq!(parsed["message"], "original message");
71+
assert_eq!(parsed["error"], "the error");
72+
}

0 commit comments

Comments
 (0)