use std::io::{self, IsTerminal};
#[cfg(unix)]
use syslog_tracing::Syslog;
use time::{UtcOffset, format_description::well_known::Rfc3339};
use tracing::level_filters::LevelFilter;
use tracing_appender::rolling::{InitError, RollingFileAppender};
use tracing_subscriber::{
fmt::{MakeWriter, time::OffsetTime},
layer::SubscriberExt,
util::SubscriberInitExt,
{EnvFilter, Layer, Registry, fmt},
};
#[cfg(unix)]
use crate::config::LogSyslogWriterConfig;
use crate::config::{
LogConfig, LogConsoleWriterConfig, LogFileWriterConfig, LogFormatConfig, LogFormatConfigOverride, LogWriterConfig,
};
pub fn init_with_config(bin_name: &str, config: &LogConfig) {
let layers: Vec<BoxedLayer> = config
.writers
.iter()
.map(|writer| writer.make_layer(bin_name, config))
.collect();
tracing_subscriber::registry().with(layers).init();
}
type BoxedLayer = Box<dyn Layer<Registry> + 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::Console(console_config) => console_config.make_layer(bin_name, global),
LogWriterConfig::File(file_config) => file_config.make_layer(bin_name, global),
#[cfg(unix)]
LogWriterConfig::Syslog(syslog_config) => syslog_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)
.expect("Failed to create file writer for logging");
make_fmt_layer(bin_name, level, &format, false, file_writer)
}
}
#[cfg(unix)]
impl MakeLayer for LogSyslogWriterConfig {
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 syslog_writer = make_syslog_writer(bin_name, self);
make_fmt_layer(bin_name, level, &format, false, syslog_writer)
}
}
fn make_fmt_layer<W>(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);
if !ansi {
layer = layer.with_ansi(false);
}
if level >= 1 {
layer = layer.with_target(true).with_thread_ids(true).with_thread_names(true);
if level >= 3 {
layer = layer.with_file(true).with_line_number(true);
}
} else {
layer = layer.with_target(false).with_thread_ids(false).with_thread_names(false);
}
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()
.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 level {
0 => EnvFilter::builder()
.with_regex(true)
.with_default_directive(LevelFilter::ERROR.into())
.parse_lossy(format!(
"warn,{}=info,shadowsocks_rust=info,shadowsocks_service=info,shadowsocks=info",
bin_name
)),
1 => EnvFilter::builder()
.with_regex(true)
.with_default_directive(LevelFilter::ERROR.into())
.parse_lossy(format!(
"warn,{}=debug,shadowsocks_rust=debug,shadowsocks_service=debug,shadowsocks=debug",
bin_name
)),
2 => EnvFilter::builder()
.with_regex(true)
.with_default_directive(LevelFilter::ERROR.into())
.parse_lossy(format!(
"warn,{}=trace,shadowsocks_rust=trace,shadowsocks_service=trace,shadowsocks=trace",
bin_name
)),
_ => EnvFilter::builder()
.with_default_directive(LevelFilter::TRACE.into())
.parse_lossy(""),
},
}
}
fn make_file_writer(bin_name: &str, config: &LogFileWriterConfig) -> Result<RollingFileAppender, InitError> {
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 {
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),
}
}
#[cfg(unix)]
fn make_syslog_writer(bin_name: &str, config: &LogSyslogWriterConfig) -> Syslog {
use std::ffi::CString;
use syslog_tracing::{Facility, Options};
let identity = config.identity.as_deref().unwrap_or(bin_name);
let facility = match config.facility {
None => Facility::default(),
Some(f) => match f {
1 => Facility::User,
2 => Facility::Mail,
3 => Facility::Daemon,
4 => Facility::Auth,
6 => Facility::Lpr,
7 => Facility::News,
8 => Facility::Uucp,
9 => Facility::Cron,
10 => Facility::AuthPriv,
16 => Facility::Local0,
17 => Facility::Local1,
18 => Facility::Local2,
19 => Facility::Local3,
20 => Facility::Local4,
21 => Facility::Local5,
22 => Facility::Local6,
23 => Facility::Local7,
_ => panic!("unsupported syslog facility: {}", f),
},
};
let options = Options::default();
let identity = CString::new(identity).expect("syslog identity contains null-byte ('\\0')");
match Syslog::new(identity, options, facility) {
Some(l) => l,
None => panic!("syslog is already initialized"),
}
}