From 49ffc57d8e560087b14b674c72b52cf299b7f0a9 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 29 Aug 2022 20:33:05 -0700 Subject: [PATCH 1/2] Implement error reporting to Sentry Signed-off-by: David Calavera --- .github/workflows/build.yml | 5 ++ crates/cargo-lambda-cli/Cargo.toml | 6 ++ .../cargo-lambda-cli/src/error_reporting.rs | 19 ++++++ crates/cargo-lambda-cli/src/main.rs | 60 +++++++++++++++++-- crates/cargo-lambda-invoke/src/lib.rs | 9 ++- 5 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 crates/cargo-lambda-cli/src/error_reporting.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72d367b0..87dc5875 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,11 @@ jobs: - name: Run cargo check run: cargo check + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy -- -D warnings + test: name: Test -- ${{ matrix.os }} -- ${{ matrix.rust_version }} runs-on: ${{ matrix.os }} diff --git a/crates/cargo-lambda-cli/Cargo.toml b/crates/cargo-lambda-cli/Cargo.toml index c10a18cc..f2db866e 100644 --- a/crates/cargo-lambda-cli/Cargo.toml +++ b/crates/cargo-lambda-cli/Cargo.toml @@ -24,6 +24,12 @@ cargo-lambda-new.workspace = true cargo-lambda-watch.workspace = true clap = { workspace = true, features = ["suggestions"] } miette = { workspace = true, features = ["fancy"] } +sentry = { version = "0.27.0", default-features = false, features = [ + "rustls", + "reqwest", + "tracing", +] } +sentry-core = "0.27.0" tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tracing.workspace = true tracing-subscriber.workspace = true diff --git a/crates/cargo-lambda-cli/src/error_reporting.rs b/crates/cargo-lambda-cli/src/error_reporting.rs new file mode 100644 index 00000000..2737647d --- /dev/null +++ b/crates/cargo-lambda-cli/src/error_reporting.rs @@ -0,0 +1,19 @@ +use sentry_core::types::Uuid; +use sentry_core::Hub; + +pub fn capture_error(err: &miette::Error) -> Uuid { + Hub::with_active(|hub| hub.capture_miette(err)) +} + +/// Hub extension methods for working with [`miette`]. +pub trait MietteHubExt { + /// Captures an [`miette::Error`] on a specific hub. + fn capture_miette(&self, e: &miette::Error) -> Uuid; +} + +impl MietteHubExt for Hub { + fn capture_miette(&self, err: &miette::Error) -> Uuid { + let event = sentry_core::event_from_error(err.root_cause()); + self.capture_event(event) + } +} diff --git a/crates/cargo-lambda-cli/src/main.rs b/crates/cargo-lambda-cli/src/main.rs index 4e3f9b7b..ce00b2d7 100644 --- a/crates/cargo-lambda-cli/src/main.rs +++ b/crates/cargo-lambda-cli/src/main.rs @@ -1,15 +1,21 @@ #![warn(rust_2018_idioms, unused_lifetimes, clippy::multiple_crate_versions)] -use std::boxed::Box; +use std::{boxed::Box, env}; use cargo_lambda_build::{Build, Zig}; use cargo_lambda_deploy::Deploy; -use cargo_lambda_invoke::Invoke; +use cargo_lambda_invoke::{is_remote_invoke_err, Invoke}; use cargo_lambda_new::New; use cargo_lambda_watch::Watch; use clap::{CommandFactory, Parser, Subcommand}; +use error_reporting::capture_error; use miette::{miette, IntoDiagnostic, Result}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +mod error_reporting; + +const SENTRY_DSN: &str = + "https://6e690ac332c844949e5ef5b71e2ded17@o1418469.ingest.sentry.io/6761625"; + #[derive(Parser)] #[command(name = "cargo", bin_name = "cargo", disable_version_flag = true)] enum App { @@ -29,6 +35,9 @@ struct Lambda { /// Print version information #[arg(short = 'V', long)] version: bool, + /// Disable telemetry and error reporting + #[clap(long, env = "DO_NOT_TRACK")] + do_not_track: bool, } #[derive(Clone, Debug, Subcommand)] @@ -58,12 +67,18 @@ impl LambdaSubcommand { } fn print_version() -> Result<()> { - println!( - "cargo-lambda {} {}", + println!("cargo-lambda {}", version()); + Ok(()) +} + +fn version() -> String { + format!( + "{} {}", env!("CARGO_PKG_VERSION"), env!("CARGO_LAMBDA_BUILD_INFO") - ); - Ok(()) + ) + .trim_end() + .into() } fn print_help() -> Result<()> { @@ -84,6 +99,26 @@ fn print_help() -> Result<()> { #[tokio::main] async fn main() -> Result<()> { + let _sentry = sentry::init(( + SENTRY_DSN, + sentry::ClientOptions { + release: Some(version().into()), + traces_sample_rate: 1.0, + ..Default::default() + }, + )); + + let res = run_command().await; + if let Err(err) = res.as_ref() { + if !is_do_not_track_enabled() && !is_remote_invoke_err(err) { + capture_error(err); + } + } + + res +} + +async fn run_command() -> Result<()> { let app = App::parse(); let lambda = match app { @@ -91,6 +126,10 @@ async fn main() -> Result<()> { App::Lambda(lambda) => lambda, }; + if lambda.do_not_track && !is_do_not_track_enabled() { + enable_do_not_track(); + } + if lambda.version { return print_version(); } @@ -113,6 +152,7 @@ async fn main() -> Result<()> { .without_time(); let subscriber = tracing_subscriber::registry() + .with(sentry::integrations::tracing::layer()) .with(tracing_subscriber::EnvFilter::new(log_directive)) .with(fmt); @@ -124,3 +164,11 @@ async fn main() -> Result<()> { subcommand.run().await } + +fn is_do_not_track_enabled() -> bool { + env::var("DO_NOT_TRACK").is_ok() +} + +fn enable_do_not_track() { + env::set_var("DO_NOT_TRACK", "true") +} diff --git a/crates/cargo-lambda-invoke/src/lib.rs b/crates/cargo-lambda-invoke/src/lib.rs index 16880f29..e74865bd 100644 --- a/crates/cargo-lambda-invoke/src/lib.rs +++ b/crates/cargo-lambda-invoke/src/lib.rs @@ -3,7 +3,7 @@ use cargo_lambda_remote::{ RemoteConfig, }; use clap::{Args, ValueHint}; -use miette::{IntoDiagnostic, Result, WrapErr}; +use miette::{IntoDiagnostic, Report, Result, WrapErr}; use reqwest::{Client, StatusCode}; use serde_json::{from_str, to_string_pretty, value::Value}; use std::{ @@ -123,6 +123,7 @@ impl Invoke { Ok(()) } + #[tracing::instrument(skip(self, data), target = "cargo_lambda")] async fn invoke_remote(&self, data: &str) -> Result { if self.function_name == DEFAULT_PACKAGE_FUNCTION { return Err(InvokeError::InvalidFunctionName.into()); @@ -157,6 +158,7 @@ impl Invoke { } } + #[tracing::instrument(skip(self, data), target = "cargo_lambda")] async fn invoke_local(&self, data: &str) -> Result { let host = parse_invoke_ip_address(&self.invoke_address)?; @@ -190,6 +192,7 @@ impl Invoke { } } +#[tracing::instrument(skip(cache), target = "cargo_lambda")] async fn download_example(name: &str, cache: Option) -> Result { let target = format!("https://github.com/LegNeato/aws-lambda-events/raw/master/aws_lambda_events/src/generated/fixtures/{name}"); @@ -227,6 +230,10 @@ fn parse_invoke_ip_address(address: &str) -> Result { Ok(invoke_address) } +pub fn is_remote_invoke_err(err: &Report) -> bool { + err.downcast_ref::().is_some() +} + #[cfg(test)] mod test { use super::*; From ef08b0b0c5b16c8c8b501511637e8e20ea490f6a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Sep 2022 19:47:59 -0700 Subject: [PATCH 2/2] Add telemetry to track usage. Signed-off-by: David Calavera --- crates/cargo-lambda-build/src/lib.rs | 4 + crates/cargo-lambda-cli/Cargo.toml | 5 + .../cargo-lambda-cli/src/error_reporting.rs | 8 +- crates/cargo-lambda-cli/src/main.rs | 153 ++++++++++-------- crates/cargo-lambda-cli/src/telemetry.rs | 105 ++++++++++++ 5 files changed, 202 insertions(+), 73 deletions(-) create mode 100644 crates/cargo-lambda-cli/src/telemetry.rs diff --git a/crates/cargo-lambda-build/src/lib.rs b/crates/cargo-lambda-build/src/lib.rs index 03c20c16..ed52d5cf 100644 --- a/crates/cargo-lambda-build/src/lib.rs +++ b/crates/cargo-lambda-build/src/lib.rs @@ -145,6 +145,7 @@ impl Build { } } +<<<<<<< HEAD let mut build_config = function_build_metadata(&metadata)?; if self.compiler == CompilerFlag::Cargo && build_config.is_zig_enabled() { build_config.compiler = CompilerOptions::from(self.compiler.to_string()); @@ -157,6 +158,9 @@ impl Build { return Err(BuildError::InvalidLinkerOption.into()); } } +======= + let mut cmd = self.build.build_command().map_err(|e| miette::miette!(e))?; +>>>>>>> Add telemetry to track usage. let rust_flags = if self.build.release { let mut rust_flags = env::var("RUSTFLAGS").unwrap_or_default(); diff --git a/crates/cargo-lambda-cli/Cargo.toml b/crates/cargo-lambda-cli/Cargo.toml index f2db866e..e148e762 100644 --- a/crates/cargo-lambda-cli/Cargo.toml +++ b/crates/cargo-lambda-cli/Cargo.toml @@ -24,15 +24,20 @@ cargo-lambda-new.workspace = true cargo-lambda-watch.workspace = true clap = { workspace = true, features = ["suggestions"] } miette = { workspace = true, features = ["fancy"] } +reqwest = { workspace = true, features = ["rustls-tls", "json"] } sentry = { version = "0.27.0", default-features = false, features = [ "rustls", "reqwest", "tracing", ] } sentry-core = "0.27.0" +serde = { version = "1.0.137", features = ["derive"] } +serde_json = "1.0.81" +sysinfo = "0.26.3" tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tracing.workspace = true tracing-subscriber.workspace = true +uuid.workspace = true [build-dependencies] build-data = "0.1" diff --git a/crates/cargo-lambda-cli/src/error_reporting.rs b/crates/cargo-lambda-cli/src/error_reporting.rs index 2737647d..633d820d 100644 --- a/crates/cargo-lambda-cli/src/error_reporting.rs +++ b/crates/cargo-lambda-cli/src/error_reporting.rs @@ -1,5 +1,8 @@ -use sentry_core::types::Uuid; -use sentry_core::Hub; +use sentry_core::{types::Uuid, Hub}; + +use crate::telemetry::Data; + +pub const SENTRY_DSN: &str = ""; pub fn capture_error(err: &miette::Error) -> Uuid { Hub::with_active(|hub| hub.capture_miette(err)) @@ -13,6 +16,7 @@ pub trait MietteHubExt { impl MietteHubExt for Hub { fn capture_miette(&self, err: &miette::Error) -> Uuid { + sentry_core::add_breadcrumb(Data::breadcrumb()); let event = sentry_core::event_from_error(err.root_cause()); self.capture_event(event) } diff --git a/crates/cargo-lambda-cli/src/main.rs b/crates/cargo-lambda-cli/src/main.rs index ce00b2d7..95d24c36 100644 --- a/crates/cargo-lambda-cli/src/main.rs +++ b/crates/cargo-lambda-cli/src/main.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms, unused_lifetimes, clippy::multiple_crate_versions)] -use std::{boxed::Box, env}; +use std::boxed::Box; -use cargo_lambda_build::{Build, Zig}; +use cargo_lambda_build::{Build, Zig as ZigBuild}; use cargo_lambda_deploy::Deploy; use cargo_lambda_invoke::{is_remote_invoke_err, Invoke}; use cargo_lambda_new::New; @@ -12,15 +12,15 @@ use miette::{miette, IntoDiagnostic, Result}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod error_reporting; - -const SENTRY_DSN: &str = - "https://6e690ac332c844949e5ef5b71e2ded17@o1418469.ingest.sentry.io/6761625"; +use error_reporting::*; +mod telemetry; +use telemetry::*; #[derive(Parser)] #[command(name = "cargo", bin_name = "cargo", disable_version_flag = true)] enum App { Lambda(Lambda), - #[command(subcommand, hide = true)] + #[command(hide = true)] Zig(Zig), } @@ -54,6 +54,72 @@ enum LambdaSubcommand { Watch(Watch), } +#[derive(Clone, Debug, Parser)] +struct Zig { + #[clap(subcommand)] + subcommand: ZigBuild, + + /// Disable telemetry and error reporting + #[clap(long, env = "DO_NOT_TRACK")] + do_not_track: bool, +} + +impl Lambda { + async fn run(self) -> Result<()> { + if self.do_not_track && !is_do_not_track_enabled() { + enable_do_not_track(); + } + + let tm_handle = send_telemetry_data(); + + if self.version { + return print_version(); + } + + let subcommand = match self.subcommand { + None => return print_help(), + Some(subcommand) => subcommand, + }; + + let log_directive = if self.verbose == 0 { + std::env::var("RUST_LOG").unwrap_or_else(|_| "cargo_lambda=info".into()) + } else if self.verbose == 1 { + "cargo_lambda=debug".into() + } else { + "cargo_lambda=trace".into() + }; + + let fmt = tracing_subscriber::fmt::layer() + .with_target(false) + .without_time(); + + let subscriber = tracing_subscriber::registry() + .with(sentry::integrations::tracing::layer()) + .with(tracing_subscriber::EnvFilter::new(log_directive)) + .with(fmt); + + if let LambdaSubcommand::Watch(w) = &*subcommand { + subscriber.with(w.xray_layer()).init(); + } else { + subscriber.init(); + } + + let res = subcommand.run().await; + let _ = tm_handle.await; + + res + } +} + +impl Zig { + fn run(self) -> Result<()> { + if self.do_not_track && !is_do_not_track_enabled() { + enable_do_not_track(); + } + self.subcommand.execute().map_err(|e| miette!(e)) + } +} + impl LambdaSubcommand { async fn run(self) -> Result<()> { match self { @@ -71,16 +137,6 @@ fn print_version() -> Result<()> { Ok(()) } -fn version() -> String { - format!( - "{} {}", - env!("CARGO_PKG_VERSION"), - env!("CARGO_LAMBDA_BUILD_INFO") - ) - .trim_end() - .into() -} - fn print_help() -> Result<()> { let mut app = App::command(); let lambda = app @@ -97,6 +153,15 @@ fn print_help() -> Result<()> { } } +async fn run_command() -> Result<()> { + let app = App::parse(); + + match app { + App::Zig(zig) => zig.run(), + App::Lambda(lambda) => lambda.run().await, + } +} + #[tokio::main] async fn main() -> Result<()> { let _sentry = sentry::init(( @@ -109,6 +174,7 @@ async fn main() -> Result<()> { )); let res = run_command().await; + if let Err(err) = res.as_ref() { if !is_do_not_track_enabled() && !is_remote_invoke_err(err) { capture_error(err); @@ -117,58 +183,3 @@ async fn main() -> Result<()> { res } - -async fn run_command() -> Result<()> { - let app = App::parse(); - - let lambda = match app { - App::Zig(zig) => return zig.execute().map_err(|e| miette!(e)), - App::Lambda(lambda) => lambda, - }; - - if lambda.do_not_track && !is_do_not_track_enabled() { - enable_do_not_track(); - } - - if lambda.version { - return print_version(); - } - - let subcommand = match lambda.subcommand { - None => return print_help(), - Some(subcommand) => subcommand, - }; - - let log_directive = if lambda.verbose == 0 { - std::env::var("RUST_LOG").unwrap_or_else(|_| "cargo_lambda=info".into()) - } else if lambda.verbose == 1 { - "cargo_lambda=debug".into() - } else { - "cargo_lambda=trace".into() - }; - - let fmt = tracing_subscriber::fmt::layer() - .with_target(false) - .without_time(); - - let subscriber = tracing_subscriber::registry() - .with(sentry::integrations::tracing::layer()) - .with(tracing_subscriber::EnvFilter::new(log_directive)) - .with(fmt); - - if let LambdaSubcommand::Watch(w) = &*subcommand { - subscriber.with(w.xray_layer()).init(); - } else { - subscriber.init(); - } - - subcommand.run().await -} - -fn is_do_not_track_enabled() -> bool { - env::var("DO_NOT_TRACK").is_ok() -} - -fn enable_do_not_track() { - env::set_var("DO_NOT_TRACK", "true") -} diff --git a/crates/cargo-lambda-cli/src/telemetry.rs b/crates/cargo-lambda-cli/src/telemetry.rs new file mode 100644 index 00000000..e9a257d2 --- /dev/null +++ b/crates/cargo-lambda-cli/src/telemetry.rs @@ -0,0 +1,105 @@ +use miette::{IntoDiagnostic, Result}; +use sentry::Breadcrumb; +use serde::Serialize; +use std::{ + collections::{BTreeMap, HashMap}, + env, +}; +use sysinfo::{System, SystemExt}; +use tokio::task::JoinHandle; + +const TELEMETRY_URL: &str = ""; + +#[derive(Debug, Serialize)] +pub(crate) struct Data<'a> { + device_id: uuid::Uuid, + os_name: Option, + os_version: Option, + app_version: String, + event_properties: HashMap<&'a str, String>, +} + +impl<'a> Default for Data<'a> { + fn default() -> Self { + let system = System::default(); + let mut args = env::args_os(); + let _ = args.next(); // program name + let _ = args.next(); // `lambda` as subcommand + let args = args + .map(|a| a.to_string_lossy().to_string()) + .collect::>() + .join(" "); + + let mut event_properties = HashMap::new(); + event_properties.insert("arguments", args); + Data { + device_id: uuid::Uuid::new_v4(), + os_name: system.name(), + os_version: system.os_version(), + app_version: version(), + event_properties, + } + } +} + +impl<'a> Data<'a> { + pub(crate) fn breadcrumb() -> Breadcrumb { + let tm = Data::default(); + let mut data = BTreeMap::new(); + if let Some(name) = tm.os_name { + data.insert("os_name".to_string(), name.into()); + } + if let Some(version) = tm.os_version { + data.insert("os_version".to_string(), version.into()); + } + data.insert( + "arguments".to_string(), + tm.event_properties["arguments"].clone().into(), + ); + Breadcrumb { + data, + ..Default::default() + } + } +} + +pub(crate) fn version() -> String { + format!( + "{} {}", + env!("CARGO_PKG_VERSION"), + env!("CARGO_LAMBDA_BUILD_INFO") + ) + .trim_end() + .into() +} + +pub(crate) async fn send_telemetry_data() -> JoinHandle> { + if is_do_not_track_enabled() { + tokio::spawn(async { Ok(()) }) + } else { + tokio::spawn(async { send_environment_data().await }) + } +} + +pub(crate) fn is_do_not_track_enabled() -> bool { + env::var("DO_NOT_TRACK").is_ok() +} + +pub(crate) fn enable_do_not_track() { + env::set_var("DO_NOT_TRACK", "true") +} + +async fn send_environment_data() -> Result<()> { + let data = Data::default(); + + let client = reqwest::Client::new(); + let res = client + .post(TELEMETRY_URL) + .json(&data) + .send() + .await + .into_diagnostic()?; + + tracing::debug!(status = %res.status(), data = ?data, "telemetry data sent"); + Ok(()) +}