Skip to content

Commit

Permalink
feat(metrics): Add send-metric command (#2063)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
elramen authored May 27, 2024
1 parent c987094 commit c805e5c
Show file tree
Hide file tree
Showing 63 changed files with 1,251 additions and 29 deletions.
122 changes: 96 additions & 26 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
39 changes: 39 additions & 0 deletions src/api/envelopes_api.rs
Original file line number Diff line number Diff line change
@@ -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<Api>,
dsn: Dsn,
}

impl EnvelopesApi {
pub fn try_new() -> ApiResult<EnvelopesApi> {
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<ApiResponse> {
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()
}
}
6 changes: 6 additions & 0 deletions src/api/errors/api_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1746,6 +1748,11 @@ impl ApiRequest {
Ok(self)
}

pub fn with_body(mut self, body: Vec<u8>) -> ApiResult<Self> {
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<Self> {
debug!("sending form data");
Expand Down
36 changes: 36 additions & 0 deletions src/commands/derive_parser.rs
Original file line number Diff line number Diff line change
@@ -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<AuthToken>,

#[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<String>,

#[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),
}
Loading

0 comments on commit c805e5c

Please sign in to comment.