//! `swarm-logs` — an agent's CLI for the swarm log store. //! //! An agent can reach VictoriaLogs only through the gateway, and until this //! existed the way to read it was to hand-roll a `client_credentials` token //! request and a curl by hand, per query. That is the gap this closes: one //! verb, a LogsQL string in, matched log lines out, so the answer pipes into //! `grep` like any other command's. //! //! **Read-only and query-only.** It posts to the store's LogsQL query //! endpoint and nothing else — there is no ingest path here, and no `tail`. //! Streaming is a different endpoint with a different response shape and is //! deliberately left for a follow-up rather than folded in. //! //! ⚠️ **The route applies no scoping.** `swarm-victorialogs.nix` forwards the //! query unmodified, so any authenticated caller reads the whole swarm's //! logs — every hive's, not just its own. That is the rule in force rather //! than an omission on this binary's part; when read permissions exist they //! attach at that location, not here. //! //! Distinct from `hive-metric`, which is the other half of the same //! observability surface from an agent's seat: that one *writes* a custom //! metric, this one *reads* logs. mod auth; mod query; use anyhow::Result; use clap::{Parser, Subcommand}; #[derive(Parser)] #[command( name = "swarm-logs", version, about = "query the swarm's log store from an agent" )] struct Cli { #[command(subcommand)] command: Verb, } #[derive(Subcommand)] enum Verb { /// Run a LogsQL query and print the matched log lines. Query { /// The LogsQL query. A bare word is a full-text search for it. /// /// Quote it: LogsQL uses characters the shell also claims, and an /// unquoted `|` pipes this command into the rest of your query /// instead of sending it. logsql: String, /// Stop after this many records. /// /// Unset means the store's own default. Worth setting on a broad /// query — nothing between here and the store narrows one, so a bare /// word matches across every hive in the swarm. #[arg(long)] limit: Option, /// What to print for each matched record. #[arg(long, value_enum, default_value = "message")] format: query::Format, }, /// Print this CLI's reference as markdown (source of `docs/tools/swarm-logs-cli.md`). /// /// No `completions` sibling, unlike `hivectl` and `swarmctl`: those are /// installed on an operator's interactive host, where a completion script /// has a shell to be sourced into. This one is run by an agent through a /// tool call, which has no line editor to complete against — the verb /// would exist only to be listed. #[command(hide = true)] MarkdownDocs, } fn main() -> Result<()> { match Cli::parse().command { Verb::Query { logsql, limit, format, } => { // Config resolved inside this arm and not up front: the two // verbs below are pure functions of the command tree and must // keep working on a machine that has no log store wired up at // all — `markdown-docs` in particular runs in a nix check, where // no credential exists and none should. let cfg = auth::Config::from_env()?; let stdout = std::io::stdout(); query::run(&cfg, &logsql, limit, format, &mut stdout.lock()) } Verb::MarkdownDocs => { // `show_footer(false)`: drop clap-markdown's own fixed // "generated automatically by..." footer, same as // hivectl/swarmctl/hive-forge — it's not our prose, and // write-good.Passive has nothing to check it against. let options = clap_markdown::MarkdownOptions::new().show_footer(false); print!("{}", clap_markdown::help_markdown_custom::(&options)); Ok(()) } } } #[cfg(test)] mod tests { use super::*; use clap::CommandFactory as _; /// clap's own consistency check over the command tree — catches a /// duplicate long flag or a malformed `about` at test time rather than on /// the first run of a verb nobody exercised. #[test] fn the_command_tree_is_well_formed() { Cli::command().debug_assert(); } /// The default matters: it is what an agent gets when it pipes into /// `grep` without reading the flags first. #[test] fn query_defaults_to_projecting_the_message() { let cli = Cli::try_parse_from(["swarm-logs", "query", "a-marker"]).expect("parses"); let Verb::Query { format, limit, .. } = cli.command else { panic!("expected the query verb"); }; assert_eq!(format, query::Format::Message); assert_eq!(limit, None, "no client-side default limit is imposed"); } }