Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
node: Add `--log-format json` to the node output
Merged did:key:z6MkkfM3...sVz5 opened 7 months ago

Adds the structured-logger crate as a dependency, that allows us by setting --log-format json to output the node output as json.

The default logger is being set as --log-format radicle or just by omitting it.

3 files changed +231 -13 9793b4e7 5fea9ac0
modified Cargo.lock
@@ -910,7 +910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
 "libc",
-
 "windows-sys 0.59.0",
+
 "windows-sys 0.60.2",
]

[[package]]
@@ -1894,6 +1894,17 @@ dependencies = [
]

[[package]]
+
name = "io-uring"
+
version = "0.7.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
+
dependencies = [
+
 "bitflags 2.9.1",
+
 "cfg-if",
+
 "libc",
+
]
+

+
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2090,6 +2101,10 @@ name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
dependencies = [
+
 "serde",
+
 "value-bag",
+
]

[[package]]
name = "matchers"
@@ -2922,6 +2937,7 @@ dependencies = [
 "snapbox",
 "socket2",
 "sqlite",
+
 "structured-logger",
 "tempfile",
 "test-log",
 "thiserror 1.0.69",
@@ -3370,6 +3386,15 @@ dependencies = [
]

[[package]]
+
name = "serde_fmt"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
+
dependencies = [
+
 "serde",
+
]
+

+
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3517,6 +3542,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"

[[package]]
+
name = "slab"
+
version = "0.4.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+

+
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3672,12 +3703,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"

[[package]]
+
name = "structured-logger"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f41647ab1dfedac6dccb4622ded5f3bea80ade9257a9ddcc89e36a43e1769cdf"
+
dependencies = [
+
 "log",
+
 "parking_lot",
+
 "serde",
+
 "serde_json",
+
 "tokio",
+
]
+

+
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"

[[package]]
+
name = "sval"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7cc9739f56c5d0c44a5ed45473ec868af02eb896af8c05f616673a31e1d1bb09"
+

+
[[package]]
+
name = "sval_buffer"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f39b07436a8c271b34dad5070c634d1d3d76d6776e938ee97b4a66a5e8003d0b"
+
dependencies = [
+
 "sval",
+
 "sval_ref",
+
]
+

+
[[package]]
+
name = "sval_dynamic"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ffcb072d857431bf885580dacecf05ed987bac931230736739a79051dbf3499b"
+
dependencies = [
+
 "sval",
+
]
+

+
[[package]]
+
name = "sval_fmt"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3f214f427ad94a553e5ca5514c95c6be84667cbc5568cce957f03f3477d03d5c"
+
dependencies = [
+
 "itoa",
+
 "ryu",
+
 "sval",
+
]
+

+
[[package]]
+
name = "sval_json"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "389ed34b32e638dec9a99c8ac92d0aa1220d40041026b625474c2b6a4d6f4feb"
+
dependencies = [
+
 "itoa",
+
 "ryu",
+
 "sval",
+
]
+

+
[[package]]
+
name = "sval_nested"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "14bae8fcb2f24fee2c42c1f19037707f7c9a29a0cda936d2188d48a961c4bb2a"
+
dependencies = [
+
 "sval",
+
 "sval_buffer",
+
 "sval_ref",
+
]
+

+
[[package]]
+
name = "sval_ref"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2a4eaea3821d3046dcba81d4b8489421da42961889902342691fb7eab491d79e"
+
dependencies = [
+
 "sval",
+
]
+

+
[[package]]
+
name = "sval_serde"
+
version = "2.14.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "172dd4aa8cb3b45c8ac8f3b4111d644cd26938b0643ede8f93070812b87fb339"
+
dependencies = [
+
 "serde",
+
 "sval",
+
 "sval_nested",
+
]
+

+
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3883,6 +4005,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

[[package]]
+
name = "tokio"
+
version = "1.47.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+
dependencies = [
+
 "backtrace",
+
 "bytes",
+
 "io-uring",
+
 "libc",
+
 "mio 1.0.4",
+
 "parking_lot",
+
 "pin-project-lite",
+
 "slab",
+
]
+

+
[[package]]
name = "toml"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4235,6 +4373,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"

[[package]]
+
name = "value-bag"
+
version = "1.11.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
+
dependencies = [
+
 "value-bag-serde1",
+
 "value-bag-sval2",
+
]
+

+
[[package]]
+
name = "value-bag-serde1"
+
version = "1.11.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "35540706617d373b118d550d41f5dfe0b78a0c195dc13c6815e92e2638432306"
+
dependencies = [
+
 "erased-serde",
+
 "serde",
+
 "serde_fmt",
+
]
+

+
[[package]]
+
name = "value-bag-sval2"
+
version = "1.11.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6fe7e140a2658cc16f7ee7a86e413e803fc8f9b5127adc8755c19f9fefa63a52"
+
dependencies = [
+
 "sval",
+
 "sval_buffer",
+
 "sval_dynamic",
+
 "sval_fmt",
+
 "sval_json",
+
 "sval_ref",
+
 "sval_serde",
+
]
+

+
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified crates/radicle-node/Cargo.toml
@@ -10,9 +10,10 @@ build = "build.rs"
rust-version.workspace = true

[features]
-
default = ["systemd"]
+
default = ["systemd", "structured_logger"]
systemd = ["dep:radicle-systemd"]
test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "radicle-protocol/test", "qcheck", "snapbox"]
+
structured_logger = ["dep:structured-logger"]

[dependencies]
amplify = { workspace = true }
@@ -44,6 +45,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] }
snapbox = { workspace = true, optional = true }
socket2 = "0.5.7"
+
structured-logger = { version = "1.0.4", optional = true }
tempfile = { workspace = true }
thiserror = { workspace = true }

modified crates/radicle-node/src/main.rs
@@ -2,6 +2,7 @@ use std::io;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::process::exit;
+
use std::str::FromStr;

use crossbeam_channel as chan;
use thiserror::Error;
@@ -24,6 +25,25 @@ use radicle_signals as signals;
/// command line arguments, such as `--log`.
const LOG_LEVEL_DEFAULT: &log::Level = &log::Level::Warn;

+
#[derive(Debug, Default, Clone)]
+
enum LogFormat {
+
    Json,
+
    #[default]
+
    Radicle,
+
}
+

+
impl FromStr for LogFormat {
+
    type Err = String;
+

+
    fn from_str(s: &str) -> Result<Self, Self::Err> {
+
        match s {
+
            "json" => Ok(LogFormat::Json),
+
            "radicle" => Ok(LogFormat::Radicle),
+
            _ => Err(format!("Invalid log format: {s}")),
+
        }
+
    }
+
}
+

pub const HELP_MSG: &str = r#"
Usage

@@ -38,6 +58,7 @@ Options
    --force                             Force start even if an existing control socket is found
    --listen             <address>      Address to listen on
    --log                <level>        Set log level (default: info)
+
    --log-format         <format>       Set log format (default: radicle)
    --version                           Print program version
    --help                              Print help
"#;
@@ -46,6 +67,7 @@ struct Options {
    config: Option<PathBuf>,
    listen: Vec<SocketAddr>,
    log: Option<log::Level>,
+
    log_format: Option<LogFormat>,
    force: bool,
}

@@ -58,6 +80,7 @@ fn parse_options() -> Result<Options, lexopt::Error> {
    let mut config = None;
    let mut force = false;
    let mut log = None;
+
    let mut log_format = None;

    while let Some(arg) = parser.next()? {
        match arg {
@@ -74,6 +97,9 @@ fn parse_options() -> Result<Options, lexopt::Error> {
            Long("log") => {
                log = Some(parser.value()?.parse_with(log::Level::from_str)?);
            }
+
            Long("log-format") => {
+
                log_format = Some(parser.value()?.parse_with(LogFormat::from_str)?);
+
            }
            Long("help") | Short('h') => {
                println!("{HELP_MSG}");
                exit(0);
@@ -92,6 +118,7 @@ fn parse_options() -> Result<Options, lexopt::Error> {
        force,
        listen,
        log,
+
        log_format,
        config,
    })
}
@@ -178,7 +205,7 @@ fn execute(options: Options) -> Result<(), ExecutionError> {
    Ok(())
}

-
fn initialize_logging() {
+
fn initialize_logging(log_format: LogFormat) {
    let level = *LOG_LEVEL_DEFAULT;

    //  - We are compiling conditionally, so cannot depend
@@ -201,7 +228,23 @@ fn initialize_logging() {
            radicle_systemd::journal::logger::<&str, &str, _>("radicle-node".to_string(), [])
                .map_err(|err| Box::new(JournalError(err)) as Error)
        }
-
        #[cfg(not(all(feature = "systemd", target_os = "linux")))]
+
        #[cfg(feature = "structured_logger")]
+
        {
+
            use structured_logger::{json, Builder};
+

+
            match log_format {
+
                LogFormat::Json => Ok(Some(Box::new(
+
                    Builder::new()
+
                        .with_default_writer(json::new_writer(io::stdout()))
+
                        .build(),
+
                ))),
+
                _ => Ok(None),
+
            }
+
        }
+
        #[cfg(not(any(
+
            feature = "structured_logger",
+
            all(feature = "systemd", target_os = "linux")
+
        )))]
        {
            // This is constant, and `rustc` will hopefully use it to
            // optimize away the `match` below.
@@ -234,16 +277,15 @@ fn main() {
        std::env::set_var(RUST_BACKTRACE, "1");
    }

-
    initialize_logging();
+
    let options = parse_options().unwrap_or_else(|err| {
+
        // Fallback to default logging format if not able to parse options.
+
        initialize_logging(LogFormat::Radicle);
+
        // The lexopt errors read nicely with a comma.
+
        log::error!(target: "node", "Failed to parse options, {err:#}");
+
        exit(2);
+
    });

-
    let options = match parse_options() {
-
        Ok(options) => options,
-
        Err(err) => {
-
            // The lexopt errors read nicely with a comma.
-
            log::error!(target: "node", "Failed to parse options, {err:#}");
-
            exit(2);
-
        }
-
    };
+
    initialize_logging(options.log_format.clone().unwrap_or_default());

    if let Err(err) = execute(options) {
        log::error!(target: "node", "{err:#}");