From 5af2060ba1945aafca2ebac7a25449aa9bb73c05 Mon Sep 17 00:00:00 2001 From: shadowsocks69420 <193321218+shadowsocks69420@users.noreply.github.com> Date: Sat, 26 Jul 2025 04:12:01 +0800 Subject: [PATCH] feat: support file logging with tracing --- Cargo.lock | 13 ++ Cargo.toml | 3 +- README.md | 45 ++++- src/config.rs | 379 ++++++++++++++++++++++++++++++----------- src/logging/mod.rs | 3 +- src/logging/tracing.rs | 146 ++++++++++++---- src/service/local.rs | 2 + src/service/manager.rs | 4 +- src/service/server.rs | 4 +- 9 files changed, 460 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73815e867bba..a6d96694071d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3432,6 +3432,7 @@ dependencies = [ "time", "tokio", "tracing", + "tracing-appender", "tracing-subscriber", "windows-service", "xdg", @@ -4012,6 +4013,18 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror 1.0.69", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.30" diff --git a/Cargo.toml b/Cargo.toml index 7ccc0bbac520..37aeccb55596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,7 @@ dns-over-https = ["shadowsocks-service/dns-over-https"] dns-over-h3 = ["shadowsocks-service/dns-over-h3"] # Enable logging output -logging = ["log4rs", "tracing", "tracing-subscriber", "time"] +logging = ["log4rs", "tracing", "tracing-subscriber", "time", "tracing-appender"] # Enable DNS-relay local-dns = ["local", "shadowsocks-service/local-dns"] @@ -208,6 +208,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [ "time", "local-time", ] } +tracing-appender = { version = "0.2.3", optional = true, default-features = false } time = { version = "0.3", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/README.md b/README.md index 2a43d3705a33..6804f04150e2 100644 --- a/README.md +++ b/README.md @@ -871,19 +871,54 @@ Example configuration: // Service configurations // Logger configuration "log": { + // Default log level to use, if not overridden by `writers`, default is `0` // Equivalent to `-v` command line option "level": 1, + // Default log format to use, if not overridden by `writers` "format": { - // Euiqvalent to `--log-without-time` + // Euiqvalent to `--log-without-time`, default is `false` "without_time": false, }, - // Equivalent to `--log-config` - // More detail could be found in https://crates.io/crates/log4rs - "config_path": "/path/to/log4rs/config.yaml" + // Advanced logging configuration for configuring multiple writers + // A stdout writer will be configured by default. + // Set this to empty array `[]` to disable logging completely + "writers": [ + { + // Configure a stdout writer + // The inner fields are optional, if not set, it will use the default values + // To minimally configure a stdout writer, simply write `"stdout": {}`. + "stdout": { + "level": 2, + "format": { + "without_time": false, + } + } + }, + { + // Configure a file writer, useful when running as a Windows Service + "file": { + // `level` and `format` can also be set here, if not set, it will use the default values + + // Required. Directory to store log files + "directory": "/var/log/shadowsocks-rust", + // Optional. Log rotation frequency, must be one of the following: + // - never (default): This will result in log file located at `directory/prefix.suffix` + // - daily: A new log file in the format of `directory/prefix.yyyy-MM-dd.suffix` will be created daily + // - hourly: A new log file in the format of `directory/prefix.yyyy-MM-dd-HH.suffix` will be created hourly + "rotation": "never", + // Optional. Prefix of log file, default is one of `sslocal`, `ssserver`, `ssmanager` depending on the service being run. + "prefix": "shadowsocks-rust", + // Optional. Suffix of log file, default is `log` + "suffix": "log", + // Optional. If set, keeps the last N log files + "max_files": 5 + } + } + ] }, // Runtime configuration "runtime": { - // single_thread or multi_thread + // `single_thread` or `multi_thread` "mode": "multi_thread", // Worker threads that are used in multi-thread runtime "worker_count": 10 diff --git a/src/config.rs b/src/config.rs index 1fef16551065..cae5c75b54af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,6 @@ use std::{ fs::OpenOptions, io::{self, Read}, path::{Path, PathBuf}, - str::FromStr, }; use clap::ArgMatches; @@ -96,7 +95,8 @@ pub enum ConfigError { } /// Configuration Options for shadowsocks service runnables -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] pub struct Config { /// Logger configuration #[cfg(feature = "logging")] @@ -120,54 +120,7 @@ impl Config { /// Load `Config` from string pub fn load_from_str(s: &str) -> Result { - let ssconfig = json5::from_str(s)?; - Self::load_from_ssconfig(ssconfig) - } - - fn load_from_ssconfig(ssconfig: SSConfig) -> Result { - let mut config = Self::default(); - - #[cfg(feature = "logging")] - if let Some(log) = ssconfig.log { - let mut nlog = LogConfig::default(); - if let Some(level) = log.level { - nlog.level = level; - } - - if let Some(format) = log.format { - let mut nformat = LogFormatConfig::default(); - if let Some(without_time) = format.without_time { - nformat.without_time = without_time; - } - nlog.format = nformat; - } - - if let Some(config_path) = log.config_path { - nlog.config_path = Some(PathBuf::from(config_path)); - } - - config.log = nlog; - } - - if let Some(runtime) = ssconfig.runtime { - let mut nruntime = RuntimeConfig::default(); - - #[cfg(feature = "multi-threaded")] - if let Some(worker_count) = runtime.worker_count { - nruntime.worker_count = Some(worker_count); - } - - if let Some(mode) = runtime.mode { - match mode.parse::() { - Ok(m) => nruntime.mode = m, - Err(..) => return Err(ConfigError::InvalidValue(mode)), - } - } - - config.runtime = nruntime; - } - - Ok(config) + json5::from_str(s).map_err(ConfigError::from) } /// Set by command line options @@ -198,31 +151,127 @@ impl Config { self.runtime.worker_count = Some(*worker_count); } + // suppress unused warning let _ = matches; } } /// Logger configuration #[cfg(feature = "logging")] -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone)] +#[serde(default)] pub struct LogConfig { - /// Default logger log level, [0, 3] + /// Default log level for all writers, [0, 3] pub level: u32, - /// Default logger format configuration + /// Default format configuration for all writers pub format: LogFormatConfig, - /// Logging configuration file path + /// Log writers configuration + pub writers: Vec, + /// Deprecated: Path to the `log4rs` config file pub config_path: Option, } +#[cfg(feature = "logging")] +impl Default for LogConfig { + fn default() -> Self { + LogConfig { + level: 0, + format: LogFormatConfig::default(), + writers: vec![LogWriterConfig::Stdout(LogConsoleWriterConfig::default())], + config_path: None, + } + } +} + /// Logger format configuration #[cfg(feature = "logging")] -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Default, Eq, PartialEq)] +#[serde(default)] pub struct LogFormatConfig { pub without_time: bool, } +/// Holds writer-specific configuration for logging +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum LogWriterConfig { + Stdout(LogConsoleWriterConfig), + File(LogFileWriterConfig), +} + +/// Console appender configuration for logging +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone, Default)] +pub struct LogConsoleWriterConfig { + /// Level override + #[serde(default)] + pub level: Option, + /// Format override + #[serde(default)] + pub format: LogFormatConfigOverride, +} + +/// Logger format override +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] +pub struct LogFormatConfigOverride { + pub without_time: Option, +} + +/// File appender configuration for logging +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Clone)] +pub struct LogFileWriterConfig { + /// Level override + #[serde(default)] + pub level: Option, + /// Format override + #[serde(default)] + pub format: LogFormatConfigOverride, + + /// Directory to store log files + pub directory: PathBuf, + /// Rotation strategy for log files. Default is `Rotation::NEVER`. + #[serde(default)] + pub rotation: LogRotation, + /// Prefix for log file names. Default is the binary name. + #[serde(default)] + pub prefix: Option, + /// Suffix for log file names. Default is "log". + #[serde(default)] + pub suffix: Option, + /// Maximum number of log files to keep. Default is `None`, meaning no limit. + #[serde(default)] + pub max_files: Option, +} + +/// Log rotation frequency +#[cfg(feature = "logging")] +#[derive(Deserialize, Debug, Copy, Clone, Default, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum LogRotation { + #[default] + Never, + Hourly, + Daily, +} + +#[cfg(feature = "logging")] +impl From for tracing_appender::rolling::Rotation { + fn from(rotation: LogRotation) -> Self { + match rotation { + LogRotation::Never => Self::NEVER, + LogRotation::Hourly => Self::HOURLY, + LogRotation::Daily => Self::DAILY, + } + } +} + /// Runtime mode (Tokio) -#[derive(Debug, Clone, Copy, Default)] +#[derive(Deserialize, Debug, Clone, Copy, Default, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] pub enum RuntimeMode { /// Single-Thread Runtime #[cfg_attr(not(feature = "multi-threaded"), default)] @@ -233,25 +282,9 @@ pub enum RuntimeMode { MultiThread, } -/// Parse `RuntimeMode` from string error -#[derive(Debug)] -pub struct RuntimeModeError; - -impl FromStr for RuntimeMode { - type Err = RuntimeModeError; - - fn from_str(s: &str) -> Result { - match s { - "single_thread" => Ok(Self::SingleThread), - #[cfg(feature = "multi-threaded")] - "multi_thread" => Ok(Self::MultiThread), - _ => Err(RuntimeModeError), - } - } -} - /// Runtime configuration -#[derive(Debug, Clone, Default)] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(default)] pub struct RuntimeConfig { /// Multithread runtime worker count, CPU count if not configured #[cfg(feature = "multi-threaded")] @@ -260,30 +293,182 @@ pub struct RuntimeConfig { pub mode: RuntimeMode, } -#[derive(Deserialize)] -struct SSConfig { - #[cfg(feature = "logging")] - log: Option, - runtime: Option, -} +#[cfg(test)] +mod tests { + use super::*; -#[cfg(feature = "logging")] -#[derive(Deserialize)] -struct SSLogConfig { - level: Option, - format: Option, - config_path: Option, -} + #[test] + fn test_deser_empty() { + // empty config should load successfully + let config: Config = Config::load_from_str("{}").unwrap(); + assert_eq!(config.runtime.mode, RuntimeMode::default()); + #[cfg(feature = "multi-threaded")] + { + assert!(config.runtime.worker_count.is_none()); + } + #[cfg(feature = "logging")] + { + assert_eq!(config.log.level, 0); + assert!(!config.log.format.without_time); + // default writer configuration should contain a stdout writer + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::Stdout(stdout_config) = &config.log.writers[0] { + assert_eq!(stdout_config.level, None); + assert_eq!(stdout_config.format.without_time, None); + } else { + panic!("Expected a stdout writer configuration"); + } + } + } -#[cfg(feature = "logging")] -#[derive(Deserialize)] -struct SSLogFormat { - without_time: Option, -} + #[test] + fn test_deser_disable_logging() { + // allow user explicitly disable logging by providing an empty writers array + let config_str = r#" + { + "log": { + "writers": [] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.level, 0); + assert!(!config.log.format.without_time); + assert!(config.log.writers.is_empty()); + } + } -#[derive(Deserialize)] -struct SSRuntimeConfig { - #[cfg(feature = "multi-threaded")] - worker_count: Option, - mode: Option, + #[test] + fn test_deser_file_writer_full() { + let config_str = r#" + { + "log": { + "writers": [ + { + "file": { + "level": 2, + "format": { + "without_time": true + }, + "directory": "/var/log/shadowsocks", + "rotation": "daily", + "prefix": "ss-rust", + "suffix": "log", + "max_files": 5 + } + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::File(file_config) = &config.log.writers[0] { + assert_eq!(file_config.level, Some(2)); + assert_eq!(file_config.format.without_time, Some(true)); + assert_eq!(file_config.directory, PathBuf::from("/var/log/shadowsocks")); + assert_eq!(file_config.rotation, LogRotation::Daily); + assert_eq!(file_config.prefix.as_deref(), Some("ss-rust")); + assert_eq!(file_config.suffix.as_deref(), Some("log")); + assert_eq!(file_config.max_files, Some(5)); + } else { + panic!("Expected a file writer configuration"); + } + } + } + + #[test] + fn test_deser_file_writer_minimal() { + // Minimal valid file writer configuration + let config_str = r#" + { + "log": { + "writers": [ + { + "file": { + "directory": "/var/log/shadowsocks" + } + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::File(file_config) = &config.log.writers[0] { + assert_eq!(file_config.level, None); + assert_eq!(file_config.format.without_time, None); + assert_eq!(file_config.directory, PathBuf::from("/var/log/shadowsocks")); + assert_eq!(file_config.rotation, LogRotation::Never); + assert!(file_config.prefix.is_none()); + assert!(file_config.suffix.is_none()); + assert!(file_config.max_files.is_none()); + } else { + panic!("Expected a file writer configuration"); + } + } + } + #[test] + fn test_deser_stdout_writer_full() { + let config_str = r#" + { + "log": { + "writers": [ + { + "stdout": { + "level": 1, + "format": { + "without_time": false + } + } + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::Stdout(stdout_config) = &config.log.writers[0] { + assert_eq!(stdout_config.level, Some(1)); + assert_eq!(stdout_config.format.without_time, Some(false)); + } else { + panic!("Expected a stdout writer configuration"); + } + } + } + + #[test] + fn test_deser_stdout_writer_minimal() { + // Minimal valid stdout writer configuration + let config_str = r#" + { + "log": { + "writers": [ + { + "stdout": {} + } + ] + } + } + "#; + let config: Config = Config::load_from_str(config_str).unwrap(); + #[cfg(feature = "logging")] + { + assert_eq!(config.log.writers.len(), 1); + if let LogWriterConfig::Stdout(stdout_config) = &config.log.writers[0] { + assert_eq!(stdout_config.level, None); + assert_eq!(stdout_config.format.without_time, None); + } else { + panic!("Expected a stdout writer configuration"); + } + } + } } diff --git a/src/logging/mod.rs b/src/logging/mod.rs index 7f5098ccd61b..4a55e74aaaf4 100644 --- a/src/logging/mod.rs +++ b/src/logging/mod.rs @@ -9,7 +9,7 @@ use crate::config::LogConfig; mod log4rs; mod tracing; -/// Initialize logger ([log4rs](https://crates.io/crates/log4rs), [trace4rs](https://crates.io/crates/trace4rs)) from yaml configuration file +/// Initialize [log4rs](https://crates.io/crates/log4rs) from yaml configuration file pub fn init_with_file

(path: P) where P: AsRef, @@ -25,7 +25,6 @@ where /// Initialize logger with provided configuration pub fn init_with_config(bin_name: &str, config: &LogConfig) { - // log4rs::init_with_config(bin_name, config); tracing::init_with_config(bin_name, config); } diff --git a/src/logging/tracing.rs b/src/logging/tracing.rs index b045946d715e..176bab63a57b 100644 --- a/src/logging/tracing.rs +++ b/src/logging/tracing.rs @@ -1,51 +1,112 @@ //! Logging facilities with tracing +use std::io; use std::io::IsTerminal; +use time::format_description::well_known::Rfc3339; use time::UtcOffset; use tracing::level_filters::LevelFilter; -use tracing_subscriber::{EnvFilter, FmtSubscriber, fmt::time::OffsetTime}; +use tracing_appender::rolling::{InitError, RollingFileAppender}; +use tracing_subscriber::fmt::time::OffsetTime; +use tracing_subscriber::fmt::MakeWriter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{fmt, EnvFilter, Layer, Registry}; -use crate::config::LogConfig; +use crate::config::{ + LogConfig, LogConsoleWriterConfig, LogFileWriterConfig, LogFormatConfig, LogFormatConfigOverride, LogWriterConfig, +}; /// Initialize logger with provided configuration pub fn init_with_config(bin_name: &str, config: &LogConfig) { - let debug_level = config.level; - let without_time = config.format.without_time; - - let mut builder = FmtSubscriber::builder() - .with_level(true) - .with_timer(match OffsetTime::local_rfc_3339() { - Ok(t) => t, - Err(..) => { - // Reinit with UTC time - OffsetTime::new(UtcOffset::UTC, time::format_description::well_known::Rfc3339) - } - }); + let layers: Vec = config + .writers + .iter() + .map(|writer| writer.make_layer(bin_name, config)) + .collect(); + tracing_subscriber::registry().with(layers).init(); +} + +type BoxedLayer = Box + Send + Sync + 'static>; + +trait MakeLayer { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer; +} + +impl MakeLayer for LogWriterConfig { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer { + match self { + LogWriterConfig::Stdout(console_config) => console_config.make_layer(bin_name, global), + LogWriterConfig::File(file_config) => file_config.make_layer(bin_name, global), + } + } +} + +impl MakeLayer for LogConsoleWriterConfig { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer { + let level = self.level.unwrap_or(global.level); + let format = apply_override(&global.format, &self.format); + let ansi = io::stdout().is_terminal(); + make_fmt_layer(bin_name, level, &format, ansi, io::stdout) + } +} + +impl MakeLayer for LogFileWriterConfig { + fn make_layer(&self, bin_name: &str, global: &LogConfig) -> BoxedLayer { + let level = self.level.unwrap_or(global.level); + let format = apply_override(&global.format, &self.format); + + let file_writer = make_file_writer(bin_name, self) + // don't have the room for a more graceful error handling here + .expect("Failed to create file writer for logging"); + make_fmt_layer(bin_name, level, &format, false, file_writer) + } +} + +/// Boilerplate for configuring a `fmt::Layer` with `level` and `format` for different writers. +fn make_fmt_layer(bin_name: &str, level: u32, format: &LogFormatConfig, ansi: bool, writer: W) -> BoxedLayer +where + W: for<'a> MakeWriter<'a> + Send + Sync + 'static, +{ + let mut layer = fmt::layer().with_level(true); // NOTE: ansi is enabled by default. // Could be disabled by `NO_COLOR` environment variable. // https://no-color.org/ - if !std::io::stdout().is_terminal() { - builder = builder.with_ansi(false); + if !ansi { + layer = layer.with_ansi(false); } - if debug_level >= 1 { - builder = builder.with_target(true).with_thread_ids(true).with_thread_names(true); + if level >= 1 { + layer = layer.with_target(true).with_thread_ids(true).with_thread_names(true); - if debug_level >= 3 { - builder = builder.with_file(true).with_line_number(true); + if level >= 3 { + layer = layer.with_file(true).with_line_number(true); } } else { - builder = builder - .with_target(false) - .with_thread_ids(false) - .with_thread_names(false); + layer = layer.with_target(false).with_thread_ids(false).with_thread_names(false); } - let filter = match EnvFilter::try_from_default_env() { + let layer = layer.with_writer(writer); + + let boxed_layer = if format.without_time { + layer.without_time().boxed() + } else { + layer + .with_timer(OffsetTime::local_rfc_3339() + // Fallback to UTC. Eagerly evaluate because it is cheap to create. + .unwrap_or(OffsetTime::new(UtcOffset::UTC, Rfc3339))) + .boxed() + }; + + let filter = make_env_filter(bin_name, level); + boxed_layer.with_filter(filter).boxed() +} + +fn make_env_filter(bin_name: &str, level: u32) -> EnvFilter { + match EnvFilter::try_from_default_env() { Ok(f) => f, - Err(..) => match debug_level { + Err(_) => match level { 0 => EnvFilter::builder() .with_regex(true) .with_default_directive(LevelFilter::ERROR.into()) @@ -71,12 +132,33 @@ pub fn init_with_config(bin_name: &str, config: &LogConfig) { .with_default_directive(LevelFilter::TRACE.into()) .parse_lossy(""), }, - }; - let builder = builder.with_env_filter(filter); + } +} - if without_time { - builder.without_time().init(); - } else { - builder.init(); +fn make_file_writer(bin_name: &str, config: &LogFileWriterConfig) -> Result { + // We provide default values here because we don't have access to the + // `bin_name` elsewhere. + let prefix = config.prefix.as_deref().unwrap_or(bin_name); + let suffix = config.suffix.as_deref().unwrap_or("log"); + + let mut builder = RollingFileAppender::builder() + .rotation(config.rotation.into()) + .filename_prefix(prefix) + .filename_suffix(suffix); + + if let Some(max_files) = config.max_files { + // setting `max_files` to `0` will cause panicking due to + // integer underflow in the `tracing_appender` crate. + if max_files > 0 { + builder = builder.max_log_files(max_files); + } + } + + builder.build(&config.directory) +} + +fn apply_override(global: &LogFormatConfig, override_config: &LogFormatConfigOverride) -> LogFormatConfig { + LogFormatConfig { + without_time: override_config.without_time.unwrap_or(global.without_time), } } diff --git a/src/service/local.rs b/src/service/local.rs index cf01f37a3a84..f142e3c47d0f 100644 --- a/src/service/local.rs +++ b/src/service/local.rs @@ -261,6 +261,8 @@ pub fn define_command_line_options(mut app: Command) -> Command { .arg( Arg::new("LOG_CONFIG") .long("log-config") + // deprecated for removal + .hide(true) .num_args(1) .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) diff --git a/src/service/manager.rs b/src/service/manager.rs index ae7f1d5ba002..0eadd30fd417 100644 --- a/src/service/manager.rs +++ b/src/service/manager.rs @@ -148,6 +148,8 @@ pub fn define_command_line_options(mut app: Command) -> Command { .arg( Arg::new("LOG_CONFIG") .long("log-config") + // deprecated for removal + .hide(true) .num_args(1) .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) @@ -297,7 +299,7 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future { - logging::init_with_config("sslocal", &service_config.log); + logging::init_with_config("ssmanager", &service_config.log); } } diff --git a/src/service/server.rs b/src/service/server.rs index 045243aa3a2e..eef03d8167ab 100644 --- a/src/service/server.rs +++ b/src/service/server.rs @@ -178,6 +178,8 @@ pub fn define_command_line_options(mut app: Command) -> Command { .arg( Arg::new("LOG_CONFIG") .long("log-config") + // deprecated for removal + .hide(true) .num_args(1) .action(ArgAction::Set) .value_parser(clap::value_parser!(PathBuf)) @@ -309,7 +311,7 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future { - logging::init_with_config("sslocal", &service_config.log); + logging::init_with_config("ssserver", &service_config.log); } }