node: Add `--log-logger structured` and `--log-format json`

Optionally depend on crate `structured-logger`. Allow enabling it via
`--log-logger structured`. For future compatibility, provide
`--log-format` which currently only supports JSON.

Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
This commit is contained in:
Sebastian Martinez 2025-09-19 11:40:00 +02:00 committed by Lorenz Leutgeb
parent e563117397
commit 5fea9ac05c
3 changed files with 217 additions and 3 deletions

176
Cargo.lock generated
View File

@ -910,7 +910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -1893,6 +1893,17 @@ dependencies = [
"popol", "popol",
] ]
[[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]] [[package]]
name = "itertools" name = "itertools"
version = "0.14.0" version = "0.14.0"
@ -2090,6 +2101,10 @@ name = "log"
version = "0.4.27" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
dependencies = [
"serde",
"value-bag",
]
[[package]] [[package]]
name = "matchers" name = "matchers"
@ -2922,6 +2937,7 @@ dependencies = [
"snapbox", "snapbox",
"socket2", "socket2",
"sqlite", "sqlite",
"structured-logger",
"tempfile", "tempfile",
"test-log", "test-log",
"thiserror 1.0.69", "thiserror 1.0.69",
@ -3369,6 +3385,15 @@ dependencies = [
"syn 2.0.89", "syn 2.0.89",
] ]
[[package]]
name = "serde_fmt"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.140"
@ -3516,6 +3541,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.13.2"
@ -3671,12 +3702,103 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" 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]] [[package]]
name = "subtle" name = "subtle"
version = "2.5.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 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]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@ -3882,6 +4004,22 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 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]] [[package]]
name = "toml" name = "toml"
version = "0.9.5" version = "0.9.5"
@ -4234,6 +4372,42 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 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]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View File

@ -10,7 +10,7 @@ build = "build.rs"
rust-version.workspace = true rust-version.workspace = true
[features] [features]
default = ["systemd"] default = ["systemd", "structured-logger"]
systemd = ["dep:radicle-systemd"] systemd = ["dep:radicle-systemd"]
test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "radicle-protocol/test", "qcheck", "snapbox"] test = ["radicle/test", "radicle-crypto/test", "radicle-crypto/cyphernet", "radicle-protocol/test", "qcheck", "snapbox"]
@ -44,6 +44,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] } serde_json = { workspace = true, features = ["preserve_order"] }
snapbox = { workspace = true, optional = true } snapbox = { workspace = true, optional = true }
socket2 = "0.5.7" socket2 = "0.5.7"
structured-logger = { version = "1.0.4", optional = true }
tempfile = { workspace = true } tempfile = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }

View File

@ -32,8 +32,9 @@ Options
--listen <address> Address to listen on --listen <address> Address to listen on
--log-level <level> Set log level --log-level <level> Set log level
(default: info) (default: info)
--log-logger (radicle | systemd) Set logger implementation --log-logger (radicle | structured | systemd) Set logger implementation
(default: radicle) (default: radicle)
--log-format json Set log format for logger implementation
--version Print program version --version Print program version
--help Print help --help Print help
"#; "#;
@ -41,6 +42,8 @@ Options
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Logger { enum Logger {
Radicle, Radicle,
#[cfg(feature = "structured-logger")]
Structured,
#[cfg(all(feature = "systemd", target_os = "linux"))] #[cfg(all(feature = "systemd", target_os = "linux"))]
Systemd, Systemd,
} }
@ -62,6 +65,8 @@ impl FromStr for Logger {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"radicle" => Ok(Logger::Radicle), "radicle" => Ok(Logger::Radicle),
#[cfg(feature = "structured-logger")]
"structured" => Ok(Logger::Structured),
#[cfg(all(feature = "systemd", target_os = "linux"))] #[cfg(all(feature = "systemd", target_os = "linux"))]
"systemd" => Ok(Logger::Systemd), "systemd" => Ok(Logger::Systemd),
_ => Err("unknown logger"), _ => Err("unknown logger"),
@ -69,9 +74,28 @@ impl FromStr for Logger {
} }
} }
#[derive(Clone, Copy)]
enum LogFormat {
#[cfg(feature = "structured-logger")]
Json,
}
impl FromStr for LogFormat {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#[cfg(feature = "structured-logger")]
"json" => Ok(LogFormat::Json),
_ => Err("unknown log format"),
}
}
}
struct LogOptions { struct LogOptions {
level: Option<log::Level>, level: Option<log::Level>,
logger: Logger, logger: Logger,
format: Option<LogFormat>,
} }
struct Options { struct Options {
@ -91,6 +115,7 @@ fn parse_options() -> Result<Options, lexopt::Error> {
let mut force = false; let mut force = false;
let mut log_level = None; let mut log_level = None;
let mut log_logger = Logger::default(); let mut log_logger = Logger::default();
let mut log_format = None;
while let Some(arg) = parser.next()? { while let Some(arg) = parser.next()? {
match arg { match arg {
@ -120,6 +145,9 @@ fn parse_options() -> Result<Options, lexopt::Error> {
} }
log_logger = parsed; log_logger = parsed;
} }
Long("log-format") => {
log_format = Some(parser.value()?.parse_with(LogFormat::from_str)?);
}
Long("help") | Short('h') => { Long("help") | Short('h') => {
println!("{HELP_MSG}"); println!("{HELP_MSG}");
exit(0); exit(0);
@ -141,6 +169,7 @@ fn parse_options() -> Result<Options, lexopt::Error> {
log: LogOptions { log: LogOptions {
level: log_level, level: log_level,
logger: log_logger, logger: log_logger,
format: log_format,
}, },
}) })
} }
@ -232,6 +261,16 @@ fn initialize_logging(options: &LogOptions) -> Result<(), Box<dyn std::error::Er
let logger: Box<dyn log::Log> = { let logger: Box<dyn log::Log> = {
match options.logger { match options.logger {
#[cfg(feature = "structured-logger")]
Logger::Structured => {
use structured_logger::{json, Builder};
let writer = match options.format.unwrap_or(LogFormat::Json) {
LogFormat::Json => json::new_writer(io::stdout()),
};
Box::new(Builder::new().with_default_writer(writer).build())
}
#[cfg(all(feature = "systemd", target_os = "linux"))] #[cfg(all(feature = "systemd", target_os = "linux"))]
Logger::Systemd => { Logger::Systemd => {
use radicle_systemd::journal::*; use radicle_systemd::journal::*;