From 82f7101f54e01ac65fc94db77b65a49bfe301138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Fri, 23 Apr 2021 09:08:55 +0200 Subject: [PATCH] chore: replace homegrown errors with anyhow --- CHANGELOG.md | 1 + Cargo.lock | 19 +++++++++++++++++++ Cargo.toml | 2 ++ src/config.rs | 21 ++++----------------- src/main.rs | 30 +++++++++++++----------------- src/metrics.rs | 19 ++++++++++--------- src/ping.rs | 40 ++++++++++++++++++---------------------- 7 files changed, 67 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf840f..c8c9172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed - update dependencies, including stable tokio this time. +- migrate error handling to anyhow ## [v0.3.0] - 2021-04-19 ### Added diff --git a/Cargo.lock b/Cargo.lock index 1e4cf91..60774b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" + +[[package]] +name = "async-anyhow-logger" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06a2f0f9e176eb6d5185a049090385608c2294b14ef1647af489414521b0de1c" +dependencies = [ + "anyhow", + "futures", + "log", +] + [[package]] name = "atty" version = "0.2.14" @@ -432,6 +449,8 @@ dependencies = [ name = "peshming" version = "0.3.0" dependencies = [ + "anyhow", + "async-anyhow-logger", "chrono", "clap", "fern", diff --git a/Cargo.toml b/Cargo.toml index ca31fbb..50edf2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,5 @@ chrono = "0.4.19" serde = { version = "1.0.125", features = ["derive"] } tokio-ping = { git = "https://github.com/jcgruenhage/tokio-ping", branch = "main" } futures-util = "0.3.14" +anyhow = "1.0.40" +async-anyhow-logger = "0.1.0" diff --git a/src/config.rs b/src/config.rs index c7268d4..ae3c58b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * * along with this program. If not, see . * ********************************************************************************/ +use anyhow::{Context, Result}; use clap::{clap_app, crate_authors, crate_description, crate_name, crate_version}; use log::info; use serde::{Deserialize, Serialize}; @@ -69,21 +70,7 @@ pub(crate) fn setup_fern(level: u64) { } } -pub(crate) fn read_config(path: &str) -> Result { - let config_file_content = std::fs::read_to_string(path)?; - Ok(toml::from_str(&config_file_content)?) -} - -pub(crate) struct Error {} - -impl std::convert::From for Error { - fn from(_: std::io::Error) -> Self { - Error {} - } -} - -impl std::convert::From for Error { - fn from(_: toml::de::Error) -> Self { - Error {} - } +pub(crate) fn read_config(path: &str) -> Result { + let config_file_content = std::fs::read_to_string(path).context("Couldn't read config file")?; + Ok(toml::from_str(&config_file_content).context("Couldn't parse config file")?) } diff --git a/src/main.rs b/src/main.rs index f97aec0..273ca02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,33 +17,29 @@ * You should have received a copy of the GNU Affero General Public License * * along with this program. If not, see . * ********************************************************************************/ -use log::error; +use anyhow::{Context, Result}; mod config; mod metrics; mod ping; + use crate::config::{read_config, setup_clap, setup_fern}; use crate::metrics::start_serving_metrics; use crate::ping::start_pinging_hosts; #[tokio::main] -async fn main() -> Result<(), ()> { +async fn main() -> Result<()> { let clap = setup_clap(); setup_fern(clap.occurrences_of("v")); - let config = match read_config(clap.value_of("config").unwrap()) { - Ok(config) => config, - Err(_) => { - error!("Couldn't read config file!"); - return Err(()); - } - }; + let config = + read_config(clap.value_of("config").unwrap()).context("Couldn't read config file!")?; - tokio::spawn(start_pinging_hosts(config.clone())); - match start_serving_metrics(config.clone()).await { - Ok(_) => Ok(()), - Err(error) => { - error!("Couldn't serve metrics: {}", error); - Err(()) - } - } + let ping_fut = start_pinging_hosts(config.clone()); + let serve_fut = start_serving_metrics(config.clone()); + + futures::pin_mut!(ping_fut); + futures::pin_mut!(serve_fut); + + futures::future::select(ping_fut, serve_fut).await; + Ok(()) } diff --git a/src/metrics.rs b/src/metrics.rs index 85a5491..d6289e3 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -18,6 +18,7 @@ * along with this program. If not, see . * ********************************************************************************/ use crate::config::Config; +use anyhow::{Context, Result}; use hyper::{ header::CONTENT_TYPE, server::Server, @@ -50,7 +51,7 @@ lazy_static! { .unwrap(); } -async fn serve_req(req: Request) -> std::result::Result, hyper::Error> { +async fn serve_req(req: Request) -> Result> { match req.uri().path() { "/metrics" => serve_metrics().await, "/health" => serve_health_check().await, @@ -58,7 +59,7 @@ async fn serve_req(req: Request) -> std::result::Result, hy } } -async fn serve_metrics() -> std::result::Result, hyper::Error> { +async fn serve_metrics() -> Result> { let encoder = TextEncoder::new(); HTTP_COUNTER.inc(); @@ -73,31 +74,31 @@ async fn serve_metrics() -> std::result::Result, hyper::Error> { .status(200) .header(CONTENT_TYPE, encoder.format_type()) .body(Body::from(buffer)) - .unwrap(); + .context("Couldn't build metrics response")?; timer.observe_duration(); Ok(response) } -async fn serve_health_check() -> std::result::Result, hyper::Error> { +async fn serve_health_check() -> Result> { Ok(Response::builder() .status(200) .body(Body::from(vec![])) - .unwrap()) + .context("Couldn't build health check response")?) } -async fn serve_not_found() -> std::result::Result, hyper::Error> { +async fn serve_not_found() -> Result> { Ok(Response::builder() .status(404) .body(Body::from(vec![])) - .unwrap()) + .context("Couldn't build not found response")?) } -pub(crate) async fn start_serving_metrics(config: Config) -> std::result::Result<(), hyper::Error> { +pub(crate) async fn start_serving_metrics(config: Config) -> Result<()> { let serve_future = Server::bind(&config.listener).serve(make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(serve_req)) })); info!("Listening on {}", &config.listener); - serve_future.await + Ok(serve_future.await?) } diff --git a/src/ping.rs b/src/ping.rs index 565d1ca..51b6384 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -18,8 +18,10 @@ * along with this program. If not, see . * ********************************************************************************/ use crate::config::Config; +use anyhow::{Context, Result}; +use async_anyhow_logger::catch; use lazy_static::lazy_static; -use log::{error, info, trace}; +use log::{info, trace}; use prometheus::*; use std::net::IpAddr; use std::time::Duration; @@ -39,41 +41,33 @@ lazy_static! { .unwrap(); } -pub(crate) async fn start_pinging_hosts( - config: Config, -) -> std::result::Result<(), tokio_ping::Error> { - let pinger = match Pinger::new().await { - Ok(pinger) => pinger, - Err(error) => { - error!("Couldn't create pinger: {}", error); - std::process::exit(1); - } - }; +pub(crate) async fn start_pinging_hosts(config: Config) -> Result<()> { + let pinger = Pinger::new().await.context("Couldn't create pinger")?; + let mut handles = vec![]; for (host, interval) in config.hosts.clone() { info!("Spawn ping task for {}", host); - tokio::spawn(ping_host(pinger.clone(), host, interval)); + handles.push(tokio::spawn(catch(ping_host(pinger.clone(), host, interval)))); } + let (result, _, _) = futures::future::select_all(handles).await; + result?; Ok(()) } -async fn ping_host(pinger: Pinger, host: IpAddr, interval: u64) { +async fn ping_host(pinger: Pinger, host: IpAddr, interval: u64) -> Result<()> { let mut pingchain = pinger.chain(host).timeout(Duration::from_secs(3)); let mut interval = tokio::time::interval(Duration::from_millis(interval)); let host_string = host.to_string(); loop { interval.tick().await; - tokio::spawn(handle_ping_result(pingchain.send(), host_string.clone())); + tokio::spawn(catch(handle_ping_result( + pingchain.send(), + host_string.clone(), + ))); } } -async fn handle_ping_result(result: PingFuture, host: String) { - let pong = match result.await { - Ok(pong) => pong, - Err(error) => { - error!("Couldn't ping {}: {}", &host, error); - return; - } - }; +async fn handle_ping_result(result: PingFuture, host: String) -> Result<()> { + let pong = result.await.context(format!("Couldn't ping {}", &host))?; match pong { Some(time) => { let ms = time.as_millis(); @@ -87,4 +81,6 @@ async fn handle_ping_result(result: PingFuture, host: String) { PING_HISTOGRAM.with_label_values(&[&host]).observe(3000.0); } }; + + Ok(()) }