use std::{
cell::RefCell,
cmp::{self, Ord},
env,
fmt::{format, Arguments},
fs::{File, OpenOptions},
io::{stdout, Stdout, Write},
net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket},
path::Path,
str::FromStr,
};
use libc;
use mio::net::UnixDatagram;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use crate::config::Config;
thread_local! {
pub static LOGGER: RefCell<Logger> = RefCell::new(Logger::new());
pub static TAG: String = LOGGER.with(|logger| {logger.borrow().tag.clone()});
}
pub static COMPAT_LOGGER: CompatLogger = CompatLogger;
pub struct Logger {
pub directives: Vec<LogDirective>,
pub backend: LoggerBackend,
pub access_backend: Option<LoggerBackend>,
pub tag: String,
pub pid: i32,
pub initialized: bool,
}
impl Default for Logger {
fn default() -> Self {
Self {
directives: vec![LogDirective {
name: None,
level: LogLevelFilter::Error,
}],
backend: LoggerBackend::Stdout(stdout()),
access_backend: None,
tag: "SOZU".to_string(),
pid: 0,
initialized: false,
}
}
}
impl Logger {
pub fn new() -> Self {
Self::default()
}
pub fn init(
tag: String,
spec: &str,
backend: LoggerBackend,
access_backend: Option<LoggerBackend>,
) {
let directives = parse_logging_spec(spec);
LOGGER.with(|l| {
let logger = &mut (*l.borrow_mut());
if !logger.initialized {
logger.set_directives(directives);
logger.backend = backend;
logger.access_backend = access_backend;
logger.tag = tag;
logger.pid = unsafe { libc::getpid() };
logger.initialized = true;
let _ = log::set_logger(&COMPAT_LOGGER)
.map_err(|e| println!("could not register compat logger: {e:?}"));
log::set_max_level(log::LevelFilter::Info);
}
});
}
pub fn log(&mut self, meta: &Metadata, args: Arguments) {
if self.enabled(meta) {
match self.backend {
LoggerBackend::Stdout(ref mut stdout) => {
let _ = stdout.write_fmt(args);
}
LoggerBackend::Unix(ref mut socket) => {
let _ = socket.send(format(args).as_bytes()).map_err(|e| {
println!("cannot write logs to Unix socket: {e:?}");
});
}
LoggerBackend::Udp(ref mut socket, ref address) => {
let _ = socket
.send_to(format(args).as_bytes(), address)
.map_err(|e| {
println!("cannot write logs to UDP socket: {e:?}");
});
}
LoggerBackend::Tcp(ref mut socket) => {
let _ = socket.write_fmt(args).map_err(|e| {
println!("cannot write logs to TCP socket: {e:?}");
});
}
LoggerBackend::File(ref mut file) => {
let _ = file.write_fmt(args).map_err(|e| {
println!("cannot write logs to file: {e:?}");
});
}
}
}
}
pub fn log_access(&mut self, meta: &Metadata, args: Arguments) {
if self.enabled(meta) {
let backend = self.access_backend.as_mut().unwrap_or(&mut self.backend);
match *backend {
LoggerBackend::Stdout(ref mut stdout) => {
let _ = stdout.write_fmt(args);
}
LoggerBackend::Unix(ref mut socket) => {
let _ = socket.send(format(args).as_bytes()).map_err(|e| {
println!("cannot write logs to Unix socket: {e:?}");
});
}
LoggerBackend::Udp(ref mut socket, ref address) => {
let _ = socket
.send_to(format(args).as_bytes(), address)
.map_err(|e| {
println!("cannot write logs to UDP socket: {e:?}");
});
}
LoggerBackend::Tcp(ref mut socket) => {
let _ = socket.write_fmt(args).map_err(|e| {
println!("cannot write logs to TCP socket: {e:?}");
});
}
LoggerBackend::File(ref mut file) => {
let _ = file.write_fmt(args).map_err(|e| {
println!("cannot write logs to file: {e:?}");
});
}
}
}
}
pub fn compat_log(&mut self, meta: &log::Metadata, args: Arguments) {
if self.compat_enabled(meta) {
match self.backend {
LoggerBackend::Stdout(ref mut stdout) => {
let _ = stdout.write_fmt(args);
}
LoggerBackend::Unix(ref mut socket) => {
let _ = socket.send(format(args).as_bytes()).map_err(|e| {
println!("cannot write logs to Unix socket: {e:?}");
});
}
LoggerBackend::Udp(ref mut socket, ref address) => {
let _ = socket
.send_to(format(args).as_bytes(), address)
.map_err(|e| {
println!("cannot write logs to UDP socket: {e:?}");
});
}
LoggerBackend::Tcp(ref mut socket) => {
let _ = socket.write_fmt(args).map_err(|e| {
println!("cannot write logs to TCP socket: {e:?}");
});
}
LoggerBackend::File(ref mut file) => {
let _ = file.write_fmt(args).map_err(|e| {
println!("cannot write logs to file: {e:?}");
});
}
}
}
}
pub fn set_directives(&mut self, directives: Vec<LogDirective>) {
self.directives = directives;
}
fn enabled(&self, meta: &Metadata) -> bool {
for directive in self.directives.iter().rev() {
match directive.name {
Some(ref name) if !meta.target.starts_with(&**name) => {}
Some(..) | None => return meta.level <= directive.level,
}
}
false
}
fn compat_enabled(&self, meta: &log::Metadata) -> bool {
for directive in self.directives.iter().rev() {
match directive.name {
Some(ref name) if !meta.target().starts_with(&**name) => {}
Some(..) | None => {
let lvl: LogLevel = meta.level().into();
return lvl <= directive.level;
}
}
}
false
}
}
pub enum LoggerBackend {
Stdout(Stdout),
Unix(UnixDatagram),
Udp(UdpSocket, SocketAddr),
Tcp(TcpStream),
File(crate::writer::MultiLineWriter<File>),
}
#[repr(usize)]
#[derive(Copy, Eq, Debug)]
pub enum LogLevel {
Error = 1, Warn,
Info,
Debug,
Trace,
}
static LOG_LEVEL_NAMES: [&str; 6] = ["OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"];
impl Clone for LogLevel {
#[inline]
fn clone(&self) -> LogLevel {
*self
}
}
impl PartialEq for LogLevel {
#[inline]
fn eq(&self, other: &LogLevel) -> bool {
*self as usize == *other as usize
}
}
impl PartialEq<LogLevelFilter> for LogLevel {
#[inline]
fn eq(&self, other: &LogLevelFilter) -> bool {
*self as usize == *other as usize
}
}
impl PartialOrd for LogLevel {
#[inline]
fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<LogLevelFilter> for LogLevel {
#[inline]
fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {
Some((*self as usize).cmp(&(*other as usize)))
}
}
impl Ord for LogLevel {
#[inline]
fn cmp(&self, other: &LogLevel) -> cmp::Ordering {
(*self as usize).cmp(&(*other as usize))
}
}
impl LogLevel {
fn from_usize(u: usize) -> Option<LogLevel> {
match u {
1 => Some(LogLevel::Error),
2 => Some(LogLevel::Warn),
3 => Some(LogLevel::Info),
4 => Some(LogLevel::Debug),
5 => Some(LogLevel::Trace),
_ => None,
}
}
#[inline]
pub fn max() -> LogLevel {
LogLevel::Trace
}
#[inline]
pub fn to_log_level_filter(self) -> LogLevelFilter {
LogLevelFilter::from_usize(self as usize).unwrap()
}
}
#[repr(usize)]
#[derive(Copy, Eq, Debug)]
pub enum LogLevelFilter {
Off,
Error,
Warn,
Info,
Debug,
Trace,
}
impl Clone for LogLevelFilter {
#[inline]
fn clone(&self) -> LogLevelFilter {
*self
}
}
impl PartialEq for LogLevelFilter {
#[inline]
fn eq(&self, other: &LogLevelFilter) -> bool {
*self as usize == *other as usize
}
}
impl PartialEq<LogLevel> for LogLevelFilter {
#[inline]
fn eq(&self, other: &LogLevel) -> bool {
other.eq(self)
}
}
impl PartialOrd for LogLevelFilter {
#[inline]
fn partial_cmp(&self, other: &LogLevelFilter) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialOrd<LogLevel> for LogLevelFilter {
#[inline]
fn partial_cmp(&self, other: &LogLevel) -> Option<cmp::Ordering> {
other.partial_cmp(self).map(|x| x.reverse())
}
}
impl Ord for LogLevelFilter {
#[inline]
fn cmp(&self, other: &LogLevelFilter) -> cmp::Ordering {
(*self as usize).cmp(&(*other as usize))
}
}
impl FromStr for LogLevelFilter {
type Err = ();
fn from_str(level: &str) -> Result<LogLevelFilter, ()> {
ok_or(
LOG_LEVEL_NAMES
.iter()
.position(|&name| name.eq_ignore_ascii_case(level))
.map(|p| LogLevelFilter::from_usize(p).unwrap()),
(),
)
}
}
impl LogLevelFilter {
fn from_usize(u: usize) -> Option<LogLevelFilter> {
match u {
0 => Some(LogLevelFilter::Off),
1 => Some(LogLevelFilter::Error),
2 => Some(LogLevelFilter::Warn),
3 => Some(LogLevelFilter::Info),
4 => Some(LogLevelFilter::Debug),
5 => Some(LogLevelFilter::Trace),
_ => None,
}
}
#[inline]
pub fn max() -> LogLevelFilter {
LogLevelFilter::Trace
}
#[inline]
pub fn to_log_level(self) -> Option<LogLevel> {
LogLevel::from_usize(self as usize)
}
}
#[derive(Debug)]
pub struct Metadata {
pub level: LogLevel,
pub target: &'static str,
}
#[derive(Debug)]
pub struct LogDirective {
name: Option<String>,
level: LogLevelFilter,
}
fn ok_or<T, E>(t: Option<T>, e: E) -> Result<T, E> {
match t {
Some(t) => Ok(t),
None => Err(e),
}
}
pub fn parse_logging_spec(spec: &str) -> Vec<LogDirective> {
let mut dirs = Vec::new();
let mut parts = spec.split('/');
let mods = parts.next();
let _ = parts.next();
if parts.next().is_some() {
println!(
"warning: invalid logging spec '{spec}', \
ignoring it (too many '/'s)"
);
return dirs;
}
if let Some(m) = mods {
for s in m.split(',') {
if s.is_empty() {
continue;
}
let mut parts = s.split('=');
let (log_level, name) =
match (parts.next(), parts.next().map(|s| s.trim()), parts.next()) {
(Some(part0), None, None) => {
match part0.parse() {
Ok(num) => (num, None),
Err(_) => (LogLevelFilter::max(), Some(part0)),
}
}
(Some(part0), Some(""), None) => (LogLevelFilter::max(), Some(part0)),
(Some(part0), Some(part1), None) => match part1.parse() {
Ok(num) => (num, Some(part0)),
_ => {
println!(
"warning: invalid logging spec '{part1}', \
ignoring it"
);
continue;
}
},
_ => {
println!(
"warning: invalid logging spec '{s}', \
ignoring it"
);
continue;
}
};
dirs.push(LogDirective {
name: name.map(|s| s.to_string()),
level: log_level,
});
}
}
dirs
}
pub fn setup_logging_with_config(config: &Config, tag: &str) {
setup_logging(
&config.log_target,
config.log_access_target.as_deref(),
&config.log_level,
tag,
)
}
pub fn setup_logging(
log_target: &str,
log_access_target: Option<&str>,
log_level: &str,
tag: &str,
) {
let backend = target_to_backend(log_target);
let access_backend = log_access_target.map(target_to_backend);
if let Ok(env_log_level) = env::var("RUST_LOG") {
Logger::init(tag.to_string(), &env_log_level, backend, access_backend);
} else {
Logger::init(tag.to_string(), log_level, backend, access_backend);
}
}
pub fn target_to_backend(target: &str) -> LoggerBackend {
if target == "stdout" {
LoggerBackend::Stdout(stdout())
} else if let Some(addr) = target.strip_prefix("udp://") {
match addr.to_socket_addrs() {
Err(e) => {
println!("invalid log target configuration ({e:?}): {target}");
LoggerBackend::Stdout(stdout())
}
Ok(mut addrs) => {
let socket = UdpSocket::bind(("0.0.0.0", 0)).unwrap();
LoggerBackend::Udp(socket, addrs.next().unwrap())
}
}
} else if let Some(addr) = target.strip_prefix("tcp://") {
match addr.to_socket_addrs() {
Err(e) => {
println!("invalid log target configuration ({e:?}): {target}");
LoggerBackend::Stdout(stdout())
}
Ok(mut addrs) => LoggerBackend::Tcp(TcpStream::connect(addrs.next().unwrap()).unwrap()),
}
} else if let Some(addr) = target.strip_prefix("unix://") {
let path = Path::new(addr);
if !path.exists() {
println!("invalid log target configuration: {addr} is not a file");
LoggerBackend::Stdout(stdout())
} else {
let mut dir = env::temp_dir();
let s: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(12)
.map(|c| c as char)
.collect();
dir.push(s);
let socket = UnixDatagram::bind(dir).unwrap();
socket.connect(path).unwrap();
LoggerBackend::Unix(socket)
}
} else if let Some(addr) = target.strip_prefix("file://") {
let path = Path::new(addr);
match OpenOptions::new().create(true).append(true).open(path) {
Ok(file) => LoggerBackend::File(crate::writer::MultiLineWriter::new(file)),
Err(e) => {
println!(
"invalid log target configuration: could not open file at {addr} (error: {e:?})"
);
LoggerBackend::Stdout(stdout())
}
}
} else {
println!("invalid log target configuration: {target}");
LoggerBackend::Stdout(stdout())
}
}
#[macro_export]
macro_rules! log {
(__inner__ $target:expr, $lvl:expr, $format:expr, $level_tag:expr,
[$($transformed_args:ident),*], [$first_ident:ident $(, $other_idents:ident)*], $first_arg:expr $(, $other_args:expr)*) => ({
let $first_ident = &$first_arg;
log!(__inner__ $target, $lvl, $format, $level_tag, [$($transformed_args,)* $first_ident], [$($other_idents),*] $(, $other_args)*);
});
(__inner__ $target:expr, $lvl:expr, $format:expr, $level_tag:expr,
[$($final_args:ident),*], [$($idents:ident),*]) => ({
static _META: $crate::logging::Metadata = $crate::logging::Metadata {
level: $lvl,
target: module_path!(),
};
{
$crate::logging::TAG.with(|tag| {
$crate::logging::LOGGER.with(|l| {
let pid = l.borrow().pid;
let (now, precise_time) = $crate::logging::now();
l.borrow_mut().log(
&_META,
format_args!(
concat!("{} {} {} {} {}\t", $format, '\n'),
now, precise_time, pid, tag,
$level_tag $(, $final_args)*)
);
})
});
}
});
($lvl:expr, $format:expr, $level_tag:expr $(, $args:expr)+) => {
log!(__inner__ module_path!(), $lvl, $format, $level_tag, [], [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v]
$(, $args)+)
};
($lvl:expr, $format:expr, $level_tag:expr) => {
log!(__inner__ module_path!(), $lvl, $format, $level_tag, [], [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v])
};
}
#[macro_export]
macro_rules! log_access {
(__inner__ $target:expr, $lvl:expr, $format:expr, $level_tag:expr,
[$($transformed_args:ident),*], [$first_ident:ident $(, $other_idents:ident)*], $first_arg:expr $(, $other_args:expr)*) => ({
let $first_ident = &$first_arg;
log_access!(__inner__ $target, $lvl, $format, $level_tag, [$($transformed_args,)* $first_ident], [$($other_idents),*] $(, $other_args)*);
});
(__inner__ $target:expr, $lvl:expr, $format:expr, $level_tag:expr,
[$($final_args:ident),*], [$($idents:ident),*]) => ({
static _META: $crate::logging::Metadata = $crate::logging::Metadata {
level: $lvl,
target: module_path!(),
};
{
$crate::logging::TAG.with(|tag| {
$crate::logging::LOGGER.with(|l| {
let pid = l.borrow().pid;
let (now, precise_time) = $crate::logging::now();
l.borrow_mut().log_access(
&_META,
format_args!(
concat!("{} {} {} {} {}\t", $format, '\n'),
now, precise_time, pid, tag,
$level_tag $(, $final_args)*)
);
})
});
}
});
($lvl:expr, $format:expr, $level_tag:expr $(, $args:expr)+) => {
log_access!(__inner__ module_path!(), $lvl, $format, $level_tag, [], [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v]
$(, $args)+)
};
($lvl:expr, $format:expr, $level_tag:expr) => {
log_access!(__inner__ module_path!(), $lvl, $format, $level_tag, [], [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v])
};
}
#[macro_export]
macro_rules! error {
($format:expr, $($arg:tt)*) => {
log!($crate::logging::LogLevel::Error, $format, "ERROR", $($arg)*)
};
($format:expr) => {
log!($crate::logging::LogLevel::Error, $format, "ERROR")
};
}
#[macro_export]
macro_rules! error_access {
($format:expr, $($arg:tt)*) => {
log_access!($crate::logging::LogLevel::Error, $format, "ERROR", $($arg)*);
};
($format:expr) => {
log_access!($crate::logging::LogLevel::Error, $format, "ERROR");
};
}
#[macro_export]
macro_rules! warn {
($format:expr, $($arg:tt)*) => {
log!($crate::logging::LogLevel::Warn, $format, "WARN", $($arg)*);
};
($format:expr) => {
log!($crate::logging::LogLevel::Warn, $format, "WARN");
}
}
#[macro_export]
macro_rules! info {
($format:expr, $($arg:tt)*) => {
log!($crate::logging::LogLevel::Info, $format, "INFO", $($arg)*);
};
($format:expr) => {
log!($crate::logging::LogLevel::Info, $format, "INFO");
}
}
#[macro_export]
macro_rules! info_access {
($format:expr, $($arg:tt)*) => {
log_access!($crate::logging::LogLevel::Info, $format, "INFO", $($arg)*);
};
($format:expr) => {
log_access!($crate::logging::LogLevel::Info, $format, "INFO");
}
}
#[macro_export]
macro_rules! debug {
($format:expr, $($arg:tt)*) => {
#[cfg(any(debug_assertions, feature = "logs-debug", feature = "logs-trace"))]
log!($crate::logging::LogLevel::Debug, concat!("{}\t", $format),
"DEBUG", {module_path!()}, $($arg)*);
};
($format:expr) => {
#[cfg(any(debug_assertions, feature = "logs-debug", feature = "logs-trace"))]
log!($crate::logging::LogLevel::Debug, concat!("{}\t", $format),
"DEBUG", {module_path!()});
}
}
#[macro_export]
macro_rules! trace {
($format:expr, $($arg:tt)*) => (
#[cfg(any(debug_assertions, feature = "logs-trace"))]
log!($crate::logging::LogLevel::Trace, concat!("{}\t", $format),
"TRACE", module_path!(), $($arg)*);
);
($format:expr) => (
#[cfg(any(debug_assertions, feature = "logs-trace"))]
log!($crate::logging::LogLevel::Trace, concat!("{}\t", $format),
"TRACE", module_path!());
)
}
#[macro_export]
macro_rules! fixme {
() => {
log!($crate::logging::LogLevel::Info, "FIXME: {}:{} in {}", "INFO", file!(), line!(), module_path!());
};
($($arg:tt)*) => {
log!($crate::logging::LogLevel::Info, "FIXME: {}:{} in {}: {}", "INFO", file!(), line!(), module_path!(), $($arg)*);
};
}
pub struct CompatLogger;
impl From<log::Level> for LogLevel {
fn from(lvl: log::Level) -> Self {
match lvl {
log::Level::Error => LogLevel::Error,
log::Level::Warn => LogLevel::Warn,
log::Level::Info => LogLevel::Info,
log::Level::Debug => LogLevel::Debug,
log::Level::Trace => LogLevel::Trace,
}
}
}
impl log::Log for CompatLogger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
TAG.with(|tag| {
LOGGER.with(|l| {
let pid = l.borrow().pid;
let (now, precise_time) = now();
l.borrow_mut().compat_log(
record.metadata(),
format_args!(
concat!("{} {} {} {} {}\t{}\n"),
now,
precise_time,
pid,
tag,
record.level(),
record.args()
),
);
})
});
}
fn flush(&self) {}
}
#[macro_export]
macro_rules! setup_test_logger {
() => {
$crate::logging::Logger::init(
module_path!().to_string(),
"error",
$crate::logging::LoggerBackend::Stdout(::std::io::stdout()),
None,
);
};
}
pub struct Rfc3339Time {
inner: ::time::OffsetDateTime,
}
impl std::fmt::Display for Rfc3339Time {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let t = self.inner;
write!(
f,
"{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
t.year(),
t.month() as u8,
t.day(),
t.hour(),
t.minute(),
t.second(),
t.microsecond()
)
}
}
pub fn now() -> (Rfc3339Time, i128) {
let t = time::OffsetDateTime::now_utc();
(
Rfc3339Time { inner: t },
(t - time::OffsetDateTime::UNIX_EPOCH).whole_nanoseconds(),
)
}