Log as JSON when IPX_LOG_FORMAT=json (#91)
The log was text, so the Grafana dashboard picked lines apart with regular expressions, and a change of wording would have blanked its panels. With IPX_LOG_FORMAT=json each line is one JSON object: the access log carries method, path, route, status and ms as fields (the route passed from the routing layer in the response's extensions), and each wire event its ev, feed, new, downloaded, failed, bytes, msg and the rest (log_wire), beside the old message. The two startup lines that were println! are logged, so no line breaks the JSON. Text stays the default, for a terminal. The dashboard reads the fields with Loki's json parser, and groups requests by route rather than path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
30
src/ipc.rs
30
src/ipc.rs
@@ -157,11 +157,7 @@ impl Emitter {
|
||||
// The outbound half of the protocol, as it goes on the wire. Progress is the
|
||||
// high-volume one, so it sits at debug.
|
||||
if let Ok(json) = serde_json::to_string(&e) {
|
||||
if matches!(e, Event::Progress { .. }) {
|
||||
tracing::debug!(target: "ipx::io", "<- {json}");
|
||||
} else {
|
||||
tracing::info!(target: "ipx::io", "<- {json}");
|
||||
}
|
||||
log_wire(&json, matches!(e, Event::Progress { .. }));
|
||||
}
|
||||
// An error here only means nobody is listening yet.
|
||||
let _ = tx.send(e.clone());
|
||||
@@ -172,6 +168,28 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
/// An event as it goes on the wire, `<- {json}`, with its fields as the log line's own as well,
|
||||
/// so Loki reads `ev`, `feed`, `new` and the rest from the JSON log without parsing the message
|
||||
/// (#91). A field an event lacks is left out.
|
||||
fn log_wire(json: &str, debug: bool) {
|
||||
let v: serde_json::Value = serde_json::from_str(json).unwrap_or_default();
|
||||
let s = |k: &str| v.get(k).and_then(|x| x.as_str());
|
||||
let n = |k: &str| v.get(k).and_then(|x| x.as_u64());
|
||||
macro_rules! wire {
|
||||
($level:ident) => {
|
||||
tracing::$level!(
|
||||
target: "ipx::io",
|
||||
ev = s("ev"), feed = s("feed"), msg = s("msg"), url = s("url"), reason = s("reason"),
|
||||
new = n("new"), downloaded = n("downloaded"), failed = n("failed"),
|
||||
torrents = n("torrents"), bytes = n("bytes"), feeds = n("feeds"),
|
||||
pending = n("pending"), enclosure = n("enclosure"), files = n("files"),
|
||||
"<- {json}"
|
||||
)
|
||||
};
|
||||
}
|
||||
if debug { wire!(debug) } else { wire!(info) }
|
||||
}
|
||||
|
||||
/// True when something is already listening -- i.e. a daemon owns this socket.
|
||||
pub async fn daemon_is_live(path: &Path) -> bool {
|
||||
UnixStream::connect(path).await.is_ok()
|
||||
@@ -258,7 +276,7 @@ async fn handle(
|
||||
tracing::info!(target: "ipx::io", "-> {line}");
|
||||
let ev = status().await;
|
||||
if let Ok(json) = serde_json::to_string(&ev) {
|
||||
tracing::info!(target: "ipx::io", "<- {json}");
|
||||
log_wire(&json, false);
|
||||
}
|
||||
let _ = reply.send(ev).await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user