"log" and "runtime" specific options configurable in file (#702)

This commit is contained in:
ty
2021-11-28 14:49:53 +08:00
committed by GitHub
parent 18d042fa57
commit d90fb8a906
8 changed files with 405 additions and 102 deletions

3
Cargo.lock generated
View File

@@ -1679,15 +1679,18 @@ dependencies = [
"futures",
"ipnet",
"jemallocator",
"json5",
"log",
"log4rs",
"mimalloc",
"qrcode",
"rpassword",
"rpmalloc",
"serde",
"shadowsocks-service",
"snmalloc-rs",
"tcmalloc",
"thiserror",
"tokio",
"xdg",
]

View File

@@ -130,6 +130,9 @@ replay-attack-detect = ["shadowsocks-service/replay-attack-detect"]
[dependencies]
log = "0.4"
log4rs = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
json5 = "0.4"
thiserror = "1.0"
clap = { version = "2", features = ["wrap_help", "suggestions"] }
cfg-if = "1"

View File

@@ -585,16 +585,34 @@ Example configuration:
"max_server_rtt": 5,
// Interval seconds between each check
"check_interval": 10,
},
// Service configurations
// Logger configuration
"log": {
// Equivalent to `-v` command line option
"level": 1,
"format": {
// Euiqvalent to `--log-without-time`
"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"
},
// Runtime configuration
"runtime": {
// single_thread or multi_thread
"mode": "multi_thread",
// Worker threads that are used in multi-thread runtime
"worker_count": 10
}
}
```
### Environment Variables
- `SS_LOG_VERBOSE_LEVEL`: Logging level for binaries (`sslocal`, `ssserver` and `ssmanager`). It is valid only when command line argument `-v` is not applied. Example: `SS_LOG_VERBOSE_LEVEL=1`
- `SS_LOG_WITHOUT_TIME`: Logging format for binaries (`sslocal`, `ssserver` and `ssmanager`). It is valid only when command line argument `--log-without-time` is not applied. Example `SS_LOG_WITHOUT_TIME=1`
- `SS_SERVER_PASSWORD`: A default password for servers that created from command line argument (`--server-addr`)
- `SS_SERVER_${SERVER_ADDR}_PASSWORD`: A default password for server with address `$SERVER_ADDR` that created from command line argument (`--server-addr`)
## Supported Ciphers

View File

@@ -1,9 +1,17 @@
//! Common configuration utilities
use std::{
env,
fs::OpenOptions,
io::{self, Read},
path::{Path, PathBuf},
str::FromStr,
};
use cfg_if::cfg_if;
use clap::{ArgMatches, ErrorKind as ClapErrorKind};
use directories::ProjectDirs;
#[cfg(unix)]
use std::path::Path;
use std::{env, path::PathBuf};
use serde::Deserialize;
/// Default configuration file path
pub fn get_default_config_path() -> Option<PathBuf> {
@@ -59,3 +67,240 @@ pub fn get_default_config_path() -> Option<PathBuf> {
None
}
/// Error while reading `Config`
#[derive(thiserror::Error, Debug)]
pub enum ConfigError {
/// Input/Output error
#[error("{0}")]
IoError(#[from] io::Error),
/// JSON parsing error
#[error("{0}")]
JsonError(#[from] json5::Error),
/// Invalid value
#[error("Invalid value: {0}")]
InvalidValue(String),
}
/// Configuration Options for shadowsocks service runnables
#[derive(Debug, Clone, Default)]
pub struct Config {
/// Logger configuration
#[cfg(feature = "logging")]
pub log: LogConfig,
/// Runtime configuration
pub runtime: RuntimeConfig,
}
impl Config {
/// Load `Config` from file
pub fn load_from_file<P: AsRef<Path>>(filename: &P) -> Result<Config, ConfigError> {
let filename = filename.as_ref();
let mut reader = OpenOptions::new().read(true).open(filename)?;
let mut content = String::new();
reader.read_to_string(&mut content)?;
Config::load_from_str(&content)
}
/// Load `Config` from string
pub fn load_from_str(s: &str) -> Result<Config, ConfigError> {
let ssconfig = json5::from_str(s)?;
Config::load_from_ssconfig(ssconfig)
}
fn load_from_ssconfig(ssconfig: SSConfig) -> Result<Config, ConfigError> {
let mut config = Config::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::<RuntimeMode>() {
Ok(m) => nruntime.mode = m,
Err(..) => return Err(ConfigError::InvalidValue(mode)),
}
}
config.runtime = nruntime;
}
Ok(config)
}
/// Set by command line options
pub fn set_options(&mut self, matches: &ArgMatches<'_>) {
#[cfg(feature = "logging")]
{
let debug_level = matches.occurrences_of("VERBOSE");
if debug_level > 0 {
self.log.level = debug_level as u32;
}
if matches.is_present("LOG_WITHOUT_TIME") {
self.log.format.without_time = true;
}
if let Some(log_config) = matches.value_of("LOG_CONFIG") {
self.log.config_path = Some(log_config.into());
}
}
#[cfg(feature = "multi-threaded")]
if matches.is_present("SINGLE_THREADED") {
self.runtime.mode = RuntimeMode::SingleThread;
}
#[cfg(feature = "multi-threaded")]
match clap::value_t!(matches.value_of("WORKER_THREADS"), usize) {
Ok(worker_count) => self.runtime.worker_count = Some(worker_count),
Err(ref err) if err.kind == ClapErrorKind::ArgumentNotFound => {}
Err(err) => err.exit(),
}
let _ = matches;
}
}
/// Logger configuration
#[cfg(feature = "logging")]
#[derive(Debug, Clone)]
pub struct LogConfig {
/// Default logger log level, [0, 3]
pub level: u32,
/// Default logger format configuration
pub format: LogFormatConfig,
/// Logging configuration file path
pub config_path: Option<PathBuf>,
}
#[cfg(feature = "logging")]
impl Default for LogConfig {
fn default() -> LogConfig {
LogConfig {
level: 0,
format: LogFormatConfig::default(),
config_path: None,
}
}
}
/// Logger format configuration
#[cfg(feature = "logging")]
#[derive(Debug, Clone)]
pub struct LogFormatConfig {
pub without_time: bool,
}
#[cfg(feature = "logging")]
impl Default for LogFormatConfig {
fn default() -> LogFormatConfig {
LogFormatConfig { without_time: false }
}
}
/// Runtime mode (Tokio)
#[derive(Debug, Clone, Copy)]
pub enum RuntimeMode {
/// Single-Thread Runtime
SingleThread,
/// Multi-Thread Runtime
#[cfg(feature = "multi-threaded")]
MultiThread,
}
impl Default for RuntimeMode {
fn default() -> RuntimeMode {
cfg_if! {
if #[cfg(feature = "multi-threaded")] {
RuntimeMode::MultiThread
} else {
RuntimeMode::SingleThread
}
}
}
}
/// Parse `RuntimeMode` from string error
#[derive(Debug)]
pub struct RuntimeModeError;
impl FromStr for RuntimeMode {
type Err = RuntimeModeError;
fn from_str(s: &str) -> Result<RuntimeMode, Self::Err> {
match s {
"single_thread" => Ok(RuntimeMode::SingleThread),
#[cfg(feature = "multi-threaded")]
"multi_thread" => Ok(RuntimeMode::MultiThread),
_ => Err(RuntimeModeError),
}
}
}
/// Runtime configuration
#[derive(Debug, Clone, Default)]
pub struct RuntimeConfig {
/// Multithread runtime worker count, CPU count if not configured
#[cfg(feature = "multi-threaded")]
pub worker_count: Option<usize>,
/// Runtime Mode, single-thread, multi-thread
pub mode: RuntimeMode,
}
#[derive(Deserialize)]
struct SSConfig {
#[cfg(feature = "logging")]
log: Option<SSLogConfig>,
runtime: Option<SSRuntimeConfig>,
}
#[cfg(feature = "logging")]
#[derive(Deserialize)]
struct SSLogConfig {
level: Option<u32>,
format: Option<SSLogFormat>,
config_path: Option<String>,
}
#[cfg(feature = "logging")]
#[derive(Deserialize)]
struct SSLogFormat {
without_time: Option<bool>,
}
#[derive(Deserialize)]
struct SSRuntimeConfig {
#[cfg(feature = "multi-threaded")]
worker_count: Option<usize>,
mode: Option<String>,
}

View File

@@ -1,8 +1,7 @@
//! Logging facilities
use std::{env, path::Path};
use std::path::Path;
use clap::ArgMatches;
use log::LevelFilter;
use log4rs::{
append::console::{ConsoleAppender, Target},
@@ -10,6 +9,8 @@ use log4rs::{
encode::pattern::PatternEncoder,
};
use crate::config::LogConfig;
/// Initialize logger ([log4rs](https://crates.io/crates/log4rs)) from yaml configuration file
pub fn init_with_file<P>(path: P)
where
@@ -19,25 +20,9 @@ where
}
/// Initialize logger with default configuration
pub fn init_with_config(bin_name: &str, matches: &ArgMatches) {
let mut debug_level = matches.occurrences_of("VERBOSE");
if debug_level == 0 {
// Override by SS_LOG_VERBOSE_LEVEL
if let Ok(verbose_level) = env::var("SS_LOG_VERBOSE_LEVEL") {
if let Ok(verbose_level) = verbose_level.parse::<u64>() {
debug_level = verbose_level;
}
}
}
let mut without_time = matches.is_present("LOG_WITHOUT_TIME");
if !without_time {
if let Ok(log_without_time) = env::var("SS_LOG_WITHOUT_TIME") {
if let Ok(log_without_time) = log_without_time.parse::<u32>() {
without_time = log_without_time != 0;
}
}
}
pub fn init_with_config(bin_name: &str, config: &LogConfig) {
let debug_level = config.level;
let without_time = config.format.without_time;
let mut pattern = String::new();
if !without_time {
@@ -92,3 +77,8 @@ pub fn init_with_config(bin_name: &str, matches: &ArgMatches) {
log4rs::init_config(config).expect("logging");
}
/// Init a default logger
pub fn init_with_default(bin_name: &str) {
init_with_config(bin_name, &LogConfig::default());
}

View File

@@ -4,7 +4,7 @@ use std::{net::IpAddr, path::PathBuf, process, time::Duration};
use clap::{clap_app, App, Arg, ArgMatches, ErrorKind as ClapErrorKind};
use futures::future::{self, Either};
use log::info;
use log::{info, trace};
use tokio::{self, runtime::Builder};
#[cfg(feature = "local-redir")]
@@ -25,7 +25,11 @@ use shadowsocks_service::{
#[cfg(feature = "logging")]
use crate::logging;
use crate::{monitor, validator};
use crate::{
config::{Config as ServiceConfig, RuntimeMode},
monitor,
validator,
};
/// Defines command line options
pub fn define_command_line_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
@@ -189,22 +193,12 @@ pub fn define_command_line_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
/// Program entrance `main`
pub fn main(matches: &ArgMatches<'_>) {
let (config, runtime) = {
#[cfg(feature = "logging")]
match matches.value_of("LOG_CONFIG") {
Some(path) => {
logging::init_with_file(path);
}
None => {
logging::init_with_config("sslocal", matches);
}
}
let config_path_opt = matches.value_of("CONFIG").map(PathBuf::from).or_else(|| {
if !matches.is_present("SERVER_CONFIG") {
match crate::config::get_default_config_path() {
None => None,
Some(p) => {
info!("loading default config from {:?}", p);
println!("loading default config {:?}", p);
Some(p)
}
}
@@ -213,6 +207,30 @@ pub fn main(matches: &ArgMatches<'_>) {
}
});
let mut service_config = match config_path_opt {
Some(ref config_path) => match ServiceConfig::load_from_file(config_path) {
Ok(c) => c,
Err(err) => {
eprintln!("loading config {:?}, {}", config_path, err);
process::exit(crate::EXIT_CODE_LOAD_CONFIG_FAILURE);
}
},
None => ServiceConfig::default(),
};
service_config.set_options(&matches);
#[cfg(feature = "logging")]
match service_config.log.config_path {
Some(ref path) => {
logging::init_with_file(path);
}
None => {
logging::init_with_config("sslocal", &service_config.log);
}
}
trace!("{:?}", service_config);
let mut config = match config_path_opt {
Some(cpath) => match Config::load_from_file(&cpath, ConfigType::Local) {
Ok(cfg) => cfg,
@@ -563,22 +581,18 @@ pub fn main(matches: &ArgMatches<'_>) {
info!("shadowsocks local {} build {}", crate::VERSION, crate::BUILD_TIME);
#[cfg(feature = "multi-threaded")]
let mut builder = if matches.is_present("SINGLE_THREADED") {
Builder::new_current_thread()
} else {
let mut builder = Builder::new_multi_thread();
match clap::value_t!(matches.value_of("WORKER_THREADS"), usize) {
Ok(worker_threads) => {
let mut builder = match service_config.runtime.mode {
RuntimeMode::SingleThread => Builder::new_current_thread(),
#[cfg(feature = "multi-threaded")]
RuntimeMode::MultiThread => {
let mut builder = Builder::new_multi_thread();
if let Some(worker_threads) = service_config.runtime.worker_count {
builder.worker_threads(worker_threads);
}
Err(ref err) if err.kind == ClapErrorKind::ArgumentNotFound => {}
Err(err) => err.exit(),
builder
}
builder
};
#[cfg(not(feature = "multi-threaded"))]
let mut builder = Builder::new_current_thread();
let runtime = builder.enable_all().build().expect("create tokio Runtime");

View File

@@ -4,7 +4,7 @@ use std::{net::IpAddr, path::PathBuf, process, time::Duration};
use clap::{clap_app, App, Arg, ArgMatches, ErrorKind as ClapErrorKind};
use futures::future::{self, Either};
use log::info;
use log::{info, trace};
use tokio::{self, runtime::Builder};
#[cfg(unix)]
@@ -22,7 +22,11 @@ use shadowsocks_service::{
#[cfg(feature = "logging")]
use crate::logging;
use crate::{monitor, validator};
use crate::{
config::{Config as ServiceConfig, RuntimeMode},
monitor,
validator,
};
/// Defines command line options
pub fn define_command_line_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
@@ -117,22 +121,12 @@ pub fn define_command_line_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
/// Program entrance `main`
pub fn main(matches: &ArgMatches<'_>) {
let (config, runtime) = {
#[cfg(feature = "logging")]
match matches.value_of("LOG_CONFIG") {
Some(path) => {
logging::init_with_file(path);
}
None => {
logging::init_with_config("ssmanager", matches);
}
}
let config_path_opt = matches.value_of("CONFIG").map(PathBuf::from).or_else(|| {
if !matches.is_present("SERVER_CONFIG") {
match crate::config::get_default_config_path() {
None => None,
Some(p) => {
info!("loading default config from {:?}", p);
println!("loading default config {:?}", p);
Some(p)
}
}
@@ -141,6 +135,30 @@ pub fn main(matches: &ArgMatches<'_>) {
}
});
let mut service_config = match config_path_opt {
Some(ref config_path) => match ServiceConfig::load_from_file(config_path) {
Ok(c) => c,
Err(err) => {
eprintln!("loading config {:?}, {}", config_path, err);
process::exit(crate::EXIT_CODE_LOAD_CONFIG_FAILURE);
}
},
None => ServiceConfig::default(),
};
service_config.set_options(&matches);
#[cfg(feature = "logging")]
match service_config.log.config_path {
Some(ref path) => {
logging::init_with_file(path);
}
None => {
logging::init_with_config("sslocal", &service_config.log);
}
}
trace!("{:?}", service_config);
let mut config = match config_path_opt {
Some(cpath) => match Config::load_from_file(&cpath, ConfigType::Manager) {
Ok(cfg) => cfg,
@@ -336,18 +354,18 @@ pub fn main(matches: &ArgMatches<'_>) {
info!("shadowsocks manager {} build {}", crate::VERSION, crate::BUILD_TIME);
#[cfg(feature = "multi-threaded")]
let mut builder = if matches.is_present("SINGLE_THREADED") {
Builder::new_current_thread()
} else {
let mut builder = Builder::new_multi_thread();
if let Some(worker_threads) = matches.value_of("WORKER_THREADS") {
builder.worker_threads(worker_threads.parse::<usize>().expect("worker-threads"));
let mut builder = match service_config.runtime.mode {
RuntimeMode::SingleThread => Builder::new_current_thread(),
#[cfg(feature = "multi-threaded")]
RuntimeMode::MultiThread => {
let mut builder = Builder::new_multi_thread();
if let Some(worker_threads) = service_config.runtime.worker_count {
builder.worker_threads(worker_threads);
}
builder
}
builder
};
#[cfg(not(feature = "multi-threaded"))]
let mut builder = Builder::new_current_thread();
let runtime = builder.enable_all().build().expect("create tokio Runtime");

View File

@@ -4,7 +4,7 @@ use std::{net::IpAddr, path::PathBuf, process, time::Duration};
use clap::{clap_app, App, Arg, ArgMatches, ErrorKind as ClapErrorKind};
use futures::future::{self, Either};
use log::info;
use log::{info, trace};
use tokio::{self, runtime::Builder};
use shadowsocks_service::{
@@ -20,7 +20,11 @@ use shadowsocks_service::{
#[cfg(feature = "logging")]
use crate::logging;
use crate::{monitor, validator};
use crate::{
config::{Config as ServiceConfig, RuntimeMode},
monitor,
validator,
};
/// Defines command line options
pub fn define_command_line_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
@@ -110,22 +114,12 @@ pub fn define_command_line_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
/// Program entrance `main`
pub fn main(matches: &ArgMatches<'_>) {
let (config, runtime) = {
#[cfg(feature = "logging")]
match matches.value_of("LOG_CONFIG") {
Some(path) => {
logging::init_with_file(path);
}
None => {
logging::init_with_config("ssserver", matches);
}
}
let config_path_opt = matches.value_of("CONFIG").map(PathBuf::from).or_else(|| {
if !matches.is_present("SERVER_CONFIG") {
match crate::config::get_default_config_path() {
None => None,
Some(p) => {
info!("loading default config from {:?}", p);
println!("loading default config {:?}", p);
Some(p)
}
}
@@ -134,6 +128,30 @@ pub fn main(matches: &ArgMatches<'_>) {
}
});
let mut service_config = match config_path_opt {
Some(ref config_path) => match ServiceConfig::load_from_file(config_path) {
Ok(c) => c,
Err(err) => {
eprintln!("loading config {:?}, {}", config_path, err);
process::exit(crate::EXIT_CODE_LOAD_CONFIG_FAILURE);
}
},
None => ServiceConfig::default(),
};
service_config.set_options(&matches);
#[cfg(feature = "logging")]
match service_config.log.config_path {
Some(ref path) => {
logging::init_with_file(path);
}
None => {
logging::init_with_config("sslocal", &service_config.log);
}
}
trace!("{:?}", service_config);
let mut config = match config_path_opt {
Some(cpath) => match Config::load_from_file(&cpath, ConfigType::Server) {
Ok(cfg) => cfg,
@@ -324,24 +342,18 @@ pub fn main(matches: &ArgMatches<'_>) {
info!("shadowsocks server {} build {}", crate::VERSION, crate::BUILD_TIME);
#[cfg(feature = "multi-threaded")]
let mut builder = if matches.is_present("SINGLE_THREADED") {
Builder::new_current_thread()
} else {
let mut builder = Builder::new_multi_thread();
match clap::value_t!(matches.value_of("WORKER_THREADS"), usize) {
Ok(worker_threads) => {
let mut builder = match service_config.runtime.mode {
RuntimeMode::SingleThread => Builder::new_current_thread(),
#[cfg(feature = "multi-threaded")]
RuntimeMode::MultiThread => {
let mut builder = Builder::new_multi_thread();
if let Some(worker_threads) = service_config.runtime.worker_count {
builder.worker_threads(worker_threads);
}
Err(ref err) if err.kind == ClapErrorKind::ArgumentNotFound => {}
Err(err) => err.exit(),
}
builder
builder
}
};
#[cfg(not(feature = "multi-threaded"))]
let mut builder = Builder::new_current_thread();
let runtime = builder.enable_all().build().expect("create tokio Runtime");