diff --git a/bin_tests/src/bin/crashtracker_bin_test.rs b/bin_tests/src/bin/crashtracker_bin_test.rs index 147104d82..9d915d48a 100644 --- a/bin_tests/src/bin/crashtracker_bin_test.rs +++ b/bin_tests/src/bin/crashtracker_bin_test.rs @@ -27,25 +27,32 @@ mod unix { pub fn main() -> anyhow::Result<()> { let mut args = env::args().skip(1); - let output_filename = args.next().context("Unexpected number of arguments")?; + let output_url = args.next().context("Unexpected number of arguments")?; let receiver_binary = args.next().context("Unexpected number of arguments")?; let stderr_filename = args.next().context("Unexpected number of arguments")?; let stdout_filename = args.next().context("Unexpected number of arguments")?; let timeout = Duration::from_secs(30); + + let endpoint = if output_url.is_empty() { + None + } else { + Some(ddcommon::Endpoint { + url: ddcommon::parse_uri(&output_url)?, + api_key: None, + }) + }; + crashtracker::init( CrashtrackerConfiguration { additional_files: vec![], create_alt_stack: true, - endpoint: Some(ddcommon::Endpoint { - url: ddcommon::parse_uri(&format!("file://{}", output_filename))?, - api_key: None, - }), resolve_frames: crashtracker::StacktraceCollection::WithoutSymbols, + endpoint, timeout, }, Some(CrashtrackerReceiverConfig::new( vec![], - vec![], + env::vars().collect(), receiver_binary, Some(stderr_filename), Some(stdout_filename), diff --git a/bin_tests/tests/crashtracker_bin_test.rs b/bin_tests/tests/crashtracker_bin_test.rs index f4af2984d..d5d8ad7bb 100644 --- a/bin_tests/tests/crashtracker_bin_test.rs +++ b/bin_tests/tests/crashtracker_bin_test.rs @@ -3,6 +3,8 @@ #![cfg(unix)] +use std::collections::HashMap; +use std::io::{Read, Write}; use std::path::Path; use std::process; use std::{fs, path::PathBuf}; @@ -23,32 +25,15 @@ fn test_crash_tracking_bin_release() { } fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) { - let crashtracker_bin = ArtifactsBuild { - name: "crashtracker_bin_test".to_owned(), - build_profile: crash_tracking_receiver_profile, - artifact_type: ArtifactType::Bin, - triple_target: None, - }; - let crashtracker_receiver = ArtifactsBuild { - name: "crashtracker_receiver".to_owned(), - build_profile: crash_tracking_receiver_profile, - artifact_type: ArtifactType::Bin, - triple_target: None, - }; - let artifacts = build_artifacts(&[&crashtracker_receiver, &crashtracker_bin]).unwrap(); - - let tmpdir = tempfile::TempDir::new().unwrap(); - - let crash_profile_path = extend_path(tmpdir.path(), "crash"); - let crash_telemetry_path = extend_path(tmpdir.path(), "crash.telemetry"); - let stdout_path = extend_path(tmpdir.path(), "out.stdout"); - let stderr_path = extend_path(tmpdir.path(), "out.stderr"); + let (crashtracker_bin, crashtracker_receiver) = + setup_crashtracking_crates(crash_tracking_receiver_profile); + let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashtracker_bin]); - let mut p = process::Command::new(&artifacts[&crashtracker_bin]) - .arg(&crash_profile_path) - .arg(artifacts[&crashtracker_receiver].as_os_str()) - .arg(&stderr_path) - .arg(&stdout_path) + let mut p = process::Command::new(&fixtures.artifacts[&crashtracker_bin]) + .arg(format!("file://{}", fixtures.crash_profile_path.display())) + .arg(fixtures.artifacts[&crashtracker_receiver].as_os_str()) + .arg(&fixtures.stderr_path) + .arg(&fixtures.stdout_path) .spawn() .unwrap(); let exit_status = bin_tests::timeit!("exit after signal", { @@ -61,10 +46,10 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) { // running before the receiver has a chance to send the report. std::thread::sleep(std::time::Duration::from_millis(100)); - let stderr = fs::read(stderr_path) + let stderr = fs::read(fixtures.stderr_path) .context("reading crashtracker stderr") .unwrap(); - let stdout = fs::read(stdout_path) + let stdout = fs::read(fixtures.stdout_path) .context("reading crashtracker stdout") .unwrap(); assert!(matches!( @@ -74,7 +59,7 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) { assert_eq!(Ok(""), String::from_utf8(stdout).as_deref()); // Check the crash data - let crash_profile = fs::read(crash_profile_path) + let crash_profile = fs::read(fixtures.crash_profile_path) .context("reading crashtracker profiling payload") .unwrap(); let crash_payload = serde_json::from_slice::(&crash_profile) @@ -97,12 +82,17 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) { crash_payload["siginfo"] ); - let crash_telemetry = fs::read(crash_telemetry_path) + let crash_telemetry = fs::read(fixtures.crash_telemetry_path) .context("reading crashtracker telemetry payload") .unwrap(); - let telemetry_payload = serde_json::from_slice::(&crash_telemetry) - .context("deserializing crashtracker telemetry payload to json") - .unwrap(); + assert_telemetry_message(&crash_telemetry); +} + +fn assert_telemetry_message(crash_telemetry: &[u8]) { + let telemetry_payload: serde_json::Value = + serde_json::from_slice::(crash_telemetry) + .context("deserializing crashtracker telemetry payload to json") + .unwrap(); assert_eq!(telemetry_payload["request_type"], "logs"); assert_eq!( serde_json::json!({ @@ -136,6 +126,87 @@ fn test_crash_tracking_bin(crash_tracking_receiver_profile: BuildProfile) { assert_eq!(telemetry_payload["payload"][0]["is_sensitive"], true); } +#[test] +#[cfg_attr(miri, ignore)] +#[cfg(unix)] +fn crash_tracking_empty_endpoint() { + use std::os::unix::net::UnixListener; + + let (crashtracker_bin, crashtracker_receiver) = setup_crashtracking_crates(BuildProfile::Debug); + let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashtracker_bin]); + + let socket_path = extend_path(fixtures.tmpdir.path(), "trace_agent.socket"); + let listener = UnixListener::bind(&socket_path).unwrap(); + + process::Command::new(&fixtures.artifacts[&crashtracker_bin]) + // empty url, endpoint will be set to none + .arg("") + .arg(fixtures.artifacts[&crashtracker_receiver].as_os_str()) + .arg(&fixtures.stderr_path) + .arg(&fixtures.stdout_path) + .env( + "DD_TRACE_AGENT_URL", + format!("unix://{}", socket_path.display()), + ) + .spawn() + .unwrap(); + + let (mut stream, _) = listener.accept().unwrap(); + let mut out = vec![0; 65536]; + let read = stream.read(&mut out).unwrap(); + + stream + .write_all(b"HTTP/1.1 404\r\nContent-Length: 0\r\n\r\n") + .unwrap(); + let resp = String::from_utf8_lossy(&out[..read]); + let pos = resp.find("\r\n\r\n").unwrap(); + let body = &resp[pos + 4..]; + assert_telemetry_message(body.as_bytes()); +} + +struct TestFixtures<'a> { + tmpdir: tempfile::TempDir, + crash_profile_path: PathBuf, + crash_telemetry_path: PathBuf, + stdout_path: PathBuf, + stderr_path: PathBuf, + + artifacts: HashMap<&'a ArtifactsBuild, PathBuf>, +} + +fn setup_test_fixtures<'a>(crates: &[&'a ArtifactsBuild]) -> TestFixtures<'a> { + let artifacts = build_artifacts(crates).unwrap(); + + let tmpdir = tempfile::TempDir::new().unwrap(); + TestFixtures { + crash_profile_path: extend_path(tmpdir.path(), "crash"), + crash_telemetry_path: extend_path(tmpdir.path(), "crash.telemetry"), + stdout_path: extend_path(tmpdir.path(), "out.stdout"), + stderr_path: extend_path(tmpdir.path(), "out.stderr"), + + artifacts, + tmpdir, + } +} + +fn setup_crashtracking_crates( + crash_tracking_receiver_profile: BuildProfile, +) -> (ArtifactsBuild, ArtifactsBuild) { + let crashtracker_bin = ArtifactsBuild { + name: "crashtracker_bin_test".to_owned(), + build_profile: crash_tracking_receiver_profile, + artifact_type: ArtifactType::Bin, + triple_target: None, + }; + let crashtracker_receiver = ArtifactsBuild { + name: "crashtracker_receiver".to_owned(), + build_profile: crash_tracking_receiver_profile, + artifact_type: ArtifactType::Bin, + triple_target: None, + }; + (crashtracker_bin, crashtracker_receiver) +} + fn extend_path>(parent: &Path, path: T) -> PathBuf { let mut parent = parent.to_path_buf(); parent.push(path); diff --git a/crashtracker/src/telemetry.rs b/crashtracker/src/telemetry.rs index b8cac4a57..e0449fc30 100644 --- a/crashtracker/src/telemetry.rs +++ b/crashtracker/src/telemetry.rs @@ -70,7 +70,7 @@ impl TelemetryCrashUploader { // ignore result because what are we going to do? let _ = if endpoint.url.scheme_str() == Some("file") { - cfg.set_url(&format!("file://{}.telemetry", endpoint.url.path())) + cfg.set_host_from_url(&format!("file://{}.telemetry", endpoint.url.path())) } else { cfg.set_endpoint(endpoint.clone()) }; @@ -261,7 +261,7 @@ mod tests { let mut t = new_test_uploader(); t.cfg - .set_url(&format!("file://{}", output_filename.to_str().unwrap())) + .set_host_from_url(&format!("file://{}", output_filename.to_str().unwrap())) .unwrap(); let mut counters = HashMap::new(); diff --git a/ddcommon/src/connector/mod.rs b/ddcommon/src/connector/mod.rs index b6aca5836..96996ebc7 100644 --- a/ddcommon/src/connector/mod.rs +++ b/ddcommon/src/connector/mod.rs @@ -14,7 +14,6 @@ use std::task::{Context, Poll}; #[cfg(unix)] pub mod uds; -#[cfg(windows)] pub mod named_pipe; pub mod errors; diff --git a/ddcommon/src/lib.rs b/ddcommon/src/lib.rs index 2ad73727d..467051e07 100644 --- a/ddcommon/src/lib.rs +++ b/ddcommon/src/lib.rs @@ -79,48 +79,45 @@ where builder.build().map_err(Error::custom) } -// TODO: we should properly handle malformed urls -// * For windows and unix schemes: -// * For compatibility reasons with existing implementation this parser stores the encoded path -// in authority section as there is no existing standard -// [see](https://github.com/whatwg/url/issues/577) that covers this. We need to pick one hack -// or another -// * For file scheme implementation will simply backfill missing authority section +/// TODO: we should properly handle malformed urls +/// * For windows and unix schemes: +/// * For compatibility reasons with existing implementation this parser stores the encoded path +/// in authority section as there is no existing standard [see](https://github.com/whatwg/url/issues/577) +/// that covers this. We need to pick one hack or another +/// * For windows, interprets everything after windows: as path +/// * For unix, interprets everything after unix:// as path +/// * For file scheme implementation will simply backfill missing authority section pub fn parse_uri(uri: &str) -> anyhow::Result { - let scheme_pos = if let Some(scheme_pos) = uri.find("://") { - scheme_pos + if let Some(path) = uri.strip_prefix("unix://") { + encode_uri_path_in_authority("unix", path) + } else if let Some(path) = uri.strip_prefix("windows:") { + encode_uri_path_in_authority("windows", path) + } else if let Some(path) = uri.strip_prefix("file://") { + let mut parts = uri::Parts::default(); + parts.scheme = uri::Scheme::from_str("file").ok(); + parts.authority = Some(uri::Authority::from_static("localhost")); + + // TODO: handle edge cases like improperly escaped url strings + // + // this is eventually user configurable field + // anything we can do to ensure invalid input becomes valid - will improve usability + parts.path_and_query = uri::PathAndQuery::from_str(path).ok(); + + Ok(hyper::Uri::from_parts(parts)?) } else { - return Ok(hyper::Uri::from_str(uri)?); - }; + Ok(hyper::Uri::from_str(uri)?) + } +} - let scheme = &uri[0..scheme_pos]; - let rest = &uri[scheme_pos + 3..]; - match scheme { - "windows" | "unix" => { - let mut parts = uri::Parts::default(); - parts.scheme = uri::Scheme::from_str(scheme).ok(); +fn encode_uri_path_in_authority(scheme: &str, path: &str) -> anyhow::Result { + let mut parts = uri::Parts::default(); + parts.scheme = uri::Scheme::from_str(scheme).ok(); - let path = hex::encode(rest); + let path = hex::encode(path); - parts.authority = uri::Authority::from_str(path.as_str()).ok(); - parts.path_and_query = Some(uri::PathAndQuery::from_static("")); - Ok(hyper::Uri::from_parts(parts)?) - } - "file" => { - let mut parts = uri::Parts::default(); - parts.scheme = uri::Scheme::from_str(scheme).ok(); - parts.authority = Some(uri::Authority::from_static("localhost")); - - // TODO: handle edge cases like improperly escaped url strings - // - // this is eventually user configurable field - // anything we can do to ensure invalid input becomes valid - will improve usability - parts.path_and_query = uri::PathAndQuery::from_str(rest).ok(); - - Ok(hyper::Uri::from_parts(parts)?) - } - _ => Ok(hyper::Uri::from_str(uri)?), - } + parts.authority = uri::Authority::from_str(path.as_str()).ok(); + parts.path_and_query = Some(uri::PathAndQuery::from_static("")); + Ok(hyper::Uri::from_parts(parts)?) } impl Endpoint { diff --git a/ddtelemetry/src/config.rs b/ddtelemetry/src/config.rs index 339eb597e..9748aa0fb 100644 --- a/ddtelemetry/src/config.rs +++ b/ddtelemetry/src/config.rs @@ -13,6 +13,9 @@ pub const PROD_INTAKE_SUBDOMAIN: &str = "instrumentation-telemetry-intake"; const DIRECT_TELEMETRY_URL_PATH: &str = "/api/v2/apmtelemetry"; const AGENT_TELEMETRY_URL_PATH: &str = "/telemetry/proxy/api/v2/apmtelemetry"; +#[cfg(unix)] +const TRACE_SOCKET_PATH: &str = "/var/run/datadog/apm.socket"; + const DEFAULT_AGENT_HOST: &str = "localhost"; const DEFAULT_AGENT_PORT: u16 = 8126; @@ -50,10 +53,13 @@ fn endpoint_with_telemetry_path( /// Settings gathers configuration options we receive from the environment /// (either through env variable, or that could be set from the ) +#[derive(Debug)] pub struct Settings { - pub agent_host: String, - pub trace_agent_port: u16, - pub trace_agent_url: Option, + // Env parameter + pub agent_host: Option, + pub trace_agent_port: Option, + pub trace_agent_url: Option, + pub trace_pipe_name: Option, pub direct_submission_enabled: bool, pub api_key: Option, pub site: Option, @@ -61,14 +67,18 @@ pub struct Settings { pub telemetry_heartbeat_interval: Duration, pub telemetry_extended_heartbeat_interval: Duration, pub shared_lib_debug: bool, + + // Filesystem check + pub agent_uds_socket_found: bool, } impl Default for Settings { fn default() -> Self { Self { - agent_host: DEFAULT_AGENT_HOST.to_owned(), - trace_agent_port: DEFAULT_AGENT_PORT.to_owned(), + agent_host: None, + trace_agent_port: None, trace_agent_url: None, + trace_pipe_name: None, direct_submission_enabled: false, api_key: None, site: None, @@ -76,15 +86,19 @@ impl Default for Settings { telemetry_heartbeat_interval: Duration::from_secs(60), telemetry_extended_heartbeat_interval: Duration::from_secs(60 * 60 * 24), shared_lib_debug: false, + + agent_uds_socket_found: false, } } } impl Settings { // Agent connection configuration + const DD_TRACE_AGENT_URL: &'static str = "DD_TRACE_AGENT_URL"; const DD_AGENT_HOST: &'static str = "DD_AGENT_HOST"; const DD_TRACE_AGENT_PORT: &'static str = "DD_TRACE_AGENT_PORT"; - const DD_TRACE_AGENT_URL: &'static str = "DD_TRACE_AGENT_URL"; + // Location of the named pipe on windows. Dotnet specific + const DD_TRACE_PIPE_NAME: &'static str = "DD_TRACE_PIPE_NAME"; // Direct submission configuration const _DD_DIRECT_SUBMISSION_ENABLED: &'static str = "_DD_DIRECT_SUBMISSION_ENABLED"; @@ -101,10 +115,12 @@ impl Settings { pub fn from_env() -> Self { let default = Self::default(); Self { - agent_host: parse_env::str_not_empty(Self::DD_AGENT_HOST).unwrap_or(default.agent_host), - trace_agent_port: parse_env::int(Self::DD_TRACE_AGENT_PORT) - .unwrap_or(default.trace_agent_port), - trace_agent_url: parse_env::uri(Self::DD_TRACE_AGENT_URL).or(default.trace_agent_url), + agent_host: parse_env::str_not_empty(Self::DD_AGENT_HOST), + trace_agent_port: parse_env::int(Self::DD_TRACE_AGENT_PORT), + trace_agent_url: parse_env::str_not_empty(Self::DD_TRACE_AGENT_URL) + .or(default.trace_agent_url), + trace_pipe_name: parse_env::str_not_empty(Self::DD_TRACE_PIPE_NAME) + .or(default.trace_pipe_name), direct_submission_enabled: parse_env::bool(Self::_DD_DIRECT_SUBMISSION_ENABLED) .unwrap_or(default.direct_submission_enabled), api_key: parse_env::str_not_empty(Self::DD_API_KEY), @@ -119,6 +135,13 @@ impl Settings { ) .unwrap_or(Duration::from_secs(60 * 60 * 24)), shared_lib_debug: parse_env::bool(Self::_DD_SHARED_LIB_DEBUG).unwrap_or(false), + + agent_uds_socket_found: (|| { + #[cfg(unix)] + return std::fs::metadata(TRACE_SOCKET_PATH).is_ok(); + #[cfg(not(unix))] + return false; + })(), } } } @@ -136,26 +159,46 @@ impl Default for Config { } impl Config { - fn url_from_settings(settings: &Settings) -> String { + // Implemented following + // https://github.com/DataDog/architecture/blob/master/rfcs/apm/integrations/trace-autodetect-agent-config/rfc.md + fn trace_agent_url_from_setting(settings: &Settings) -> String { None.or_else(|| { - if !settings.direct_submission_enabled || settings.api_key.is_none() { - return None; - } - settings.telemetry_dd_url.clone().or_else(|| { - Some(format!( - "https://{}.{}{}", - PROD_INTAKE_SUBDOMAIN, - settings.site.as_ref()?, - DIRECT_TELEMETRY_URL_PATH - )) - }) + settings + .trace_agent_url + .as_deref() + .filter(|u| { + u.starts_with("unix://") + || u.starts_with("http://") + || u.starts_with("https://") + }) + .map(ToString::to_string) }) - .unwrap_or_else(|| { - format!( - "http://{}:{}{}", - settings.agent_host, settings.trace_agent_port, AGENT_TELEMETRY_URL_PATH - ) + .or_else(|| { + #[cfg(windows)] + return settings + .trace_pipe_name + .as_ref() + .map(|pipe_name| format!("windows:{pipe_name}")); + #[cfg(not(windows))] + return None; + }) + .or_else(|| match (&settings.agent_host, settings.trace_agent_port) { + (None, None) => None, + _ => Some(format!( + "http://{}:{}", + settings.agent_host.as_deref().unwrap_or(DEFAULT_AGENT_HOST), + settings.trace_agent_port.unwrap_or(DEFAULT_AGENT_PORT), + )), }) + .or_else(|| { + #[cfg(unix)] + return settings + .agent_uds_socket_found + .then(|| format!("unix://{TRACE_SOCKET_PATH}")); + #[cfg(not(unix))] + return None; + }) + .unwrap_or_else(|| format!("http://{DEFAULT_AGENT_HOST}:{DEFAULT_AGENT_PORT}")) } fn api_key_from_settings(settings: &Settings) -> Option> { @@ -174,7 +217,7 @@ impl Config { } pub fn from_settings(settings: &Settings) -> Self { - let url = Self::url_from_settings(settings); + let trace_agent_url = Self::trace_agent_url_from_setting(settings); let api_key = Self::api_key_from_settings(settings); let mut this = Self { @@ -184,7 +227,7 @@ impl Config { direct_submission_enabled: settings.direct_submission_enabled, restartable: false, }; - if let Ok(url) = parse_uri(&url) { + if let Ok(url) = parse_uri(&trace_agent_url) { let _res = this.set_endpoint(Endpoint { url, api_key }); } @@ -203,91 +246,208 @@ impl Config { &CFG } - pub fn set_url(&mut self, url: &str) -> anyhow::Result<()> { + /// set_host sets the host telemetry should connect to. + /// + /// It handles the following schemes + /// * http/https + /// * unix sockets unix://\ + /// * windows pipes of the format windows:\ + /// * files, with the format file://\ + /// + /// If the host_url is http/https, any path will be ignored and replaced by the + /// appropriate telemetry endpoint path + pub fn set_host_from_url(&mut self, host_url: &str) -> anyhow::Result<()> { let api_key = self.endpoint.take().and_then(|e| e.api_key); self.set_endpoint(Endpoint { - url: parse_uri(url)?, + url: parse_uri(host_url)?, api_key, }) } } -#[cfg(all(test, target_family = "unix"))] +#[cfg(test)] mod tests { + #[cfg(unix)] use ddcommon::connector::uds; - use super::Config; + use ddcommon::connector::named_pipe; + + use super::{Config, Settings}; #[test] - fn test_config_url_update() { - let mut cfg = Config::default(); + fn test_agent_host_detection_trace_agent_url_should_take_precedence() { + let cases = [ + ( + "http://localhost:1234", + "http://localhost:1234/telemetry/proxy/api/v2/apmtelemetry", + ), + ( + "unix://./here", + "unix://2e2f68657265/telemetry/proxy/api/v2/apmtelemetry", + ), + ]; + for (trace_agent_url, expected) in cases { + let settings = Settings { + trace_agent_url: Some(trace_agent_url.to_owned()), + agent_host: Some("example.org".to_owned()), + trace_agent_port: Some(1), + trace_pipe_name: Some("C:\\foo".to_owned()), + agent_uds_socket_found: true, + ..Default::default() + }; + let cfg = Config::from_settings(&settings); + assert_eq!(cfg.endpoint.unwrap().url.to_string(), expected); + } + } - cfg.set_url("http://example.com/any_path_will_be_ignored") - .unwrap(); + #[test] + fn test_agent_host_detection_agent_host_and_port() { + let cases = [ + ( + Some("example.org"), + Some(1), + "http://example.org:1/telemetry/proxy/api/v2/apmtelemetry", + ), + ( + Some("example.org"), + None, + "http://example.org:8126/telemetry/proxy/api/v2/apmtelemetry", + ), + ( + None, + Some(1), + "http://localhost:1/telemetry/proxy/api/v2/apmtelemetry", + ), + ]; + for (agent_host, trace_agent_port, expected) in cases { + let settings = Settings { + trace_agent_url: None, + agent_host: agent_host.map(ToString::to_string), + trace_agent_port, + trace_pipe_name: None, + agent_uds_socket_found: true, + ..Default::default() + }; + let cfg = Config::from_settings(&settings); + assert_eq!(cfg.endpoint.unwrap().url.to_string(), expected); + } + } + #[test] + #[cfg(unix)] + fn test_agent_host_detection_socket_found() { + let settings = Settings { + trace_agent_url: None, + agent_host: None, + trace_agent_port: None, + trace_pipe_name: None, + agent_uds_socket_found: true, + ..Default::default() + }; + let cfg = Config::from_settings(&settings); assert_eq!( - "http://example.com/telemetry/proxy/api/v2/apmtelemetry", - cfg.clone().endpoint.unwrap().url + cfg.endpoint.unwrap().url.to_string(), + "unix://2f7661722f72756e2f64617461646f672f61706d2e736f636b6574/telemetry/proxy/api/v2/apmtelemetry" ); + } - cfg.set_url("file:///absolute/path").unwrap(); + #[test] + fn test_agent_host_detection_fallback() { + let settings = Settings { + trace_agent_url: None, + agent_host: None, + trace_agent_port: None, + trace_pipe_name: None, + agent_uds_socket_found: false, + ..Default::default() + }; + let cfg = Config::from_settings(&settings); assert_eq!( - "file", - cfg.clone() - .endpoint - .unwrap() - .url - .scheme() - .unwrap() - .to_string() + cfg.endpoint.unwrap().url.to_string(), + "http://localhost:8126/telemetry/proxy/api/v2/apmtelemetry" ); + } + + #[test] + fn test_config_set_url() { + let mut cfg = Config::default(); + + cfg.set_host_from_url("http://example.com/any_path_will_be_ignored") + .unwrap(); + assert_eq!( - "/absolute/path", - cfg.clone() - .endpoint - .unwrap() - .url - .into_parts() - .path_and_query - .unwrap() - .as_str() + "http://example.com/telemetry/proxy/api/v2/apmtelemetry", + cfg.clone().endpoint.unwrap().url ); + } + + #[test] + fn test_config_set_url_file() { + let cases = [ + ("file:///absolute/path", "/absolute/path"), + ("file://./relative/path", "./relative/path"), + ("file://relative/path", "relative/path"), + ]; + + for (input, expected) in cases { + let mut cfg = Config::default(); + cfg.set_host_from_url(input).unwrap(); + + assert_eq!( + "file", + cfg.clone() + .endpoint + .unwrap() + .url + .scheme() + .unwrap() + .to_string() + ); + assert_eq!( + expected, + cfg.clone() + .endpoint + .unwrap() + .url + .into_parts() + .path_and_query + .unwrap() + .as_str() + ); + } + } + + #[test] + #[cfg(unix)] + fn test_config_set_url_unix_socket() { + let mut cfg = Config::default(); - cfg.set_url("file://./relative/path").unwrap(); + cfg.set_host_from_url("unix:///compatiliby/path").unwrap(); assert_eq!( - "./relative/path", - cfg.clone() - .endpoint - .unwrap() - .url - .into_parts() - .path_and_query - .unwrap() - .as_str() + "unix://2f636f6d706174696c6962792f70617468/telemetry/proxy/api/v2/apmtelemetry", + cfg.clone().endpoint.unwrap().url.to_string() ); - - cfg.set_url("file://relative/path").unwrap(); assert_eq!( - "relative/path", - cfg.clone() - .endpoint - .unwrap() - .url - .into_parts() - .path_and_query + "/compatiliby/path", + uds::socket_path_from_uri(&cfg.clone().endpoint.unwrap().url) .unwrap() - .as_str() + .to_string_lossy() ); + } + + #[test] + fn test_config_set_url_windows_pipe() { + let mut cfg = Config::default(); - cfg.set_url("unix:///compatiliby/path").unwrap(); + cfg.set_host_from_url("windows:C:\\system32\\foo").unwrap(); assert_eq!( - "unix://2f636f6d706174696c6962792f70617468/telemetry/proxy/api/v2/apmtelemetry", + "windows://433a5c73797374656d33325c666f6f/telemetry/proxy/api/v2/apmtelemetry", cfg.clone().endpoint.unwrap().url.to_string() ); assert_eq!( - "/compatiliby/path", - uds::socket_path_from_uri(&cfg.clone().endpoint.unwrap().url) + "C:\\system32\\foo", + named_pipe::named_pipe_path_from_uri(&cfg.clone().endpoint.unwrap().url) .unwrap() .to_string_lossy() ); diff --git a/profiling-ffi/src/crashtracker/datatypes.rs b/profiling-ffi/src/crashtracker/datatypes.rs index 2980c4541..6e9c930f5 100644 --- a/profiling-ffi/src/crashtracker/datatypes.rs +++ b/profiling-ffi/src/crashtracker/datatypes.rs @@ -31,6 +31,9 @@ pub struct CrashtrackerConfiguration<'a> { pub additional_files: Slice<'a, CharSlice<'a>>, pub create_alt_stack: bool, /// The endpoint to send the crash report to (can be a file://) + /// + /// If ProfilingEndpoint is left to a zero value (enum value for Agent + empty charslice), + /// the crashtracker will infer the agent host from env variables. pub endpoint: ProfilingEndpoint<'a>, pub resolve_frames: StacktraceCollection, pub timeout_secs: u64, @@ -89,7 +92,7 @@ impl<'a> TryFrom> vec }; let create_alt_stack = value.create_alt_stack; - let endpoint = unsafe { Some(exporter::try_to_endpoint(value.endpoint)?) }; + let endpoint = unsafe { exporter::try_to_endpoint(value.endpoint).ok() }; let resolve_frames = value.resolve_frames; let timeout = Duration::from_secs(value.timeout_secs); Self::new(