feat! make cibtool log read cib log output as well

Previously we only supported reading `cib` log output wrapped into
`journald` messages as JSON.

This is a breaking change because the command line syntax changes
slightly.

Signed-off-by: Lars Wirzenius <liw@liw.fi>
This commit is contained in:
Lars Wirzenius
2025-08-13 09:40:40 +03:00
parent cf188b6713
commit b84fffaf02
2 changed files with 94 additions and 20 deletions
+2 -2
View File
@@ -2411,11 +2411,11 @@ _Who:_ `cib-devs`
given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
given file journal.json
when I run cibtool log --journal journal.json --output x.json
when I run cibtool log --format journald journal.json --output x.json
when I run jq . x.json
then command is successful
when I run cibtool log --journal journal.json --broker-run-id 62c45727-a4d8-4a29-9dae-88c6e8b61655 --output x.json
when I run cibtool log --format journald journal.json --broker-run-id 62c45727-a4d8-4a29-9dae-88c6e8b61655 --output x.json
when I run grep -c timestamp x.json
then stdout has one line
~~~
+91 -17
View File
@@ -5,6 +5,7 @@ use std::{
path::{Path, PathBuf},
};
use clap::ValueEnum;
use logger::LogLevel;
use serde::Deserialize;
use serde_json::Value;
@@ -19,10 +20,9 @@ use super::*;
/// default text format that's hard to parse.
#[derive(Debug, Parser)]
pub struct LogCmd {
/// Read journalctl JSON output from this file. Default is to read
/// from the standard input.
/// Input is this format.
#[clap(long)]
journal: Option<PathBuf>,
format: LogKind,
/// One JSON object per line.
#[clap(long)]
@@ -49,6 +49,10 @@ pub struct LogCmd {
/// output.
#[clap(long)]
output: Option<PathBuf>,
/// Read input from this file. Default is to read from the
/// standard input.
log_file: Option<PathBuf>,
}
#[allow(clippy::unwrap_used)]
@@ -146,10 +150,21 @@ fn jsonl(msg: Value) -> Result<String, LogError> {
impl Leaf for LogCmd {
fn run(&self, _args: &Args) -> Result<(), CibToolError> {
let journal = if let Some(filename) = &self.journal {
JournalLines::from_file(filename)?
let journal = match self.format {
LogKind::Cib => {
if let Some(filename) = &self.log_file {
JournalLines::cib_from_file(filename)?
} else {
JournalLines::from_stdin()?
JournalLines::cib_from_stdin()?
}
}
LogKind::Journald => {
if let Some(filename) = &self.log_file {
JournalLines::journal_from_file(filename)?
} else {
JournalLines::journal_from_stdin()?
}
}
};
if let Some(filename) = &self.output {
@@ -174,22 +189,68 @@ impl Leaf for LogCmd {
}
struct JournalLines {
kind: LogKind,
data: Vec<u8>,
next: usize,
}
impl JournalLines {
fn from_file(filename: &Path) -> Result<Self, LogError> {
let data = read(filename).map_err(|err| LogError::Read(filename.into(), err))?;
Ok(Self { data, next: 0 })
fn read_file(filename: &Path) -> Result<Vec<u8>, LogError> {
read(filename).map_err(|err| LogError::Read(filename.into(), err))
}
fn from_stdin() -> Result<Self, LogError> {
fn read_stdin() -> Result<Vec<u8>, LogError> {
let mut data = vec![];
std::io::stdin()
.read_to_end(&mut data)
.map_err(LogError::ReadStdin)?;
Ok(Self { data, next: 0 })
Ok(data)
}
fn journal_from_file(filename: &Path) -> Result<Self, LogError> {
let data = Self::read_file(filename)?;
Ok(Self {
data,
next: 0,
kind: LogKind::Journald,
})
}
fn journal_from_stdin() -> Result<Self, LogError> {
let data = Self::read_stdin()?;
Ok(Self {
data,
next: 0,
kind: LogKind::Journald,
})
}
fn parse_journal_line(line: String) -> Result<Value, LogError> {
let jj: JournalJson = serde_json::from_str(&line)
.map_err(|err| LogError::JsonParseJournal(line.clone(), err))?;
serde_json::from_str(&jj.message).map_err(|err| (LogError::JsonParse(line, err)))
}
fn cib_from_file(filename: &Path) -> Result<Self, LogError> {
let data = Self::read_file(filename)?;
Ok(Self {
data,
next: 0,
kind: LogKind::Cib,
})
}
fn cib_from_stdin() -> Result<Self, LogError> {
let data = Self::read_stdin()?;
Ok(Self {
data,
next: 0,
kind: LogKind::Cib,
})
}
fn parse_cib_line(line: String) -> Result<Value, LogError> {
serde_json::from_str(&line).map_err(|err| LogError::JsonParseCib(line.clone(), err))
}
}
@@ -203,12 +264,9 @@ impl Iterator for JournalLines {
let line = String::from_utf8_lossy(&self.data[self.next..i]).to_string();
self.next = i + 1;
match serde_json::from_str::<JournalJson>(&line) {
Err(err) => return Some(Err(LogError::JsonParseJournal(line, err))),
Ok(jj) => match serde_json::from_str(&jj.message) {
Err(err) => return Some(Err(LogError::JsonParse(line, err))),
Ok(value) => return Some(Ok(value)),
},
match self.kind {
LogKind::Cib => return Some(Self::parse_cib_line(line)),
LogKind::Journald => return Some(Self::parse_journal_line(line)),
}
}
i += 1;
@@ -217,12 +275,25 @@ impl Iterator for JournalLines {
}
}
#[derive(Debug, Copy, Clone, ValueEnum)]
enum LogKind {
Journald,
Cib,
}
#[derive(Debug, Deserialize)]
struct JournalJson {
#[serde(rename = "MESSAGE")]
message: String,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct CibJson {
name: String,
value: Value,
}
#[derive(Debug, thiserror::Error)]
pub enum LogError {
#[error("failed to read journal file {0}")]
@@ -237,6 +308,9 @@ pub enum LogError {
#[error("failed to parse a log line as JSON: {0:?}")]
JsonParse(String, #[source] serde_json::Error),
#[error("failed to parse a cib log line as JSON: {0:?}")]
JsonParseCib(String, #[source] serde_json::Error),
#[error("failed to serialize a log line as pretty JSON: {0:?}")]
JsonSer(serde_json::Value, #[source] serde_json::Error),