diff --git a/Cargo.lock b/Cargo.lock index c7babb356..8a6b1b2b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,7 +658,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", + "wasm-bindgen", "windows-link", ] @@ -3605,12 +3607,14 @@ name = "sentry-core" version = "0.47.0" dependencies = [ "anyhow", + "chrono", "criterion", "futures", "log", "rand 0.9.3", "rayon", "sentry", + "sentry-time", "sentry-types", "serde", "serde_json", @@ -3637,6 +3641,7 @@ dependencies = [ "pretty_env_logger", "sentry", "sentry-core", + "sentry-time", ] [[package]] @@ -3670,6 +3675,13 @@ dependencies = [ "slog", ] +[[package]] +name = "sentry-time" +version = "0.47.0" +dependencies = [ + "chrono", +] + [[package]] name = "sentry-tower" version = "0.47.0" @@ -3710,10 +3722,12 @@ dependencies = [ name = "sentry-types" version = "0.47.0" dependencies = [ + "chrono", "debugid", "hex", "rand 0.9.3", "rstest", + "sentry-time", "serde", "serde_json", "thiserror 2.0.18", diff --git a/Cargo.toml b/Cargo.toml index bb40a26e1..485a59d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ "sentry-log", "sentry-opentelemetry", "sentry-panic", - "sentry-slog", + "sentry-slog", "sentry-time", "sentry-tower", "sentry-tracing", "sentry-types", diff --git a/sentry-contexts/Cargo.toml b/sentry-contexts/Cargo.toml index 40f0f7de6..a417d1284 100644 --- a/sentry-contexts/Cargo.toml +++ b/sentry-contexts/Cargo.toml @@ -16,9 +16,11 @@ rust-version = { workspace = true } [dependencies] sentry-core = { version = "0.47.0", path = "../sentry-core" } libc = "0.2.66" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] hostname = "0.4" -[target."cfg(not(windows))".dependencies] +[target.'cfg(all(not(windows), not(target_arch = "wasm32")))'.dependencies] uname = "0.1.1" [target."cfg(windows)".dependencies] diff --git a/sentry-contexts/src/utils.rs b/sentry-contexts/src/utils.rs index 8acd9c83b..3b45ba6a1 100644 --- a/sentry-contexts/src/utils.rs +++ b/sentry-contexts/src/utils.rs @@ -110,12 +110,19 @@ mod model_support { } /// Returns the server name (hostname) if available. +#[cfg(not(target_arch = "wasm32"))] pub fn server_name() -> Option { hostname::get().ok().and_then(|s| s.into_string().ok()) } +#[cfg(target_arch = "wasm32")] +pub fn server_name() -> Option { + // TODO: What other options are available? + None +} + /// Returns the OS context -#[cfg(not(windows))] +#[cfg(all(not(windows), not(target_arch = "wasm32")))] pub fn os_context() -> Option { use uname::uname; if let Ok(info) = uname() { @@ -168,6 +175,17 @@ pub fn os_context() -> Option { ) } +#[cfg(target_arch = "wasm32")] +pub fn os_context() -> Option { + Some( + OsContext { + name: Some("WASM".into()), + ..Default::default() + } + .into(), + ) +} + /// Returns the rust info. pub fn rust_context() -> Context { RuntimeContext { diff --git a/sentry-core/Cargo.toml b/sentry-core/Cargo.toml index c37d9d949..6ad015259 100644 --- a/sentry-core/Cargo.toml +++ b/sentry-core/Cargo.toml @@ -29,11 +29,13 @@ logs = [] [dependencies] log = { version = "0.4.8", optional = true, features = ["std"] } rand = { version = "0.9.3", optional = true } +sentry-time = { path = "../sentry-time" } sentry-types = { version = "0.47.0", path = "../sentry-types" } serde = { version = "1.0.104", features = ["derive"] } serde_json = { version = "1.0.46" } url = { version = "2.1.1" } uuid = { version = "1.0.0", features = ["v4", "serde"], optional = true } +chrono = { version = "0.4.44" } [dev-dependencies] # Because we re-export all the public API in `sentry`, we actually run all the diff --git a/sentry-core/src/batcher.rs b/sentry-core/src/batcher.rs index dfc28bf5e..bca9cc541 100644 --- a/sentry-core/src/batcher.rs +++ b/sentry-core/src/batcher.rs @@ -2,17 +2,17 @@ use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::thread::JoinHandle; -use std::time::{Duration, Instant}; use crate::client::TransportArc; use crate::protocol::EnvelopeItem; use crate::Envelope; +use chrono::{Duration, Utc}; use sentry_types::protocol::v7::Log; // Flush when there's 100 items in the buffer const MAX_ITEMS: usize = 100; // Or when 5 seconds have passed from the last flush -const FLUSH_INTERVAL: Duration = Duration::from_secs(5); +const FLUSH_INTERVAL: Duration = Duration::seconds(5); #[derive(Debug)] struct BatchQueue { @@ -71,21 +71,24 @@ where if *shutdown { return; } - let mut last_flush = Instant::now(); + let mut last_flush = Utc::now(); loop { + let elapsed = Utc::now() - last_flush; let timeout = FLUSH_INTERVAL - .checked_sub(last_flush.elapsed()) - .unwrap_or_else(|| Duration::from_secs(0)); + .checked_sub(&elapsed) + .unwrap_or_else(|| Duration::seconds(0)) + .to_std() + .unwrap_or_else(|_| std::time::Duration::from_secs(0)); shutdown = cvar.wait_timeout(shutdown, timeout).unwrap().0; if *shutdown { return; } - if last_flush.elapsed() >= FLUSH_INTERVAL { + if Utc::now() - last_flush >= FLUSH_INTERVAL { Batcher::flush_queue_internal( worker_queue.lock().unwrap(), &worker_transport, ); - last_flush = Instant::now(); + last_flush = Utc::now(); } } }) diff --git a/sentry-core/src/lib.rs b/sentry-core/src/lib.rs index 6a2a1b5dd..d1687df05 100644 --- a/sentry-core/src/lib.rs +++ b/sentry-core/src/lib.rs @@ -158,6 +158,9 @@ pub mod test; // public api from other crates #[doc(inline)] pub use sentry_types as types; + +#[doc(hidden)] +pub use sentry_time; pub use sentry_types::protocol::v7 as protocol; pub use sentry_types::protocol::v7::{Breadcrumb, Envelope, Level, User}; diff --git a/sentry-core/src/logger.rs b/sentry-core/src/logger.rs index 293bf3740..c8190489c 100644 --- a/sentry-core/src/logger.rs +++ b/sentry-core/src/logger.rs @@ -10,7 +10,7 @@ macro_rules! logger_log { level: $level, body: $msg.to_owned(), trace_id: None, - timestamp: ::std::time::SystemTime::now(), + timestamp: $crate::sentry_time::now_system_time(), severity_number: None, attributes: $crate::protocol::Map::new(), }; @@ -39,7 +39,7 @@ macro_rules! logger_log { level: $level, body: format!($fmt, $($arg),*), trace_id: None, - timestamp: ::std::time::SystemTime::now(), + timestamp: $crate::sentry_time::now_system_time(), severity_number: None, attributes, }; @@ -58,7 +58,7 @@ macro_rules! logger_log { level: $level, body: $msg.to_owned(), trace_id: None, - timestamp: ::std::time::SystemTime::now(), + timestamp: $crate::sentry_time::now_system_time(), severity_number: None, #[allow(clippy::redundant_field_names)] attributes: $attrs, @@ -86,7 +86,7 @@ macro_rules! logger_log { level: $level, body: format!($fmt, $($arg),*), trace_id: None, - timestamp: ::std::time::SystemTime::now(), + timestamp: $crate::sentry_time::now_system_time(), severity_number: None, #[allow(clippy::redundant_field_names)] attributes: $attrs, diff --git a/sentry-core/src/performance.rs b/sentry-core/src/performance.rs index 5416746ea..4737c735f 100644 --- a/sentry-core/src/performance.rs +++ b/sentry-core/src/performance.rs @@ -4,6 +4,7 @@ use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::SystemTime; +use sentry_time::now_system_time; use sentry_types::protocol::v7::SpanId; use crate::{protocol, Hub}; @@ -898,7 +899,7 @@ impl Transaction { /// This records the current timestamp as the end timestamp and sends the transaction together with /// all finished child spans to Sentry. pub fn finish(self) { - self.finish_with_timestamp(SystemTime::now()); + self.finish_with_timestamp(now_system_time()); } /// Starts a new child Span with the given `op` and `description`. @@ -1149,7 +1150,7 @@ impl Span { /// This will record the current timestamp as the end timestamp and add the span to the /// transaction in which it was started. pub fn finish(self) { - self.finish_with_timestamp(SystemTime::now()); + self.finish_with_timestamp(now_system_time()); } /// Starts a new child Span with the given `op` and `description`. diff --git a/sentry-core/src/session.rs b/sentry-core/src/session.rs index e9fd2f298..04a7f0cc5 100644 --- a/sentry-core/src/session.rs +++ b/sentry-core/src/session.rs @@ -11,7 +11,10 @@ mod session_impl { use std::collections::HashMap; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::thread::JoinHandle; - use std::time::{Duration, Instant, SystemTime}; + use std::time::{Duration, SystemTime}; + + use chrono::{DateTime, Utc}; + use sentry_time::now_system_time; use crate::client::TransportArc; use crate::clientoptions::SessionMode; @@ -29,7 +32,7 @@ mod session_impl { pub struct Session { client: Arc, session_update: SessionUpdate<'static>, - started: Instant, + started: DateTime, dirty: bool, } @@ -62,7 +65,7 @@ mod session_impl { distinct_id, sequence: None, timestamp: None, - started: SystemTime::now(), + started: now_system_time(), init: true, duration: None, status: SessionStatus::Ok, @@ -74,7 +77,7 @@ mod session_impl { user_agent: None, }, }, - started: Instant::now(), + started: Utc::now(), dirty: true, }) } @@ -112,7 +115,8 @@ mod session_impl { SessionStatus::Ok => SessionStatus::Exited, s => s, }; - self.session_update.duration = Some(self.started.elapsed().as_secs_f64()); + let elapsed = Utc::now() - self.started; + self.session_update.duration = Some(elapsed.as_seconds_f64()); self.session_update.status = status; self.dirty = true; } @@ -214,23 +218,27 @@ mod session_impl { if *shutdown { return; } - let mut last_flush = Instant::now(); + let mut last_flush = Utc::now(); + let flush_interval = chrono::Duration::from_std(FLUSH_INTERVAL).unwrap(); loop { - let timeout = FLUSH_INTERVAL - .checked_sub(last_flush.elapsed()) - .unwrap_or_else(|| Duration::from_secs(0)); + let elapsed = Utc::now() - last_flush; + let timeout = flush_interval + .checked_sub(&elapsed) + .unwrap_or_else(|| chrono::Duration::seconds(0)) + .to_std() + .unwrap_or_else(|_| Duration::from_secs(0)); shutdown = cvar.wait_timeout(shutdown, timeout).unwrap().0; if *shutdown { return; } - if last_flush.elapsed() < FLUSH_INTERVAL { + if Utc::now() - last_flush < flush_interval { continue; } SessionFlusher::flush_queue_internal( worker_queue.lock().unwrap(), &worker_transport, ); - last_flush = Instant::now(); + last_flush = Utc::now(); } }) .unwrap(); diff --git a/sentry-log/Cargo.toml b/sentry-log/Cargo.toml index 09f3c6b3b..7743141d5 100644 --- a/sentry-log/Cargo.toml +++ b/sentry-log/Cargo.toml @@ -18,6 +18,7 @@ logs = ["sentry-core/logs"] [dependencies] sentry-core = { version = "0.47.0", path = "../sentry-core" } +sentry-time = { path = "../sentry-time" } log = { version = "0.4.8", features = ["std", "kv"] } bitflags = "2.9.4" diff --git a/sentry-log/src/converters.rs b/sentry-log/src/converters.rs index 1a0b12533..7a7a7c2cf 100644 --- a/sentry-log/src/converters.rs +++ b/sentry-log/src/converters.rs @@ -2,9 +2,9 @@ use sentry_core::protocol::{Event, Value}; #[cfg(feature = "logs")] use sentry_core::protocol::{Log, LogAttribute, LogLevel}; use sentry_core::{Breadcrumb, Level}; -use std::collections::BTreeMap; #[cfg(feature = "logs")] -use std::time::SystemTime; +use sentry_time::now_system_time; +use std::collections::BTreeMap; /// Converts a [`log::Level`] to a Sentry [`Level`], used for [`Event`] and [`Breadcrumb`]. pub fn convert_log_level(level: log::Level) -> Level { @@ -160,7 +160,7 @@ pub fn log_from_record(record: &log::Record<'_>) -> Log { level: convert_log_level_to_sentry_log_level(record.level()), body: format!("{}", record.args()), trace_id: None, - timestamp: SystemTime::now(), + timestamp: now_system_time(), severity_number: None, attributes, } diff --git a/sentry-time/Cargo.toml b/sentry-time/Cargo.toml new file mode 100644 index 000000000..5be1f93c7 --- /dev/null +++ b/sentry-time/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sentry-time" +version = "0.47.0" +authors.workspace = true +license = "MIT" +readme = "README.md" +repository.workspace = true +homepage.workspace = true +edition.workspace = true +rust-version.workspace = true +description = """ +Time based functions supporting all targets including WASM. +""" + +[dependencies] + +[target.'cfg(target_arch = "wasm32")'.dependencies] +chrono = "0.4.44" diff --git a/sentry-time/src/lib.rs b/sentry-time/src/lib.rs new file mode 100644 index 000000000..ec047dbde --- /dev/null +++ b/sentry-time/src/lib.rs @@ -0,0 +1,19 @@ +use std::time::SystemTime; + +/// Returns the current wall-clock time as a [`std::time::SystemTime`], sourced from +/// [`chrono::Utc::now`] so it works on `wasm32-unknown-unknown` (where +/// [`std::time::SystemTime::now`] panics). +pub fn now_system_time() -> SystemTime { + #[cfg(not(target_arch = "wasm32"))] + { + SystemTime::now() + } + + #[cfg(target_arch = "wasm32")] + { + let now = chrono::Utc::now(); + let secs = now.timestamp() as u64; + let nanos = now.timestamp_subsec_nanos(); + SystemTime::UNIX_EPOCH + std::time::Duration::new(secs, nanos) + } +} diff --git a/sentry-types/Cargo.toml b/sentry-types/Cargo.toml index 274737f08..59438de23 100644 --- a/sentry-types/Cargo.toml +++ b/sentry-types/Cargo.toml @@ -27,9 +27,11 @@ rand = "0.9.3" serde = { version = "1.0.104", features = ["derive"] } serde_json = "1.0.46" thiserror = "2.0.12" -time = { version = "0.3.47", features = ["formatting", "parsing"] } +time = { version = "0.3.47", default-features = false, features = ["formatting", "parsing"] } url = { version = "2.1.1", features = ["serde"] } uuid = { version = "1.0.0", features = ["serde"] } +chrono = { version = "0.4.44" } +sentry-time = { path = "../sentry-time" } [dev-dependencies] rstest = "0.25.0" diff --git a/sentry-types/src/auth.rs b/sentry-types/src/auth.rs index 5edb715a8..b36daa67a 100644 --- a/sentry-types/src/auth.rs +++ b/sentry-types/src/auth.rs @@ -3,6 +3,7 @@ use std::fmt; use std::str::FromStr; use std::time::SystemTime; +use sentry_time::now_system_time; use serde::{Deserialize, Serialize}; use thiserror::Error; use url::form_urlencoded; @@ -175,7 +176,7 @@ impl FromStr for Auth { pub(crate) fn auth_from_dsn_and_client(dsn: &Dsn, client: Option<&str>) -> Auth { Auth { - timestamp: Some(SystemTime::now()), + timestamp: Some(now_system_time()), client: client.map(|x| x.to_string()), version: protocol::LATEST, key: dsn.public_key().to_string(), diff --git a/sentry-types/src/protocol/session.rs b/sentry-types/src/protocol/session.rs index 948efd301..5fe06c586 100644 --- a/sentry-types/src/protocol/session.rs +++ b/sentry-types/src/protocol/session.rs @@ -108,7 +108,7 @@ pub struct SessionUpdate<'a> { pub timestamp: Option, /// The timestamp of when the session itself started. - #[serde(default = "SystemTime::now", with = "ts_rfc3339")] + #[serde(default = "sentry_time::now_system_time", with = "ts_rfc3339")] pub started: SystemTime, /// A flag that indicates that this is the initial transmission of the session. diff --git a/sentry-types/src/protocol/v7.rs b/sentry-types/src/protocol/v7.rs index b2f8ee99a..8f9d563a4 100644 --- a/sentry-types/src/protocol/v7.rs +++ b/sentry-types/src/protocol/v7.rs @@ -16,6 +16,7 @@ use std::str; use std::time::SystemTime; use self::debugid::{CodeId, DebugId}; +use sentry_time::now_system_time; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; @@ -739,7 +740,7 @@ mod breadcrumb { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Breadcrumb { /// The timestamp of the breadcrumb. This is required. - #[serde(default = "SystemTime::now", with = "ts_seconds_float")] + #[serde(default = "sentry_time::now_system_time", with = "ts_seconds_float")] pub timestamp: SystemTime, /// The type of the breadcrumb. #[serde( @@ -769,7 +770,7 @@ pub struct Breadcrumb { impl Default for Breadcrumb { fn default() -> Breadcrumb { Breadcrumb { - timestamp: SystemTime::now(), + timestamp: now_system_time(), ty: breadcrumb::default_type(), category: Default::default(), level: breadcrumb::default_level(), @@ -1667,7 +1668,7 @@ pub struct Event<'a> { /// The timestamp of when the event was created. /// /// This can be set to `None` in which case the server will set a timestamp. - #[serde(default = "SystemTime::now", with = "ts_seconds_float")] + #[serde(default = "sentry_time::now_system_time", with = "ts_seconds_float")] pub timestamp: SystemTime, /// Optionally the server (or device) name of this event. #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1736,7 +1737,7 @@ impl Default for Event<'_> { logger: Default::default(), modules: Default::default(), platform: event::default_platform(), - timestamp: SystemTime::now(), + timestamp: now_system_time(), server_name: Default::default(), release: Default::default(), dist: Default::default(), @@ -1843,7 +1844,7 @@ pub struct Span { )] pub timestamp: Option, /// The timestamp at the measuring of the span started. - #[serde(default = "SystemTime::now", with = "ts_seconds_float")] + #[serde(default = "sentry_time::now_system_time", with = "ts_seconds_float")] pub start_timestamp: SystemTime, /// Describes the status of the span (e.g. `ok`, `cancelled`, etc.) #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1863,7 +1864,7 @@ impl Default for Span { trace_id: Default::default(), timestamp: Default::default(), tags: Default::default(), - start_timestamp: SystemTime::now(), + start_timestamp: now_system_time(), description: Default::default(), status: Default::default(), parent_span_id: Default::default(), @@ -1887,7 +1888,7 @@ impl Span { /// Finalizes the span. pub fn finish(&mut self) { - self.timestamp = Some(SystemTime::now()); + self.timestamp = Some(now_system_time()); } } @@ -2060,7 +2061,7 @@ pub struct Transaction<'a> { )] pub timestamp: Option, /// The start time of the transaction. - #[serde(default = "SystemTime::now", with = "ts_seconds_float")] + #[serde(default = "sentry_time::now_system_time", with = "ts_seconds_float")] pub start_timestamp: SystemTime, /// The collection of finished spans part of this transaction. pub spans: Vec, @@ -2092,7 +2093,7 @@ impl Default for Transaction<'_> { sdk: Default::default(), platform: event::default_platform(), timestamp: Default::default(), - start_timestamp: SystemTime::now(), + start_timestamp: now_system_time(), spans: Default::default(), contexts: Default::default(), request: Default::default(), @@ -2130,7 +2131,7 @@ impl<'a> Transaction<'a> { /// Finalizes the transaction to be dispatched. pub fn finish(&mut self) { - self.timestamp = Some(SystemTime::now()); + self.timestamp = Some(now_system_time()); } /// Finalizes the transaction to be dispatched with the given end timestamp.