From 6a45ec0e0ceb3615b1ddaaae1930716075efda1d Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Jan 2026 11:05:09 -0500 Subject: [PATCH 1/2] feat(cli-utils): add tracing subscriber initialization and refactor logging Adds full tracing_subscriber initialization with support for stdout and file logging, multiple log formats (Full, Json, Pretty, Compact, Logfmt), and configurable file rotation. Refactors logging configuration into separate structs for better flexibility. - Add tracing.rs with LogConfig::init_tracing_subscriber() and LogfmtFormatter - Add LogArgs CLI struct with BASE_* environment variable support - Expand LogFormat with Pretty and Logfmt variants - Add FileLogConfig, StdoutLogConfig, and LogConfig structs - Update GlobalArgs to use alloy_chains::Chain for l2_chain_id - Add comprehensive rstest-based unit tests (38 tests) --- Cargo.lock | 544 +++++++++++++++++++------ Cargo.toml | 4 + crates/shared/cli-utils/Cargo.toml | 22 +- crates/shared/cli-utils/src/args.rs | 225 +++++++++- crates/shared/cli-utils/src/lib.rs | 9 +- crates/shared/cli-utils/src/logging.rs | 201 +++++---- crates/shared/cli-utils/src/tracing.rs | 175 ++++++++ 7 files changed, 967 insertions(+), 213 deletions(-) create mode 100644 crates/shared/cli-utils/src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index 419739ab..c52e0c3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,8 +309,8 @@ checksum = "527b47dc39850c6168002ddc1f7a2063e15d26137c1bb5330f6065a7524c1aa9" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-hardforks", - "alloy-op-hardforks", + "alloy-hardforks 0.4.7", + "alloy-op-hardforks 0.4.7", "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -319,8 +319,8 @@ dependencies = [ "derive_more", "op-alloy-consensus", "op-alloy-rpc-types-engine", - "op-revm", - "revm", + "op-revm 12.0.2", + "revm 31.0.2", "thiserror 2.0.17", ] @@ -339,6 +339,19 @@ dependencies = [ "serde_with", ] +[[package]] +name = "alloy-hardforks" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165210652f71dfc094b051602bafd691f506c54050a174b1cba18fb5ef706a3" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives 1.5.2", + "auto_impl", + "dyn-clone", +] + [[package]] name = "alloy-hardforks" version = "0.4.7" @@ -440,15 +453,26 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-op-hardforks", + "alloy-op-hardforks 0.4.7", "alloy-primitives 1.5.2", "auto_impl", "op-alloy-consensus", - "op-revm", - "revm", + "op-revm 12.0.2", + "revm 31.0.2", "thiserror 2.0.17", ] +[[package]] +name = "alloy-op-hardforks" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3417f4187eaf7f7fb0d7556f0197bca26f0b23c4bb3aca0c9d566dc1c5d727a2" +dependencies = [ + "alloy-chains", + "alloy-hardforks 0.2.13", + "auto_impl", +] + [[package]] name = "alloy-op-hardforks" version = "0.4.7" @@ -456,7 +480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" dependencies = [ "alloy-chains", - "alloy-hardforks", + "alloy-hardforks 0.4.7", "alloy-primitives 1.5.2", "auto_impl", ] @@ -1854,11 +1878,11 @@ dependencies = [ "alloy-sol-types 1.5.2", "base-primitives", "eyre", - "op-revm", + "op-revm 12.0.2", "reth-evm", "reth-optimism-chainspec", "reth-optimism-evm", - "revm", + "revm 31.0.2", "serde", "tracing", ] @@ -1884,14 +1908,22 @@ dependencies = [ name = "base-cli-utils" version = "0.2.1" dependencies = [ + "alloy-chains", + "alloy-primitives 1.5.2", "clap", "eyre", + "kona-registry", "libc", "metrics-exporter-prometheus 0.18.1", "metrics-process", "reth-node-core", + "rstest", + "serde", + "serde_json", "tokio", "tracing", + "tracing-appender", + "tracing-subscriber 0.3.22", ] [[package]] @@ -2002,8 +2034,8 @@ dependencies = [ "reth-testing-utils", "reth-tracing", "reth-transaction-pool", - "revm", - "revm-database", + "revm 31.0.2", + "revm-database 9.0.6", "rstest", "serde", "serde_json", @@ -2058,7 +2090,7 @@ dependencies = [ "reth-provider", "reth-revm", "reth-transaction-pool", - "revm-database", + "revm-database 9.0.6", "serde", "tokio", "tracing", @@ -5654,6 +5686,42 @@ dependencies = [ "sha3-asm", ] +[[package]] +name = "kona-genesis" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b122a661c9f5efca5783f56674383eb742472796d2d4cf3d12a803b37f6bb11b" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-hardforks 0.2.13", + "alloy-op-hardforks 0.2.13", + "alloy-primitives 1.5.2", + "alloy-sol-types 1.5.2", + "derive_more", + "op-revm 8.1.0", + "serde", + "serde_repr", + "thiserror 2.0.17", +] + +[[package]] +name = "kona-registry" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bfcd0cdb9e3ef882d403dd33f11e9d6a203ce8e00d333b14792454bd7e01de" +dependencies = [ + "alloy-chains", + "alloy-op-hardforks 0.2.13", + "alloy-primitives 1.5.2", + "kona-genesis", + "lazy_static", + "serde", + "serde_json", + "toml 0.8.23", +] + [[package]] name = "kqueue" version = "1.1.1" @@ -5679,6 +5747,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -7139,7 +7210,7 @@ dependencies = [ "op-alloy-network", "op-alloy-rpc-types", "op-alloy-rpc-types-engine", - "op-revm", + "op-revm 12.0.2", "opentelemetry", "p2p", "parking_lot", @@ -7190,7 +7261,7 @@ dependencies = [ "reth-tracing-otlp", "reth-transaction-pool", "reth-trie", - "revm", + "revm 31.0.2", "rlimit", "secp256k1 0.30.0", "serde", @@ -7217,6 +7288,18 @@ dependencies = [ "vergen-git2", ] +[[package]] +name = "op-revm" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce1dc7533f4e5716c55cd3d62488c6200cb4dfda96e0c75a7e484652464343b" +dependencies = [ + "auto_impl", + "once_cell", + "revm 27.1.0", + "serde", +] + [[package]] name = "op-revm" version = "12.0.2" @@ -7224,7 +7307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31622d03b29c826e48800f4c8f389c8a9c440eb796a3e35203561a288f12985" dependencies = [ "auto_impl", - "revm", + "revm 31.0.2", "serde", ] @@ -7565,17 +7648,38 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", + "serde", +] + [[package]] name = "phf" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", - "phf_shared", + "phf_macros 0.13.1", + "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + [[package]] name = "phf_generator" version = "0.13.1" @@ -7583,7 +7687,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ "fastrand", - "phf_shared", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -7592,13 +7709,22 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.13.1", + "phf_shared 0.13.1", "proc-macro2", "quote", "syn 2.0.114", ] +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "phf_shared" version = "0.13.1" @@ -8514,8 +8640,8 @@ dependencies = [ "reth-primitives-traits", "reth-storage-api", "reth-trie", - "revm-database", - "revm-state", + "revm-database 9.0.6", + "revm-state 8.1.1", "serde", "tokio", "tokio-stream", @@ -9011,7 +9137,7 @@ dependencies = [ "reth-tasks", "reth-tokio-util", "reth-tracing", - "revm", + "revm 31.0.2", "serde_json", "tempfile", "tokio", @@ -9168,8 +9294,8 @@ dependencies = [ "reth-trie-parallel", "reth-trie-sparse", "reth-trie-sparse-parallel", - "revm", - "revm-primitives", + "revm 31.0.2", + "revm-primitives 21.0.2", "schnellru", "smallvec", "thiserror 2.0.17", @@ -9305,7 +9431,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-eips", - "alloy-hardforks", + "alloy-hardforks 0.4.7", "alloy-primitives 1.5.2", "alloy-rlp", "bytes", @@ -9358,7 +9484,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eip2124", - "alloy-hardforks", + "alloy-hardforks 0.4.7", "alloy-primitives 1.5.2", "arbitrary", "auto_impl", @@ -9391,7 +9517,7 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-transaction-pool", - "revm", + "revm 31.0.2", "tracing", ] @@ -9445,7 +9571,7 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-trie-common", - "revm", + "revm 31.0.2", ] [[package]] @@ -9465,7 +9591,7 @@ dependencies = [ "reth-execution-types", "reth-primitives-traits", "reth-storage-errors", - "revm", + "revm 31.0.2", ] [[package]] @@ -9494,7 +9620,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-primitives-traits", "reth-trie-common", - "revm", + "revm 31.0.2", "serde", "serde_with", ] @@ -9582,9 +9708,9 @@ dependencies = [ "reth-rpc-api", "reth-tracing", "reth-trie", - "revm", - "revm-bytecode", - "revm-database", + "revm 31.0.2", + "revm-bytecode 7.1.1", + "revm-database 9.0.6", "serde", "serde_json", ] @@ -9996,7 +10122,7 @@ dependencies = [ "reth-rpc-server-types", "reth-tracing", "reth-transaction-pool", - "revm", + "revm 31.0.2", "tokio", ] @@ -10091,7 +10217,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-hardforks", + "alloy-hardforks 0.4.7", "alloy-primitives 1.5.2", "derive_more", "miniz_oxide", @@ -10180,7 +10306,7 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-trie-common", - "revm", + "revm 31.0.2", "thiserror 2.0.17", "tracing", ] @@ -10197,7 +10323,7 @@ dependencies = [ "alloy-primitives 1.5.2", "op-alloy-consensus", "op-alloy-rpc-types-engine", - "op-revm", + "op-revm 12.0.2", "reth-chainspec", "reth-evm", "reth-execution-errors", @@ -10209,7 +10335,7 @@ dependencies = [ "reth-primitives-traits", "reth-rpc-eth-api", "reth-storage-errors", - "revm", + "revm 31.0.2", "thiserror 2.0.17", ] @@ -10257,7 +10383,7 @@ name = "reth-optimism-forks" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-op-hardforks", + "alloy-op-hardforks 0.4.7", "alloy-primitives 1.5.2", "once_cell", "reth-ethereum-forks", @@ -10277,7 +10403,7 @@ dependencies = [ "eyre", "op-alloy-consensus", "op-alloy-rpc-types-engine", - "op-revm", + "op-revm 12.0.2", "reth-chainspec", "reth-consensus", "reth-e2e-test-utils", @@ -10306,7 +10432,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "reth-trie-common", - "revm", + "revm 31.0.2", "serde", "serde_json", "tokio", @@ -10346,7 +10472,7 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-transaction-pool", - "revm", + "revm 31.0.2", "serde", "sha2", "thiserror 2.0.17", @@ -10401,7 +10527,7 @@ dependencies = [ "op-alloy-rpc-jsonrpsee", "op-alloy-rpc-types", "op-alloy-rpc-types-engine", - "op-revm", + "op-revm 12.0.2", "reqwest", "reth-chain-state", "reth-chainspec", @@ -10425,7 +10551,7 @@ dependencies = [ "reth-storage-api", "reth-tasks", "reth-transaction-pool", - "revm", + "revm 31.0.2", "serde_json", "thiserror 2.0.17", "tokio", @@ -10463,7 +10589,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-flz", "op-alloy-rpc-types", - "op-revm", + "op-revm 12.0.2", "parking_lot", "reth-chain-state", "reth-chainspec", @@ -10591,9 +10717,9 @@ dependencies = [ "proptest-arbitrary-interop", "rayon", "reth-codecs", - "revm-bytecode", - "revm-primitives", - "revm-state", + "revm-bytecode 7.1.1", + "revm-primitives 21.0.2", + "revm-state 8.1.1", "secp256k1 0.30.0", "serde", "serde_with", @@ -10637,8 +10763,8 @@ dependencies = [ "reth-storage-errors", "reth-trie", "reth-trie-db", - "revm-database", - "revm-state", + "revm-database 9.0.6", + "revm-state 8.1.1", "strum 0.27.2", "tokio", "tracing", @@ -10696,7 +10822,7 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-trie", - "revm", + "revm 31.0.2", ] [[package]] @@ -10764,9 +10890,9 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie-common", - "revm", + "revm 31.0.2", "revm-inspectors", - "revm-primitives", + "revm-primitives 21.0.2", "serde", "serde_json", "sha2", @@ -10862,13 +10988,13 @@ dependencies = [ "op-alloy-consensus", "op-alloy-network", "op-alloy-rpc-types", - "op-revm", + "op-revm 12.0.2", "reth-ethereum-primitives", "reth-evm", "reth-optimism-primitives", "reth-primitives-traits", "reth-storage-api", - "revm-context", + "revm-context 11.0.2", "thiserror 2.0.17", ] @@ -10940,7 +11066,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie-common", - "revm", + "revm 31.0.2", "revm-inspectors", "tokio", "tracing", @@ -10983,7 +11109,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie", - "revm", + "revm 31.0.2", "revm-inspectors", "schnellru", "serde", @@ -11164,7 +11290,7 @@ dependencies = [ "reth-stages-types", "reth-storage-errors", "reth-trie-common", - "revm-database", + "revm-database 9.0.6", ] [[package]] @@ -11179,7 +11305,7 @@ dependencies = [ "reth-primitives-traits", "reth-prune-types", "reth-static-file-types", - "revm-database-interface", + "revm-database-interface 8.0.5", "thiserror 2.0.17", ] @@ -11289,8 +11415,8 @@ dependencies = [ "reth-primitives-traits", "reth-storage-api", "reth-tasks", - "revm-interpreter", - "revm-primitives", + "revm-interpreter 29.0.1", + "revm-primitives 21.0.2", "rustc-hash 2.1.1", "schnellru", "serde", @@ -11322,7 +11448,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "reth-trie-sparse", - "revm-database", + "revm-database 9.0.6", "tracing", "triehash", ] @@ -11349,7 +11475,7 @@ dependencies = [ "rayon", "reth-codecs", "reth-primitives-traits", - "revm-database", + "revm-database 9.0.6", "serde", "serde_with", ] @@ -11437,23 +11563,54 @@ dependencies = [ "zstd", ] +[[package]] +name = "revm" +version = "27.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6bf82101a1ad8a2b637363a37aef27f88b4efc8a6e24c72bf5f64923dc5532" +dependencies = [ + "revm-bytecode 6.2.2", + "revm-context 8.0.4", + "revm-context-interface 9.0.0", + "revm-database 7.0.5", + "revm-database-interface 7.0.5", + "revm-handler 8.1.0", + "revm-inspector 8.1.0", + "revm-interpreter 24.0.0", + "revm-precompile 25.0.0", + "revm-primitives 20.2.1", + "revm-state 7.0.5", +] + [[package]] name = "revm" version = "31.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb67a5223602113cae59a305acde2d9936bc18f2478dda879a6124b267cebfb6" dependencies = [ - "revm-bytecode", - "revm-context", - "revm-context-interface", - "revm-database", - "revm-database-interface", - "revm-handler", - "revm-inspector", - "revm-interpreter", - "revm-precompile", - "revm-primitives", - "revm-state", + "revm-bytecode 7.1.1", + "revm-context 11.0.2", + "revm-context-interface 12.0.1", + "revm-database 9.0.6", + "revm-database-interface 8.0.5", + "revm-handler 12.0.2", + "revm-inspector 12.0.2", + "revm-interpreter 29.0.1", + "revm-precompile 29.0.1", + "revm-primitives 21.0.2", + "revm-state 8.1.1", +] + +[[package]] +name = "revm-bytecode" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c52031b73cae95d84cd1b07725808b5fd1500da3e5e24574a3b2dc13d9f16d" +dependencies = [ + "bitvec", + "phf 0.11.3", + "revm-primitives 20.2.1", + "serde", ] [[package]] @@ -11463,8 +11620,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2c6b5e6e8dd1e28a4a60e5f46615d4ef0809111c9e63208e55b5c7058200fb0" dependencies = [ "bitvec", - "phf", - "revm-primitives", + "phf 0.13.1", + "revm-primitives 21.0.2", + "serde", +] + +[[package]] +name = "revm-context" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd508416a35a4d8a9feaf5ccd06ac6d6661cd31ee2dc0252f9f7316455d71f9" +dependencies = [ + "cfg-if", + "derive-where", + "revm-bytecode 6.2.2", + "revm-context-interface 9.0.0", + "revm-database-interface 7.0.5", + "revm-primitives 20.2.1", + "revm-state 7.0.5", "serde", ] @@ -11477,11 +11650,27 @@ dependencies = [ "bitvec", "cfg-if", "derive-where", - "revm-bytecode", - "revm-context-interface", - "revm-database-interface", - "revm-primitives", - "revm-state", + "revm-bytecode 7.1.1", + "revm-context-interface 12.0.1", + "revm-database-interface 8.0.5", + "revm-primitives 21.0.2", + "revm-state 8.1.1", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90302642d21c8f93e0876e201f3c5f7913c4fcb66fb465b0fd7b707dfe1c79" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface 7.0.5", + "revm-primitives 20.2.1", + "revm-state 7.0.5", "serde", ] @@ -11495,9 +11684,23 @@ dependencies = [ "alloy-eip7702", "auto_impl", "either", - "revm-database-interface", - "revm-primitives", - "revm-state", + "revm-database-interface 8.0.5", + "revm-primitives 21.0.2", + "revm-state 8.1.1", + "serde", +] + +[[package]] +name = "revm-database" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a276ed142b4718dcf64bc9624f474373ed82ef20611025045c3fb23edbef9c" +dependencies = [ + "alloy-eips", + "revm-bytecode 6.2.2", + "revm-database-interface 7.0.5", + "revm-primitives 20.2.1", + "revm-state 7.0.5", "serde", ] @@ -11508,10 +11711,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "980d8d6bba78c5dd35b83abbb6585b0b902eb25ea4448ed7bfba6283b0337191" dependencies = [ "alloy-eips", - "revm-bytecode", - "revm-database-interface", - "revm-primitives", - "revm-state", + "revm-bytecode 7.1.1", + "revm-database-interface 8.0.5", + "revm-primitives 21.0.2", + "revm-state 8.1.1", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c523c77e74eeedbac5d6f7c092e3851dbe9c7fec6f418b85992bd79229db361" +dependencies = [ + "auto_impl", + "either", + "revm-primitives 20.2.1", + "revm-state 7.0.5", "serde", ] @@ -11523,8 +11739,27 @@ checksum = "8cce03e3780287b07abe58faf4a7f5d8be7e81321f93ccf3343c8f7755602bae" dependencies = [ "auto_impl", "either", - "revm-primitives", - "revm-state", + "revm-primitives 21.0.2", + "revm-state 8.1.1", + "serde", +] + +[[package]] +name = "revm-handler" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1529c8050e663be64010e80ec92bf480315d21b1f2dbf65540028653a621b27d" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode 6.2.2", + "revm-context 8.0.4", + "revm-context-interface 9.0.0", + "revm-database-interface 7.0.5", + "revm-interpreter 24.0.0", + "revm-precompile 25.0.0", + "revm-primitives 20.2.1", + "revm-state 7.0.5", "serde", ] @@ -11536,14 +11771,31 @@ checksum = "b45418ed95cfdf0cb19effdbb7633cf2144cab7fb0e6ffd6b0eb9117a50adff6" dependencies = [ "auto_impl", "derive-where", - "revm-bytecode", - "revm-context", - "revm-context-interface", - "revm-database-interface", - "revm-interpreter", - "revm-precompile", - "revm-primitives", - "revm-state", + "revm-bytecode 7.1.1", + "revm-context 11.0.2", + "revm-context-interface 12.0.1", + "revm-database-interface 8.0.5", + "revm-interpreter 29.0.1", + "revm-precompile 29.0.1", + "revm-primitives 21.0.2", + "revm-state 8.1.1", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "8.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78db140e332489094ef314eaeb0bd1849d6d01172c113ab0eb6ea8ab9372926" +dependencies = [ + "auto_impl", + "either", + "revm-context 8.0.4", + "revm-database-interface 7.0.5", + "revm-handler 8.1.0", + "revm-interpreter 24.0.0", + "revm-primitives 20.2.1", + "revm-state 7.0.5", "serde", ] @@ -11555,12 +11807,12 @@ checksum = "c99801eac7da06cc112df2244bd5a64024f4ef21240e923b26e73c4b4a0e5da6" dependencies = [ "auto_impl", "either", - "revm-context", - "revm-database-interface", - "revm-handler", - "revm-interpreter", - "revm-primitives", - "revm-state", + "revm-context 11.0.2", + "revm-database-interface 8.0.5", + "revm-handler 12.0.2", + "revm-interpreter 29.0.1", + "revm-primitives 21.0.2", + "revm-state 8.1.1", "serde", "serde_json", ] @@ -11577,25 +11829,59 @@ dependencies = [ "alloy-sol-types 1.5.2", "anstyle", "colorchoice", - "revm", + "revm 31.0.2", "serde", "serde_json", "thiserror 2.0.17", ] +[[package]] +name = "revm-interpreter" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff9d7d9d71e8a33740b277b602165b6e3d25fff091ba3d7b5a8d373bf55f28a7" +dependencies = [ + "revm-bytecode 6.2.2", + "revm-context-interface 9.0.0", + "revm-primitives 20.2.1", + "serde", +] + [[package]] name = "revm-interpreter" version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22789ce92c5808c70185e3bc49732f987dc6fd907f77828c8d3470b2299c9c65" dependencies = [ - "revm-bytecode", - "revm-context-interface", - "revm-primitives", - "revm-state", + "revm-bytecode 7.1.1", + "revm-context-interface 12.0.1", + "revm-primitives 21.0.2", + "revm-state 8.1.1", "serde", ] +[[package]] +name = "revm-precompile" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cee3f336b83621294b4cfe84d817e3eef6f3d0fce00951973364cc7f860424d" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "cfg-if", + "k256", + "once_cell", + "p256", + "revm-primitives 20.2.1", + "ripemd", + "sha2", +] + [[package]] name = "revm-precompile" version = "29.0.1" @@ -11614,13 +11900,25 @@ dependencies = [ "cfg-if", "k256", "p256", - "revm-primitives", + "revm-primitives 21.0.2", "ripemd", "rug", "secp256k1 0.31.1", "sha2", ] +[[package]] +name = "revm-primitives" +version = "20.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" +dependencies = [ + "alloy-primitives 1.5.2", + "num_enum", + "once_cell", + "serde", +] + [[package]] name = "revm-primitives" version = "21.0.2" @@ -11633,6 +11931,18 @@ dependencies = [ "serde", ] +[[package]] +name = "revm-state" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d" +dependencies = [ + "bitflags 2.10.0", + "revm-bytecode 6.2.2", + "revm-primitives 20.2.1", + "serde", +] + [[package]] name = "revm-state" version = "8.1.1" @@ -11640,8 +11950,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8be953b7e374dbdea0773cf360debed8df394ea8d82a8b240a6b5da37592fc" dependencies = [ "bitflags 2.10.0", - "revm-bytecode", - "revm-primitives", + "revm-bytecode 7.1.1", + "revm-primitives 21.0.2", "serde", ] @@ -12645,6 +12955,12 @@ dependencies = [ "sha1", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" diff --git a/Cargo.toml b/Cargo.toml index 7b9cfb14..e727a771 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -195,6 +195,9 @@ alloy-op-hardforks = "0.4.4" # op-revm op-revm = { version = "12.0.2", default-features = false } +# kona +kona-registry = "0.4.5" + # tokio tokio = "1.48.0" tokio-stream = "0.1.17" @@ -244,6 +247,7 @@ derive_more = "2.1.0" serde_json = "1.0.145" metrics-derive = "0.1.0" tracing-subscriber = "0.3.22" +tracing-appender = "0.2" thiserror = "2.0" async-trait = "0.1.83" parking_lot = "0.12.3" diff --git a/crates/shared/cli-utils/Cargo.toml b/crates/shared/cli-utils/Cargo.toml index 858aa3e6..81f5ad12 100644 --- a/crates/shared/cli-utils/Cargo.toml +++ b/crates/shared/cli-utils/Cargo.toml @@ -12,16 +12,32 @@ description = "CLI Utilities" workspace = true [dependencies] +# General eyre.workspace = true -tracing.workspace = true -reth-node-core.workspace = true - tokio = { workspace = true, features = ["full"] } clap = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive"] } + +# Alloy +alloy-primitives.workspace = true +alloy-chains.workspace = true + +# Kona +kona-registry.workspace = true + +# Tracing +tracing.workspace = true +tracing-appender.workspace = true +tracing-subscriber = { workspace = true, features = ["fmt", "env-filter", "json", "tracing-log"] } +reth-node-core.workspace = true # Metrics metrics-process.workspace = true metrics-exporter-prometheus = { workspace = true, features = ["http-listener"] } +[dev-dependencies] +rstest.workspace = true +serde_json.workspace = true + [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/crates/shared/cli-utils/src/args.rs b/crates/shared/cli-utils/src/args.rs index de4895f1..d293ef60 100644 --- a/crates/shared/cli-utils/src/args.rs +++ b/crates/shared/cli-utils/src/args.rs @@ -1,17 +1,228 @@ -//! Global CLI arguments for the Base Reth node. +//! Global CLI arguments for the Base stack. -use super::LoggingArgs; +use std::path::PathBuf; + +use alloy_primitives::Address; +use clap::{ArgAction, Parser}; +use kona_registry::OPCHAINS; + +use crate::{ + FileLogConfig, LogConfig, LogFormat, LogRotation, StdoutLogConfig, verbosity_to_level_filter, +}; + +/// Log-related CLI arguments. +/// +/// Verbosity levels: 1=ERROR, 2=WARN, 3=INFO (default), 4=DEBUG, 5=TRACE. +/// Use `-q` to suppress stdout logging entirely. +#[derive(Debug, Clone, Parser)] +pub struct LogArgs { + /// Increase logging verbosity (1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=TRACE). + #[arg( + short = 'v', + long = "verbose", + action = ArgAction::Count, + default_value = "3", + env = "BASE_LOG_LEVEL", + global = true + )] + pub level: u8, + + /// Suppress stdout logging. + #[arg(long = "quiet", short = 'q', global = true)] + pub stdout_quiet: bool, + + /// Stdout log format. + #[arg(long = "log-format", default_value = "full", env = "BASE_STDOUT_FORMAT", global = true)] + pub stdout_format: LogFormat, + + /// Directory for file logging (enables file logging when set). + #[arg(long = "log-dir", env = "BASE_LOG_DIR", global = true)] + pub file_directory: Option, + + /// File log format. + #[arg(long = "log-file-format", default_value = "json", global = true)] + pub file_format: LogFormat, + + /// File log rotation strategy. + #[arg(long = "log-rotation", default_value = "never", global = true)] + pub file_rotation: LogRotation, +} + +impl Default for LogArgs { + fn default() -> Self { + Self { + level: 3, // INFO + stdout_quiet: false, + stdout_format: LogFormat::Full, + file_directory: None, + file_format: LogFormat::Json, + file_rotation: LogRotation::Never, + } + } +} + +impl From for LogConfig { + fn from(args: LogArgs) -> Self { + let stdout_logs = if args.stdout_quiet { + None + } else { + Some(StdoutLogConfig { format: args.stdout_format }) + }; + + let file_logs = args.file_directory.map(|dir| FileLogConfig { + directory_path: dir, + format: args.file_format, + rotation: args.file_rotation, + }); + + Self { global_level: verbosity_to_level_filter(args.level), stdout_logs, file_logs } + } +} /// Global arguments shared across all CLI commands. /// /// Chain ID defaults to Base Mainnet (8453). Can be set via `--chain-id` or `BASE_NETWORK` env. -#[derive(Debug, Clone, clap::Args)] +#[derive(Debug, Clone, Parser)] pub struct GlobalArgs { - /// Chain ID (8453 = Base Mainnet, 84532 = Base Sepolia). - #[arg(long = "chain-id", env = "BASE_NETWORK", default_value = "8453")] - pub chain_id: u64, + /// L2 Chain ID (8453 = Base Mainnet, 84532 = Base Sepolia). + #[arg( + long = "chain", + alias = "chain-id", + short = 'c', + global = true, + default_value = "8453", + env = "BASE_NETWORK" + )] + pub l2_chain_id: alloy_chains::Chain, /// Logging configuration. #[command(flatten)] - pub logging: LoggingArgs, + pub logging: LogArgs, +} + +impl GlobalArgs { + /// Returns the signer [`Address`] from the rollup config for the given l2 chain id. + pub fn genesis_signer(&self) -> eyre::Result
{ + let id = self.l2_chain_id; + OPCHAINS + .get(&id.id()) + .ok_or(eyre::eyre!("No chain config found for chain ID: {id}"))? + .roles + .as_ref() + .ok_or(eyre::eyre!("No roles found for chain ID: {id}"))? + .unsafe_block_signer + .ok_or(eyre::eyre!("No unsafe block signer found for chain ID: {id}")) + } +} + +#[cfg(test)] +mod tests { + use clap::Parser; + use rstest::rstest; + use tracing::level_filters::LevelFilter; + + use super::*; + + #[derive(Parser)] + struct TestCli { + #[command(flatten)] + log: LogArgs, + } + + fn parse_log_args(args: &[&str]) -> LogArgs { + TestCli::parse_from(std::iter::once("test").chain(args.iter().copied())).log + } + + #[rstest] + #[case::default(&[], 3)] + #[case::single_v(&["-v"], 1)] + #[case::double_v(&["-vv"], 2)] + #[case::triple_v(&["-vvv"], 3)] + #[case::quad_v(&["-vvvv"], 4)] + #[case::quint_v(&["-vvvvv"], 5)] + fn verbosity_parsing(#[case] args: &[&str], #[case] expected: u8) { + // Note: ArgAction::Count counts occurrences. When no -v flags are passed, + // the default_value of "3" (INFO) is used. + assert_eq!(parse_log_args(args).level, expected); + } + + #[test] + fn quiet_mode_short_flag() { + assert!(parse_log_args(&["-q"]).stdout_quiet); + } + + #[test] + fn quiet_mode_long_flag() { + assert!(parse_log_args(&["--quiet"]).stdout_quiet); + } + + #[rstest] + #[case::full("full", LogFormat::Full)] + #[case::compact("compact", LogFormat::Compact)] + #[case::json("json", LogFormat::Json)] + #[case::pretty("pretty", LogFormat::Pretty)] + #[case::logfmt("logfmt", LogFormat::Logfmt)] + fn stdout_format_parsing(#[case] format_str: &str, #[case] expected: LogFormat) { + assert_eq!(parse_log_args(&["--log-format", format_str]).stdout_format, expected); + } + + #[test] + fn log_args_to_config_default() { + let args = LogArgs::default(); + let config: LogConfig = args.into(); + + assert_eq!(config.global_level, LevelFilter::INFO); + assert!(config.stdout_logs.is_some()); + assert!(config.file_logs.is_none()); + } + + #[test] + fn log_args_to_config_quiet_disables_stdout() { + let args = LogArgs { stdout_quiet: true, ..Default::default() }; + let config: LogConfig = args.into(); + + assert!(config.stdout_logs.is_none()); + } + + #[test] + fn log_args_to_config_with_file_logging() { + let args = LogArgs { + file_directory: Some("/var/log/test".into()), + file_format: LogFormat::Json, + file_rotation: LogRotation::Hourly, + ..Default::default() + }; + let config: LogConfig = args.into(); + + let file_config = config.file_logs.expect("file_logs should be Some"); + assert_eq!(file_config.directory_path, std::path::PathBuf::from("/var/log/test")); + assert_eq!(file_config.format, LogFormat::Json); + assert_eq!(file_config.rotation, LogRotation::Hourly); + } + + #[test] + fn global_args_default_chain() { + #[derive(Parser)] + struct GlobalCli { + #[command(flatten)] + global: GlobalArgs, + } + + let cli = GlobalCli::parse_from(["test"]); + assert_eq!(cli.global.l2_chain_id.id(), 8453); // Base Mainnet + } + + #[rstest] + #[case::base_mainnet("8453", 8453)] + #[case::base_sepolia("84532", 84532)] + fn chain_id_parsing(#[case] chain_str: &str, #[case] expected_id: u64) { + #[derive(Parser)] + struct GlobalCli { + #[command(flatten)] + global: GlobalArgs, + } + + let cli = GlobalCli::parse_from(["test", "--chain", chain_str]); + assert_eq!(cli.global.l2_chain_id.id(), expected_id); + } } diff --git a/crates/shared/cli-utils/src/lib.rs b/crates/shared/cli-utils/src/lib.rs index 348b41a0..2563d895 100644 --- a/crates/shared/cli-utils/src/lib.rs +++ b/crates/shared/cli-utils/src/lib.rs @@ -10,13 +10,18 @@ mod backtrace; pub use backtrace::Backtracing; mod args; -pub use args::GlobalArgs; +pub use args::{GlobalArgs, LogArgs}; mod prometheus; pub use prometheus::PrometheusServer; mod logging; -pub use logging::{LogFormat, LogRotation, LoggingArgs}; +pub use logging::{ + FileLogConfig, LogConfig, LogFormat, LogRotation, StdoutLogConfig, verbosity_to_level_filter, +}; + +mod tracing; +pub use tracing::{LogfmtFormatter, init_test_tracing}; mod version; pub use version::Version; diff --git a/crates/shared/cli-utils/src/logging.rs b/crates/shared/cli-utils/src/logging.rs index 0ab096e1..2dfcc37f 100644 --- a/crates/shared/cli-utils/src/logging.rs +++ b/crates/shared/cli-utils/src/logging.rs @@ -1,136 +1,163 @@ -//! Logging configuration for CLI applications. +//! Logging Configuration Types use std::path::PathBuf; -use clap::{ArgAction, Parser, ValueEnum}; +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; +use tracing::level_filters::LevelFilter; /// Log output format. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, ValueEnum, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum LogFormat { /// Full format with timestamp, level, target, and spans. #[default] Full, - /// Compact format with minimal metadata. - Compact, /// JSON format for structured logging. Json, + /// Pretty format with colors (for development). + Pretty, + /// Compact format with minimal metadata. + Compact, + /// Logfmt format (key=value pairs). + Logfmt, } /// Log rotation strategy. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, ValueEnum, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum LogRotation { /// Rotate every minute (for testing). Minutely, /// Rotate every hour. - #[default] Hourly, /// Rotate every day. Daily, - /// Never rotate. + /// Never rotate (default). + #[default] Never, } -/// Logging configuration arguments. -/// -/// Verbosity: `-v` (INFO), `-vv` (DEBUG), `-vvv` (TRACE). Default is WARN. -#[derive(Debug, Clone, Default, PartialEq, Eq, Parser)] -pub struct LoggingArgs { - /// Increase logging verbosity (-v, -vv, -vvv). - #[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)] - pub verbosity: u8, - - /// Log output format (full, compact, json). - #[arg(long = "log-format", default_value = "full", global = true)] +/// Stdout logging configuration. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct StdoutLogConfig { + /// Output format for stdout logs. pub format: LogFormat, - - /// Path to write logs to a file. - #[arg(long = "log-file", global = true)] - pub log_file: Option, - - /// Log file rotation (minutely, hourly, daily, never). - #[arg(long = "log-rotation", global = true)] - pub log_rotation: Option, } -impl LoggingArgs { - /// Converts verbosity to a [`tracing::Level`]. - #[inline] - pub const fn log_level(&self) -> tracing::Level { - match self.verbosity { - 0 => tracing::Level::WARN, - 1 => tracing::Level::INFO, - 2 => tracing::Level::DEBUG, - _ => tracing::Level::TRACE, - } +impl Default for StdoutLogConfig { + fn default() -> Self { + Self { format: LogFormat::Full } } +} - /// Converts verbosity to a [`tracing::level_filters::LevelFilter`]. - #[inline] - pub const fn log_level_filter(&self) -> tracing::level_filters::LevelFilter { - match self.verbosity { - 0 => tracing::level_filters::LevelFilter::WARN, - 1 => tracing::level_filters::LevelFilter::INFO, - 2 => tracing::level_filters::LevelFilter::DEBUG, - _ => tracing::level_filters::LevelFilter::TRACE, - } - } +/// File logging configuration. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FileLogConfig { + /// Directory path for log files. + pub directory_path: PathBuf, + /// Output format for file logs. + pub format: LogFormat, + /// Log rotation strategy. + pub rotation: LogRotation, +} - /// Returns `true` if file logging is enabled. - #[inline] - pub const fn has_file_logging(&self) -> bool { - self.log_file.is_some() - } +/// Complete logging configuration. +#[derive(Debug, Clone)] +pub struct LogConfig { + /// Global log level filter. + pub global_level: LevelFilter, + /// Stdout logging config (None = disabled). + pub stdout_logs: Option, + /// File logging config (None = disabled). + pub file_logs: Option, +} - /// Returns the log rotation strategy, defaulting to [`LogRotation::Hourly`]. - #[inline] - pub fn rotation(&self) -> LogRotation { - self.log_rotation.unwrap_or_default() +impl Default for LogConfig { + fn default() -> Self { + Self { + global_level: LevelFilter::INFO, + stdout_logs: Some(StdoutLogConfig::default()), + file_logs: None, + } } +} - /// Returns `true` if the log format is JSON. - #[inline] - pub const fn is_json_format(&self) -> bool { - matches!(self.format, LogFormat::Json) +/// Converts verbosity count (1-5) to [`LevelFilter`]. +/// +/// - 1 = ERROR +/// - 2 = WARN +/// - 3 = INFO (default) +/// - 4 = DEBUG +/// - 5+ = TRACE +#[inline] +pub const fn verbosity_to_level_filter(verbosity: u8) -> LevelFilter { + match verbosity { + 1 => LevelFilter::ERROR, + 2 => LevelFilter::WARN, + 3 => LevelFilter::INFO, + 4 => LevelFilter::DEBUG, + _ => LevelFilter::TRACE, } } #[cfg(test)] mod tests { + use rstest::rstest; + use super::*; + #[rstest] + #[case::error(1, LevelFilter::ERROR)] + #[case::warn(2, LevelFilter::WARN)] + #[case::info(3, LevelFilter::INFO)] + #[case::debug(4, LevelFilter::DEBUG)] + #[case::trace(5, LevelFilter::TRACE)] + #[case::saturates_high(10, LevelFilter::TRACE)] + fn verbosity_mapping(#[case] level: u8, #[case] expected: LevelFilter) { + assert_eq!(verbosity_to_level_filter(level), expected); + } + + #[rstest] + #[case::full(LogFormat::Full, "full")] + #[case::json(LogFormat::Json, "json")] + #[case::pretty(LogFormat::Pretty, "pretty")] + #[case::compact(LogFormat::Compact, "compact")] + #[case::logfmt(LogFormat::Logfmt, "logfmt")] + fn log_format_serde(#[case] format: LogFormat, #[case] str_val: &str) { + let json = serde_json::to_string(&format).unwrap(); + assert!(json.contains(str_val)); + let parsed: LogFormat = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed, format); + } + + #[rstest] + #[case::minutely(LogRotation::Minutely, "minutely")] + #[case::hourly(LogRotation::Hourly, "hourly")] + #[case::daily(LogRotation::Daily, "daily")] + #[case::never(LogRotation::Never, "never")] + fn log_rotation_serde(#[case] rotation: LogRotation, #[case] str_val: &str) { + let json = serde_json::to_string(&rotation).unwrap(); + assert!(json.contains(str_val)); + let parsed: LogRotation = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed, rotation); + } + #[test] - fn test_verbosity_levels() { - assert_eq!(LoggingArgs::default().log_level(), tracing::Level::WARN); - assert_eq!( - LoggingArgs { verbosity: 1, ..Default::default() }.log_level(), - tracing::Level::INFO - ); - assert_eq!( - LoggingArgs { verbosity: 2, ..Default::default() }.log_level(), - tracing::Level::DEBUG - ); - assert_eq!( - LoggingArgs { verbosity: 3, ..Default::default() }.log_level(), - tracing::Level::TRACE - ); + fn log_format_default_is_full() { + assert_eq!(LogFormat::default(), LogFormat::Full); } #[test] - fn test_format_and_rotation() { - let args = LoggingArgs::default(); - assert_eq!(args.format, LogFormat::Full); - assert!(!args.is_json_format()); - assert_eq!(args.rotation(), LogRotation::Hourly); - - let args = LoggingArgs { format: LogFormat::Json, ..Default::default() }; - assert!(args.is_json_format()); + fn log_rotation_default_is_never() { + assert_eq!(LogRotation::default(), LogRotation::Never); } #[test] - fn test_file_logging() { - assert!(!LoggingArgs::default().has_file_logging()); - let args = - LoggingArgs { log_file: Some(PathBuf::from("/tmp/test.log")), ..Default::default() }; - assert!(args.has_file_logging()); + fn log_config_default() { + let cfg = LogConfig::default(); + assert_eq!(cfg.global_level, LevelFilter::INFO); + assert!(cfg.stdout_logs.is_some()); + assert!(cfg.file_logs.is_none()); } } diff --git a/crates/shared/cli-utils/src/tracing.rs b/crates/shared/cli-utils/src/tracing.rs new file mode 100644 index 00000000..e93e9357 --- /dev/null +++ b/crates/shared/cli-utils/src/tracing.rs @@ -0,0 +1,175 @@ +//! Tracing subscriber initialization for CLI applications. + +use std::{fmt, io, sync::Once}; + +use tracing::Subscriber; +use tracing_appender::rolling::{RollingFileAppender, Rotation}; +use tracing_subscriber::{ + EnvFilter, Layer, + fmt::{ + FmtContext, FormattedFields, + format::{FormatEvent, FormatFields, Writer}, + time::{FormatTime, SystemTime}, + }, + layer::SubscriberExt, + registry::LookupSpan, + util::SubscriberInitExt, +}; + +use crate::{FileLogConfig, LogConfig, LogFormat, LogRotation, StdoutLogConfig}; + +/// Custom logfmt formatter for tracing events. +/// +/// Outputs logs in logfmt format: `ts=... level=info target=myapp msg="hello world"` +#[derive(Debug, Clone, Copy, Default)] +pub struct LogfmtFormatter; + +impl FormatEvent for LogfmtFormatter +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &tracing::Event<'_>, + ) -> fmt::Result { + let meta = event.metadata(); + + // Write timestamp + let time_format = SystemTime; + write!(writer, "time=\"")?; + time_format.format_time(&mut writer)?; + write!(writer, "\" ")?; + + // Write level + write!(writer, "level={} ", meta.level())?; + + // Write target + write!(writer, "target={} ", meta.target())?; + + // Write the message and fields + write!(writer, "msg=\"")?; + ctx.field_format().format_fields(writer.by_ref(), event)?; + write!(writer, "\"")?; + + // Write span context + if let Some(scope) = ctx.event_scope() { + for span in scope.from_root() { + write!(writer, " {}={{", span.name())?; + if let Some(fields) = span.extensions().get::>() { + write!(writer, "{}", fields)?; + } + write!(writer, "}}")?; + } + } + + writeln!(writer) + } +} + +impl LogConfig { + /// Initialize the tracing subscriber with the configured options. + /// + /// This sets the global default subscriber. Should only be called once. + pub fn init_tracing_subscriber(&self) -> eyre::Result<()> { + // Build base filter from config, allowing env override + let filter = + EnvFilter::builder().with_default_directive(self.global_level.into()).from_env_lossy(); + + let registry = tracing_subscriber::registry().with(filter); + + // Build stdout layer + let stdout_layer = self.stdout_logs.as_ref().map(build_stdout_layer); + + // Build file layer + let file_layer = self.file_logs.as_ref().map(build_file_layer); + + // Combine and init + registry + .with(stdout_layer) + .with(file_layer) + .try_init() + .map_err(|e| eyre::eyre!("Failed to initialize tracing subscriber: {}", e)) + } +} + +/// Build a stdout layer with the specified format. +fn build_stdout_layer(config: &StdoutLogConfig) -> Box + Send + Sync> +where + S: Subscriber + for<'a> LookupSpan<'a> + Send + Sync, +{ + let base = tracing_subscriber::fmt::layer() + .with_writer(io::stdout) + .with_ansi(true) + .with_timer(SystemTime); + + match config.format { + LogFormat::Full => Box::new(base), + LogFormat::Compact => Box::new(base.compact()), + LogFormat::Json => Box::new(base.json()), + LogFormat::Pretty => Box::new(base.pretty()), + LogFormat::Logfmt => Box::new(base.event_format(LogfmtFormatter)), + } +} + +/// Build a file layer with the specified format and rotation. +fn build_file_layer(config: &FileLogConfig) -> Box + Send + Sync> +where + S: Subscriber + for<'a> LookupSpan<'a> + Send + Sync, +{ + let rotation = match config.rotation { + LogRotation::Minutely => Rotation::MINUTELY, + LogRotation::Hourly => Rotation::HOURLY, + LogRotation::Daily => Rotation::DAILY, + LogRotation::Never => Rotation::NEVER, + }; + + let appender = RollingFileAppender::new(rotation, &config.directory_path, "base-node.log"); + + let (non_blocking, guard) = tracing_appender::non_blocking(appender); + + // Store guard in a static to prevent dropping (leak intentionally to keep writer alive) + std::mem::forget(guard); + + let base = tracing_subscriber::fmt::layer() + .with_writer(non_blocking) + .with_ansi(false) + .with_timer(SystemTime); + + match config.format { + LogFormat::Full => Box::new(base), + LogFormat::Compact => Box::new(base.compact()), + LogFormat::Json => Box::new(base.json()), + LogFormat::Pretty => Box::new(base.pretty()), + LogFormat::Logfmt => Box::new(base.event_format(LogfmtFormatter)), + } +} + +/// Initialize tracing for tests with sensible defaults. +/// +/// Uses `tracing_subscriber::fmt().with_test_writer()` for test output capture. +/// Only initializes once (safe to call from multiple tests). +pub fn init_test_tracing() { + static INIT: Once = Once::new(); + + INIT.call_once(|| { + let filter = EnvFilter::builder() + .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) + .from_env_lossy(); + + let _ = tracing_subscriber::fmt().with_env_filter(filter).with_test_writer().try_init(); + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn init_test_tracing_idempotent() { + init_test_tracing(); + init_test_tracing(); // Should not panic + } +} From 3ec85cf068522e20b0133ea958241c74eef21831 Mon Sep 17 00:00:00 2001 From: Andreas Bigger Date: Tue, 13 Jan 2026 11:33:42 -0500 Subject: [PATCH 2/2] chore(cli-utils): network arg --- crates/shared/cli-utils/Cargo.toml | 2 +- crates/shared/cli-utils/src/args.rs | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/shared/cli-utils/Cargo.toml b/crates/shared/cli-utils/Cargo.toml index 81f5ad12..da64b7fa 100644 --- a/crates/shared/cli-utils/Cargo.toml +++ b/crates/shared/cli-utils/Cargo.toml @@ -32,7 +32,7 @@ tracing-subscriber = { workspace = true, features = ["fmt", "env-filter", "json" reth-node-core.workspace = true # Metrics -metrics-process.workspace = true +metrics-process = { workspace = true, features = ["metrics-rs"] } metrics-exporter-prometheus = { workspace = true, features = ["http-listener"] } [dev-dependencies] diff --git a/crates/shared/cli-utils/src/args.rs b/crates/shared/cli-utils/src/args.rs index d293ef60..4e147400 100644 --- a/crates/shared/cli-utils/src/args.rs +++ b/crates/shared/cli-utils/src/args.rs @@ -81,14 +81,14 @@ impl From for LogConfig { /// Global arguments shared across all CLI commands. /// -/// Chain ID defaults to Base Mainnet (8453). Can be set via `--chain-id` or `BASE_NETWORK` env. +/// Chain ID defaults to Base Mainnet (8453). Can be set via `--network` or `BASE_NETWORK` env. #[derive(Debug, Clone, Parser)] pub struct GlobalArgs { - /// L2 Chain ID (8453 = Base Mainnet, 84532 = Base Sepolia). + /// L2 Chain ID or name (8453 = Base Mainnet, 84532 = Base Sepolia). #[arg( - long = "chain", + long = "network", alias = "chain-id", - short = 'c', + short = 'n', global = true, default_value = "8453", env = "BASE_NETWORK" @@ -222,7 +222,19 @@ mod tests { global: GlobalArgs, } - let cli = GlobalCli::parse_from(["test", "--chain", chain_str]); + let cli = GlobalCli::parse_from(["test", "--network", chain_str]); assert_eq!(cli.global.l2_chain_id.id(), expected_id); } + + #[test] + fn network_name_parsing() { + #[derive(Parser)] + struct GlobalCli { + #[command(flatten)] + global: GlobalArgs, + } + + let cli = GlobalCli::parse_from(["test", "--network", "base"]); + assert_eq!(cli.global.l2_chain_id.id(), 8453); // Base Mainnet + } }