/******************************************************************************** * Prometheus exporter for monitoring network connectivity using icmp pings * * * * Copyright (C) 2019-2020 Jan Christian Grünhage * * Copyright (C) 2020 Famedly GmbH * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Affero General Public License for more details. * * * * 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::Parser; use log::info; use metrics::{describe_histogram, register_histogram, Unit}; use metrics_exporter_prometheus::{Matcher, PrometheusBuilder, PrometheusHandle}; use serde::Deserialize; use serde_with::{serde_as, DurationMilliSeconds}; use std::{collections::HashMap, time::Duration}; use crate::cli::Cli; pub(crate) struct App { pub(crate) config: Config, pub(crate) handle: PrometheusHandle, } #[derive(Deserialize, Clone)] pub(crate) struct Config { pub(crate) listener: std::net::SocketAddr, pub(crate) ping: PingConfig, #[serde(default)] pub(crate) log: LogConfig, } #[serde_as] #[derive(Deserialize, Clone)] pub(crate) struct PingConfig { #[serde_as(as = "DurationMilliSeconds")] #[serde(default = "default_timeout")] pub(crate) timeout: Duration, #[serde(default = "default_buckets")] pub(crate) bucket_sizes: Vec, pub(crate) hosts: HashMap, } fn default_timeout() -> Duration { Duration::from_secs(3) } fn default_buckets() -> Vec { vec![ 0.5, 1.0, 5.0, 10.0, 15.0, 20.0, 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0, 350.0, 400.0, 450.0, 500.0, 550.0, 600.0, 650.0, 700.0, 750.0, 800.0, 900.0, 1000.0, 1250.0, 1500.0, 1750.0, 2000.0, ] } #[derive(Deserialize, Clone)] pub(crate) struct LogConfig { pub(crate) level: log::LevelFilter, } impl Default for LogConfig { fn default() -> Self { Self { level: log::LevelFilter::Error, } } } fn setup_clap() -> Cli { Cli::parse() } fn setup_fern(level: log::LevelFilter) { match fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( "[{}][{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), message )) }) .level(level) .chain(std::io::stdout()) .apply() { Err(_) => { eprintln!("error setting up logging!"); } _ => info!("logging set up properly"), } } fn read_config(path: &std::path::Path) -> Result { let config_file_content = std::fs::read_to_string(path).context("Couldn't read config file")?; toml::from_str(&config_file_content).context("Couldn't parse config file") } pub(crate) fn setup_app() -> Result { let cli = setup_clap(); let config = read_config(&cli.config).context("Couldn't read config file!")?; setup_fern(determine_level(cli.verbose, config.log.level)); let handle = setup_prometheus(&config)?; Ok(App { config, handle }) } pub(crate) fn setup_prometheus(config: &Config) -> Result { let handle = PrometheusBuilder::new() .set_buckets_for_metric( Matcher::Full("ping_rtt_milliseconds".into()), &config.ping.bucket_sizes, )? .install_recorder()?; for target in config.ping.hosts.keys() { register_histogram!("ping_rtt_milliseconds", "target" => target.to_string()); } describe_histogram!( "ping_rtt_milliseconds", Unit::Milliseconds, "The ping round trip time in milliseconds" ); Ok(handle) } fn determine_level(verbose_occurrences: usize, config_level: log::LevelFilter) -> log::LevelFilter { let cli_level = match verbose_occurrences { 0 => log::LevelFilter::Error, 1 => log::LevelFilter::Warn, 2 => log::LevelFilter::Info, 3 => log::LevelFilter::Debug, _ => log::LevelFilter::Trace, }; Ord::max(cli_level, config_level) }