logging: use tracing instead of custom logging code

This commit is contained in:
Terts Diepraam
2025-04-23 11:37:00 +02:00
parent fa974de9b2
commit bb54843b1e
9 changed files with 159 additions and 107 deletions
+23 -7
View File
@@ -10,10 +10,10 @@ use domain::rdata::Soa;
use domain::tsig::Key;
use domain::utils::base16;
use lexopt::Arg;
use tracing::warn;
use crate::env::Env;
use crate::error::Error;
use crate::log::warning;
use crate::parse::TSigInfo;
use crate::Args;
@@ -224,20 +224,17 @@ impl Notify {
}
let Ok(name) = Name::<Vec<u8>>::from_str(server) else {
warning!(env, "invalid domain name \"{server}\", skipping.");
warn!("invalid domain name \"{server}\", skipping.");
continue;
};
let Ok(hosts) = resolver.lookup_host(&name).await else {
warning!(env, "could not resolve host \"{name}\", skipping.");
warn!("could not resolve host \"{name}\", skipping.");
continue;
};
if hosts.is_empty() {
warning!(
env,
"skipping bad address: {name}: Name or service not known"
);
warn!("skipping bad address: {name}: Name or service not known");
continue;
}
@@ -582,4 +579,23 @@ mod tests {
assert_eq!(res.exit_code, 0);
assert!(res.stderr.contains("Name or service not known"));
}
#[test]
fn invalid_domain_name() {
let rpl = format!(
"
CONFIG_END
SCENARIO_BEGIN
SCENARIO_END
"
);
let cmd = FakeCmd::new(["dnst", "notify", "-z", "nlnetlabs.test", ""])
.stelline(rpl.as_bytes(), "notify.rpl");
let res = cmd.run();
assert!(res.stderr.contains("invalid domain name"));
}
}
+16 -11
View File
@@ -60,16 +60,16 @@ impl Env for FakeEnv {
self.cmd.cmd.iter().map(Into::into)
}
fn stdout(&self) -> Stream<impl fmt::Write> {
fn stdout(&self) -> Stream<impl io::Write> {
Stream {
writer: self.stdout.clone(),
writer: Mutex::new(self.stdout.clone()),
is_terminal: false,
}
}
fn stderr(&self) -> Stream<impl fmt::Write> {
fn stderr(&self) -> Stream<impl io::Write + Send + Sync + 'static> {
Stream {
writer: self.stderr.clone(),
writer: Mutex::new(self.stderr.clone()),
is_terminal: false,
}
}
@@ -192,27 +192,32 @@ impl FakeCmd {
impl FakeEnv {
pub fn get_stdout(&self) -> String {
self.stdout.0.lock().unwrap().clone()
String::from_utf8(self.stdout.0.lock().unwrap().clone()).unwrap()
}
pub fn get_stderr(&self) -> String {
self.stderr.0.lock().unwrap().clone()
String::from_utf8(self.stderr.0.lock().unwrap().clone()).unwrap()
}
}
/// A type to used to mock stdout and stderr
#[derive(Clone, Default)]
pub struct FakeStream(Arc<Mutex<String>>);
pub struct FakeStream(Arc<Mutex<Vec<u8>>>);
impl fmt::Write for FakeStream {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.lock().unwrap().push_str(s);
impl io::Write for FakeStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
// do nothing
Ok(())
}
}
impl fmt::Display for FakeStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.lock().unwrap().as_ref())
f.write_str(std::str::from_utf8(&self.0.lock().unwrap()).unwrap())
}
}
+35 -9
View File
@@ -1,8 +1,10 @@
use std::borrow::Cow;
use std::ffi::OsString;
use std::fmt;
use std::net::SocketAddr;
use std::ops::DerefMut;
use std::path::Path;
use std::sync::Mutex;
use std::{fmt, io};
mod real;
@@ -12,6 +14,7 @@ pub mod fake;
use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend};
use domain::resolv::{stub::conf::ResolvConf, StubResolver};
pub use real::RealEnv;
use tracing_subscriber::fmt::MakeWriter;
pub trait Env {
/// Get an iterator over the command line arguments passed to the program
@@ -22,12 +25,12 @@ pub trait Env {
/// Get a reference to stdout
///
/// Equivalent to [`std::io::stdout`]
fn stdout(&self) -> Stream<impl fmt::Write>;
fn stdout(&self) -> Stream<impl io::Write>;
/// Get a reference to stderr
///
/// Equivalent to [`std::io::stderr`]
fn stderr(&self) -> Stream<impl fmt::Write>;
fn stderr(&self) -> Stream<impl io::Write + Send + Sync + 'static>;
// /// Get a reference to stdin
// fn stdin(&self) -> impl io::Read;
@@ -59,19 +62,42 @@ pub trait Env {
/// [`std::io::Write`]. Additionally, this `write_fmt` does not return a
/// result. This means that we can use the [`write!`] and [`writeln`] macros
/// without handling errors.
pub struct Stream<T: fmt::Write> {
writer: T,
pub struct Stream<T: io::Write> {
writer: Mutex<T>,
is_terminal: bool,
}
impl<T: fmt::Write> Stream<T> {
impl<'writer, T: io::Write + 'writer> MakeWriter<'writer> for Stream<T> {
type Writer = &'writer Self;
fn make_writer(&'writer self) -> Self::Writer {
&self
}
}
impl<T: io::Write> io::Write for &Stream<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.writer.lock().unwrap().deref_mut().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.writer.lock().unwrap().deref_mut().flush()
}
}
impl<T: io::Write> Stream<T> {
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) {
// This unwrap is not _really_ safe, but we are using this as stdout.
// The `println` macro also ignores errors and `push_str` of the
// fake stream also does not return an error. If this fails, it means
// we can't write to stdout anymore so a graceful exit will be very
// hard anyway.
self.writer.write_fmt(args).unwrap();
self.writer
.lock()
.unwrap()
.deref_mut()
.write_fmt(args)
.unwrap();
}
pub fn is_terminal(&self) -> bool {
@@ -92,11 +118,11 @@ impl<E: Env> Env for &E {
(**self).args_os()
}
fn stdout(&self) -> Stream<impl fmt::Write> {
fn stdout(&self) -> Stream<impl io::Write> {
(**self).stdout()
}
fn stderr(&self) -> Stream<impl fmt::Write> {
fn stderr(&self) -> Stream<impl io::Write + Send + Sync + 'static> {
(**self).stderr()
}
+5 -17
View File
@@ -1,7 +1,7 @@
use std::ffi::OsString;
use std::fmt;
use std::io::{self, IsTerminal};
use std::path::Path;
use std::sync::Mutex;
use domain::net::client::protocol::{AsyncConnect, AsyncDgramRecv, AsyncDgramSend, UdpConnect};
use domain::resolv::stub::conf::ResolvConf;
@@ -18,19 +18,19 @@ impl Env for RealEnv {
std::env::args_os()
}
fn stdout(&self) -> Stream<impl fmt::Write> {
fn stdout(&self) -> Stream<impl io::Write> {
let stdout = io::stdout();
Stream {
is_terminal: stdout.is_terminal(),
writer: FmtWriter(io::stdout()),
writer: Mutex::new(stdout),
}
}
fn stderr(&self) -> Stream<impl fmt::Write> {
fn stderr(&self) -> Stream<impl io::Write + Send + Sync + 'static> {
let stderr = io::stderr();
Stream {
is_terminal: stderr.is_terminal(),
writer: FmtWriter(io::stdout()),
writer: Mutex::new(stderr),
}
}
@@ -53,15 +53,3 @@ impl Env for RealEnv {
StubResolver::from_conf(config)
}
}
struct FmtWriter<T: io::Write>(T);
impl<T: io::Write> fmt::Write for FmtWriter<T> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
}
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
self.0.write_fmt(args).map_err(|_| fmt::Error)
}
}
+5 -6
View File
@@ -1,10 +1,10 @@
use std::fmt;
use std::{error, io};
use std::io;
use domain::base::wire::ParseError;
use tracing::error;
use crate::env::Env;
use crate::log::error;
//------------ Error ---------------------------------------------------------
@@ -58,8 +58,6 @@ impl Error {
/// Pretty-print this error.
pub fn pretty_print(&self, env: impl Env) {
let mut err = env.stderr();
let msg = match &self.0.primary {
// Clap errors are already styled. We don't want our own pretty
// styling around that and context does not make sense for command
@@ -73,7 +71,8 @@ impl Error {
PrimaryError::Other(error) => error,
};
error!(env, "{msg}");
error!("{msg}");
let mut err = env.stderr();
for context in &self.0.context {
writeln!(err, "... while {context}");
}
@@ -155,7 +154,7 @@ impl fmt::Debug for Error {
//--- Error
impl error::Error for Error {}
impl std::error::Error for Error {}
//------------ Macros --------------------------------------------------------
+19 -7
View File
@@ -10,6 +10,7 @@ use commands::update::Update;
use commands::LdnsCommand;
use env::Env;
use error::Error;
use log::LogFormatter;
pub use self::args::Args;
@@ -90,12 +91,23 @@ fn parse_args(env: impl Env) -> Result<Args, Error> {
}
pub fn run(env: impl Env) -> u8 {
let res = parse_args(&env).and_then(|args| args.execute(&env));
match res {
Ok(()) => 0,
Err(err) => {
err.pretty_print(&env);
err.exit_code()
let stderr = env.stderr();
let subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_ansi(stderr.is_terminal())
.with_writer(stderr)
.event_format(LogFormatter {
program: env.args_os().next().unwrap().to_string_lossy().to_string(),
})
.finish();
tracing::subscriber::with_default(subscriber, || {
let res = parse_args(&env).and_then(|args| args.execute(&env));
match res {
Ok(()) => 0,
Err(err) => {
err.pretty_print(&env);
err.exit_code()
}
}
}
})
}
+44 -42
View File
@@ -1,54 +1,56 @@
use std::fmt::Display;
use std::fmt;
use crate::env::Env;
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::{
fmt::{format, FmtContext, FormatEvent, FormatFields},
registry::LookupSpan,
};
mod color {
pub const BLUE: u8 = 34;
pub const YELLOW: u8 = 33;
pub const RED: u8 = 31;
pub const GREEN: u8 = 32;
pub const YELLOW: u8 = 33;
pub const BLUE: u8 = 34;
pub const PURPLE: u8 = 35;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LogLevel {
Info,
Warning,
Error,
pub struct LogFormatter {
pub program: String,
}
impl LogLevel {
fn color(self) -> u8 {
match self {
Self::Info => color::BLUE,
Self::Warning => color::YELLOW,
Self::Error => color::RED,
impl<S, N> FormatEvent<S, N> for LogFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: format::Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
// Format values from the event's's metadata:
let metadata = event.metadata();
write!(&mut writer, "[{}] ", &self.program)?;
let level = *metadata.level();
if writer.has_ansi_escapes() {
let color = match level {
Level::ERROR => color::RED,
Level::WARN => color::YELLOW,
Level::INFO => color::BLUE,
Level::DEBUG => color::GREEN,
Level::TRACE => color::PURPLE,
};
write!(&mut writer, "\x1B[{color}m{level}\x1B[0m: ",)?;
} else {
write!(&mut writer, "{level}: ")?;
}
}
fn text(self) -> &'static str {
match self {
LogLevel::Info => "INFO",
LogLevel::Warning => "WARNING",
LogLevel::Error => "ERROR",
}
}
}
impl Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.text())
}
}
struct Logger(&'static Env);
pub fn log(env: impl Env, level: LogLevel, text: impl Display) {
let mut err = env.stderr();
let prog = std::env::args().next().unwrap();
if err.is_terminal() {
let color = level.color();
writeln!(err, "[{prog}] \x1B[{color}m{level}\x1B[0m: {text}");
} else {
writeln!(err, "[{prog}] {level}: {text}");
// Write fields on the event
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}