Skip to content

Commit 5f46038

Browse files
authored
feat(l1,l2): enable log levels update in runtime (#4324)
**Motivation** <!-- Why does this pull request exist? What are its goals? --> Sometimes we want to change the log level, primarly for debugging, and restarting the node would break the case of study. **Description** <!-- A clear and concise general description of the changes this PR introduces --> Add a custom `admin_setLogLevel` endpoint that enables the node operator to specify a new log filter, just like with `RUST_LOG`. How to test: 1. Run a node (e.g. `ethrex --dev`) 2. Change the log levels: ``` curl localhost:8545 -H 'content-type: application/json' -d '{"jsonrpc": "2.0", "id": "1", "method": "admin_setLogLevel", "params": ["ethrex_dev::block_producer=info"]}' ``` 3. You should now only see `ethrex_dev::block_producer` logs > [!WARNING] > The `admin` namespace is currently unauthenticated and cannot be turned off. Be aware of this in public nodes <!-- Link to issues: Resolves #111, Resolves #222 --> Closes #4299
1 parent 667399b commit 5f46038

File tree

11 files changed

+84
-15
lines changed

11 files changed

+84
-15
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/ethrex/cli.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,9 @@ impl Subcommand {
313313
// L2 has its own init_tracing because of the ethrex monitor
314314
match self {
315315
Self::L2(_) => {}
316-
_ => init_tracing(opts),
316+
_ => {
317+
init_tracing(opts);
318+
}
317319
}
318320
match self {
319321
Subcommand::RemoveDB { datadir, force } => {

cmd/ethrex/ethrex.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ async fn main() -> eyre::Result<()> {
3737
return subcommand.run(&opts).await;
3838
}
3939

40-
init_tracing(&opts);
40+
let log_filter_handler = init_tracing(&opts);
4141

42-
let (data_dir, cancel_token, peer_table, local_node_record) = init_l1(opts).await?;
42+
let (data_dir, cancel_token, peer_table, local_node_record) =
43+
init_l1(opts, Some(log_filter_handler)).await?;
4344

4445
let mut signal_terminate = signal(SignalKind::terminate())?;
4546

cmd/ethrex/initializers.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,18 @@ use tokio::sync::Mutex;
3737
use tokio_util::{sync::CancellationToken, task::TaskTracker};
3838
use tracing::{debug, error, info, warn};
3939
use tracing_subscriber::{
40-
EnvFilter, Layer, Registry, filter::Directive, fmt, layer::SubscriberExt,
40+
EnvFilter, Layer, Registry, filter::Directive, fmt, layer::SubscriberExt, reload,
4141
};
4242

43-
pub fn init_tracing(opts: &Options) {
43+
pub fn init_tracing(opts: &Options) -> reload::Handle<EnvFilter, Registry> {
4444
let log_filter = EnvFilter::builder()
4545
.with_default_directive(Directive::from(opts.log_level))
4646
.from_env_lossy()
4747
.add_directive(Directive::from(opts.log_level));
4848

49-
let fmt_layer = fmt::layer().with_filter(log_filter);
49+
let (filter, filter_handle) = reload::Layer::new(log_filter);
50+
51+
let fmt_layer = fmt::layer().with_filter(filter);
5052
let subscriber: Box<dyn tracing::Subscriber + Send + Sync> = if opts.metrics_enabled {
5153
let profiling_layer = FunctionProfilingLayer::default();
5254
Box::new(Registry::default().with(fmt_layer).with(profiling_layer))
@@ -55,6 +57,8 @@ pub fn init_tracing(opts: &Options) {
5557
};
5658

5759
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
60+
61+
filter_handle
5862
}
5963

6064
pub fn init_metrics(opts: &Options, tracker: TaskTracker) {
@@ -131,6 +135,7 @@ pub async fn init_rpc_api(
131135
blockchain: Arc<Blockchain>,
132136
cancel_token: CancellationToken,
133137
tracker: TaskTracker,
138+
log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
134139
) {
135140
let peer_handler = PeerHandler::new(peer_table);
136141

@@ -156,6 +161,7 @@ pub async fn init_rpc_api(
156161
syncer,
157162
peer_handler,
158163
get_client_version(),
164+
log_filter_handler,
159165
);
160166

161167
tracker.spawn(rpc_api);
@@ -366,6 +372,7 @@ async fn set_sync_block(store: &Store) {
366372

367373
pub async fn init_l1(
368374
opts: Options,
375+
log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
369376
) -> eyre::Result<(String, CancellationToken, Kademlia, Arc<Mutex<NodeRecord>>)> {
370377
let data_dir = init_datadir(&opts.datadir);
371378

@@ -406,6 +413,7 @@ pub async fn init_l1(
406413
blockchain.clone(),
407414
cancel_token.clone(),
408415
tracker.clone(),
416+
log_filter_handler,
409417
)
410418
.await;
411419

cmd/ethrex/l2/command.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,19 @@ impl L2Command {
6464

6565
let matches = app.try_get_matches_from(args_with_program)?;
6666
let init_options = Options::from_arg_matches(&matches)?;
67-
l2::init_tracing(&init_options);
67+
let log_filter_handler = l2::init_tracing(&init_options);
6868
let mut l2_options = init_options;
6969

7070
if l2_options.node_opts.dev {
7171
println!("Removing L1 and L2 databases...");
7272
remove_db(DB_ETHREX_DEV_L1, true);
7373
remove_db(DB_ETHREX_DEV_L2, true);
7474
println!("Initializing L1");
75-
init_l1(crate::cli::Options::default_l1()).await?;
75+
init_l1(
76+
crate::cli::Options::default_l1(),
77+
log_filter_handler.clone(),
78+
)
79+
.await?;
7680
println!("Deploying contracts...");
7781
let contract_addresses =
7882
l2::deployer::deploy_l1_contracts(l2::deployer::DeployerOptions::default()).await?;
@@ -89,7 +93,7 @@ impl L2Command {
8993
Some(contract_addresses.bridge_address);
9094
println!("Initializing L2");
9195
}
92-
l2::init_l2(l2_options).await?;
96+
l2::init_l2(l2_options, log_filter_handler).await?;
9397
Ok(())
9498
}
9599
}
@@ -268,7 +272,7 @@ impl Command {
268272
..Default::default()
269273
}),
270274
_ => init_tracing(&crate::cli::Options::default()),
271-
}
275+
};
272276

273277
match self {
274278
Command::Prover {

cmd/ethrex/l2/initializers.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use tokio::task::JoinSet;
2121
use tokio_util::sync::CancellationToken;
2222
use tokio_util::task::TaskTracker;
2323
use tracing::{error, info, warn};
24-
use tracing_subscriber::EnvFilter;
2524
use tracing_subscriber::layer::SubscriberExt;
25+
use tracing_subscriber::{EnvFilter, Registry, reload};
2626
use tui_logger::{LevelFilter, TuiTracingSubscriberLayer};
2727

2828
use crate::cli::Options as L1Options;
@@ -47,6 +47,7 @@ async fn init_rpc_api(
4747
cancel_token: CancellationToken,
4848
tracker: TaskTracker,
4949
rollup_store: StoreRollup,
50+
log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
5051
) {
5152
let peer_handler = PeerHandler::new(peer_table);
5253

@@ -75,6 +76,7 @@ async fn init_rpc_api(
7576
get_valid_delegation_addresses(l2_opts),
7677
l2_opts.sponsor_private_key,
7778
rollup_store,
79+
log_filter_handler,
7880
);
7981

8082
tracker.spawn(rpc_api);
@@ -129,7 +131,7 @@ fn init_metrics(opts: &L1Options, tracker: TaskTracker) {
129131
tracker.spawn(metrics_api);
130132
}
131133

132-
pub fn init_tracing(opts: &L2Options) {
134+
pub fn init_tracing(opts: &L2Options) -> Option<reload::Handle<EnvFilter, Registry>> {
133135
if !opts.sequencer_opts.no_monitor {
134136
let level_filter = EnvFilter::builder()
135137
.parse_lossy("debug,tower_http::trace=debug,reqwest_tracing=off,hyper=off,libsql=off,ethrex::initializers=off,ethrex::l2::initializers=off,ethrex::l2::command=off");
@@ -139,12 +141,18 @@ pub fn init_tracing(opts: &L2Options) {
139141
tracing::subscriber::set_global_default(subscriber)
140142
.expect("setting default subscriber failed");
141143
tui_logger::init_logger(LevelFilter::max()).expect("Failed to initialize tui_logger");
144+
145+
// Monitor already registers all log levels
146+
None
142147
} else {
143-
initializers::init_tracing(&opts.node_opts);
148+
Some(initializers::init_tracing(&opts.node_opts))
144149
}
145150
}
146151

147-
pub async fn init_l2(opts: L2Options) -> eyre::Result<()> {
152+
pub async fn init_l2(
153+
opts: L2Options,
154+
log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
155+
) -> eyre::Result<()> {
148156
if opts.node_opts.evm == EvmEngine::REVM {
149157
panic!("L2 Doesn't support REVM, use LEVM instead.");
150158
}
@@ -189,6 +197,7 @@ pub async fn init_l2(opts: L2Options) -> eyre::Result<()> {
189197
cancel_token.clone(),
190198
tracker.clone(),
191199
rollup_store.clone(),
200+
log_filter_handler,
192201
)
193202
.await;
194203

crates/l2/networking/rpc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ serde_json = "1.0.117"
2222
tokio = { workspace = true, features = ["full"] }
2323
bytes.workspace = true
2424
tracing.workspace = true
25+
tracing-subscriber.workspace = true
2526
thiserror.workspace = true
2627
secp256k1.workspace = true
2728
keccak-hash.workspace = true

crates/l2/networking/rpc/rpc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use std::{
2828
use tokio::{net::TcpListener, sync::Mutex as TokioMutex};
2929
use tower_http::cors::CorsLayer;
3030
use tracing::{debug, info};
31+
use tracing_subscriber::{EnvFilter, Registry, reload};
3132

3233
use crate::l2::transaction::SponsoredTx;
3334
use ethrex_common::Address;
@@ -76,6 +77,7 @@ pub async fn start_api(
7677
valid_delegation_addresses: Vec<Address>,
7778
sponsor_pk: SecretKey,
7879
rollup_store: StoreRollup,
80+
log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
7981
) -> Result<(), RpcErr> {
8082
// TODO: Refactor how filters are handled,
8183
// filters are used by the filters endpoints (eth_newFilter, eth_getFilterChanges, ...etc)
@@ -94,6 +96,7 @@ pub async fn start_api(
9496
client_version,
9597
},
9698
gas_tip_estimator: Arc::new(TokioMutex::new(GasTipEstimator::new())),
99+
log_filter_handler,
97100
},
98101
valid_delegation_addresses,
99102
sponsor_pk,

crates/networking/rpc/admin/mod.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ use ethrex_storage::Store;
33
use serde::Serialize;
44
use serde_json::Value;
55
use std::collections::HashMap;
6+
use tracing_subscriber::{EnvFilter, Registry, reload};
67

7-
use crate::{rpc::NodeData, utils::RpcErr};
8+
use crate::{
9+
rpc::NodeData,
10+
utils::{RpcErr, RpcRequest},
11+
};
812
mod peers;
913
pub use peers::peers;
1014

@@ -58,3 +62,32 @@ pub fn node_info(storage: Store, node_data: &NodeData) -> Result<Value, RpcErr>
5862
};
5963
serde_json::to_value(node_info).map_err(|error| RpcErr::Internal(error.to_string()))
6064
}
65+
66+
pub async fn set_log_level(
67+
req: &RpcRequest,
68+
log_filter_handler: &Option<reload::Handle<EnvFilter, Registry>>,
69+
) -> Result<Value, RpcErr> {
70+
let params = req
71+
.params
72+
.clone()
73+
.ok_or(RpcErr::MissingParam("log level".to_string()))?;
74+
let log_level = params
75+
.first()
76+
.ok_or(RpcErr::MissingParam("log level".to_string()))?
77+
.as_str()
78+
.ok_or(RpcErr::WrongParam("Expected string".to_string()))?;
79+
80+
let filter = EnvFilter::try_new(log_level)
81+
.map_err(|_| RpcErr::BadParams(format!("Cannot parse {log_level} as a log directive")))?;
82+
83+
if let Some(handle) = log_filter_handler {
84+
handle
85+
.reload(filter)
86+
.map_err(|e| RpcErr::Internal(format!("Failed to reload log filter: {}", e)))?;
87+
Ok(Value::Bool(true))
88+
} else {
89+
Err(RpcErr::Internal(
90+
"Log filter handler not available".to_string(),
91+
))
92+
}
93+
}

crates/networking/rpc/rpc.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ use std::{
6666
use tokio::{net::TcpListener, sync::Mutex as TokioMutex};
6767
use tower_http::cors::CorsLayer;
6868
use tracing::{error, info};
69+
use tracing_subscriber::{EnvFilter, Registry, reload};
6970

7071
#[derive(Deserialize)]
7172
#[serde(untagged)]
@@ -83,6 +84,7 @@ pub struct RpcApiContext {
8384
pub peer_handler: PeerHandler,
8485
pub node_data: NodeData,
8586
pub gas_tip_estimator: Arc<TokioMutex<GasTipEstimator>>,
87+
pub log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
8688
}
8789

8890
#[derive(Debug, Clone)]
@@ -125,6 +127,7 @@ pub async fn start_api(
125127
syncer: SyncManager,
126128
peer_handler: PeerHandler,
127129
client_version: String,
130+
log_filter_handler: Option<reload::Handle<EnvFilter, Registry>>,
128131
) -> Result<(), RpcErr> {
129132
// TODO: Refactor how filters are handled,
130133
// filters are used by the filters endpoints (eth_newFilter, eth_getFilterChanges, ...etc)
@@ -142,6 +145,7 @@ pub async fn start_api(
142145
client_version,
143146
},
144147
gas_tip_estimator: Arc::new(TokioMutex::new(GasTipEstimator::new())),
148+
log_filter_handler,
145149
};
146150

147151
// Periodically clean up the active filters for the filters endpoints.
@@ -386,6 +390,7 @@ pub async fn map_admin_requests(req: &RpcRequest, context: RpcApiContext) -> Res
386390
match req.method.as_str() {
387391
"admin_nodeInfo" => admin::node_info(context.storage, &context.node_data),
388392
"admin_peers" => admin::peers(&context).await,
393+
"admin_setLogLevel" => admin::set_log_level(req, &context.log_filter_handler).await,
389394
unknown_admin_method => Err(RpcErr::MethodNotFound(unknown_admin_method.to_owned())),
390395
}
391396
}

0 commit comments

Comments
 (0)