#[cfg(all(feature = "systemd", target_os = "linux"))]
pub mod systemd {
use sd_notify::{NotifyState, notify, watchdog_enabled};
use std::time::Duration;
use tracing::level_filters::LevelFilter;
use tracing::{Level, debug, error, info, instrument, warn};
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_systemd::SystemdLayer;
use crate::{Logger, Notify, Reload, Shutdown, Watchdog};
#[derive(Copy, Clone)]
pub struct SystemdIntegration;
impl Logger for SystemdIntegration {
#[instrument(skip_all)]
fn setup_logger(&self, level: Level) {
let systemd_logger = SystemdLayer::stdout()
.with_target(true)
.with_thread_ids(true)
.with_filter(LevelFilter::from_level(level));
tracing_subscriber::registry().with(systemd_logger).init();
info!("Initialized logger, level is level {level}");
}
}
impl Watchdog for SystemdIntegration {
#[instrument(skip_all)]
fn notify_interval(&self) -> Option<Duration> {
match watchdog_enabled() {
Some(interval) => {
debug!(
"Systemd watchdog is enabled. Trigger interval is {} µs.",
interval.as_micros()
);
Some(interval)
}
None => {
debug!("Systemd watchdog is disabled.");
None
}
}
}
#[instrument(skip_all)]
fn notify(&self) {
if let Err(err) = notify(&[NotifyState::Watchdog]) {
error!("Failed to trigger systemd watchdog. Error was: {err}");
}
}
}
impl Shutdown for SystemdIntegration {
#[instrument(skip_all)]
async fn wait_for_shutdown(&self) {
#[cfg(not(feature = "tokio"))]
{
use async_signal::{Signal, Signals};
use futures::StreamExt;
let mut signals =
Signals::new([Signal::Int, Signal::Term, Signal::Quit, Signal::Abort]).unwrap();
match signals.next().await {
Some(Ok(Signal::Int)) => debug!("Received OS signal SIGINT."),
Some(Ok(Signal::Term)) => debug!("Received OS signal SIGTERM."),
Some(Ok(Signal::Quit)) => debug!("Received OS signal SIGQUIT."),
Some(Ok(Signal::Abort)) => debug!("Received OS signal SIGABRT."),
_ => panic!(
"Something unexpected happened while waiting for signals. This is a bug."
),
}
}
#[cfg(feature = "tokio")]
{
use libc::{SIGABRT, SIGINT, SIGQUIT, SIGTERM};
use tokio::signal::unix::{SignalKind, signal};
let mut sigint = signal(SignalKind::from_raw(SIGINT)).unwrap();
let mut sigterm = signal(SignalKind::from_raw(SIGTERM)).unwrap();
let mut sigquit = signal(SignalKind::from_raw(SIGQUIT)).unwrap();
let mut sigabrt = signal(SignalKind::from_raw(SIGABRT)).unwrap();
tokio::select! {
_ = sigint.recv() => debug!("Received OS signal SIGINT."),
_ = sigterm.recv() => debug!("Received OS signal SIGTERM."),
_ = sigquit.recv() => debug!("Received OS signal SIGQUIT."),
_ = sigabrt.recv() => debug!("Received OS signal SIGABRT."),
}
}
}
}
impl Reload for SystemdIntegration {
#[instrument(skip_all)]
async fn wait_for_reload(&self) {
#[cfg(not(feature = "tokio"))]
{
use async_signal::{Signal, Signals};
use futures::StreamExt;
let mut signals = Signals::new([Signal::Hup]).unwrap();
match signals.next().await {
Some(Ok(Signal::Hup)) => debug!("Received OS signal SIGHUP."),
_ => panic!(
"Something unexpected happened while waiting for signals. This is a bug."
),
}
}
#[cfg(feature = "tokio")]
{
use libc::SIGHUP;
use tokio::signal::unix::{SignalKind, signal};
signal(SignalKind::from_raw(SIGHUP)).unwrap().recv().await;
debug!("Received OS signal SIGHUP.")
}
}
}
impl Notify for SystemdIntegration {
#[instrument(skip_all)]
fn notify_ready(&self) {
match NotifyState::monotonic_usec_now()
.and_then(|now| notify(&[NotifyState::Ready, now]))
{
Ok(()) => debug!("Daemon is ready"),
Err(err) => error!("Daemon is ready notification failed. Error was {err}"),
}
}
#[instrument(skip_all)]
fn notify_reloading(&self) {
match NotifyState::monotonic_usec_now()
.and_then(|now| notify(&[NotifyState::Reloading, now]))
{
Ok(()) => debug!("Daemon is reloading"),
Err(err) => error!("Daemon is reloading notification failed. Error was {err}"),
}
}
#[instrument(skip_all)]
fn notify_stopping(&self) {
match notify(&[NotifyState::Stopping]) {
Ok(()) => debug!("Daemon is stopping"),
Err(err) => error!("Daemon is stopping notification failed. Error was {err}"),
}
}
}
}
#[cfg(test)]
pub mod mocks {
use mockall::mock;
use std::time::Duration;
use tracing::Level;
use crate::{Logger, Notify, Reload, Shutdown, Watchdog};
mock! {
pub Integration {}
impl Logger for Integration {
fn setup_logger(&self, max_level: Level);
}
impl Watchdog for Integration {
fn notify_interval(&self) -> Option<Duration>;
fn notify(&self);
}
impl Reload for Integration {
fn wait_for_reload(&self)-> impl Future<Output = ()> + Send;
}
impl Shutdown for Integration {
fn wait_for_shutdown(&self) -> impl Future<Output = ()> + Send;
}
impl Notify for Integration {
fn notify_ready(&self);
fn notify_reloading(&self);
fn notify_stopping(&self);
}
}
}