Skip to content

Commit c805e5c

Browse files
authored
feat(metrics): Add send-metric command (#2063)
Add CLI command send-metric for emitting metrics to Sentry. Add new command-parser that uses clap's Derive API. Future commands should use this as the Derive API makes it: -Easier to read, write, and modify arguments and commands. -Easier to keep the argument declaration and reading of argument in sync. -Easier to reuse shared arguments. Fixes GH-2001
1 parent c987094 commit c805e5c

File tree

63 files changed

+1251
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1251
-29
lines changed

Cargo.lock

Lines changed: 96 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ bytecount = "0.6.3"
1616
chardet = "0.2.4"
1717
chrono = { version = "0.4.31", features = ["serde"] }
1818
clap = { version = "4.1.6", default-features = false, features = [
19+
"derive",
1920
"std",
2021
"suggestions",
2122
"wrap_help",
@@ -58,10 +59,11 @@ regex = "1.7.3"
5859
runas = "1.0.0"
5960
rust-ini = "0.18.0"
6061
semver = "1.0.16"
61-
sentry = { version = "0.32.2", default-features = false, features = [
62+
sentry = { version = "0.33.0", default-features = false, features = [
6263
"anyhow",
6364
"curl",
6465
"contexts",
66+
"UNSTABLE_metrics",
6567
] }
6668
serde = { version = "1.0.152", features = ["derive"] }
6769
serde_json = "1.0.93"

src/api/envelopes_api.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use super::{
2+
errors::{ApiErrorKind, ApiResult},
3+
Api, ApiResponse, Method,
4+
};
5+
use crate::{api::errors::ApiError, constants::USER_AGENT};
6+
use log::debug;
7+
use sentry::{types::Dsn, Envelope};
8+
use std::sync::Arc;
9+
10+
pub struct EnvelopesApi {
11+
api: Arc<Api>,
12+
dsn: Dsn,
13+
}
14+
15+
impl EnvelopesApi {
16+
pub fn try_new() -> ApiResult<EnvelopesApi> {
17+
let api = Api::current();
18+
api.config
19+
.get_dsn()
20+
.map(|dsn| EnvelopesApi { api, dsn })
21+
.map_err(|_| ApiErrorKind::DsnMissing.into())
22+
}
23+
24+
pub fn send_envelope(&self, envelope: Envelope) -> ApiResult<ApiResponse> {
25+
let mut body = vec![];
26+
envelope
27+
.to_writer(&mut body)
28+
.map_err(|e| ApiError::with_source(ApiErrorKind::CannotSerializeEnvelope, e))?;
29+
let url = self.dsn.envelope_api_url();
30+
let auth = self.dsn.to_auth(Some(USER_AGENT));
31+
debug!("Sending envelope:\n{}", String::from_utf8_lossy(&body));
32+
self.api
33+
.request(Method::Post, url.as_str(), None)?
34+
.with_header("X-Sentry-Auth", &auth.to_string())?
35+
.with_body(body)?
36+
.send()?
37+
.into_result()
38+
}
39+
}

src/api/errors/api_error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub struct ApiError {
1212
pub(in crate::api) enum ApiErrorKind {
1313
#[error("could not serialize value as JSON")]
1414
CannotSerializeAsJson,
15+
#[error("could not serialize envelope")]
16+
CannotSerializeEnvelope,
1517
#[error("could not parse JSON response")]
1618
BadJson,
1719
#[error("not a JSON response")]
@@ -38,6 +40,10 @@ pub(in crate::api) enum ApiErrorKind {
3840
"Auth token is required for this request. Please run `sentry-cli login` and try again!"
3941
)]
4042
AuthMissing,
43+
#[error(
44+
"DSN missing. Please set the `SENTRY_DSN` environment variable to your project's DSN."
45+
)]
46+
DsnMissing,
4147
}
4248

4349
impl fmt::Display for ApiError {

src/api/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//! to the GitHub API to figure out if there are new releases of the
44
//! sentry-cli tool.
55
6+
pub mod envelopes_api;
7+
68
mod connection_manager;
79
mod encoding;
810
mod errors;
@@ -1746,6 +1748,11 @@ impl ApiRequest {
17461748
Ok(self)
17471749
}
17481750

1751+
pub fn with_body(mut self, body: Vec<u8>) -> ApiResult<Self> {
1752+
self.body = Some(body);
1753+
Ok(self)
1754+
}
1755+
17491756
/// attaches some form data to the request.
17501757
pub fn with_form_data(mut self, form: curl::easy::Form) -> ApiResult<Self> {
17511758
debug!("sending form data");

src/commands/derive_parser.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::utils::auth_token::AuthToken;
2+
use crate::utils::value_parsers::kv_parser;
3+
use clap::{command, value_parser, ArgAction::SetTrue, Parser, Subcommand};
4+
5+
use super::send_metric::SendMetricArgs;
6+
7+
#[derive(Parser)]
8+
pub(super) struct SentryCLI {
9+
#[command(subcommand)]
10+
pub(super) command: SentryCLICommand,
11+
12+
#[arg(global=true, long="header", value_name="KEY:VALUE", value_parser=kv_parser)]
13+
#[arg(help = "Custom headers that should be attached to all requests{n}in key:value format")]
14+
pub(super) headers: Vec<(String, String)>,
15+
16+
#[arg(global=true, long, value_parser=value_parser!(AuthToken))]
17+
#[arg(help = "Use the given Sentry auth token")]
18+
pub(super) auth_token: Option<AuthToken>,
19+
20+
#[arg(global=true, ignore_case=true, value_parser=["trace", "debug", "info", "warn", "error"])]
21+
#[arg(long, help = "Set the log output verbosity")]
22+
pub(super) log_level: Option<String>,
23+
24+
#[arg(global=true, action=SetTrue, visible_alias="silent", long)]
25+
#[arg(help = "Do not print any output while preserving correct exit code. \
26+
This flag is currently implemented only for selected subcommands")]
27+
pub(super) quiet: bool,
28+
29+
#[arg(global=true, action=SetTrue, long, hide=true, help="Always return 0 exit code")]
30+
pub(super) allow_failure: bool,
31+
}
32+
33+
#[derive(Subcommand)]
34+
pub(super) enum SentryCLICommand {
35+
SendMetric(SendMetricArgs),
36+
}

0 commit comments

Comments
 (0)