diff --git a/src/main.rs b/src/main.rs index 8fba053..a681d5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use regex::Regex; use rocket::{ data::{FromData, ToByteUnit}, futures::TryFutureExt, + get, http::{HeaderMap, Status}, outcome::Outcome::{self, Failure, Success}, post, routes, @@ -31,6 +32,7 @@ use std::{ fs::File, io::BufReader, net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::Mutex, }; #[derive(Debug, Error)] @@ -72,6 +74,19 @@ struct Opts { command: Option, } +#[derive(Debug, Default)] +struct WebhookeyMetrics { + requests_received: Mutex, + requests_invalid: Mutex, + hooks_successful: Mutex, + hooks_forbidden: Mutex, + hooks_unmatched: Mutex, + commands_executed: Mutex, + commands_execution_failed: Mutex, + commands_successful: Mutex, + commands_failed: Mutex, +} + #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields, untagged)] enum AddrType { @@ -425,6 +440,18 @@ impl<'r> FromData<'r> for Hooks { request: &'r Request<'_>, data: Data<'r>, ) -> Outcome> { + { + let requests_received = &mut request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .requests_received + .lock() + .unwrap(); // TODO: Check if unwrap need to be fixed + + **requests_received += 1; + } + match Hooks::get_commands(request, data).await { Ok(hooks) => { if hooks.inner.is_empty() { @@ -432,6 +459,16 @@ impl<'r> FromData<'r> for Hooks { .client_ip() .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); + let hooks_unmatched = &mut request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .hooks_unmatched + .lock() + .unwrap(); // TODO: Check if unwrap need to be fixed + + **hooks_unmatched += 1; + warn!("Unmatched hook from {}", &client_ip); return Failure((Status::NotFound, WebhookeyError::UnmatchedHook(*client_ip))); } @@ -440,10 +477,32 @@ impl<'r> FromData<'r> for Hooks { } Err(WebhookeyError::Unauthorized(e)) => { error!("{}", WebhookeyError::Unauthorized(e)); + + let hooks_forbidden = &mut request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .hooks_forbidden + .lock() + .unwrap(); // TODO: Check if unwrap need to be fixed + + **hooks_forbidden += 1; + Failure((Status::Unauthorized, WebhookeyError::Unauthorized(e))) } Err(e) => { error!("{}", e); + + let requests_invalid = &mut request + .guard::<&State>() + .await + .unwrap() // TODO: Check if unwrap need to be fixed + .requests_invalid + .lock() + .unwrap(); // TODO: Check if unwrap need to be fixed + + **requests_invalid += 1; + Failure((Status::BadRequest, e)) } } @@ -451,7 +510,11 @@ impl<'r> FromData<'r> for Hooks { } #[post("/", format = "json", data = "")] -async fn receive_hook<'a>(address: SocketAddr, hooks: Hooks) -> Status { +async fn receive_hook<'a>( + address: SocketAddr, + hooks: Hooks, + metrics: &State, +) -> Status { info!("Post request received from: {}", address); hooks.inner.iter().for_each(|(name, command)| { @@ -462,9 +525,27 @@ async fn receive_hook<'a>(address: SocketAddr, hooks: Hooks) -> Status { info!("Command `{}` exited with return code: {}", &command, status); trace!("Output of command `{}` on stdout: {:?}", &command, &stdout); debug!("Output of command `{}` on stderr: {:?}", &command, &stderr); + + let commands_executed = &mut metrics.commands_executed.lock().unwrap(); // TODO: Check if unwrap need to be fixed + **commands_executed += 1; + + match status { + 0 => { + let commands_successful = &mut metrics.commands_successful.lock().unwrap(); // TODO: Check if unwrap need to be fixed + **commands_successful += 1; + } + _ => { + let commands_failed = &mut metrics.commands_failed.lock().unwrap(); // TODO: Check if unwrap need to be fixed + **commands_failed += 1; + } + } } Err(e) => { error!("Execution of `{}` failed: {}", &command, e); + + let command_execution_failed = + &mut metrics.commands_execution_failed.lock().unwrap(); // TODO: Check if unwrap need to be fixed + **command_execution_failed += 1; } } }); @@ -472,6 +553,49 @@ async fn receive_hook<'a>(address: SocketAddr, hooks: Hooks) -> Status { Status::Ok } +#[get("/metrics")] +async fn metrics(metrics: &State) -> String { + format!( + r"# HELP webhookey_requests_received Number of requests received +# TYPE webhookey_requests_received gauge +webhookey_requests_received {:?} +# HELP webhookey_requests_invalid Number of invalid requests received +# TYPE webhookey_requests_invalid gauge +webhookey_requests_invalid {:?} +# HELP webhookey_hooks_successful Number of successfully executed hooks +# TYPE webhookey_hooks_successful gauge +webhookey_hooks_sucessful {:?} +# HELP webhookey_hooks_forbidden Number of forbidden requests +# TYPE webhookey_hooks_forbidden gauge +webhookey_hooks_forbidden {:?} +# HELP webhookey_hooks_unmatched Number of unmatched requests +# TYPE webhookey_hooks_unmatched gauge +webhookey_hooks_unmatched {:?} +# HELP webhookey_commands_executed Number of commands executed +# TYPE webhookey_commands_executed gauge +webhookey_commands_executed {:?} +# HELP webhookey_commands_execution_failed Number of commands failed to execute +# TYPE webhookey_commands_execution_failed gauge +webhookey_commands_execution_failed {:?} +# HELP webhookey_commands_successful Number of executed commands returning return code 0 +# TYPE webhookey_commands_successful gauge +webhookey_commands_successful {:?} +# HELP webhookey_commands_failed Number of executed commands returning different return code than 0 +# TYPE webhookey_commands_failed gauge +webhookey_commands_failed {:?} +", + metrics.requests_received.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.requests_invalid.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.hooks_successful.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.hooks_forbidden.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.hooks_unmatched.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.commands_executed.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.commands_execution_failed.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.commands_successful.lock().unwrap(), // TODO: Check if unwrap need to be fixed + metrics.commands_failed.lock().unwrap() // TODO: Check if unwrap need to be fixed + ) +} + #[rocket::main] async fn main() -> Result<()> { env_logger::init(); @@ -492,8 +616,9 @@ async fn main() -> Result<()> { } rocket::build() - .mount("/", routes![receive_hook]) + .mount("/", routes![receive_hook, metrics]) .manage(config) + .manage(WebhookeyMetrics::default()) .launch() .await?; @@ -531,7 +656,8 @@ mod tests { let rocket = rocket::build() .mount("/", routes![receive_hook]) - .manage(config); + .manage(config) + .manage(WebhookeyMetrics::default()); let client = Client::tracked(rocket).await.unwrap(); let response = client @@ -718,7 +844,8 @@ mod tests { let rocket = rocket::build() .mount("/", routes![receive_hook]) - .manage(config); + .manage(config) + .manage(WebhookeyMetrics::default()); let client = Client::tracked(rocket).await.unwrap(); @@ -775,7 +902,8 @@ mod tests { let rocket = rocket::build() .mount("/", routes![receive_hook]) - .manage(config); + .manage(config) + .manage(WebhookeyMetrics::default()); let client = Client::tracked(rocket).await.unwrap();