Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
node: Enable logging via systemd journal
Merged lorenz opened 8 months ago

The radicle-systemd crate is split into two modules, one for socket activation, and one for logging. Both are behind feature flags.

radicle-node continues to depend on radicle-systemd.

Logging initialization code was removed from radicle, as it is a library crate.

17 files changed +310 -146 7a9d4512 d7b48b9e
modified Cargo.lock
@@ -327,7 +327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
 "memchr",
-
 "regex-automata",
+
 "regex-automata 0.4.9",
 "serde",
]

@@ -809,6 +809,27 @@ dependencies = [
]

[[package]]
+
name = "env_filter"
+
version = "0.1.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+
dependencies = [
+
 "log",
+
]
+

+
[[package]]
+
name = "env_logger"
+
version = "0.11.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
+
dependencies = [
+
 "anstream",
+
 "anstyle",
+
 "env_filter",
+
 "log",
+
]
+

+
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -853,8 +874,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
dependencies = [
 "bit-set",
-
 "regex-automata",
-
 "regex-syntax",
+
 "regex-automata 0.4.9",
+
 "regex-syntax 0.8.5",
]

[[package]]
@@ -1841,7 +1862,7 @@ dependencies = [
 "percent-encoding",
 "referencing",
 "regex",
-
 "regex-syntax",
+
 "regex-syntax 0.8.5",
 "serde",
 "serde_json",
 "uuid-simd",
@@ -1957,6 +1978,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"

[[package]]
+
name = "matchers"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+
dependencies = [
+
 "regex-automata 0.1.10",
+
]
+

+
[[package]]
name = "maybe-async"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2082,6 +2112,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"

[[package]]
+
name = "nu-ansi-term"
+
version = "0.46.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+
dependencies = [
+
 "overload",
+
 "winapi",
+
]
+

+
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2197,6 +2237,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"

[[package]]
+
name = "overload"
+
version = "0.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+

+
[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2301,6 +2347,12 @@ dependencies = [
]

[[package]]
+
name = "pin-project-lite"
+
version = "0.2.16"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+

+
[[package]]
name = "pkcs1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2687,6 +2739,7 @@ dependencies = [
 "socket2",
 "sqlite",
 "tempfile",
+
 "test-log",
 "thiserror 1.0.69",
]

@@ -2719,6 +2772,7 @@ dependencies = [
name = "radicle-remote-helper"
version = "0.11.0"
dependencies = [
+
 "log",
 "radicle",
 "radicle-cli",
 "radicle-crypto",
@@ -2782,6 +2836,10 @@ dependencies = [
[[package]]
name = "radicle-systemd"
version = "0.9.0"
+
dependencies = [
+
 "log",
+
 "systemd-journal-logger",
+
]

[[package]]
name = "radicle-term"
@@ -2883,8 +2941,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
 "aho-corasick",
 "memchr",
-
 "regex-automata",
-
 "regex-syntax",
+
 "regex-automata 0.4.9",
+
 "regex-syntax 0.8.5",
+
]
+

+
[[package]]
+
name = "regex-automata"
+
version = "0.1.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
dependencies = [
+
 "regex-syntax 0.6.29",
]

[[package]]
@@ -2895,11 +2962,17 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
 "aho-corasick",
 "memchr",
-
 "regex-syntax",
+
 "regex-syntax 0.8.5",
]

[[package]]
name = "regex-syntax"
+
version = "0.6.29"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+

+
[[package]]
+
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
@@ -3142,6 +3215,15 @@ dependencies = [
]

[[package]]
+
name = "sharded-slab"
+
version = "0.1.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+
dependencies = [
+
 "lazy_static",
+
]
+

+
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3431,6 +3513,16 @@ dependencies = [
]

[[package]]
+
name = "systemd-journal-logger"
+
version = "2.2.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7266304d24ca5a4b230545fc558c80e18bd3e1d2eb1be149b6bcd04398d3e79c"
+
dependencies = [
+
 "log",
+
 "rustix 1.0.7",
+
]
+

+
[[package]]
name = "tar"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3454,6 +3546,28 @@ dependencies = [
]

[[package]]
+
name = "test-log"
+
version = "0.2.18"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1e33b98a582ea0be1168eba097538ee8dd4bbe0f2b01b22ac92ea30054e5be7b"
+
dependencies = [
+
 "env_logger",
+
 "test-log-macros",
+
 "tracing-subscriber",
+
]
+

+
[[package]]
+
name = "test-log-macros"
+
version = "0.2.18"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "451b374529930d7601b1eef8d32bc79ae870b6079b069401709c2a8bf9e75f36"
+
dependencies = [
+
 "proc-macro2",
+
 "quote",
+
 "syn 2.0.89",
+
]
+

+
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3494,6 +3608,15 @@ dependencies = [
]

[[package]]
+
name = "thread_local"
+
version = "1.1.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+
dependencies = [
+
 "cfg-if",
+
]
+

+
[[package]]
name = "timeago"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3525,6 +3648,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"

[[package]]
+
name = "tracing"
+
version = "0.1.41"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+
dependencies = [
+
 "pin-project-lite",
+
 "tracing-core",
+
]
+

+
[[package]]
+
name = "tracing-core"
+
version = "0.1.34"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+
dependencies = [
+
 "once_cell",
+
 "valuable",
+
]
+

+
[[package]]
+
name = "tracing-log"
+
version = "0.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+
dependencies = [
+
 "log",
+
 "once_cell",
+
 "tracing-core",
+
]
+

+
[[package]]
+
name = "tracing-subscriber"
+
version = "0.3.19"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+
dependencies = [
+
 "matchers",
+
 "nu-ansi-term",
+
 "once_cell",
+
 "regex",
+
 "sharded-slab",
+
 "thread_local",
+
 "tracing",
+
 "tracing-core",
+
 "tracing-log",
+
]
+

+
[[package]]
name = "tree-sitter"
version = "0.24.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3532,7 +3703,7 @@ checksum = "b67baf55e7e1b6806063b1e51041069c90afff16afcbbccd278d899f9d84bca4"
dependencies = [
 "cc",
 "regex",
-
 "regex-syntax",
+
 "regex-syntax 0.8.5",
 "streaming-iterator",
 "tree-sitter-language",
]
@@ -3781,6 +3952,12 @@ dependencies = [
]

[[package]]
+
name = "valuable"
+
version = "0.1.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+

+
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified crates/radicle-cli-test/src/lib.rs
@@ -207,9 +207,13 @@ impl TestFormula {
        BUILD.call_once(|| {
            use escargot::format::Message;
            use radicle::logger::env_level;
-
            use radicle::logger::test as logger;
+
            use radicle::logger::test::Logger;

-
            logger::init(env_level().unwrap_or(log::Level::Debug));
+
            let level = env_level().unwrap_or(log::Level::Debug);
+
            let logger = Box::new(Logger::new(level));
+

+
            log::set_boxed_logger(logger).expect("no other logger should have been set already");
+
            log::set_max_level(level.to_level_filter());

            for (package, binary) in binaries {
                log::debug!(target: "test", "Building binaries for package `{package}`..");
modified crates/radicle-cli/src/main.rs
@@ -30,7 +30,9 @@ enum Command {

fn main() {
    if let Some(lvl) = radicle::logger::env_level() {
-
        radicle::logger::init(lvl).ok();
+
        let logger = Box::new(radicle::logger::Logger::new(lvl));
+
        log::set_boxed_logger(logger).expect("no other logger should have been set already");
+
        log::set_max_level(lvl.to_level_filter());
    }
    if let Err(e) = radicle::io::set_file_limit(4096) {
        log::warn!(target: "cli", "Unable to set open file limit: {e}");
modified crates/radicle-cli/tests/commands.rs
@@ -744,7 +744,6 @@ fn rad_node_connect() {

#[test]
fn rad_node_connect_without_address() {
-
    logger::init(log::Level::Debug);
    let mut environment = Environment::new();
    let mut alice = environment.node("alice");
    let bob = environment.node("bob");
@@ -922,7 +921,6 @@ fn rad_patch_ahead_behind() {

#[test]
fn rad_patch_change_base() {
-
    logger::init(log::Level::Debug);
    Environment::alice(["rad-init", "rad-patch-change-base"]);
}

@@ -1229,8 +1227,6 @@ fn rad_clone_partial_fail() {
    let mut eve = environment.node("eve");
    let carol = NodeId::from_str("z6MksFqXN3Yhqk8pTJdUGLwBTkRfQvwZXPqR2qMEhbS9wzpT").unwrap();

-
    logger::init(log::Level::Debug);
-

    // Setup a test project.
    let acme = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");

@@ -1990,8 +1986,6 @@ fn rad_patch_pull_update() {

#[test]
fn rad_patch_open_explore() {
-
    logger::init(log::Level::Debug);
-

    let mut environment = Environment::new();
    let seed = environment
        .node_with(Config {
modified crates/radicle-node/Cargo.toml
@@ -55,3 +55,4 @@ qcheck-macros = { workspace = true }
radicle = { workspace = true, features = ["test"] }
radicle-crypto = { workspace = true, features = ["test", "cyphernet"] }
snapbox = { workspace = true }
+
test-log = "0.2.18"

\ No newline at end of file
modified crates/radicle-node/src/main.rs
@@ -4,7 +4,6 @@ use std::{env, fs, net, path::PathBuf, process};
use anyhow::Context;
use crossbeam_channel as chan;

-
use radicle::logger;
use radicle::node::device::Device;
use radicle::profile;
use radicle_node::crypto::ssh::keystore::{Keystore, MemorySigner};
@@ -91,7 +90,24 @@ fn execute() -> anyhow::Result<()> {
    let config = options.config.unwrap_or_else(|| home.config());
    let mut config = profile::Config::load(&config)?;

-
    logger::init(options.log.unwrap_or(config.node.log))?;
+
    let level = options.log.unwrap_or(config.node.log);
+

+
    let logger = {
+
        let journal = if cfg!(all(feature = "systemd", target_family = "unix")) {
+
            radicle_systemd::journal::logger::<&str, &str, _>("radicle-node".to_string(), [])?
+
        } else {
+
            None
+
        };
+

+
        if let Some(logger) = journal {
+
            logger
+
        } else {
+
            Box::new(radicle::logger::Logger::new(level))
+
        }
+
    };
+

+
    log::set_boxed_logger(logger).expect("no other logger should have been set already");
+
    log::set_max_level(level.to_level_filter());

    log::info!(target: "node", "Starting node..");
    log::info!(target: "node", "Version {} ({})", env!("RADICLE_VERSION"), env!("GIT_HEAD"));
modified crates/radicle-node/src/runtime.rs
@@ -341,7 +341,7 @@ impl Runtime {
    #[cfg(all(feature = "systemd", target_family = "unix"))]
    fn receive_listener() -> Option<UnixListener> {
        use std::os::fd::FromRawFd;
-
        match radicle_systemd::listen_fd("control") {
+
        match radicle_systemd::listen::fd("control") {
            Ok(Some(fd)) => {
                // NOTE: Here, we should make a call to [`fstat(2)`](man:fstat(2))
                // and make sure that the file descriptor we received actually
modified crates/radicle-node/src/tests.rs
@@ -846,8 +846,6 @@ fn test_refs_announcement_relay_private() {
/// Even if Alice is not tracking Bob, Alice will fetch Bob's refs for a repo she doesn't have.
#[test]
fn test_refs_announcement_fetch_trusted_no_inventory() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Peer::with_storage(
        "alice",
@@ -892,8 +890,6 @@ fn test_refs_announcement_fetch_trusted_no_inventory() {
/// Later Alice follows Bob, and will be able to fetch Bob's refs.
#[test]
fn test_refs_announcement_followed() {
-
    logger::init(log::Level::Debug);
-

    // Create MockStorage for Alice and Bob. Both will have repo with `rid`.
    let storage_alice = arbitrary::nonempty_storage(1);
    let rid = *storage_alice.repos.keys().next().unwrap();
@@ -1068,8 +1064,6 @@ fn test_refs_announcement_offline() {

#[test]
fn test_inventory_relay() {
-
    logger::init(log::Level::Debug);
-

    // Topology is eve <-> alice <-> bob
    let mut alice = Peer::new("alice", [7, 7, 7, 7]);
    let bob = Peer::new("bob", [8, 8, 8, 8]);
@@ -1486,8 +1480,6 @@ fn test_queued_fetch_max_capacity() {
    let mut alice = Peer::with_storage("alice", [7, 7, 7, 7], storage);
    let bob = Peer::new("bob", [8, 8, 8, 8]);

-
    logger::init(log::Level::Debug);
-

    alice.connect_to(&bob);

    // Send the first fetch.
@@ -1608,8 +1600,6 @@ fn test_queued_fetch_from_command_same_rid() {
    let eve = Peer::new("eve", [9, 9, 9, 9]);
    let carol = Peer::new("carol", [10, 10, 10, 10]);

-
    logger::init(log::Level::Debug);
-

    alice.connect_to(&bob);
    alice.connect_to(&eve);
    alice.connect_to(&carol);
modified crates/radicle-node/src/tests/e2e.rs
@@ -1,5 +1,7 @@
use std::{collections::HashSet, thread, time};

+
use test_log::test;
+

use radicle::node::device::Device;
use radicle::node::policy::Scope;
use radicle::node::Event;
@@ -16,7 +18,6 @@ use crate::node::config::Limits;
use crate::node::{Config, ConnectOptions};
use crate::service;
use crate::storage::git::transport;
-
use crate::test::logger;
use crate::test::node::{converge, Node};

mod config {
@@ -48,8 +49,6 @@ mod config {
//     alice -- bob
//
fn test_inventory_sync_basic() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -72,8 +71,6 @@ fn test_inventory_sync_basic() {
//     alice -- bob -- eve
//
fn test_inventory_sync_bridge() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -102,8 +99,6 @@ fn test_inventory_sync_bridge() {
//     carol -- eve
//
fn test_inventory_sync_ring() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -139,8 +134,6 @@ fn test_inventory_sync_ring() {
//            carol
//
fn test_inventory_sync_star() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -172,8 +165,6 @@ fn test_inventory_sync_star() {

#[test]
fn test_replication() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let alice = Node::init(tmp.path(), config::relay("alice"));
    let mut bob = Node::init(tmp.path(), config::relay("bob"));
@@ -247,8 +238,6 @@ fn test_replication() {

#[test]
fn test_replication_ref_in_sigrefs() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let alice = Node::init(tmp.path(), config::relay("alice"));
    let mut bob = Node::init(tmp.path(), config::relay("bob"));
@@ -341,8 +330,6 @@ fn test_replication_invalid() {

#[test]
fn test_migrated_clone() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -394,8 +381,6 @@ fn test_migrated_clone() {

#[test]
fn test_dont_fetch_owned_refs() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -421,8 +406,6 @@ fn test_dont_fetch_owned_refs() {

#[test]
fn test_fetch_followed_remotes() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -476,8 +459,6 @@ fn test_fetch_followed_remotes() {

#[test]
fn test_missing_remote() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -505,8 +486,6 @@ fn test_missing_remote() {

#[test]
fn test_fetch_preserve_owned_refs() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -551,8 +530,6 @@ fn test_fetch_preserve_owned_refs() {

#[test]
fn test_clone() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let alice = Node::init(tmp.path(), config::relay("alice"));
    let mut bob = Node::init(tmp.path(), config::relay("bob"));
@@ -609,8 +586,6 @@ fn test_clone() {

#[test]
fn test_fetch_up_to_date() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let alice = Node::init(tmp.path(), config::relay("alice"));
    let mut bob = Node::init(tmp.path(), config::relay("bob"));
@@ -638,8 +613,6 @@ fn test_fetch_up_to_date() {

#[test]
fn test_fetch_unseeded() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let alice = Node::init(tmp.path(), config::relay("alice"));
    let mut bob = Node::init(tmp.path(), config::relay("bob"));
@@ -667,8 +640,6 @@ fn test_fetch_unseeded() {

#[test]
fn test_large_fetch() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let scale = config::scale();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -703,8 +674,6 @@ fn test_large_fetch() {

#[test]
fn test_concurrent_fetches() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let scale = config::scale();
    let repos = scale.max(4);
@@ -810,8 +779,6 @@ fn test_concurrent_fetches() {

#[test]
fn test_connection_crossing() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -877,8 +844,6 @@ fn test_connection_crossing() {
/// Alice is going to try to fetch outdated refs of Bob, from Eve. This is a non-fastfoward fetch
/// on the sigrefs branch.
fn test_non_fastforward_sigrefs() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let alice = Node::init(tmp.path(), config::relay("alice"));
@@ -985,8 +950,6 @@ fn test_non_fastforward_sigrefs() {

#[test]
fn test_outdated_sigrefs() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -1081,8 +1044,6 @@ fn test_outdated_sigrefs() {

#[test]
fn test_outdated_delegate_sigrefs() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -1169,8 +1130,6 @@ fn test_outdated_delegate_sigrefs() {

#[test]
fn missing_default_branch() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -1227,9 +1186,6 @@ fn missing_delegate_default_branch() {
    use radicle::git::raw;
    use radicle::identity::Identity;
    use radicle::storage::git::Repository;
-

-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -1350,8 +1306,6 @@ fn missing_delegate_default_branch() {

#[test]
fn test_background_foreground_fetch() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -1439,8 +1393,6 @@ fn test_background_foreground_fetch() {
/// Alice is offline while Bob pushes some changes to the repo. When Alice reconnects,
/// she is made aware of the changes via the `subscribe` message, and fetches from the seed.
fn test_catchup_on_refs_announcements() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let bob = Node::init(tmp.path(), config::relay("bob"));
@@ -1477,8 +1429,6 @@ fn test_catchup_on_refs_announcements() {

#[test]
fn test_multiple_offline_inits() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();

    let mut alice = Node::init(tmp.path(), config::relay("alice"));
@@ -1505,8 +1455,6 @@ fn test_multiple_offline_inits() {

#[test]
fn test_channel_reader_limit() {
-
    logger::init(log::Level::Debug);
-

    let tmp = tempfile::tempdir().unwrap();
    let mut alice = Node::init(tmp.path(), config::relay("alice"));
    let limits = radicle::node::config::Limits {
modified crates/radicle-remote-helper/Cargo.toml
@@ -14,6 +14,7 @@ name = "git-remote-rad"
path = "src/git-remote-rad.rs"

[dependencies]
+
log = { workspace = true }
radicle = { workspace = true }
radicle-cli = { workspace = true }
radicle-crypto = { workspace = true }
modified crates/radicle-remote-helper/src/git-remote-rad.rs
@@ -14,7 +14,10 @@ fn main() {
    let mut args = env::args();

    if let Some(lvl) = radicle::logger::env_level() {
-
        radicle::logger::set(radicle::logger::StderrLogger::new(lvl), lvl).ok();
+
        let logger = radicle::logger::StderrLogger::new(lvl);
+
        log::set_boxed_logger(Box::new(logger))
+
            .expect("no other logger should have been set already");
+
        log::set_max_level(lvl.to_level_filter());
    }
    if args.nth(1).as_deref() == Some("--version") {
        if let Err(e) = VERSION.write(std::io::stdout()) {
modified crates/radicle-systemd/Cargo.toml
@@ -7,3 +7,12 @@ license.workspace = true
edition.workspace = true
version = "0.9.0"
rust-version.workspace = true
+

+
[dependencies]
+
log = { workspace = true, optional = true }
+
systemd-journal-logger = { version = "2.2.2", optional = true }
+

+
[features]
+
default = ["journal", "listen"]
+
journal = ["dep:log", "dep:systemd-journal-logger"]
+
listen = []

\ No newline at end of file
added crates/radicle-systemd/src/journal.rs
@@ -0,0 +1,23 @@
+
use systemd_journal_logger::{connected_to_journal, current_exe_identifier, JournalLog};
+

+
/// If the current process is directly connected to the systemd journal,
+
/// return a logger that will write to it.
+
pub fn logger<K, V, I>(
+
    default_identifier: String,
+
    extra_fields: I,
+
) -> std::io::Result<Option<Box<dyn log::Log>>>
+
where
+
    I: IntoIterator<Item = (K, V)>,
+
    K: AsRef<str>,
+
    V: AsRef<[u8]>,
+
{
+
    if !connected_to_journal() {
+
        return Ok(None);
+
    }
+

+
    Ok(Some(Box::new(
+
        JournalLog::new()?
+
            .with_syslog_identifier(current_exe_identifier().unwrap_or(default_identifier))
+
            .with_extra_fields(extra_fields),
+
    )))
+
}
modified crates/radicle-systemd/src/lib.rs
@@ -1,41 +1,7 @@
//! Library for interaction with systemd, specialized for Radicle.

-
use std::env::{remove_var, var, VarError};
-
use std::os::fd::RawFd;
-
use std::process::id;
+
#[cfg(feature = "journal")]
+
pub mod journal;

-
const LISTEN_PID: &str = "LISTEN_PID";
-
const LISTEN_FDS: &str = "LISTEN_FDS";
-
const LISTEN_FDNAMES: &str = "LISTEN_FDNAMES";
-

-
/// Minimum file descriptor used by systemd.
-
/// See <https://github.com/systemd/systemd/blob/v254/src/systemd/sd-daemon.h#L56>.
-
const SD_LISTEN_FDS_START: RawFd = 3;
-

-
/// Checks whether *at most one* file descriptor with given name was passed, returning it.
-
/// systemd sending none, more than one, or a file descriptor with a different name, all
-
/// results in [`Option::None`], but errors decoding environment variables or missing
-
/// environment variables will error.
-
/// This is a specialization of [`sd_listen_fds_with_names(3)`](man:sd_listen_fds_with_names(3)).
-
/// See:
-
///  - <https://www.freedesktop.org/software/systemd/man/254/sd_listen_fds_with_names.html>
-
///  - <https://github.com/systemd/systemd/blob/v254/src/libsystemd/sd-daemon/sd-daemon.c>
-
///  - <https://0pointer.de/blog/projects/socket-activation.html>
-
///  - <https://0pointer.de/blog/projects/socket-activation2.html>
-
pub fn listen_fd(name: &str) -> Result<Option<RawFd>, VarError> {
-
    let fd = match var(LISTEN_PID) {
-
        Err(VarError::NotPresent) => Ok(None),
-
        Err(err) => Err(err),
-
        Ok(pid) if pid != id().to_string() => Ok(None),
-
        _ if var(LISTEN_FDS)? != "1" || var(LISTEN_FDNAMES).ok() != Some(name.to_string()) => {
-
            Ok(None)
-
        }
-
        _ => Ok(Some(SD_LISTEN_FDS_START)),
-
    };
-

-
    remove_var(LISTEN_PID);
-
    remove_var(LISTEN_FDS);
-
    remove_var(LISTEN_FDNAMES);
-

-
    fd
-
}
+
#[cfg(feature = "listen")]
+
pub mod listen;
added crates/radicle-systemd/src/listen.rs
@@ -0,0 +1,39 @@
+
use std::env::{remove_var, var, VarError};
+
use std::os::fd::RawFd;
+
use std::process::id;
+

+
const LISTEN_PID: &str = "LISTEN_PID";
+
const LISTEN_FDS: &str = "LISTEN_FDS";
+
const LISTEN_FDNAMES: &str = "LISTEN_FDNAMES";
+

+
/// Minimum file descriptor used by systemd.
+
/// See <https://github.com/systemd/systemd/blob/v254/src/systemd/sd-daemon.h#L56>.
+
const SD_LISTEN_FDS_START: RawFd = 3;
+

+
/// Checks whether *at most one* file descriptor with given name was passed, returning it.
+
/// systemd sending none, more than one, or a file descriptor with a different name, all
+
/// results in [`Option::None`], but errors decoding environment variables or missing
+
/// environment variables will error.
+
/// This is a specialization of [`sd_listen_fds_with_names(3)`](man:sd_listen_fds_with_names(3)).
+
/// See:
+
///  - <https://www.freedesktop.org/software/systemd/man/254/sd_listen_fds_with_names.html>
+
///  - <https://github.com/systemd/systemd/blob/v254/src/libsystemd/sd-daemon/sd-daemon.c>
+
///  - <https://0pointer.de/blog/projects/socket-activation.html>
+
///  - <https://0pointer.de/blog/projects/socket-activation2.html>
+
pub fn fd(name: &str) -> Result<Option<RawFd>, VarError> {
+
    let fd = match var(LISTEN_PID) {
+
        Err(VarError::NotPresent) => Ok(None),
+
        Err(err) => Err(err),
+
        Ok(pid) if pid != id().to_string() => Ok(None),
+
        _ if var(LISTEN_FDS)? != "1" || var(LISTEN_FDNAMES).ok() != Some(name.to_string()) => {
+
            Ok(None)
+
        }
+
        _ => Ok(Some(SD_LISTEN_FDS_START)),
+
    };
+

+
    remove_var(LISTEN_PID);
+
    remove_var(LISTEN_FDS);
+
    remove_var(LISTEN_FDNAMES);
+

+
    fd
+
}
modified crates/radicle/src/logger.rs
@@ -10,13 +10,19 @@ use std::io::Write;

use chrono::prelude::*;
use colored::*;
-
use log::{Level, Log, Metadata, Record, SetLoggerError};
+
use log::{Level, Log, Metadata, Record};

/// A logger that logs to `stdout`.
pub struct Logger {
    level: Level,
}

+
impl Logger {
+
    pub fn new(level: Level) -> Self {
+
        Self { level }
+
    }
+
}
+

impl Log for Logger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= self.level
@@ -84,19 +90,6 @@ impl Log for StderrLogger {
    fn flush(&self) {}
}

-
/// Initialize a new logger.
-
pub fn init(level: Level) -> Result<(), SetLoggerError> {
-
    set(Logger { level }, level)
-
}
-

-
/// Set a logger.
-
pub fn set(logger: impl Log + 'static, level: Level) -> Result<(), SetLoggerError> {
-
    log::set_boxed_logger(Box::new(logger))?;
-
    log::set_max_level(level.to_level_filter());
-

-
    Ok(())
-
}
-

/// Get the level set by the environment variable `RUST_LOG`, if
/// present.
pub fn env_level() -> Option<Level> {
modified crates/radicle/src/logger/test.rs
@@ -1,10 +1,16 @@
use localtime::LocalTime;
use log::*;

-
struct Logger {
+
pub struct Logger {
    level: Level,
}

+
impl Logger {
+
    pub fn new(level: Level) -> Self {
+
        Self { level }
+
    }
+
}
+

impl Log for Logger {
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= self.level
@@ -57,11 +63,3 @@ impl Log for Logger {

    fn flush(&self) {}
}
-

-
#[allow(dead_code)]
-
pub fn init(level: Level) {
-
    let logger = Logger { level };
-

-
    log::set_boxed_logger(Box::new(logger)).ok();
-
    log::set_max_level(level.to_level_filter());
-
}