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(())
}