From b40f9ce14eb31b3e69667f363bf238b34d6f4093 Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Tue, 14 May 2024 16:12:44 +0200 Subject: [PATCH] feat(commands): add send-metric command Add CLI command that can emit metrics. Fixes GH-2001 --- Cargo.lock | 107 +++++++++--- Cargo.toml | 5 +- package.json | 16 +- src/api/envelopes_api.rs | 46 +++++ src/api/errors/api_error.rs | 2 + src/api/mod.rs | 22 ++- src/commands/derive_parser.rs | 36 ++++ src/commands/mod.rs | 3 + src/commands/send_metric/common_args.rs | 27 +++ src/commands/send_metric/distribution.rs | 15 ++ src/commands/send_metric/gauge.rs | 15 ++ src/commands/send_metric/increment.rs | 25 +++ src/commands/send_metric/mod.rs | 50 ++++++ src/commands/send_metric/set.rs | 26 +++ src/utils/metrics/arg_parsers.rs | 27 +++ src/utils/metrics/mod.rs | 7 + src/utils/metrics/normalized_key.rs | 36 ++++ src/utils/metrics/normalized_payload.rs | 80 +++++++++ src/utils/metrics/normalized_tags.rs | 163 ++++++++++++++++++ src/utils/metrics/normalized_unit.rs | 65 +++++++ src/utils/metrics/types.rs | 20 +++ src/utils/metrics/values.rs | 16 ++ src/utils/mod.rs | 2 + src/utils/value_parsers.rs | 12 ++ .../_cases/help/help-windows.trycmd | 1 + tests/integration/_cases/help/help.trycmd | 1 + ...n-all-options-long-with-float-value.trycmd | 10 ++ ...on-all-options-short-with-int-value.trycmd | 10 ++ .../send_metric-distribution-help.trycmd | 23 +++ ...send_metric-distribution-no-options.trycmd | 12 ++ ...d_metric-distribution-normalization.trycmd | 10 ++ ...c-distribution-numerical-key-prefix.trycmd | 8 + ...equired-options-with-negative-value.trycmd | 10 ++ ...e-all-options-long-with-float-value.trycmd | 10 ++ ...ge-all-options-short-with-int-value.trycmd | 10 ++ .../send_metric/send_metric-gauge-help.trycmd | 23 +++ .../send_metric-gauge-no-options.trycmd | 12 ++ .../send_metric-gauge-normalization.trycmd | 10 ++ ...d_metric-gauge-numerical-key-prefix.trycmd | 8 + ...equired-options-with-negative-value.trycmd | 10 ++ .../send_metric-global-options.trycmd | 14 ++ .../send_metric/send_metric-help.trycmd | 45 +++++ ...t-all-options-long-with-float-value.trycmd | 10 ++ ...nt-all-options-short-with-int-value.trycmd | 10 ++ .../send_metric-increment-help.trycmd | 23 +++ .../send_metric-increment-no-dsn.trycmd | 9 + .../send_metric-increment-no-options.trycmd | 11 ++ ...t-normalization-with-negative-value.trycmd | 10 ++ ...tric-increment-numerical-key-prefix.trycmd | 8 + ...d_metric-increment-required-options.trycmd | 10 ++ .../send_metric-increment-tag-no-colon.trycmd | 8 + ...ric-increment-unsuccessful-api-call.trycmd | 9 + .../send_metric-no-subcommand.trycmd | 26 +++ ...-options-long-with-alphabetic-value.trycmd | 10 ++ ...et-all-options-short-with-int-value.trycmd | 10 ++ .../send_metric-set-float-value.trycmd | 8 + .../send_metric/send_metric-set-help.trycmd | 23 +++ .../send_metric-set-no-options.trycmd | 12 ++ .../send_metric-set-normalization.trycmd | 10 ++ ...end_metric-set-numerical-key-prefix.trycmd | 8 + ...equired-options-with-negative-value.trycmd | 10 ++ tests/integration/mod.rs | 23 ++- tests/integration/send_metric/distribution.rs | 49 ++++++ tests/integration/send_metric/gauge.rs | 49 ++++++ tests/integration/send_metric/increment.rs | 69 ++++++++ tests/integration/send_metric/mod.rs | 34 ++++ tests/integration/send_metric/set.rs | 50 ++++++ 67 files changed, 1507 insertions(+), 42 deletions(-) create mode 100644 src/api/envelopes_api.rs create mode 100644 src/commands/derive_parser.rs create mode 100644 src/commands/send_metric/common_args.rs create mode 100644 src/commands/send_metric/distribution.rs create mode 100644 src/commands/send_metric/gauge.rs create mode 100644 src/commands/send_metric/increment.rs create mode 100644 src/commands/send_metric/mod.rs create mode 100644 src/commands/send_metric/set.rs create mode 100644 src/utils/metrics/arg_parsers.rs create mode 100644 src/utils/metrics/mod.rs create mode 100644 src/utils/metrics/normalized_key.rs create mode 100644 src/utils/metrics/normalized_payload.rs create mode 100644 src/utils/metrics/normalized_tags.rs create mode 100644 src/utils/metrics/normalized_unit.rs create mode 100644 src/utils/metrics/types.rs create mode 100644 src/utils/metrics/values.rs create mode 100644 src/utils/value_parsers.rs create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-all-options-long-with-float-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-all-options-short-with-int-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-help.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-no-options.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-normalization.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-numerical-key-prefix.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-distribution-required-options-with-negative-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-all-options-long-with-float-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-all-options-short-with-int-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-help.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-no-options.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-normalization.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-numerical-key-prefix.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-gauge-required-options-with-negative-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-global-options.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-help.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-all-options-long-with-float-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-all-options-short-with-int-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-help.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-no-dsn.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-no-options.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-normalization-with-negative-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-numerical-key-prefix.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-required-options.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-tag-no-colon.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-increment-unsuccessful-api-call.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-no-subcommand.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-all-options-long-with-alphabetic-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-all-options-short-with-int-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-float-value.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-help.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-no-options.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-normalization.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-numerical-key-prefix.trycmd create mode 100644 tests/integration/_cases/send_metric/send_metric-set-required-options-with-negative-value.trycmd create mode 100644 tests/integration/send_metric/distribution.rs create mode 100644 tests/integration/send_metric/gauge.rs create mode 100644 tests/integration/send_metric/increment.rs create mode 100644 tests/integration/send_metric/mod.rs create mode 100644 tests/integration/send_metric/set.rs diff --git a/Cargo.lock b/Cargo.lock index c7b26a6574..67bb6af4b1 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]] @@ -1484,12 +1505,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" @@ -2007,6 +2022,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 +2435,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "sentry" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "766448f12e44d68e675d5789a261515c46ac6ccd240abdd451a9c46c84a49523" +checksum = "00421ed8fa0c995f07cde48ba6c89e80f2b312f74ff637326f392fbfd23abe02" dependencies = [ "curl", "httpdate", @@ -2409,9 +2448,9 @@ dependencies = [ [[package]] name = "sentry-anyhow" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da4015667c99f88d68ca7ff02b90c762d6154a4ceb7c02922b9a1dbd3959eeb" +checksum = "ddcbce6e6785c2d91e67c55196f60ac561fab5946b6c7d60cc29f498fc126076" dependencies = [ "anyhow", "sentry-backtrace", @@ -2420,9 +2459,9 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32701cad8b3c78101e1cd33039303154791b0ff22e7802ed8cc23212ef478b45" +checksum = "a79194074f34b0cbe5dd33896e5928bbc6ab63a889bd9df2264af5acb186921e" dependencies = [ "backtrace", "once_cell", @@ -2446,6 +2485,7 @@ dependencies = [ "clap", "clap_complete", "console", + "crc32fast", "crossbeam-channel", "curl", "data-encoding", @@ -2508,9 +2548,9 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ddd2a91a13805bd8dab4ebf47323426f758c35f7bf24eacc1aded9668f3824" +checksum = "eba8870c5dba2bfd9db25c75574a11429f6b95957b0a78ac02e2970dd7a5249a" dependencies = [ "hostname", "libc", @@ -2522,9 +2562,9 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1189f68d7e7e102ef7171adf75f83a59607fafd1a5eecc9dc06c026ff3bdec4" +checksum = "46a75011ea1c0d5c46e9e57df03ce81f5c7f0a9e199086334a1f9c0a541e0826" dependencies = [ "once_cell", "rand", @@ -2535,9 +2575,9 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7173fd594569091f68a7c37a886e202f4d0c1db1e1fa1d18a051ba695b2e2ec" +checksum = "4519c900ce734f7a0eb7aba0869dfb225a7af8820634a7dd51449e3b093cfb7c" dependencies = [ "debugid", "hex", @@ -3355,6 +3395,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..04324d5534 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", @@ -26,6 +27,7 @@ clap = { version = "4.1.6", default-features = false, features = [ ] } clap_complete = "4.4.3" console = "0.15.5" +crc32fast = "1.4.0" curl = { version = "0.4.44", features = ["static-curl", "static-ssl"] } dirs = "4.0.0" dotenv = "0.15.0" @@ -58,10 +60,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.32.3", default-features = false, features = [ "anyhow", "curl", "contexts", + "UNSTABLE_metrics", ] } serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" diff --git a/package.json b/package.json index 62a20c0ddb..f01e4e3696 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli", - "version": "2.31.2", + "version": "2.31.0", "description": "A command line utility to work with Sentry. https://docs.sentry.io/hosted/learn/cli/", "repository": "git://github.com/getsentry/sentry-cli.git", "homepage": "https://docs.sentry.io/hosted/learn/cli/", @@ -30,13 +30,13 @@ "prettier": "2.8.8" }, "optionalDependencies": { - "@sentry/cli-darwin": "2.31.2", - "@sentry/cli-linux-arm": "2.31.2", - "@sentry/cli-linux-arm64": "2.31.2", - "@sentry/cli-linux-i686": "2.31.2", - "@sentry/cli-linux-x64": "2.31.2", - "@sentry/cli-win32-i686": "2.31.2", - "@sentry/cli-win32-x64": "2.31.2" + "@sentry/cli-darwin": "2.31.0", + "@sentry/cli-linux-arm": "2.31.0", + "@sentry/cli-linux-arm64": "2.31.0", + "@sentry/cli-linux-i686": "2.31.0", + "@sentry/cli-linux-x64": "2.31.0", + "@sentry/cli-win32-i686": "2.31.0", + "@sentry/cli-win32-x64": "2.31.0" }, "scripts": { "postinstall": "node ./scripts/install.js", diff --git a/src/api/envelopes_api.rs b/src/api/envelopes_api.rs new file mode 100644 index 0000000000..dabf5f9ffc --- /dev/null +++ b/src/api/envelopes_api.rs @@ -0,0 +1,46 @@ +use super::{ + errors::{ApiErrorKind, ApiResult}, + Api, Method, +}; +use crate::constants::USER_AGENT; +use anyhow::{anyhow, Result}; +use sentry::{protocol::EnvelopeItem, 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(); + match api.config.get_dsn() { + Ok(dsn) => Ok(EnvelopesApi { api, dsn }), + Err(_) => Err(ApiErrorKind::DsnMissing.into()), + } + } + + pub fn send_item(&self, item: EnvelopeItem) -> Result<()> { + let mut envelope = Envelope::new(); + envelope.add_item(item); + self.send_envelope(envelope) + } + + pub fn send_envelope(&self, envelope: Envelope) -> Result<()> { + let mut body: Vec = Vec::new(); + envelope.to_writer(&mut body)?; + let url = self.dsn.envelope_api_url(); + let auth = self.dsn.to_auth(Some(USER_AGENT)); + let response = self + .api + .request(Method::Post, url.as_str(), None)? + .with_header("X-Sentry-Auth", &auth.to_string())? + .with_body(body)? + .send()?; + match response.ok() { + true => Ok(()), + false => Err(anyhow!("Failed to send envelope.")), + } + } +} diff --git a/src/api/errors/api_error.rs b/src/api/errors/api_error.rs index f23fc309ee..0243f17663 100644 --- a/src/api/errors/api_error.rs +++ b/src/api/errors/api_error.rs @@ -38,6 +38,8 @@ pub(in crate::api) enum ApiErrorKind { "Auth token is required for this request. Please run `sentry-cli login` and try again!" )] AuthMissing, + #[error("DSN not found. See: https://docs.sentry.io/product/crons/getting-started/cli/#configuration")] + DsnMissing, } impl fmt::Display for ApiError { diff --git a/src/api/mod.rs b/src/api/mod.rs index e67366c7e2..934e890226 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -5,6 +5,7 @@ mod connection_manager; mod encoding; +pub(super) mod envelopes_api; mod errors; mod pagination; @@ -26,6 +27,7 @@ use brotli2::write::BrotliEncoder; use chrono::Duration; use chrono::{DateTime, FixedOffset, Utc}; use clap::ArgMatches; +use curl::easy::InfoType; use flate2::write::GzEncoder; use if_chain::if_chain; use lazy_static::lazy_static; @@ -1606,13 +1608,21 @@ fn handle_req( })?; handle.debug_function(move |info, data| match info { - curl::easy::InfoType::HeaderIn => { + InfoType::HeaderIn => { log_headers(false, data); } - curl::easy::InfoType::HeaderOut => { + InfoType::HeaderOut => { log_headers(true, data); } - _ => {} + InfoType::DataIn | InfoType::SslDataIn => { + debug!("< {}", String::from_utf8_lossy(data)) + } + InfoType::DataOut | InfoType::SslDataOut => { + debug!(">\n{}", String::from_utf8_lossy(data)) + } + _ => { + debug!("{}", String::from_utf8_lossy(data)) + } })?; handle.header_function(move |data| { @@ -1741,8 +1751,12 @@ impl ApiRequest { serde_json::to_writer(&mut body_bytes, &body) .map_err(|err| ApiError::with_source(ApiErrorKind::CannotSerializeAsJson, err))?; debug!("json body: {}", String::from_utf8_lossy(&body_bytes)); - self.body = Some(body_bytes); self.headers.append("Content-Type: application/json")?; + self.with_body(body_bytes) + } + + pub fn with_body(mut self, body: Vec) -> ApiResult { + self.body = Some(body); Ok(self) } 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..3014c5a8d6 --- /dev/null +++ b/src/commands/send_metric/common_args.rs @@ -0,0 +1,27 @@ +use crate::utils::metrics::arg_parsers; +use crate::utils::value_parsers; +use clap::command; +use clap::Args; + +#[derive(Args)] +pub(super) struct FloatValueMetricArgs { + #[command(flatten)] + pub(super) common: CommonMetricArgs, + + #[arg(short, long, help = "Metric value")] + pub(super) value: f64, +} + +#[derive(Args)] +pub struct CommonMetricArgs { + #[arg(short, long, visible_alias = "name", visible_short_alias = 'n')] + #[arg(help = "Metric key/name", value_parser = arg_parsers::key_parser)] + pub key: String, + + #[arg(short, long, help = "Metric unit")] + pub unit: Option, + + #[arg(short, long, value_delimiter=',', value_name = "KEY:VALUE", num_args = 1..)] + #[arg(value_parser=value_parsers::kv_parser, help = "Metric tags as key:value pairs")] + pub tags: Vec<(String, String)>, +} diff --git a/src/commands/send_metric/distribution.rs b/src/commands/send_metric/distribution.rs new file mode 100644 index 0000000000..b09f2f2b8b --- /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::{ + normalized_payload::NormalizedPayload, types::MetricType, values::MetricValue, + }, +}; +use anyhow::Result; +use sentry::protocol::EnvelopeItem; + +pub(super) fn execute(args: FloatValueMetricArgs) -> Result<()> { + let value = MetricValue::Float(args.value); + let payload = NormalizedPayload::from_cli_args(&args.common, value, MetricType::Distribution); + EnvelopesApi::try_new()?.send_item(EnvelopeItem::Statsd(payload.to_bytes()?)) +} diff --git a/src/commands/send_metric/gauge.rs b/src/commands/send_metric/gauge.rs new file mode 100644 index 0000000000..ddfdfebc8a --- /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::{ + normalized_payload::NormalizedPayload, types::MetricType, values::MetricValue, + }, +}; +use anyhow::Result; +use sentry::protocol::EnvelopeItem; + +pub(super) fn execute(args: FloatValueMetricArgs) -> Result<()> { + let value = MetricValue::Float(args.value); + let payload = NormalizedPayload::from_cli_args(&args.common, value, MetricType::Gauge); + EnvelopesApi::try_new()?.send_item(EnvelopeItem::Statsd(payload.to_bytes()?)) +} diff --git a/src/commands/send_metric/increment.rs b/src/commands/send_metric/increment.rs new file mode 100644 index 0000000000..80bca7608a --- /dev/null +++ b/src/commands/send_metric/increment.rs @@ -0,0 +1,25 @@ +use super::common_args::CommonMetricArgs; +use crate::{ + api::envelopes_api::EnvelopesApi, + utils::metrics::{ + normalized_payload::NormalizedPayload, types::MetricType, values::MetricValue, + }, +}; +use anyhow::Result; +use clap::{command, Args}; +use sentry::protocol::EnvelopeItem; + +#[derive(Args)] +pub(super) struct IncrementArgs { + #[command(flatten)] + common: CommonMetricArgs, + + #[arg(short, long, help = "Metric value", default_value = "1")] + value: f64, +} + +pub(super) fn execute(args: IncrementArgs) -> Result<()> { + let value = MetricValue::Float(args.value); + let payload = NormalizedPayload::from_cli_args(&args.common, value, MetricType::Counter); + EnvelopesApi::try_new()?.send_item(EnvelopeItem::Statsd(payload.to_bytes()?)) +} diff --git a/src/commands/send_metric/mod.rs b/src/commands/send_metric/mod.rs new file mode 100644 index 0000000000..0dc5bb99ed --- /dev/null +++ b/src/commands/send_metric/mod.rs @@ -0,0 +1,50 @@ +use self::common_args::FloatValueMetricArgs; +use self::increment::IncrementArgs; +use self::set::SetArgs; +use super::derive_parser::{SentryCLI, SentryCLICommand}; +use anyhow::Result; +use clap::{command, Args, Subcommand}; +use clap::{ArgMatches, Command, Parser}; + +pub mod common_args; +mod distribution; +mod gauge; +mod increment; +mod set; + +#[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.")] +enum SendMetricSubcommand { + #[command(about = "Send an increment to a counter metric")] + Increment(IncrementArgs), + #[command(about = "Send a value to a distribution metric")] + Distribution(FloatValueMetricArgs), + #[command(about = "Send a value to a gauge metric")] + Gauge(FloatValueMetricArgs), + #[command(about = "Send a value to a set metric")] + Set(SetArgs), +} + +pub(super) fn make_command(command: Command) -> Command { + SendMetricSubcommand::augment_subcommands(command) +} + +pub(super) fn execute(_: &ArgMatches) -> Result<()> { + 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..4a71830998 --- /dev/null +++ b/src/commands/send_metric/set.rs @@ -0,0 +1,26 @@ +use super::common_args::CommonMetricArgs; +use crate::{ + api::envelopes_api::EnvelopesApi, + utils::metrics::{ + arg_parsers, normalized_payload::NormalizedPayload, types::MetricType, values::MetricValue, + }, +}; +use anyhow::Result; +use clap::{command, Args}; +use sentry::protocol::EnvelopeItem; + +#[derive(Args)] +pub(super) struct SetArgs { + #[command(flatten)] + common: CommonMetricArgs, + + #[arg(short, long, value_parser=arg_parsers::set_value_parser)] + #[arg(help = "Metric value")] + value: i64, +} + +pub(super) fn execute(args: SetArgs) -> Result<()> { + let value = MetricValue::Int(args.value); + let payload = NormalizedPayload::from_cli_args(&args.common, value, MetricType::Set); + EnvelopesApi::try_new()?.send_item(EnvelopeItem::Statsd(payload.to_bytes()?)) +} diff --git a/src/utils/metrics/arg_parsers.rs b/src/utils/metrics/arg_parsers.rs new file mode 100644 index 0000000000..3dd2e5a8c7 --- /dev/null +++ b/src/utils/metrics/arg_parsers.rs @@ -0,0 +1,27 @@ +use anyhow::{anyhow, Result}; + +// Parse the value argument for a set metric. +pub fn set_value_parser(s: &str) -> Result { + match s.parse::() { + Ok(res) => Ok(res), + Err(_) => match s.parse::() { + Ok(_) => Err(anyhow!(format!("floats are not supported for set metrics"))), + Err(_) => Ok(crc32fast::hash(s.as_bytes()) as i64), + }, + } +} + +/// Parse the key argument for any metric. +pub fn key_parser(s: &str) -> Result { + if s.chars() + .next() + .ok_or_else(|| anyhow!(format!("metric name cannot be empty")))? + .is_ascii_alphabetic() + { + Ok(s.to_string()) + } else { + Err(anyhow!(format!( + "metric name must start with an alphabetic character" + ))) + } +} diff --git a/src/utils/metrics/mod.rs b/src/utils/metrics/mod.rs new file mode 100644 index 0000000000..fee1e3fc2f --- /dev/null +++ b/src/utils/metrics/mod.rs @@ -0,0 +1,7 @@ +pub mod arg_parsers; +mod normalized_key; +pub mod normalized_payload; +mod normalized_tags; +mod normalized_unit; +pub mod types; +pub mod values; diff --git a/src/utils/metrics/normalized_key.rs b/src/utils/metrics/normalized_key.rs new file mode 100644 index 0000000000..ff04ab90bc --- /dev/null +++ b/src/utils/metrics/normalized_key.rs @@ -0,0 +1,36 @@ +use regex::Regex; +use std::borrow::Cow; + +pub(super) struct NormalizedKey<'a> { + key: Cow<'a, str>, +} + +impl<'a> From<&'a str> for NormalizedKey<'a> { + fn from(key: &'a str) -> Self { + Self { + key: Regex::new(r"[^a-zA-Z0-9_\-.]") + .expect("Regex should compile") + .replace_all(key, "_"), + } + } +} + +impl std::fmt::Display for NormalizedKey<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.key) + } +} + +#[cfg(test)] +mod test { + use crate::utils::metrics::normalized_key::NormalizedKey; + + #[test] + fn test_from() { + let expected = "aA1_-.____________"; + + let actual = NormalizedKey::from("aA1_-./+รถ{๐Ÿ˜€\n\t\r\\| ,").to_string(); + + assert_eq!(expected, actual); + } +} diff --git a/src/utils/metrics/normalized_payload.rs b/src/utils/metrics/normalized_payload.rs new file mode 100644 index 0000000000..f7fb4b3c25 --- /dev/null +++ b/src/utils/metrics/normalized_payload.rs @@ -0,0 +1,80 @@ +use super::{ + normalized_key::NormalizedKey, normalized_tags::NormalizedTags, + normalized_unit::NormalizedUnit, types::MetricType, values::MetricValue, +}; +use crate::commands::send_metric::common_args::CommonMetricArgs; +use anyhow::Result; +use std::{ + io::Write, + time::{SystemTime, UNIX_EPOCH}, +}; + +pub struct NormalizedPayload<'a> { + key: NormalizedKey<'a>, + value: MetricValue, + metric_type: MetricType, + unit: NormalizedUnit<'a>, + tags: NormalizedTags<'a>, +} + +impl<'a> NormalizedPayload<'a> { + pub fn from_cli_args( + common_args: &'a CommonMetricArgs, + value: MetricValue, + metric_type: MetricType, + ) -> Self { + Self { + key: NormalizedKey::from(common_args.key.as_ref()), + value, + metric_type, + unit: NormalizedUnit::from(&common_args.unit), + tags: NormalizedTags::from(&common_args.tags).with_default_tags(), + } + } + + pub fn to_bytes(&self) -> Result> { + let mut data = Vec::new(); + write!(data, "{}", self.key)?; + write!(data, "@{}", self.unit)?; + write!(data, ":{}", self.value)?; + write!(data, "|{}", self.metric_type)?; + write!(data, "|#{}", self.tags)?; + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + write!(data, "|T{timestamp}")?; + Ok(data) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + commands::send_metric::common_args::CommonMetricArgs, + config::Config, + utils::metrics::{ + normalized_payload::NormalizedPayload, types::MetricType, values::MetricValue, + }, + }; + use regex::Regex; + + #[test] + fn test_to_bytes() { + Config::from_cli_config().unwrap().bind_to_process(); + let common_args = CommonMetricArgs { + key: "nรถme".to_string(), + unit: Some("mรถb".to_string()), + tags: vec![("atagรถ".to_string(), "aval|รถ".to_string())], + }; + let expected = Regex::new( + r"^n_me@mb:1\|s\|#atag:aval\\u\{7c}รถ,environment:production,release:.+\|T\d{10}$", + ) + .unwrap(); + + let bytes = + NormalizedPayload::from_cli_args(&common_args, MetricValue::Int(1), MetricType::Set) + .to_bytes() + .unwrap(); + let actual = String::from_utf8_lossy(&bytes); + + assert!(expected.is_match(&actual)); + } +} diff --git a/src/utils/metrics/normalized_tags.rs b/src/utils/metrics/normalized_tags.rs new file mode 100644 index 0000000000..b3db222269 --- /dev/null +++ b/src/utils/metrics/normalized_tags.rs @@ -0,0 +1,163 @@ +use crate::{config::Config, utils::releases}; +use itertools::Itertools; +use regex::Regex; +use std::{borrow::Cow, cmp::min, collections::HashMap}; + +pub(super) struct NormalizedTags<'a> { + tags: HashMap, String>, +} + +impl<'a> From<&'a Vec<(String, String)>> for NormalizedTags<'a> { + fn from(tags: &'a Vec<(String, String)>) -> Self { + Self { + tags: tags + .iter() + .map(|(k, v)| (Self::normalize_key(k), Self::normalize_value(v))) + .filter(|(k, v)| !v.is_empty() && !k.is_empty()) + .collect(), + } + } +} + +impl NormalizedTags<'_> { + fn normalize_key(key: &str) -> Cow { + Regex::new(r"[^a-zA-Z0-9_\-./]") + .expect("Tag normalization regex should compile") + .replace_all(&key[..min(key.len(), 32)], "") + } + + fn normalize_value(value: &str) -> String { + value[..min(value.len(), 200)] + .replace('\\', "\\\\") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") + .replace('|', "\\u{7c}") + .replace(',', "\\u{2c}") + } + + pub(super) fn with_default_tags(mut self) -> Self { + if let Ok(release) = releases::detect_release_name() { + self.tags + .entry(Cow::Borrowed("release")) + .or_insert(Self::normalize_value(&release)); + } + self.tags + .entry(Cow::Borrowed("environment")) + .or_insert(Self::normalize_value( + &Config::current() + .get_environment() + .unwrap_or("production".to_string()), + )); + self + } +} + +impl std::fmt::Display for NormalizedTags<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let res = self + .tags + .iter() + .map(|(k, v)| format!("{}:{}", k, v)) + .sorted() + .join(","); + write!(f, "{res}") + } +} + +#[cfg(test)] +mod test { + use std::env; + + use crate::config::Config; + + use super::NormalizedTags; + + #[test] + fn test_replacement_characters() { + let tags = vec![ + ("a\na", "a\na"), + ("b\rb", "b\rb"), + ("c\tc", "c\tc"), + ("d\\d", "d\\d"), + ("e|e", "e|e"), + ("f,f", "f,f"), + ] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + let expected = "aa:a\\na,bb:b\\rb,cc:c\\tc,dd:d\\\\d,ee:e\\u{7c}e,ff:f\\u{2c}f"; + + let actual = NormalizedTags::from(&tags).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_empty_tags() { + let tags = vec![("+", "a"), ("a", ""), ("", "a"), ("", "")] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + let expected = ""; + + let actual = NormalizedTags::from(&tags).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_special_characters() { + let tags = vec![("aA1_-./+รถ{ ๐Ÿ˜€", "aA1_-./+รถ{ ๐Ÿ˜€")] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + let expected = "aA1_-./:aA1_-./+รถ{ ๐Ÿ˜€"; + + let actual = NormalizedTags::from(&tags).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_add_default_tags() { + Config::from_cli_config().unwrap().bind_to_process(); + env::set_var("SOURCE_VERSION", "my-release"); + let expected = "environment:production,release:my-release"; + + let actual = NormalizedTags::from(&Vec::new()) + .with_default_tags() + .to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_override_default_tags() { + Config::from_cli_config().unwrap().bind_to_process(); + env::set_var("SOURCE_VERSION", "my-release"); + let expected = "environment:env_override,release:release_override"; + + let actual = NormalizedTags::from(&vec![ + ("release".to_string(), "release_override".to_string()), + ("environment".to_string(), "env_override".to_string()), + ]) + .with_default_tags() + .to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_tag_lengths() { + let expected = "abcdefghijklmnopqrstuvwxyzabcdef:abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr"; + + let actual = NormalizedTags::from(&vec![ + ("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz".to_string(), + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz".to_string()), + ]) + .to_string(); + + assert_eq!(expected, actual); + } +} diff --git a/src/utils/metrics/normalized_unit.rs b/src/utils/metrics/normalized_unit.rs new file mode 100644 index 0000000000..48f47bb362 --- /dev/null +++ b/src/utils/metrics/normalized_unit.rs @@ -0,0 +1,65 @@ +use regex::Regex; +use std::borrow::Cow; + +pub(super) struct NormalizedUnit<'a> { + unit: Cow<'a, str>, +} + +impl<'a> From<&'a Option> for NormalizedUnit<'a> { + fn from(unit: &'a Option) -> Self { + let safe_unit = match unit { + Some(unit) => Regex::new(r"[^a-zA-Z0-9_]") + .expect("Regex should compile") + .replace_all(unit, ""), + None => Cow::from(""), + }; + Self { + unit: if safe_unit.is_empty() { + Cow::from("none") + } else { + safe_unit + }, + } + } +} + +impl std::fmt::Display for NormalizedUnit<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.unit) + } +} + +#[cfg(test)] +mod test { + use crate::utils::metrics::normalized_unit::NormalizedUnit; + + #[test] + fn test_from() { + let unit = Some("aA1_-./+รถ{๐Ÿ˜€\n\t\r\\| ,".to_string()); + let expected = "aA1_"; + + let actual = NormalizedUnit::from(&unit).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_from_empty() { + let unit = Some("".to_string()); + let expected = "none"; + + let actual = NormalizedUnit::from(&unit).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_from_empty_after_normalization() { + let unit = Some("+".to_string()); + let expected = "none"; + + let actual = NormalizedUnit::from(&unit).to_string(); + + assert_eq!(expected, actual); + } +} diff --git a/src/utils/metrics/types.rs b/src/utils/metrics/types.rs new file mode 100644 index 0000000000..ab33c1925a --- /dev/null +++ b/src/utils/metrics/types.rs @@ -0,0 +1,20 @@ +use core::fmt; +use std::fmt::{Display, Formatter}; + +pub enum MetricType { + Gauge, + Distribution, + Counter, + Set, +} + +impl Display for MetricType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + MetricType::Gauge => write!(f, "g"), + MetricType::Distribution => write!(f, "d"), + MetricType::Counter => write!(f, "c"), + MetricType::Set => write!(f, "s"), + } + } +} diff --git a/src/utils/metrics/values.rs b/src/utils/metrics/values.rs new file mode 100644 index 0000000000..ac914e295b --- /dev/null +++ b/src/utils/metrics/values.rs @@ -0,0 +1,16 @@ +use core::fmt; +use std::fmt::{Display, Formatter}; + +pub enum MetricValue { + Int(i64), + Float(f64), +} + +impl Display for MetricValue { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + MetricValue::Int(value) => write!(f, "{value}"), + MetricValue::Float(value) => write!(f, "{value}"), + } + } +} 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..bdfd2d5309 --- /dev/null +++ b/src/utils/value_parsers.rs @@ -0,0 +1,12 @@ +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)> { + let pos = s + .find(':') + .ok_or_else(|| anyhow!(format!("`{s}` is missing a `:`")))?; + Ok(( + s[..pos].parse().expect("infallible"), + s[pos + 1..].parse().expect("infallible"), + )) +} 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..be7d85a263 --- /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 --key 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..d473d01733 --- /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 -k 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-help.trycmd b/tests/integration/_cases/send_metric/send_metric-distribution-help.trycmd new file mode 100644 index 0000000000..8b6cc384e5 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-distribution-help.trycmd @@ -0,0 +1,23 @@ +``` +$ sentry-cli send-metric distribution --help +? success +Send a value to a distribution metric + +Usage: sentry-cli[EXE] send-metric distribution [OPTIONS] --key --value + +Options: + -k, --key Metric key/name [aliases: name] [short aliases: n] + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Metric unit + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs + -v, --value Metric value + --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..806a30a1b3 --- /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: + --key + --value + +Usage: sentry-cli[EXE] send-metric distribution --key --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..e8cd9d2299 --- /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 -k "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:1|d|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:production,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..1df4a9761d --- /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 -k 1mymetric +? failed +error: invalid value '1mymetric' for '--key ': 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..1d2273c727 --- /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 -k 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..239dc54407 --- /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 --key 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..f1c53cdfe4 --- /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 -k 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-help.trycmd b/tests/integration/_cases/send_metric/send_metric-gauge-help.trycmd new file mode 100644 index 0000000000..eca75e8d79 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-gauge-help.trycmd @@ -0,0 +1,23 @@ +``` +$ sentry-cli send-metric gauge --help +? success +Send a value to a gauge metric + +Usage: sentry-cli[EXE] send-metric gauge [OPTIONS] --key --value + +Options: + -k, --key Metric key/name [aliases: name] [short aliases: n] + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Metric unit + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs + -v, --value Metric value + --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..28defdedae --- /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: + --key + --value + +Usage: sentry-cli[EXE] send-metric gauge --key --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..fb168b9ee8 --- /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 -k "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:1|g|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:production,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..8a42a07b64 --- /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 -k 1mymetric +? failed +error: invalid value '1mymetric' for '--key ': 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..fe9d80e3f3 --- /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 -k 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..5f197a6f8c --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-help.trycmd @@ -0,0 +1,45 @@ +``` +$ 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. + +Usage: sentry-cli[EXE] send-metric [OPTIONS] [COMMAND] + +Commands: + increment + Send an increment to a counter metric + distribution + Send a value to a distribution metric + gauge + Send a value to a gauge metric + set + Send a value to a set metric + 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..b42ce7c5d9 --- /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 --key 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..e826eab540 --- /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 -k 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-help.trycmd b/tests/integration/_cases/send_metric/send_metric-increment-help.trycmd new file mode 100644 index 0000000000..e9fda56785 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-help.trycmd @@ -0,0 +1,23 @@ +``` +$ sentry-cli send-metric increment --help +? success +Send an increment to a counter metric + +Usage: sentry-cli[EXE] send-metric increment [OPTIONS] --key + +Options: + -k, --key Metric key/name [aliases: name] [short aliases: n] + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Metric unit + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs + -v, --value Metric value [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..05458efe3e --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-no-dsn.trycmd @@ -0,0 +1,9 @@ +``` +$ sentry-cli send-metric increment -k testmetric +? failed +error: DSN not found. See: https://docs.sentry.io/product/crons/getting-started/cli/#configuration + +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..e9bf9c8c9e --- /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: + --key + +Usage: sentry-cli[EXE] send-metric increment --key + +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..438ee83f84 --- /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 -k "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:-3|c|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:production,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..86c8f48d29 --- /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 -k 1mymetric +? failed +error: invalid value '1mymetric' for '--key ': 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..244770e6d5 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-required-options.trycmd @@ -0,0 +1,10 @@ +``` +$ sentry-cli send-metric increment -k 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..57ee990517 --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-tag-no-colon.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli send-metric increment -k 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..6c2f16a67c --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-increment-unsuccessful-api-call.trycmd @@ -0,0 +1,9 @@ +``` +$ sentry-cli send-metric increment -k testmetric +? failed +error: Failed to send envelope. + +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..0614d567aa --- /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 Send an increment to a counter metric + distribution Send a value to a distribution metric + gauge Send a value to a gauge metric + set Send a value to a set metric + 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..b27b4cc01e --- /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 --key 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..3e7f434e59 --- /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 -k testMetric -v 2 -u mb -t a:b,environment:testEnvironment,release:testRelease +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@mb:2|s|#a:b,environment:testEnvironment,release:testRelease|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..3c3f43f9bc --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-float-value.trycmd @@ -0,0 +1,8 @@ +``` +$ sentry-cli send-metric set -k testmetric -v 1.9 --log-level debug +? failed +error: invalid value '1.9' for '--value ': floats are not supported for set metrics + +For more information, try '--help'. + +``` 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..5509a324ca --- /dev/null +++ b/tests/integration/_cases/send_metric/send_metric-set-help.trycmd @@ -0,0 +1,23 @@ +``` +$ sentry-cli send-metric set --help +? success +Send a value to a set metric + +Usage: sentry-cli[EXE] send-metric set [OPTIONS] --key --value + +Options: + -k, --key Metric key/name [aliases: name] [short aliases: n] + --header Custom headers that should be attached to all requests + in key:value format. + -u, --unit Metric unit + --auth-token Use the given Sentry auth token. + -t, --tags ... Metric tags as key:value pairs + -v, --value Metric value + --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..c77bdd3fa6 --- /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: + --key + --value + +Usage: sentry-cli[EXE] send-metric set --key --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..2f8e14ba4a --- /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 -k "aA1_-./+รถ{๐Ÿ˜€ |\\," -u "aA1_-./+รถ{๐Ÿ˜€ |\\," -t "aA1_-./+รถ{๐Ÿ˜€ |\\:aA1_-./+รถ{๐Ÿ˜€ |\\" release:testRelease +:b +:+ a: :b : +? success +... +{} +{"type":"statsd","length":[..]} +aA1_-.__________@aA1_:1|s|#aA1_-./:aA1_-./+รถ{๐Ÿ˜€ /t/u{7c}//,environment:production,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..f319a20145 --- /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 -k 1mymetric +? failed +error: invalid value '1mymetric' for '--key ': 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..2a85323f75 --- /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 -k testMetric -v=-3 --log-level debug +? success +... +{} +{"type":"statsd","length":[..]} +testMetric@none:-3|s|#environment:production,release:[..]|T[..] +... + +``` diff --git a/tests/integration/mod.rs b/tests/integration/mod.rs index 0387b6c869..d67dd15be2 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, 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_URL", mockito::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,10 +95,15 @@ impl EndpointOptions { self.matcher = Some(matcher); self } + + 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 { - let mut mock = mock(opts.method.as_str(), opts.endpoint.as_str()) + let mut mock = mockito::mock(opts.method.as_str(), opts.endpoint.as_str()) .with_status(opts.status) .with_header("content-type", "application/json"); @@ -110,6 +119,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() } @@ -185,7 +198,7 @@ pub fn mock_common_upload_endpoints( \"hashAlgorithm\": \"sha1\", \"accept\": [{}] }}", - server_url(), + mockito::server_url(), accept, ); diff --git a/tests/integration/send_metric/distribution.rs b/tests/integration/send_metric/distribution.rs new file mode 100644 index 0000000000..b4dbc19a0d --- /dev/null +++ b/tests/integration/send_metric/distribution.rs @@ -0,0 +1,49 @@ +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_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..3a2931e389 --- /dev/null +++ b/tests/integration/send_metric/gauge.rs @@ -0,0 +1,49 @@ +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_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..fffcba0e1d --- /dev/null +++ b/tests/integration/send_metric/increment.rs @@ -0,0 +1,69 @@ +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_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..3654bbb5ba --- /dev/null +++ b/tests/integration/send_metric/set.rs @@ -0,0 +1,50 @@ +use super::mock_envelopes_endpoint; +use crate::integration::register_test; + +#[test] +fn command_send_metric_set_all_options_long_with_alphabetic_value() { + let _m = mock_envelopes_endpoint(); + 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(); + register_test("send_metric/send_metric-set-all-options-short-with-int-value.trycmd"); +} + +#[test] +fn command_send_metric_set_float_value() { + let _m = mock_envelopes_endpoint(); + register_test("send_metric/send_metric-set-float-value.trycmd"); +} + +#[test] +fn command_send_metric_set_help() { + let _m = mock_envelopes_endpoint(); + register_test("send_metric/send_metric-set-help.trycmd"); +} + +#[test] +fn command_send_metric_set_no_options() { + let _m = mock_envelopes_endpoint(); + register_test("send_metric/send_metric-set-no-options.trycmd"); +} + +#[test] +fn command_send_metric_set_normalization() { + let _m = mock_envelopes_endpoint(); + register_test("send_metric/send_metric-set-normalization.trycmd"); +} + +#[test] +fn command_send_metric_set_numerical_key_prefix() { + let _m = mock_envelopes_endpoint(); + 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(); + register_test("send_metric/send_metric-set-required-options-with-negative-value.trycmd"); +}