diff --git a/Cargo.lock b/Cargo.lock index c7b26a6574..cd31ff82c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,7 +356,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" dependencies = [ "bitflags 1.3.2", + "clap_derive", "clap_lex", + "once_cell", "strsim", "terminal_size", ] @@ -370,6 +372,19 @@ dependencies = [ "clap", ] +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "clap_lex" version = "0.3.1" @@ -462,9 +477,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -1086,6 +1101,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1121,13 +1142,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ + "cfg-if", "libc", - "match_cfg", - "winapi 0.3.9", + "windows", ] [[package]] @@ -1318,6 +1339,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -1484,12 +1514,6 @@ dependencies = [ "vlq", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "maybe-owned" version = "0.3.4" @@ -1981,7 +2005,7 @@ checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", - "itertools", + "itertools 0.10.5", "normalize-line-endings", "predicates-core", "regex", @@ -2007,6 +2031,30 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.107", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -2396,9 +2444,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "sentry" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "766448f12e44d68e675d5789a261515c46ac6ccd240abdd451a9c46c84a49523" +checksum = "3b73ab9d5b35ed718611e89db8c886647821cfce79723b9e2e4e7cb3fd9cdd49" dependencies = [ "curl", "httpdate", @@ -2409,9 +2457,9 @@ dependencies = [ [[package]] name = "sentry-anyhow" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da4015667c99f88d68ca7ff02b90c762d6154a4ceb7c02922b9a1dbd3959eeb" +checksum = "3c7825549eb646ef0224b0b7fc6d38faa459d7e5e1f902a4c12bbd8de4ad060f" dependencies = [ "anyhow", "sentry-backtrace", @@ -2420,9 +2468,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32701cad8b3c78101e1cd33039303154791b0ff22e7802ed8cc23212ef478b45" +checksum = "8ce52a2633e0332821389f865762b94852e71b359cd8e95cbf91f38c154302cb" dependencies = [ "backtrace", "once_cell", @@ -2460,7 +2508,7 @@ dependencies = [ "ignore", "indicatif", "insta", - "itertools", + "itertools 0.10.5", "java-properties", "lazy_static", "libc", @@ -2508,9 +2556,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ddd2a91a13805bd8dab4ebf47323426f758c35f7bf24eacc1aded9668f3824" +checksum = "10565e6eb013737bf7555250f8b4b8248a571e1de4de7230037f5dd5ea51bc63" dependencies = [ "hostname", "libc", @@ -2522,12 +2570,15 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1189f68d7e7e102ef7171adf75f83a59607fafd1a5eecc9dc06c026ff3bdec4" +checksum = "a2c5e7a0430cfa86b7cb6e7bc01a56331937b2ba08ab703c2e7331139a99f121" dependencies = [ + "crc32fast", + "itertools 0.13.0", "once_cell", "rand", + "regex", "sentry-types", "serde", "serde_json", @@ -2535,9 +2586,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7173fd594569091f68a7c37a886e202f4d0c1db1e1fa1d18a051ba695b2e2ec" +checksum = "10d5ad9e33b9f6f598387a6a23aa23c7f13e4e7740a34cd4d9c8db851bfdae05" dependencies = [ "debugid", "hex", @@ -3355,6 +3406,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index b3bd90408b..a1e61819ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ bytecount = "0.6.3" chardet = "0.2.4" chrono = { version = "0.4.31", features = ["serde"] } clap = { version = "4.1.6", default-features = false, features = [ + "derive", "std", "suggestions", "wrap_help", @@ -58,10 +59,11 @@ regex = "1.7.3" runas = "1.0.0" rust-ini = "0.18.0" semver = "1.0.16" -sentry = { version = "0.32.2", default-features = false, features = [ +sentry = { version = "0.33.0", default-features = false, features = [ "anyhow", "curl", "contexts", + "UNSTABLE_metrics", ] } serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" diff --git a/src/api/envelopes_api.rs b/src/api/envelopes_api.rs new file mode 100644 index 0000000000..62c4db6ccc --- /dev/null +++ b/src/api/envelopes_api.rs @@ -0,0 +1,39 @@ +use super::{ + errors::{ApiErrorKind, ApiResult}, + Api, ApiResponse, Method, +}; +use crate::{api::errors::ApiError, constants::USER_AGENT}; +use log::debug; +use sentry::{types::Dsn, Envelope}; +use std::sync::Arc; + +pub struct EnvelopesApi { + api: Arc, + dsn: Dsn, +} + +impl EnvelopesApi { + pub fn try_new() -> ApiResult { + let api = Api::current(); + api.config + .get_dsn() + .map(|dsn| EnvelopesApi { api, dsn }) + .map_err(|_| ApiErrorKind::DsnMissing.into()) + } + + pub fn send_envelope(&self, envelope: Envelope) -> ApiResult { + let mut body = vec![]; + envelope + .to_writer(&mut body) + .map_err(|e| ApiError::with_source(ApiErrorKind::CannotSerializeEnvelope, e))?; + let url = self.dsn.envelope_api_url(); + let auth = self.dsn.to_auth(Some(USER_AGENT)); + debug!("Sending envelope:\n{}", String::from_utf8_lossy(&body)); + self.api + .request(Method::Post, url.as_str(), None)? + .with_header("X-Sentry-Auth", &auth.to_string())? + .with_body(body)? + .send()? + .into_result() + } +} diff --git a/src/api/errors/api_error.rs b/src/api/errors/api_error.rs index f23fc309ee..c32b9df51b 100644 --- a/src/api/errors/api_error.rs +++ b/src/api/errors/api_error.rs @@ -12,6 +12,8 @@ pub struct ApiError { pub(in crate::api) enum ApiErrorKind { #[error("could not serialize value as JSON")] CannotSerializeAsJson, + #[error("could not serialize envelope")] + CannotSerializeEnvelope, #[error("could not parse JSON response")] BadJson, #[error("not a JSON response")] @@ -38,6 +40,10 @@ pub(in crate::api) enum ApiErrorKind { "Auth token is required for this request. Please run `sentry-cli login` and try again!" )] AuthMissing, + #[error( + "DSN missing. Please set the `SENTRY_DSN` environment variable to your project's DSN." + )] + DsnMissing, } impl fmt::Display for ApiError { diff --git a/src/api/mod.rs b/src/api/mod.rs index e67366c7e2..45b0b0e131 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -3,6 +3,8 @@ //! to the GitHub API to figure out if there are new releases of the //! sentry-cli tool. +pub mod envelopes_api; + mod connection_manager; mod encoding; mod errors; @@ -1746,6 +1748,11 @@ impl ApiRequest { Ok(self) } + pub fn with_body(mut self, body: Vec) -> ApiResult { + self.body = Some(body); + Ok(self) + } + /// attaches some form data to the request. pub fn with_form_data(mut self, form: curl::easy::Form) -> ApiResult { debug!("sending form data"); diff --git a/src/commands/derive_parser.rs b/src/commands/derive_parser.rs new file mode 100644 index 0000000000..219fc3052e --- /dev/null +++ b/src/commands/derive_parser.rs @@ -0,0 +1,36 @@ +use crate::utils::auth_token::AuthToken; +use crate::utils::value_parsers::kv_parser; +use clap::{command, value_parser, ArgAction::SetTrue, Parser, Subcommand}; + +use super::send_metric::SendMetricArgs; + +#[derive(Parser)] +pub(super) struct SentryCLI { + #[command(subcommand)] + pub(super) command: SentryCLICommand, + + #[arg(global=true, long="header", value_name="KEY:VALUE", value_parser=kv_parser)] + #[arg(help = "Custom headers that should be attached to all requests{n}in key:value format")] + pub(super) headers: Vec<(String, String)>, + + #[arg(global=true, long, value_parser=value_parser!(AuthToken))] + #[arg(help = "Use the given Sentry auth token")] + pub(super) auth_token: Option, + + #[arg(global=true, ignore_case=true, value_parser=["trace", "debug", "info", "warn", "error"])] + #[arg(long, help = "Set the log output verbosity")] + pub(super) log_level: Option, + + #[arg(global=true, action=SetTrue, visible_alias="silent", long)] + #[arg(help = "Do not print any output while preserving correct exit code. \ + This flag is currently implemented only for selected subcommands")] + pub(super) quiet: bool, + + #[arg(global=true, action=SetTrue, long, hide=true, help="Always return 0 exit code")] + pub(super) allow_failure: bool, +} + +#[derive(Subcommand)] +pub(super) enum SentryCLICommand { + SendMetric(SendMetricArgs), +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 26339c6206..8e31b6ed83 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -18,6 +18,8 @@ use crate::utils::logging::Logger; use crate::utils::system::{init_backtrace, load_dotenv, print_error, QuietExit}; use crate::utils::update::run_sentrycli_update_nagger; +mod derive_parser; + macro_rules! each_subcommand { ($mac:ident) => { $mac!(bash_hook); @@ -36,6 +38,7 @@ macro_rules! each_subcommand { $mac!(repos); $mac!(send_event); $mac!(send_envelope); + $mac!(send_metric); $mac!(sourcemaps); #[cfg(not(feature = "managed"))] $mac!(uninstall); diff --git a/src/commands/send_metric/common_args.rs b/src/commands/send_metric/common_args.rs new file mode 100644 index 0000000000..9fe68749ca --- /dev/null +++ b/src/commands/send_metric/common_args.rs @@ -0,0 +1,67 @@ +use crate::utils::value_parsers; +use anyhow::{anyhow, Result}; +use clap::command; +use clap::Args; +use sentry::metrics::MetricStr; +use std::str::FromStr; + +/// Arguments for send-metric subcommands using float as value type and no default value. +#[derive(Args)] +pub(super) struct FloatValueMetricArgs { + #[command(flatten)] + pub(super) common: CommonMetricArgs, + + #[arg(short, long, help = "Metric value, any finite 64 bit float.")] + pub(super) value: f64, +} + +/// Common arguments for all send-metric subcommands. +#[derive(Args)] +pub(super) struct CommonMetricArgs { + #[arg(short, long)] + #[arg(help = "The name of the metric, identifying it in Sentry.")] + pub(super) name: MetricName, + + #[arg(short, long)] + #[arg( + help = "Any custom unit. You can have multiple metrics with the same name but different \ + units." + )] + pub(super) unit: Option, + + #[arg(short, long, value_delimiter=',', value_name = "KEY:VALUE", num_args = 1..)] + #[arg(value_parser=value_parsers::kv_parser)] + #[arg( + help = "Metric tags as key:value pairs. Tags allow you to add dimensions to your metrics \ + and can be filtered or grouped by in Sentry." + )] + pub(super) tags: Vec<(String, String)>, +} + +#[derive(Clone)] +pub(super) struct MetricName(String); + +impl FromStr for MetricName { + type Err = anyhow::Error; + + /// Metric name must start with an alphabetic character. + fn from_str(s: &str) -> Result { + if s.chars() + .next() + .ok_or_else(|| anyhow!("metric name cannot be empty"))? + .is_ascii_alphabetic() + { + Ok(MetricName(s.to_string())) + } else { + Err(anyhow!( + "metric name must start with an alphabetic character" + )) + } + } +} + +impl From for MetricStr { + fn from(name: MetricName) -> MetricStr { + MetricStr::from(name.0) + } +} diff --git a/src/commands/send_metric/distribution.rs b/src/commands/send_metric/distribution.rs new file mode 100644 index 0000000000..11283fc6f7 --- /dev/null +++ b/src/commands/send_metric/distribution.rs @@ -0,0 +1,15 @@ +use super::common_args::FloatValueMetricArgs; +use crate::{api::envelopes_api::EnvelopesApi, utils::metrics::DefaultTags}; +use anyhow::Result; +use sentry::metrics::Metric; + +pub(super) fn execute(args: FloatValueMetricArgs) -> Result<()> { + EnvelopesApi::try_new()?.send_envelope( + Metric::distribution(args.common.name, args.value) + .with_unit(args.common.unit) + .with_tags(args.common.tags.with_default_tags()) + .finish() + .to_envelope(), + )?; + Ok(()) +} diff --git a/src/commands/send_metric/gauge.rs b/src/commands/send_metric/gauge.rs new file mode 100644 index 0000000000..7ac3a80a33 --- /dev/null +++ b/src/commands/send_metric/gauge.rs @@ -0,0 +1,15 @@ +use super::common_args::FloatValueMetricArgs; +use crate::{api::envelopes_api::EnvelopesApi, utils::metrics::DefaultTags}; +use anyhow::Result; +use sentry::metrics::Metric; + +pub(super) fn execute(args: FloatValueMetricArgs) -> Result<()> { + EnvelopesApi::try_new()?.send_envelope( + Metric::gauge(args.common.name, args.value) + .with_unit(args.common.unit) + .with_tags(args.common.tags.with_default_tags()) + .finish() + .to_envelope(), + )?; + Ok(()) +} diff --git a/src/commands/send_metric/increment.rs b/src/commands/send_metric/increment.rs new file mode 100644 index 0000000000..95b77529a4 --- /dev/null +++ b/src/commands/send_metric/increment.rs @@ -0,0 +1,26 @@ +use super::common_args::CommonMetricArgs; +use crate::{api::envelopes_api::EnvelopesApi, utils::metrics::DefaultTags}; +use anyhow::Result; +use clap::{command, Args}; +use sentry::metrics::Metric; + +#[derive(Args)] +pub(super) struct IncrementMetricArgs { + #[command(flatten)] + common: CommonMetricArgs, + + #[arg(short, long, default_value = "1")] + #[arg(help = "Value to increment the metric by, any finite 64 bit float.")] + value: f64, +} + +pub(super) fn execute(args: IncrementMetricArgs) -> Result<()> { + EnvelopesApi::try_new()?.send_envelope( + Metric::incr(args.common.name, args.value) + .with_unit(args.common.unit) + .with_tags(args.common.tags.with_default_tags()) + .finish() + .to_envelope(), + )?; + Ok(()) +} diff --git a/src/commands/send_metric/mod.rs b/src/commands/send_metric/mod.rs new file mode 100644 index 0000000000..906dd4308a --- /dev/null +++ b/src/commands/send_metric/mod.rs @@ -0,0 +1,57 @@ +pub mod common_args; + +mod distribution; +mod gauge; +mod increment; +mod set; + +use self::common_args::FloatValueMetricArgs; +use self::increment::IncrementMetricArgs; +use self::set::SetMetricArgs; +use super::derive_parser::{SentryCLI, SentryCLICommand}; +use anyhow::Result; +use clap::{command, Args, Subcommand}; +use clap::{ArgMatches, Command, Parser}; + +#[derive(Args)] +pub(super) struct SendMetricArgs { + #[command(subcommand)] + subcommand: SendMetricSubcommand, +} + +#[derive(Subcommand)] +#[command(about = "Send a metric to Sentry.")] +#[command(long_about = "Send a metric event to Sentry.{n}{n}\ +This command will validate input parameters and attempt to send a metric to \ +Sentry. Due to network errors and rate limits, the metric is not guaranteed to \ +arrive. Check the debug output for transmission errors by passing --log-level=\ +debug or setting `SENTRY_LOG_LEVEL=debug`.")] +enum SendMetricSubcommand { + #[command(about = "Increment a counter metric")] + Increment(IncrementMetricArgs), + #[command(about = "Update a distribution metric with the provided value")] + Distribution(FloatValueMetricArgs), + #[command(about = "Update a gauge metric with the provided value")] + Gauge(FloatValueMetricArgs), + #[command(about = "Update a set metric with the provided value")] + Set(SetMetricArgs), +} + +pub(super) fn make_command(command: Command) -> Command { + SendMetricSubcommand::augment_subcommands(command) +} + +pub(super) fn execute(_: &ArgMatches) -> Result<()> { + // When adding a new subcommand to the derive_parser SentryCLI, replace the line below with the following: + // let subcommand = match SentryCLI::parse().command { + // SentryCLICommand::SendMetric(SendMetricArgs { subcommand }) => subcommand, + // _ => panic!("expected send-metric subcommand"), + // }; + let SentryCLICommand::SendMetric(SendMetricArgs { subcommand }) = SentryCLI::parse().command; + match subcommand { + SendMetricSubcommand::Increment(args) => increment::execute(args), + SendMetricSubcommand::Distribution(args) => distribution::execute(args), + SendMetricSubcommand::Gauge(args) => gauge::execute(args), + SendMetricSubcommand::Set(args) => set::execute(args), + } +} diff --git a/src/commands/send_metric/set.rs b/src/commands/send_metric/set.rs new file mode 100644 index 0000000000..ecc36bf977 --- /dev/null +++ b/src/commands/send_metric/set.rs @@ -0,0 +1,29 @@ +use super::common_args::CommonMetricArgs; +use crate::{api::envelopes_api::EnvelopesApi, utils::metrics::DefaultTags}; +use anyhow::Result; +use clap::{command, Args}; +use sentry::metrics::Metric; + +#[derive(Args)] +pub(super) struct SetMetricArgs { + #[command(flatten)] + common: CommonMetricArgs, + + #[arg(short, long)] + #[arg( + help = "Value to add to the set. If the set already contains the provided value, the \ + set's unique count will not increase." + )] + value: String, +} + +pub(super) fn execute(args: SetMetricArgs) -> Result<()> { + EnvelopesApi::try_new()?.send_envelope( + Metric::set(args.common.name, &args.value) + .with_unit(args.common.unit) + .with_tags(args.common.tags.with_default_tags()) + .finish() + .to_envelope(), + )?; + Ok(()) +} diff --git a/src/utils/metrics.rs b/src/utils/metrics.rs new file mode 100644 index 0000000000..2f331614da --- /dev/null +++ b/src/utils/metrics.rs @@ -0,0 +1,29 @@ +use crate::config::Config; + +use super::releases; + +pub trait DefaultTags { + fn with_default_tags(self) -> Self; +} + +impl DefaultTags for Vec<(String, String)> { + fn with_default_tags(mut self) -> Self { + let contains_release = self.iter().any(|(key, _)| key == "release"); + let contains_environment = self.iter().any(|(key, _)| key == "environment"); + if !contains_release { + if let Ok(release) = releases::detect_release_name() { + self.push(("release".into(), release)); + } + } + if !contains_environment { + self.push(( + "environment".into(), + Config::current() + .get_environment() + .filter(|e| !e.is_empty()) + .unwrap_or("production".into()), + )); + } + self + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3f7704d273..3a7930f8dc 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -14,6 +14,7 @@ pub mod formatting; pub mod fs; pub mod http; pub mod logging; +pub mod metrics; pub mod progress; pub mod releases; pub mod retry; @@ -21,5 +22,6 @@ pub mod sourcemaps; pub mod system; pub mod ui; pub mod update; +pub mod value_parsers; pub mod vcs; pub mod xcode; diff --git a/src/utils/value_parsers.rs b/src/utils/value_parsers.rs new file mode 100644 index 0000000000..f63a18cc0c --- /dev/null +++ b/src/utils/value_parsers.rs @@ -0,0 +1,8 @@ +use anyhow::{anyhow, Result}; + +/// Parse key:value pair from string, used as a value_parser for Clap arguments +pub fn kv_parser(s: &str) -> Result<(String, String)> { + s.split_once(':') + .map(|(k, v)| (k.into(), v.into())) + .ok_or_else(|| anyhow!("`{s}` is missing a `:`")) +} diff --git a/tests/integration/_cases/help/help-windows.trycmd b/tests/integration/_cases/help/help-windows.trycmd index abc5a066d4..216691e7f1 100644 --- a/tests/integration/_cases/help/help-windows.trycmd +++ b/tests/integration/_cases/help/help-windows.trycmd @@ -26,6 +26,7 @@ Commands: repos Manage repositories on Sentry. send-event Send a manual event to Sentry. send-envelope Send a stored envelope to Sentry. + send-metric Send a metric to Sentry. sourcemaps Manage sourcemaps for Sentry releases. upload-proguard Upload ProGuard mapping files to a project. help Print this message or the help of the given subcommand(s) diff --git a/tests/integration/_cases/help/help.trycmd b/tests/integration/_cases/help/help.trycmd index 62e5ef0829..d3b5ecd539 100644 --- a/tests/integration/_cases/help/help.trycmd +++ b/tests/integration/_cases/help/help.trycmd @@ -26,6 +26,7 @@ Commands: repos Manage repositories on Sentry. send-event Send a manual event to Sentry. send-envelope Send a stored envelope to Sentry. + send-metric Send a metric to Sentry. sourcemaps Manage sourcemaps for Sentry releases. uninstall Uninstall the sentry-cli executable. upload-proguard Upload ProGuard mapping files to a project. diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-all-options-long-with-float-value.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-all-options-long-with-float-value.trycmd new file mode 100644 index 0000000000..cd1f21bca5 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-all-options-long-with-float-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric distribution --name testMetric --value 2.3 --unit mb --tags a:b --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2.3|d|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-all-options-short-with-int-value.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-all-options-short-with-int-value.trycmd new file mode 100644 index 0000000000..2db60885db --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-all-options-short-with-int-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric distribution -n testMetric -v 2 -u mb -t a:b --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2|d|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-default-tags.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-default-tags.trycmd new file mode 100644 index 0000000000..54a1ad9401 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-default-tags.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric distribution -n testMetric -v 1 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:1|d|#environment:def_env,release:def_release|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-help.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-help.trycmd new file mode 100644 index 0000000000..0a861b376c --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-help.trycmd @@ -0,0 +1,25 @@ +``` +$ sentry-cli send-metric distribution --help +? success +Update a distribution metric with the provided value + +Usage: sentry-cli[EXE] send-metric distribution [OPTIONS] --name --value + +Options: + -n, --name The name of the metric, identifying it in Sentry. + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Any custom unit. You can have multiple metrics with the same name + but different units. + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs. Tags allow you to add dimensions to + your metrics and can be filtered or grouped by in Sentry. + -v, --value Metric value, any finite 64 bit float. + --log-level Set the log output verbosity. [possible values: trace, debug, info, + warn, error] + --quiet Do not print any output while preserving correct exit code. This + flag is currently implemented only for selected subcommands. + [aliases: silent] + -h, --help Print help + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-no-options.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-no-options.trycmd new file mode 100644 index 0000000000..0b64c89e97 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-no-options.trycmd @@ -0,0 +1,12 @@ +``` +$ sentry-cli send-metric distribution +? failed +error: the following required arguments were not provided: + --name + --value + +Usage: sentry-cli[EXE] send-metric distribution --name --value + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-normalization.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-normalization.trycmd new file mode 100644 index 0000000000..a63a9aec91 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-normalization.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli --log-level debug send-metric distribution -v 1 -n "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease environment:testEnv +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:1|d|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:testEnv,release:testRelease|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-numerical-key-prefix.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-numerical-key-prefix.trycmd new file mode 100644 index 0000000000..8451c5f63b --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-numerical-key-prefix.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli --log-level debug send-metric distribution -v 1 -n 1mymetric +? failed +error: invalid value '1mymetric' for '--name ': metric name must start with an alphabetic character + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-distribution-required-options-with-negative-value.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-required-options-with-negative-value.trycmd new file mode 100644 index 0000000000..aea6b25b25 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-required-options-with-negative-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric distribution -n testMetric -v=-3.2 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:-3.2|d|#environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-all-options-long-with-float-value.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-all-options-long-with-float-value.trycmd new file mode 100644 index 0000000000..021a8d6938 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-all-options-long-with-float-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric gauge --name testMetric --value 2.3 --unit mb --tags a:b --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2.3|g|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-all-options-short-with-int-value.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-all-options-short-with-int-value.trycmd new file mode 100644 index 0000000000..c81d7cf8d3 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-all-options-short-with-int-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric gauge -n testMetric -v 2 -u mb -t a:b --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2|g|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-default-tags.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-default-tags.trycmd new file mode 100644 index 0000000000..f86ab472c4 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-default-tags.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric gauge -n testMetric -v 1 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:1|g|#environment:def_env,release:def_release|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-help.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-help.trycmd new file mode 100644 index 0000000000..95ef289ba6 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-help.trycmd @@ -0,0 +1,25 @@ +``` +$ sentry-cli send-metric gauge --help +? success +Update a gauge metric with the provided value + +Usage: sentry-cli[EXE] send-metric gauge [OPTIONS] --name --value + +Options: + -n, --name The name of the metric, identifying it in Sentry. + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Any custom unit. You can have multiple metrics with the same name + but different units. + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs. Tags allow you to add dimensions to + your metrics and can be filtered or grouped by in Sentry. + -v, --value Metric value, any finite 64 bit float. + --log-level Set the log output verbosity. [possible values: trace, debug, info, + warn, error] + --quiet Do not print any output while preserving correct exit code. This + flag is currently implemented only for selected subcommands. + [aliases: silent] + -h, --help Print help + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-no-options.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-no-options.trycmd new file mode 100644 index 0000000000..a5a791d61d --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-no-options.trycmd @@ -0,0 +1,12 @@ +``` +$ sentry-cli send-metric gauge +? failed +error: the following required arguments were not provided: + --name + --value + +Usage: sentry-cli[EXE] send-metric gauge --name --value + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-normalization.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-normalization.trycmd new file mode 100644 index 0000000000..3645f06212 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-normalization.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli --log-level debug send-metric gauge -v 1 -n "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease environment:testEnv +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:1|g|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:testEnv,release:testRelease|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-numerical-key-prefix.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-numerical-key-prefix.trycmd new file mode 100644 index 0000000000..25a0e7c00b --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-numerical-key-prefix.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli --log-level debug send-metric gauge -v 1 -n 1mymetric +? failed +error: invalid value '1mymetric' for '--name ': metric name must start with an alphabetic character + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-gauge-required-options-with-negative-value.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-required-options-with-negative-value.trycmd new file mode 100644 index 0000000000..ea027e0965 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-required-options-with-negative-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric gauge -n testMetric -v=-3.2 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:-3.2|g|#environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-global-options.trycmd b/tests/integration/_cases/send_metric/send_metric-global-options.trycmd new file mode 100644 index 0000000000..2ebd6e00b1 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-global-options.trycmd @@ -0,0 +1,14 @@ +``` +$ sentry-cli send-metric --header a:b --auth-token "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" --allow-failure --quiet --log-level debug +? failed + INFO [..] Loaded config from [CWD]/.sentryclirc + DEBUG [..] sentry-cli version: [VERSION], platform: [..], architecture: [..] + INFO [..] sentry-cli was invoked with the following command line: "[CWD]/target/debug/sentry-cli[EXE]" "send-metric" "--header" "a:b" "--auth-token" "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" "--allow-failure" "--quiet" "--log-level" "debug" +error: 'sentry-cli[EXE] send-metric' requires a subcommand but one was not provided + [subcommands: increment, distribution, gauge, set, help] + +Usage: sentry-cli[EXE] send-metric [OPTIONS] + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-help.trycmd b/tests/integration/_cases/send_metric/send_metric-help.trycmd new file mode 100644 index 0000000000..ae5452ff41 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-help.trycmd @@ -0,0 +1,46 @@ +``` +$ sentry-cli send-metric --help +? success +Send a metric event to Sentry. + +This command will validate input parameters and attempt to send a metric to Sentry. Due to network +errors and rate limits, the metric is not guaranteed to arrive. Check the debug output for +transmission errors by passing --log-level=debug or setting `SENTRY_LOG_LEVEL=debug`. + +Usage: sentry-cli[EXE] send-metric [OPTIONS] [COMMAND] + +Commands: + increment + Increment a counter metric + distribution + Update a distribution metric with the provided value + gauge + Update a gauge metric with the provided value + set + Update a set metric with the provided value + help + Print this message or the help of the given subcommand(s) + +Options: + --header + Custom headers that should be attached to all requests + in key:value format. + + --auth-token + Use the given Sentry auth token. + + --log-level + Set the log output verbosity. + + [possible values: trace, debug, info, warn, error] + + --quiet + Do not print any output while preserving correct exit code. This flag is currently + implemented only for selected subcommands. + + [aliases: silent] + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-all-options-long-with-float-value.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-all-options-long-with-float-value.trycmd new file mode 100644 index 0000000000..fbd50d4dbd --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-all-options-long-with-float-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment --name testMetric --value 2.3 --unit mb --tags a:b --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2.3|c|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-all-options-short-with-int-value.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-all-options-short-with-int-value.trycmd new file mode 100644 index 0000000000..bd5fd53205 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-all-options-short-with-int-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment -n testMetric -v 2 -u mb -t a:b --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2|c|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-default-tags.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-default-tags.trycmd new file mode 100644 index 0000000000..ad14884813 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-default-tags.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment -n testMetric --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:1|c|#environment:def_env,release:def_release|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-help.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-help.trycmd new file mode 100644 index 0000000000..c21ecc361d --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-help.trycmd @@ -0,0 +1,26 @@ +``` +$ sentry-cli send-metric increment --help +? success +Increment a counter metric + +Usage: sentry-cli[EXE] send-metric increment [OPTIONS] --name + +Options: + -n, --name The name of the metric, identifying it in Sentry. + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Any custom unit. You can have multiple metrics with the same name + but different units. + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs. Tags allow you to add dimensions to + your metrics and can be filtered or grouped by in Sentry. + -v, --value Value to increment the metric by, any finite 64 bit float. + [default: 1] + --log-level Set the log output verbosity. [possible values: trace, debug, info, + warn, error] + --quiet Do not print any output while preserving correct exit code. This + flag is currently implemented only for selected subcommands. + [aliases: silent] + -h, --help Print help + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-no-dsn.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-no-dsn.trycmd new file mode 100644 index 0000000000..87fec5a093 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-no-dsn.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment -n testmetric +? failed +... +error: DSN missing. Please set the `SENTRY_DSN` environment variable to your project's DSN. + +Add --log-level=[info|debug] or export SENTRY_LOG_LEVEL=[info|debug] to see more output. +Please attach the full debug log to all bug reports. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-no-options.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-no-options.trycmd new file mode 100644 index 0000000000..da03248fce --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-no-options.trycmd @@ -0,0 +1,11 @@ +``` +$ sentry-cli send-metric increment +? failed +error: the following required arguments were not provided: + --name + +Usage: sentry-cli[EXE] send-metric increment --name + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-normalization-with-negative-value.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-normalization-with-negative-value.trycmd new file mode 100644 index 0000000000..2a20cc6769 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-normalization-with-negative-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli --log-level debug send-metric increment -v=-3 -n "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease environment:testEnv +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:-3|c|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:testEnv,release:testRelease|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-numerical-key-prefix.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-numerical-key-prefix.trycmd new file mode 100644 index 0000000000..496cbc39ec --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-numerical-key-prefix.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli --log-level debug send-metric increment -v 1 -n 1mymetric +? failed +error: invalid value '1mymetric' for '--name ': metric name must start with an alphabetic character + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-required-options.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-required-options.trycmd new file mode 100644 index 0000000000..6e22cfaea3 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-required-options.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment -n testMetric --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:1|c|#environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-tag-no-colon.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-tag-no-colon.trycmd new file mode 100644 index 0000000000..249c39edf9 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-tag-no-colon.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli send-metric increment -n testmetric -t ab,release:testRelease --log-level debug +? failed +error: invalid value 'ab' for '--tags ...': `ab` is missing a `:` + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-increment-unsuccessful-api-call.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-unsuccessful-api-call.trycmd new file mode 100644 index 0000000000..d8dacf11fd --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-unsuccessful-api-call.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment -n testmetric +? failed +error: API request failed + caused by: sentry reported an error: internal server error (http status: 500) + +Add --log-level=[info|debug] or export SENTRY_LOG_LEVEL=[info|debug] to see more output. +Please attach the full debug log to all bug reports. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-no-subcommand.trycmd b/tests/integration/_cases/send_metric/send_metric-no-subcommand.trycmd new file mode 100644 index 0000000000..84d871d095 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-no-subcommand.trycmd @@ -0,0 +1,26 @@ +``` +$ sentry-cli send-metric +? failed +Send a metric to Sentry. + +Usage: sentry-cli[EXE] send-metric [OPTIONS] + +Commands: + increment Increment a counter metric + distribution Update a distribution metric with the provided value + gauge Update a gauge metric with the provided value + set Update a set metric with the provided value + help Print this message or the help of the given subcommand(s) + +Options: + --header Custom headers that should be attached to all requests + in key:value format + --auth-token Use the given Sentry auth token + --log-level Set the log output verbosity [possible values: trace, debug, info, + warn, error] + --quiet Do not print any output while preserving correct exit code. This + flag is currently implemented only for selected subcommands + [aliases: silent] + -h, --help Print help (see more with '--help') + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-all-options-long-with-alphabetic-value.trycmd b/tests/integration/_cases/send_metric/send_metric-set-all-options-long-with-alphabetic-value.trycmd new file mode 100644 index 0000000000..5d76c42e20 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-all-options-long-with-alphabetic-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli --log-level debug send-metric set --name testMetric --value abc --unit mb --tags a:b +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:891568578|s|#a:b,environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-all-options-short-with-int-value.trycmd b/tests/integration/_cases/send_metric/send_metric-set-all-options-short-with-int-value.trycmd new file mode 100644 index 0000000000..9ffe0110c1 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-all-options-short-with-int-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli --log-level debug send-metric set -n testMetric -v 2 -u mb -t a:b,environment:testEnvironment,release:testRelease +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:450215437|s|#a:b,environment:testEnvironment,release:testRelease|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-default-tags.trycmd b/tests/integration/_cases/send_metric/send_metric-set-default-tags.trycmd new file mode 100644 index 0000000000..c93c89430a --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-default-tags.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric set -n testMetric -v 2 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:450215437|s|#environment:def_env,release:def_release|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-float-value.trycmd b/tests/integration/_cases/send_metric/send_metric-set-float-value.trycmd new file mode 100644 index 0000000000..b0de3c82e5 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-float-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric set -n testmetric -v 1.9 --log-level debug +? success +... +{} +{"type":"statsd","length":113} +testmetric@none:2397755281|s|#environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-help.trycmd b/tests/integration/_cases/send_metric/send_metric-set-help.trycmd new file mode 100644 index 0000000000..72a7014ae3 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-help.trycmd @@ -0,0 +1,26 @@ +``` +$ sentry-cli send-metric set --help +? success +Update a set metric with the provided value + +Usage: sentry-cli[EXE] send-metric set [OPTIONS] --name --value + +Options: + -n, --name The name of the metric, identifying it in Sentry. + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Any custom unit. You can have multiple metrics with the same name + but different units. + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs. Tags allow you to add dimensions to + your metrics and can be filtered or grouped by in Sentry. + -v, --value Value to add to the set. If the set already contains the provided + value, the set's unique count will not increase. + --log-level Set the log output verbosity. [possible values: trace, debug, info, + warn, error] + --quiet Do not print any output while preserving correct exit code. This + flag is currently implemented only for selected subcommands. + [aliases: silent] + -h, --help Print help + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-no-options.trycmd b/tests/integration/_cases/send_metric/send_metric-set-no-options.trycmd new file mode 100644 index 0000000000..5f49058130 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-no-options.trycmd @@ -0,0 +1,12 @@ +``` +$ sentry-cli send-metric set +? failed +error: the following required arguments were not provided: + --name + --value + +Usage: sentry-cli[EXE] send-metric set --name --value + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-normalization.trycmd b/tests/integration/_cases/send_metric/send_metric-set-normalization.trycmd new file mode 100644 index 0000000000..27155540fe --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-normalization.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli --log-level debug send-metric set -v 1 -n "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease environment:testEnv +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:2212294583|s|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:testEnv,release:testRelease|T[..] +... + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-numerical-key-prefix.trycmd b/tests/integration/_cases/send_metric/send_metric-set-numerical-key-prefix.trycmd new file mode 100644 index 0000000000..7823e55db8 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-numerical-key-prefix.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli --log-level debug send-metric set -v 1 -n 1mymetric +? failed +error: invalid value '1mymetric' for '--name ': metric name must start with an alphabetic character + +For more information, try '--help'. + +``` diff --git a/tests/integration/_cases/send_metric/send_metric-set-required-options-with-negative-value.trycmd b/tests/integration/_cases/send_metric/send_metric-set-required-options-with-negative-value.trycmd new file mode 100644 index 0000000000..bde4cb9642 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-required-options-with-negative-value.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric set -n testMetric -v=-3 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:3726846214|s|#environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index 0387b6c869..0b99a9099e 100644 --- a/tests/integration/mod.rs +++ b/tests/integration/mod.rs @@ -15,6 +15,7 @@ mod react_native; mod releases; mod send_envelope; mod send_event; +mod send_metric; mod sourcemaps; mod token_validation; mod uninstall; @@ -26,7 +27,7 @@ use std::fs; use std::io; use std::path::Path; -use mockito::{mock, server_url, Matcher, Mock}; +use mockito::{self, mock, server_url, Matcher, Mock}; use trycmd::TestCases; pub const UTC_DATE_FORMAT: &str = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6,9}Z"; @@ -34,13 +35,14 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn register_test_without_token(path: &str) -> TestCases { let test_case = TestCases::new(); + let server_addr = mockito::server_address(); test_case .env("SENTRY_INTEGRATION_TEST", "1") .env("SENTRY_DUMP_RESPONSES", "dump") // reused default directory of `trycmd` output dumps .env("SENTRY_URL", server_url()) .env("SENTRY_ORG", "wat-org") .env("SENTRY_PROJECT", "wat-project") - .env("SENTRY_DSN", format!("https://test@{}/1337", server_url())) + .env("SENTRY_DSN", format!("http://test@{}/1337", server_addr)) .case(format!("tests/integration/_cases/{path}")); test_case.insert_var("[VERSION]", VERSION).unwrap(); test_case @@ -60,6 +62,7 @@ pub struct EndpointOptions { pub response_body: Option, pub response_file: Option, pub matcher: Option, + pub header_matcher: Option<(&'static str, Matcher)>, } impl EndpointOptions { @@ -71,6 +74,7 @@ impl EndpointOptions { response_body: None, response_file: None, matcher: None, + header_matcher: None, } } @@ -91,6 +95,13 @@ impl EndpointOptions { self.matcher = Some(matcher); self } + + /// Matches a header of the mock endpoint. The header must be present and its value must + /// match the provided matcher in order for the endpoint to be reached. + pub fn with_header_matcher(mut self, key: &'static str, matcher: Matcher) -> Self { + self.header_matcher = Some((key, matcher)); + self + } } pub fn mock_endpoint(opts: EndpointOptions) -> Mock { @@ -110,6 +121,10 @@ pub fn mock_endpoint(opts: EndpointOptions) -> Mock { mock = mock.match_body(matcher); } + if let Some((key, matcher)) = opts.header_matcher { + mock = mock.match_header(key, matcher); + } + mock.create() } diff --git a/tests/integration/send_metric/distribution.rs b/tests/integration/send_metric/distribution.rs new file mode 100644 index 0000000000..08e9242fdd --- /dev/null +++ b/tests/integration/send_metric/distribution.rs @@ -0,0 +1,57 @@ +use crate::integration; + +#[test] +fn command_send_metric_distribution_all_options_long_with_float_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-distribution-all-options-long-with-float-value.trycmd", + ); +} + +#[test] +fn command_send_metric_distribution_all_options_short_with_int_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-distribution-all-options-short-with-int-value.trycmd", + ); +} + +#[test] +fn command_send_metric_distribution_default_tags() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-distribution-default-tags.trycmd") + .env("SENTRY_RELEASE", "def_release") + .env("SENTRY_ENVIRONMENT", "def_env"); +} + +#[test] +fn command_send_metric_distribution_help() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-distribution-help.trycmd"); +} + +#[test] +fn command_send_metric_distribution_no_options() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-distribution-no-options.trycmd"); +} + +#[test] +fn command_send_metric_distribution_normalization() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-distribution-normalization.trycmd"); +} + +#[test] +fn command_send_metric_distribution_numerical_key_prefix() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-distribution-numerical-key-prefix.trycmd"); +} + +#[test] +fn command_send_metric_distribution_required_options_with_negative_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-distribution-required-options-with-negative-value.trycmd", + ); +} diff --git a/tests/integration/send_metric/gauge.rs b/tests/integration/send_metric/gauge.rs new file mode 100644 index 0000000000..c60e4567c7 --- /dev/null +++ b/tests/integration/send_metric/gauge.rs @@ -0,0 +1,57 @@ +use crate::integration; + +#[test] +fn command_send_metric_gauge_all_options_long_with_float_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-gauge-all-options-long-with-float-value.trycmd", + ); +} + +#[test] +fn command_send_metric_gauge_all_options_short_with_int_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-gauge-all-options-short-with-int-value.trycmd", + ); +} + +#[test] +fn command_send_metric_gauge_default_tags() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-gauge-default-tags.trycmd") + .env("SENTRY_RELEASE", "def_release") + .env("SENTRY_ENVIRONMENT", "def_env"); +} + +#[test] +fn command_send_metric_gauge_help() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-gauge-help.trycmd"); +} + +#[test] +fn command_send_metric_gauge_no_options() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-gauge-no-options.trycmd"); +} + +#[test] +fn command_send_metric_gauge_normalization() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-gauge-normalization.trycmd"); +} + +#[test] +fn command_send_metric_gauge_numerical_key_prefix() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-gauge-numerical-key-prefix.trycmd"); +} + +#[test] +fn command_send_metric_gauge_required_options_with_negative_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-gauge-required-options-with-negative-value.trycmd", + ); +} diff --git a/tests/integration/send_metric/increment.rs b/tests/integration/send_metric/increment.rs new file mode 100644 index 0000000000..8bd6520d1e --- /dev/null +++ b/tests/integration/send_metric/increment.rs @@ -0,0 +1,77 @@ +use crate::integration::{self, EndpointOptions}; +use trycmd::TestCases; + +#[test] +fn command_send_metric_increment_all_options_long_with_float_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-increment-all-options-long-with-float-value.trycmd", + ); +} + +#[test] +fn command_send_metric_increment_all_options_short_with_int_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-increment-all-options-short-with-int-value.trycmd", + ); +} + +#[test] +fn command_send_metric_increment_default_tags() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-increment-default-tags.trycmd") + .env("SENTRY_RELEASE", "def_release") + .env("SENTRY_ENVIRONMENT", "def_env"); +} + +#[test] +fn command_send_metric_increment_help() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-increment-help.trycmd"); +} + +#[test] +fn command_send_metric_increment_no_dsn() { + let _m = super::mock_envelopes_endpoint(); + TestCases::new() + .case("tests/integration/_cases/send_metric/send_metric-increment-no-dsn.trycmd"); +} + +#[test] +fn command_send_metric_increment_no_options() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-increment-no-options.trycmd"); +} + +#[test] +fn command_send_metric_increment_normalization_with_negative_value() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-increment-normalization-with-negative-value.trycmd", + ); +} + +#[test] +fn command_send_metric_increment_numerical_key_prefix() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-increment-numerical-key-prefix.trycmd"); +} + +#[test] +fn command_send_metric_increment_required_options() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-increment-required-options.trycmd"); +} + +#[test] +fn command_send_metric_increment_tag_no_colon() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-increment-tag-no-colon.trycmd"); +} + +#[test] +fn command_send_metric_increment_unsuccessful_api_call() { + let _m = integration::mock_endpoint(EndpointOptions::new("POST", "/api/1337/envelope/", 500)); + integration::register_test("send_metric/send_metric-increment-unsuccessful-api-call.trycmd"); +} diff --git a/tests/integration/send_metric/mod.rs b/tests/integration/send_metric/mod.rs new file mode 100644 index 0000000000..7d0b24ff06 --- /dev/null +++ b/tests/integration/send_metric/mod.rs @@ -0,0 +1,34 @@ +use super::EndpointOptions; +use crate::integration; +use mockito::{Matcher, Mock}; + +mod distribution; +mod gauge; +mod increment; +mod set; + +fn mock_envelopes_endpoint() -> Mock { + let expected_auth_header = Matcher::Regex( + r#"^Sentry sentry_key=test, sentry_version=7, sentry_timestamp=\d{10}(\.[0-9]+)?, sentry_client=sentry-cli/.*"# + .to_string(), + ); + integration::mock_endpoint( + EndpointOptions::new("POST", "/api/1337/envelope/", 200) + .with_header_matcher("X-Sentry-Auth", expected_auth_header), + ) +} + +#[test] +fn command_send_metric_help() { + integration::register_test("send_metric/send_metric-help.trycmd"); +} + +#[test] +fn command_send_metric_no_subcommand() { + integration::register_test("send_metric/send_metric-no-subcommand.trycmd"); +} + +#[test] +fn command_send_metric_global_options() { + integration::register_test("send_metric/send_metric-global-options.trycmd"); +} diff --git a/tests/integration/send_metric/set.rs b/tests/integration/send_metric/set.rs new file mode 100644 index 0000000000..893a59dbd7 --- /dev/null +++ b/tests/integration/send_metric/set.rs @@ -0,0 +1,64 @@ +use super::mock_envelopes_endpoint; +use crate::integration; + +#[test] +fn command_send_metric_set_all_options_long_with_alphabetic_value() { + let _m = mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-set-all-options-long-with-alphabetic-value.trycmd", + ); +} + +#[test] +fn command_send_metric_set_all_options_short_with_int_value() { + let _m = mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-set-all-options-short-with-int-value.trycmd", + ); +} + +#[test] +fn command_send_metric_set_default_tags() { + let _m = super::mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-set-default-tags.trycmd") + .env("SENTRY_RELEASE", "def_release") + .env("SENTRY_ENVIRONMENT", "def_env"); +} + +#[test] +fn command_send_metric_set_float_value() { + let _m = mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-set-float-value.trycmd"); +} + +#[test] +fn command_send_metric_set_help() { + let _m = mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-set-help.trycmd"); +} + +#[test] +fn command_send_metric_set_no_options() { + let _m = mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-set-no-options.trycmd"); +} + +#[test] +fn command_send_metric_set_normalization() { + let _m = mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-set-normalization.trycmd"); +} + +#[test] +fn command_send_metric_set_numerical_key_prefix() { + let _m = mock_envelopes_endpoint(); + integration::register_test("send_metric/send_metric-set-numerical-key-prefix.trycmd"); +} + +#[test] +fn command_send_metric_set_required_options_with_negative_value() { + let _m = mock_envelopes_endpoint(); + integration::register_test( + "send_metric/send_metric-set-required-options-with-negative-value.trycmd", + ); +}