Implement hot-reload for config and log level

Added hot-reload functionality for configuration and log level.
This commit is contained in:
artemws 2026-02-20 14:18:09 +02:00 committed by GitHub
parent 5be81952f3
commit b8add81018
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 34 additions and 5 deletions

View File

@ -27,6 +27,7 @@ mod tls_front;
mod util;
use crate::config::{LogLevel, ProxyConfig};
use crate::config::hot_reload::spawn_config_watcher;
use crate::crypto::SecureRandom;
use crate::ip_tracker::UserIpTracker;
use crate::network::probe::{decide_network_capabilities, log_probe_result, run_probe};
@ -469,6 +470,16 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
// Freeze config after possible fallback decision
let config = Arc::new(config);
// ── Hot-reload watcher ────────────────────────────────────────────────
// Spawns a background task that watches the config file and reloads it
// on SIGHUP (Unix) or every 60 seconds. Each accept-loop clones the
// receiver and calls `.borrow_and_update().clone()` per connection.
let (config_rx, mut log_level_rx) = spawn_config_watcher(
std::path::PathBuf::from(&config_path),
config.clone(),
std::time::Duration::from_secs(60),
);
let replay_checker = Arc::new(ReplayChecker::new(
config.access.replay_check_len,
Duration::from_secs(config.access.replay_window_secs),
@ -760,7 +771,7 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
has_unix_listener = true;
let config = config.clone();
let mut config_rx_unix = config_rx.clone();
let stats = stats.clone();
let upstream_manager = upstream_manager.clone();
let replay_checker = replay_checker.clone();
@ -779,7 +790,8 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
let conn_id = unix_conn_counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let fake_peer = SocketAddr::from(([127, 0, 0, 1], (conn_id % 65535) as u16));
let config = config.clone();
// Pick up the latest config atomically for this connection.
let config = config_rx_unix.borrow_and_update().clone();
let stats = stats.clone();
let upstream_manager = upstream_manager.clone();
let replay_checker = replay_checker.clone();
@ -813,7 +825,7 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
std::process::exit(1);
}
// Switch to user-configured log level after startup
// Switch to user-configured log level after startup (before starting listeners)
let runtime_filter = if has_rust_log {
EnvFilter::from_default_env()
} else if matches!(effective_log_level, LogLevel::Silent) {
@ -825,6 +837,22 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
.reload(runtime_filter)
.expect("Failed to switch log filter");
// Apply log_level changes to the tracing reload handle.
// Note: the initial runtime_filter is applied below (after startup); this
// task handles subsequent hot-reload changes only.
tokio::spawn(async move {
loop {
if log_level_rx.changed().await.is_err() {
break;
}
let level = log_level_rx.borrow_and_update().clone();
let new_filter = tracing_subscriber::EnvFilter::new(level.to_filter_str());
if let Err(e) = filter_handle.reload(new_filter) {
tracing::error!("config reload: failed to update log filter: {}", e);
}
}
});
if let Some(port) = config.server.metrics_port {
let stats = stats.clone();
let whitelist = config.server.metrics_whitelist.clone();
@ -834,7 +862,7 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
}
for listener in listeners {
let config = config.clone();
let mut config_rx = config_rx.clone();
let stats = stats.clone();
let upstream_manager = upstream_manager.clone();
let replay_checker = replay_checker.clone();
@ -848,7 +876,8 @@ match crate::transport::middle_proxy::fetch_proxy_secret(proxy_secret_path).awai
loop {
match listener.accept().await {
Ok((stream, peer_addr)) => {
let config = config.clone();
// Pick up the latest config atomically for this connection.
let config = config_rx.borrow_and_update().clone();
let stats = stats.clone();
let upstream_manager = upstream_manager.clone();
let replay_checker = replay_checker.clone();