Skip to content

Commit dd916cf

Browse files
feat: support file logging with tracing
1 parent b6204b1 commit dd916cf

File tree

9 files changed

+157
-19
lines changed

9 files changed

+157
-19
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ dns-over-https = ["shadowsocks-service/dns-over-https"]
124124
dns-over-h3 = ["shadowsocks-service/dns-over-h3"]
125125

126126
# Enable logging output
127-
logging = ["log4rs", "tracing", "tracing-subscriber", "time"]
127+
logging = ["log4rs", "tracing", "tracing-subscriber", "time", "tracing-appender"]
128128

129129
# Enable DNS-relay
130130
local-dns = ["local", "shadowsocks-service/local-dns"]
@@ -208,6 +208,7 @@ tracing-subscriber = { version = "0.3", optional = true, features = [
208208
"time",
209209
"local-time",
210210
] }
211+
tracing-appender = { version = "0.2.3", optional = true, default-features = false }
211212
time = { version = "0.3", optional = true }
212213

213214
serde = { version = "1.0", features = ["derive"] }

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -877,9 +877,23 @@ Example configuration:
877877
// Euiqvalent to `--log-without-time`
878878
"without_time": false,
879879
},
880-
// Equivalent to `--log-config`
881-
// More detail could be found in https://crates.io/crates/log4rs
882-
"config_path": "/path/to/log4rs/config.yaml"
880+
// File logging configuration (will disable stdout logging)
881+
// This is particularly useful for running as a Windows Service
882+
"file": {
883+
// Directory to store log files. If not set, it will not log to file
884+
"directory": "/var/log/shadowsocks-rust",
885+
// Log rotation frequency, must be one of the following:
886+
// - never (default): This will result in log file located at `directory/prefix.suffix`
887+
// - daily: A new log file in the format of `directory/prefix.yyyy-MM-dd.suffix` will be created daily
888+
// - hourly: A new log file in the format of `directory/prefix.yyyy-MM-dd-HH.suffix` will be created hourly
889+
"rotation": "never",
890+
// Prefix of log file, default is one of `sslocal`, `ssserver`, `ssmanager` depending on the service being run.
891+
"prefix": "shadowsocks-rust",
892+
// Suffix of log file, default is `log`
893+
"suffix": "log",
894+
// Keeps the last N log files. If not set, no limit will be applied.
895+
"max_files": 5
896+
}
883897
},
884898
// Runtime configuration
885899
"runtime": {

src/config.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,31 @@ impl Config {
142142
nlog.format = nformat;
143143
}
144144

145+
if let Some(file_config) = log.file
146+
// directory must be configured for file logging
147+
&& let Some(directory) = file_config.directory
148+
{
149+
let mut nfile = LogFileConfig::new(directory);
150+
if let Some(rotation) = file_config.rotation {
151+
nfile.rotation = match rotation.as_str() {
152+
"never" => tracing_appender::rolling::Rotation::NEVER,
153+
"hourly" => tracing_appender::rolling::Rotation::HOURLY,
154+
"daily" => tracing_appender::rolling::Rotation::DAILY,
155+
_ => return Err(ConfigError::InvalidValue(rotation)),
156+
};
157+
}
158+
if let Some(prefix) = file_config.prefix {
159+
nfile.prefix = Some(prefix);
160+
}
161+
if let Some(suffix) = file_config.suffix {
162+
nfile.suffix = Some(suffix);
163+
}
164+
if let Some(max_files) = file_config.max_files {
165+
nfile.max_files = Some(max_files);
166+
}
167+
nlog.file = Some(nfile);
168+
}
169+
145170
if let Some(config_path) = log.config_path {
146171
nlog.config_path = Some(PathBuf::from(config_path));
147172
}
@@ -210,6 +235,8 @@ pub struct LogConfig {
210235
pub level: u32,
211236
/// Default logger format configuration
212237
pub format: LogFormatConfig,
238+
/// File appender configuration
239+
pub file: Option<LogFileConfig>,
213240
/// Logging configuration file path
214241
pub config_path: Option<PathBuf>,
215242
}
@@ -221,6 +248,35 @@ pub struct LogFormatConfig {
221248
pub without_time: bool,
222249
}
223250

251+
/// File appender configuration for logging
252+
#[cfg(feature = "logging")]
253+
#[derive(Debug, Clone)]
254+
pub struct LogFileConfig {
255+
/// Directory to store log files
256+
pub directory: PathBuf,
257+
/// Rotation strategy for log files. Default is `Rotation::NEVER`.
258+
pub rotation: tracing_appender::rolling::Rotation,
259+
/// Prefix for log file names. Default is the binary name.
260+
pub prefix: Option<String>,
261+
/// Suffix for log file names. Default is "log".
262+
pub suffix: Option<String>,
263+
/// Maximum number of log files to keep. Default is `None`, meaning no limit.
264+
pub max_files: Option<usize>,
265+
}
266+
267+
#[cfg(feature = "logging")]
268+
impl LogFileConfig {
269+
fn new(directory: impl Into<PathBuf>) -> Self {
270+
Self {
271+
directory: directory.into(),
272+
rotation: tracing_appender::rolling::Rotation::NEVER,
273+
prefix: None,
274+
suffix: None,
275+
max_files: None,
276+
}
277+
}
278+
}
279+
224280
/// Runtime mode (Tokio)
225281
#[derive(Debug, Clone, Copy, Default)]
226282
pub enum RuntimeMode {
@@ -272,6 +328,7 @@ struct SSConfig {
272328
struct SSLogConfig {
273329
level: Option<u32>,
274330
format: Option<SSLogFormat>,
331+
file: Option<SSLogFileConfig>,
275332
config_path: Option<String>,
276333
}
277334

@@ -281,6 +338,16 @@ struct SSLogFormat {
281338
without_time: Option<bool>,
282339
}
283340

341+
#[cfg(feature = "logging")]
342+
#[derive(Deserialize)]
343+
struct SSLogFileConfig {
344+
directory: Option<String>,
345+
rotation: Option<String>,
346+
prefix: Option<String>,
347+
suffix: Option<String>,
348+
max_files: Option<usize>,
349+
}
350+
284351
#[derive(Deserialize)]
285352
struct SSRuntimeConfig {
286353
#[cfg(feature = "multi-threaded")]

src/logging/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::config::LogConfig;
99
mod log4rs;
1010
mod tracing;
1111

12-
/// Initialize logger ([log4rs](https://crates.io/crates/log4rs), [trace4rs](https://crates.io/crates/trace4rs)) from yaml configuration file
12+
/// Initialize [log4rs](https://crates.io/crates/log4rs) from yaml configuration file
1313
pub fn init_with_file<P>(path: P)
1414
where
1515
P: AsRef<Path>,
@@ -25,7 +25,6 @@ where
2525

2626
/// Initialize logger with provided configuration
2727
pub fn init_with_config(bin_name: &str, config: &LogConfig) {
28-
// log4rs::init_with_config(bin_name, config);
2928
tracing::init_with_config(bin_name, config);
3029
}
3130

src/logging/tracing.rs

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,27 @@
22
33
use std::io::IsTerminal;
44

5+
use time::format_description::well_known::Rfc3339;
56
use time::UtcOffset;
67
use tracing::level_filters::LevelFilter;
7-
use tracing_subscriber::{EnvFilter, FmtSubscriber, fmt::time::OffsetTime};
8+
use tracing_appender::rolling::{InitError, RollingFileAppender};
9+
use tracing_subscriber::fmt::format::{DefaultFields, Format, Full};
10+
use tracing_subscriber::fmt::time::OffsetTime;
11+
use tracing_subscriber::fmt::{MakeWriter, SubscriberBuilder};
12+
use tracing_subscriber::{EnvFilter, FmtSubscriber};
813

9-
use crate::config::LogConfig;
14+
use crate::config::{LogConfig, LogFileConfig};
1015

1116
/// Initialize logger with provided configuration
1217
pub fn init_with_config(bin_name: &str, config: &LogConfig) {
1318
let debug_level = config.level;
1419
let without_time = config.format.without_time;
1520

16-
let mut builder = FmtSubscriber::builder()
17-
.with_level(true)
18-
.with_timer(match OffsetTime::local_rfc_3339() {
19-
Ok(t) => t,
20-
Err(..) => {
21-
// Reinit with UTC time
22-
OffsetTime::new(UtcOffset::UTC, time::format_description::well_known::Rfc3339)
23-
}
24-
});
21+
let mut builder = FmtSubscriber::builder().with_level(true).with_timer(
22+
OffsetTime::local_rfc_3339()
23+
// Fallback to UTC. Eagerly evaluate because it is cheap to create.
24+
.unwrap_or(OffsetTime::new(UtcOffset::UTC, Rfc3339)),
25+
);
2526

2627
// NOTE: ansi is enabled by default.
2728
// Could be disabled by `NO_COLOR` environment variable.
@@ -74,6 +75,43 @@ pub fn init_with_config(bin_name: &str, config: &LogConfig) {
7475
};
7576
let builder = builder.with_env_filter(filter);
7677

78+
if let Some(ref file_config) = config.file {
79+
let file_writer = make_file_writer(bin_name, file_config)
80+
// don't have the room for a more graceful error handling here
81+
.expect("Failed to create file writer for logging");
82+
init(builder.with_ansi(false).with_writer(file_writer), without_time);
83+
} else {
84+
init(builder, without_time);
85+
}
86+
}
87+
88+
fn make_file_writer(bin_name: &str, config: &LogFileConfig) -> Result<RollingFileAppender, InitError> {
89+
let rotation = config.rotation.clone();
90+
// We provide default values here because we don't have access to the
91+
// `bin_name` elsewhere.
92+
let prefix = config.prefix.as_deref().unwrap_or(bin_name);
93+
let suffix = config.suffix.as_deref().unwrap_or("log");
94+
95+
let mut builder = RollingFileAppender::builder()
96+
.rotation(rotation)
97+
.filename_prefix(prefix)
98+
.filename_suffix(suffix);
99+
100+
if let Some(max_files) = config.max_files {
101+
builder = builder.max_log_files(max_files);
102+
}
103+
104+
builder.build(&config.directory)
105+
}
106+
107+
/// Initialize the logger with the provided builder and options.
108+
///
109+
/// This handles the `without_time` option generically for builders that
110+
/// are configured with different `MakeWriter` concrete types.
111+
fn init<W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static>(
112+
builder: SubscriberBuilder<DefaultFields, Format<Full, OffsetTime<Rfc3339>>, EnvFilter, W>,
113+
without_time: bool,
114+
) {
77115
if without_time {
78116
builder.without_time().init();
79117
} else {

src/service/local.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ pub fn define_command_line_options(mut app: Command) -> Command {
261261
.arg(
262262
Arg::new("LOG_CONFIG")
263263
.long("log-config")
264+
// deprecated for removal
265+
.hide(true)
264266
.num_args(1)
265267
.action(ArgAction::Set)
266268
.value_parser(clap::value_parser!(PathBuf))

src/service/manager.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ pub fn define_command_line_options(mut app: Command) -> Command {
148148
.arg(
149149
Arg::new("LOG_CONFIG")
150150
.long("log-config")
151+
// deprecated for removal
152+
.hide(true)
151153
.num_args(1)
152154
.action(ArgAction::Set)
153155
.value_parser(clap::value_parser!(PathBuf))
@@ -297,7 +299,7 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<O
297299
logging::init_with_file(path);
298300
}
299301
None => {
300-
logging::init_with_config("sslocal", &service_config.log);
302+
logging::init_with_config("ssmanager", &service_config.log);
301303
}
302304
}
303305

src/service/server.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ pub fn define_command_line_options(mut app: Command) -> Command {
178178
.arg(
179179
Arg::new("LOG_CONFIG")
180180
.long("log-config")
181+
// deprecated for removal
182+
.hide(true)
181183
.num_args(1)
182184
.action(ArgAction::Set)
183185
.value_parser(clap::value_parser!(PathBuf))
@@ -309,7 +311,7 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<O
309311
logging::init_with_file(path);
310312
}
311313
None => {
312-
logging::init_with_config("sslocal", &service_config.log);
314+
logging::init_with_config("ssserver", &service_config.log);
313315
}
314316
}
315317

0 commit comments

Comments
 (0)