#![deny(
warnings,
clippy::all,
clippy::pedantic,
missing_docs,
missing_debug_implementations
)]
#![forbid(unsafe_code)]
use std::fmt::{Display, Formatter};
use std::os::{fd::AsFd, linux::fs::MetadataExt};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LogLevel {
Emerg,
Alert,
Crit,
Err,
Warning,
Notice,
Info,
Debug,
}
#[derive(Debug, Copy, Clone)]
pub struct LogLevelParseError;
impl Display for LogLevelParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid log level")
}
}
impl std::error::Error for LogLevelParseError {}
impl TryFrom<&str> for LogLevel {
type Error = LogLevelParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"emerg" => Ok(LogLevel::Emerg),
"alert" => Ok(LogLevel::Alert),
"crit" => Ok(LogLevel::Crit),
"err" => Ok(LogLevel::Err),
"warning" => Ok(LogLevel::Warning),
"notice" => Ok(LogLevel::Notice),
"info" => Ok(LogLevel::Info),
"debug" => Ok(LogLevel::Debug),
_ => Err(LogLevelParseError),
}
}
}
impl Display for LogLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let level = match self {
LogLevel::Emerg => "emerg",
LogLevel::Alert => "alert",
LogLevel::Crit => "crit",
LogLevel::Err => "err",
LogLevel::Warning => "warning",
LogLevel::Notice => "notice",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
};
write!(f, "{level}")
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum KnownLogTarget {
Console,
Kmsg,
Journal,
Syslog,
Null,
Auto,
}
impl KnownLogTarget {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
KnownLogTarget::Console => "console",
KnownLogTarget::Kmsg => "kmsg",
KnownLogTarget::Journal => "journal",
KnownLogTarget::Syslog => "syslog",
KnownLogTarget::Null => "null",
KnownLogTarget::Auto => "auto",
}
}
}
#[derive(Debug, Clone)]
pub struct LogTargetParseError(String);
impl Display for LogTargetParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid log target: '{}'", self.0)
}
}
impl std::error::Error for LogTargetParseError {}
impl From<LogTargetParseError> for LogControl1Error {
fn from(value: LogTargetParseError) -> Self {
Self::UnsupportedLogTarget(value.0)
}
}
impl TryFrom<&str> for KnownLogTarget {
type Error = LogTargetParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"console" => Ok(KnownLogTarget::Console),
"kmsg" => Ok(KnownLogTarget::Kmsg),
"journal" => Ok(KnownLogTarget::Journal),
"syslog" => Ok(KnownLogTarget::Syslog),
"null" => Ok(KnownLogTarget::Null),
"auto" => Ok(KnownLogTarget::Auto),
_ => Err(LogTargetParseError(value.to_string())),
}
}
}
impl Display for KnownLogTarget {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug)]
pub enum LogControl1Error {
UnsupportedLogLevel(LogLevel),
UnsupportedLogTarget(String),
InputOutputError(std::io::Error),
Failure(String),
}
impl Display for LogControl1Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LogControl1Error::UnsupportedLogLevel(log_level) => {
write!(f, "The log level {log_level} is not supported")
}
LogControl1Error::UnsupportedLogTarget(target) => {
write!(f, "The log target {target} is not supported")
}
LogControl1Error::InputOutputError(error) => error.fmt(f),
LogControl1Error::Failure(message) => message.fmt(f),
}
}
}
impl std::error::Error for LogControl1Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
LogControl1Error::InputOutputError(error) => Some(error),
_ => None,
}
}
}
impl From<std::io::Error> for LogControl1Error {
fn from(error: std::io::Error) -> Self {
Self::InputOutputError(error)
}
}
pub trait LogControl1 {
fn level(&self) -> LogLevel;
fn set_level(&mut self, level: LogLevel) -> Result<(), LogControl1Error>;
fn target(&self) -> &str;
fn set_target<S: AsRef<str>>(&mut self, target: S) -> Result<(), LogControl1Error>;
fn syslog_identifier(&self) -> &str;
}
pub static DBUS_OBJ_PATH: &str = "/org/freedesktop/LogControl1";
#[must_use]
pub fn stderr_connected_to_journal() -> bool {
std::io::stderr()
.as_fd()
.try_clone_to_owned()
.and_then(|fd| std::fs::File::from(fd).metadata())
.map(|metadata| format!("{}:{}", metadata.st_dev(), metadata.st_ino()))
.ok()
.and_then(|stderr| {
std::env::var_os("JOURNAL_STREAM").map(|s| s.to_string_lossy() == stderr.as_str())
})
.unwrap_or(false)
}
#[must_use]
pub fn syslog_identifier() -> String {
std::env::current_exe()
.ok()
.as_ref()
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default()
}