#![warn(rustdoc::invalid_rust_codeblocks)]
#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
use std::collections::HashMap;
use signal_hook::consts::signal::*;
use signal_hook_tokio::{Signals, Handle};
use futures::stream::StreamExt;
use log::info;
use anyhow::Result;
use tokio::task::JoinHandle;
async fn handle_signals(mut signals: Signals, tx: flume::Sender<i32>) {
while let Some(signal) = signals.next().await {
match signal {
SIGHUP => {
info!("Reloading");
}
SIGTERM | SIGINT | SIGQUIT => {
info!("Exit signal received, doing friendly shutdown...");
tx.send_async(0).await.unwrap();
},
_ => unreachable!(),
}
}
}
pub fn setup_logger(outfile: Option<&str>, level: Option<log::LevelFilter>, extra_levels: Option<HashMap<&str, log::LevelFilter>>) -> Result<()>{
let mut chain = fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
))
})
.level(level.unwrap_or(log::LevelFilter::Info));
if let Some(extra_levels) = extra_levels {
for (module, level) in extra_levels.into_iter() {
chain = chain.level_for(module.to_owned(), level);
}
}
if let Some(outfile) = outfile {
chain = chain.chain(fern::log_file(outfile)?);
}
chain.chain(std::io::stdout())
.apply()?;
Ok(())
}
pub struct InitContext {
handle: Handle,
join: JoinHandle<()>
}
impl InitContext {
pub async fn stop(self) -> Result<()> {
self.handle.close();
self.join.await?;
Ok(())
}
}
pub async fn init(log_file: Option<&str>, level: Option<log::LevelFilter>, extra_levels: Option<HashMap<&str, log::LevelFilter>>) -> Result<(InitContext, flume::Receiver<i32>)> {
setup_logger(log_file, level, extra_levels)?;
let signals = Signals::new([
SIGHUP,
SIGTERM,
SIGINT,
SIGQUIT,
])?;
let handle = signals.handle();
let (tx, rx) = flume::unbounded();
let signals_task = tokio::spawn(handle_signals(signals, tx));
Ok((InitContext{handle, join: signals_task}, rx))
}