From 94b805de0bc785ed15baa1b8033235492d10e344 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sat, 3 Aug 2024 05:34:38 +0000 Subject: [PATCH] generalize log capture to all admin commands; simplify handler Signed-off-by: Jason Volk --- src/admin/command.rs | 5 +- src/admin/debug/commands.rs | 28 ++------ src/admin/handler.rs | 140 ++++++++++++++++++++---------------- 3 files changed, 87 insertions(+), 86 deletions(-) diff --git a/src/admin/command.rs b/src/admin/command.rs index fbfdd2ba..a26564fc 100644 --- a/src/admin/command.rs +++ b/src/admin/command.rs @@ -1,6 +1,9 @@ -use service::Services; +use std::time::SystemTime; + +use conduit_service::Services; pub(crate) struct Command<'a> { pub(crate) services: &'a Services, pub(crate) body: &'a [&'a str], + pub(crate) timer: SystemTime, } diff --git a/src/admin/debug/commands.rs b/src/admin/debug/commands.rs index 720e6f63..2d967006 100644 --- a/src/admin/debug/commands.rs +++ b/src/admin/debug/commands.rs @@ -1,16 +1,12 @@ use std::{ collections::{BTreeMap, HashMap}, fmt::Write, - sync::{Arc, Mutex}, + sync::Arc, time::{Instant, SystemTime}, }; use api::client::validate_and_add_event_id; -use conduit::{ - debug, debug_error, err, info, log, - log::{capture, Capture}, - utils, warn, Error, PduEvent, Result, -}; +use conduit::{debug, debug_error, err, info, trace, utils, warn, Error, PduEvent, Result}; use ruma::{ api::{client::error::ErrorKind, federation::event::get_room_state}, events::room::message::RoomMessageEventContent, @@ -717,30 +713,14 @@ pub(super) async fn resolve_true_destination( )); } - let filter: &capture::Filter = &|data| { - data.level() <= log::Level::DEBUG - && data.mod_name().starts_with("conduit") - && matches!(data.span_name(), "actual" | "well-known" | "srv") - }; - - let state = &self.services.server.log.capture; - let logs = Arc::new(Mutex::new(String::new())); - let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone())); - - let capture_scope = capture.start(); let actual = self .services .resolver .resolve_actual_dest(&server_name, !no_cache) .await?; - drop(capture_scope); - let msg = format!( - "{}\nDestination: {}\nHostname URI: {}", - logs.lock().expect("locked"), - actual.dest, - actual.host, - ); + let msg = format!("Destination: {}\nHostname URI: {}", actual.dest, actual.host,); + Ok(RoomMessageEventContent::text_markdown(msg)) } diff --git a/src/admin/handler.rs b/src/admin/handler.rs index 16518839..fd4fe839 100644 --- a/src/admin/handler.rs +++ b/src/admin/handler.rs @@ -1,7 +1,21 @@ -use std::{panic::AssertUnwindSafe, sync::Arc, time::Instant}; +use std::{ + panic::AssertUnwindSafe, + sync::{Arc, Mutex}, + time::SystemTime, +}; use clap::{CommandFactory, Parser}; -use conduit::{checked, error, trace, utils::string::common_prefix, Error, Result}; +use conduit::{ + debug, error, + log::{ + capture, + capture::Capture, + fmt::{markdown_table, markdown_table_head}, + }, + trace, + utils::string::{collect_stream, common_prefix}, + Error, Result, +}; use futures_util::future::FutureExt; use ruma::{ events::{ @@ -14,6 +28,7 @@ use service::{ admin::{CommandInput, CommandOutput, HandlerFuture, HandlerResult}, Services, }; +use tracing::Level; use crate::{admin, admin::AdminCommand, Command}; @@ -34,10 +49,21 @@ async fn handle_command(services: Arc, command: CommandInput) -> Handl .or_else(|error| handle_panic(&error, command)) } -async fn process_command(services: Arc, command: &CommandInput) -> CommandOutput { - process(services, &command.command) +async fn process_command(services: Arc, input: &CommandInput) -> CommandOutput { + let (command, args, body) = match parse(&services, input) { + Err(error) => return error, + Ok(parsed) => parsed, + }; + + let context = Command { + services: &services, + body: &body, + timer: SystemTime::now(), + }; + + process(&context, command, &args) .await - .and_then(|content| reply(content, command.reply_id.clone())) + .and_then(|content| reply(content, input.reply_id.clone())) } fn handle_panic(error: &Error, command: CommandInput) -> HandlerResult { @@ -59,69 +85,61 @@ fn reply(mut content: RoomMessageEventContent, reply_id: Option) - } // Parse and process a message from the admin room -async fn process(services: Arc, msg: &str) -> CommandOutput { - let lines = msg.lines().filter(|l| !l.trim().is_empty()); - let command = lines - .clone() - .next() - .expect("each string has at least one line"); - let (parsed, body) = match parse_command(command) { - Ok(parsed) => parsed, - Err(error) => { - let server_name = services.globals.server_name(); - let message = error.replace("server.name", server_name.as_str()); - return Some(RoomMessageEventContent::notice_markdown(message)); - }, +async fn process(context: &Command<'_>, command: AdminCommand, args: &[String]) -> CommandOutput { + let filter: &capture::Filter = + &|data| data.level() <= Level::DEBUG && data.mod_name().starts_with("conduit") && data.scope.contains(&"admin"); + let logs = Arc::new(Mutex::new( + collect_stream(|s| markdown_table_head(s)).expect("markdown table header"), + )); + + let capture = Capture::new( + &context.services.server.log.capture, + Some(filter), + capture::fmt(markdown_table, logs.clone()), + ); + + let capture_scope = capture.start(); + let result = Box::pin(admin::process(command, context)).await; + drop(capture_scope); + + debug!( + ok = result.is_ok(), + elapsed = ?context.timer.elapsed(), + command = ?args, + "command processed" + ); + + let logs = logs.lock().expect("locked"); + let output = match result { + Err(error) => format!("{logs}\nEncountered an error while handling the command:\n```\n{error:#?}\n```"), + Ok(reply) => format!("{logs}\n{}", reply.body()), //TODO: content is recreated to add logs }; - let body = parse_body(AdminCommand::command(), &body, lines.skip(1).collect()).expect("trailing body parsed"); - let context = Command { - services: &services, - body: &body, - }; - let timer = Instant::now(); - let result = Box::pin(admin::process(parsed, &context)).await; - let elapsed = timer.elapsed(); - conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}"); - match result { - Ok(reply) => Some(reply), - Err(error) => Some(RoomMessageEventContent::notice_markdown(format!( - "Encountered an error while handling the command:\n```\n{error:#?}\n```" - ))), - } + Some(RoomMessageEventContent::notice_markdown(output)) } // Parse chat messages from the admin room into an AdminCommand object -fn parse_command(command_line: &str) -> Result<(AdminCommand, Vec), String> { - let argv = parse_line(command_line); - let com = AdminCommand::try_parse_from(&argv).map_err(|error| error.to_string())?; - Ok((com, argv)) +fn parse<'a>( + services: &Arc, input: &'a CommandInput, +) -> Result<(AdminCommand, Vec, Vec<&'a str>), CommandOutput> { + let lines = input.command.lines().filter(|line| !line.trim().is_empty()); + let command_line = lines.clone().next().expect("command missing first line"); + let body = lines.skip(1).collect(); + match parse_command(command_line) { + Ok((command, args)) => Ok((command, args, body)), + Err(error) => { + let message = error + .to_string() + .replace("server.name", services.globals.server_name().as_str()); + Err(Some(RoomMessageEventContent::notice_markdown(message))) + }, + } } -fn parse_body<'a>(mut cmd: clap::Command, body: &'a [String], lines: Vec<&'a str>) -> Result> { - let mut start = 1; - 'token: for token in body.iter().skip(1) { - let cmd_ = cmd.clone(); - for sub in cmd_.get_subcommands() { - if sub.get_name() == *token { - start = checked!(start + 1)?; - cmd = sub.clone(); - continue 'token; - } - } - - // positional arguments have to be skipped too - let num_posargs = cmd_.get_positionals().count(); - start = checked!(start + num_posargs)?; - break; - } - - Ok(body - .iter() - .skip(start) - .map(String::as_str) - .chain(lines) - .collect::>()) +fn parse_command(line: &str) -> Result<(AdminCommand, Vec)> { + let argv = parse_line(line); + let command = AdminCommand::try_parse_from(&argv)?; + Ok((command, argv)) } fn complete_command(mut cmd: clap::Command, line: &str) -> String {