Skip to content

Commit

Permalink
feat: Add scope changes via ipc
Browse files Browse the repository at this point in the history
  • Loading branch information
timfish committed Feb 25, 2024
1 parent 8390f19 commit ff271e9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 14 deletions.
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ license = "MIT OR Apache-2.0"
name = "sentry-rust-minidump"
readme = "README.md"
repository = "https://github.com/timfish/sentry-rust-minidump"
version = "0.6.5"
version = "0.7.0"

[dependencies]
minidumper-child = "0.2"
sentry = "0.32"
thiserror = "1"
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true }

[dev-dependencies]
actix-rt = "2.7"
sadness-generator = "0.5"
sentry-test-server = {git = "https://github.com/timfish/sentry-test-server.git", rev = "134c30e"}
sentry-test-server = {git = "https://github.com/timfish/sentry-test-server.git", rev = "df19c85"}

[features]
ipc = [ "dep:serde", "dep:serde_json"]
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ your application code.
```toml
[dependencies]
sentry = "0.32"
sentry-rust-minidump = "0.6"
sentry-rust-minidump = "0.7"
```

```rust
Expand All @@ -39,3 +39,29 @@ fn main() {
}
}
```

## `ipc` feature

By default there is no scope synchronisation from the app process to the crash
reporter process. This means that native crash event will be missing
breadcrumbs, user, tags or extra added to the scope in the app.

When the `ipc` feature is enabled, you can send scope updates to the crash
reporter process:

```rust
fn main() {
let client = sentry::init("__YOUR_DSN__");

// Everything before here runs in both app and crash reporter processes
let crash_reporter = sentry_rust_minidump::init(&client).expect("crash reported didn't start");
// Everything after here runs in only the app process

crash_reporter.add_breadcrumb(...);
crash_reporter.set_user(...);
crash_reporter.set_extra(...);
crash_reporter.set_tag(...);

// Don't drop crash_reporter or the reporter process will close!
}
```
9 changes: 8 additions & 1 deletion examples/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ fn main() {
let client = sentry::init("http://abc123@127.0.0.1:8123/12345");

// Everything before here runs in both app and crash reporter processes
let _guard = sentry_rust_minidump::init(&client);
let crash_handler =
sentry_rust_minidump::init(&client).expect("could not initialize crash reporter");
// Everything after here runs in only the app process

crash_handler.set_user(Some(sentry::User {

Check failure on line 9 in examples/app.rs

View workflow job for this annotation

GitHub Actions / Test windows-latest

no method named `set_user` found for struct `Handle` in the current scope

Check failure on line 9 in examples/app.rs

View workflow job for this annotation

GitHub Actions / Test macos-latest

no method named `set_user` found for struct `Handle` in the current scope

Check failure on line 9 in examples/app.rs

View workflow job for this annotation

GitHub Actions / Test ubuntu-latest

no method named `set_user` found for struct `Handle` in the current scope
username: Some("john_doe".into()),
email: Some("john@doe.town".into()),
..Default::default()
}));

std::thread::sleep(std::time::Duration::from_secs(10));

unsafe { sadness_generator::raise_segfault() };
Expand Down
70 changes: 65 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,76 @@
pub use minidumper_child::MinidumperChild;
use minidumper_child::{ClientHandle, Error, MinidumperChild};
use sentry::{
protocol::{Attachment, AttachmentType, Event, Value},
Level,
};

pub use minidumper_child::{ClientHandle, Error};
#[cfg(feature = "ipc")]
use sentry::{Breadcrumb, User};

pub struct Handle {
_handle: ClientHandle,
}

#[cfg(feature = "ipc")]
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
pub enum ScopeUpdate {
AddBreadcrumb(Breadcrumb),
SetUser(Option<User>),
SetExtra(String, Option<Value>),
SetTag(String, Option<String>),
}

#[cfg(feature = "ipc")]
impl Handle {
fn send_message(&self, update: &ScopeUpdate) {
let buffer = serde_json::to_vec(update).expect("could not serialize scope update");
self._handle.send_message(0, buffer).ok();
}

pub fn add_breadcrumb(&self, breadcrumb: Breadcrumb) {
self.send_message(&ScopeUpdate::AddBreadcrumb(breadcrumb));
}

pub fn set_user(&self, user: Option<User>) {
self.send_message(&ScopeUpdate::SetUser(user));
}

pub fn set_extra(&self, key: String, value: Option<Value>) {
self.send_message(&ScopeUpdate::SetExtra(key, value));
}

pub fn set_tag(&self, key: String, value: Option<String>) {
self.send_message(&ScopeUpdate::SetTag(key, value));
}
}

#[must_use = "The return value from init() should not be dropped until the program exits"]
pub fn init(sentry_client: &sentry::Client) -> Result<ClientHandle, Error> {
pub fn init(sentry_client: &sentry::Client) -> Result<Handle, Error> {
let sentry_client = sentry_client.clone();

let child = MinidumperChild::new().on_minidump(move |buffer, path| {
let child = MinidumperChild::new();

#[cfg(feature = "ipc")]
let child = child.on_message(|_kind, buffer| {
if let Ok(update) = serde_json::from_slice::<ScopeUpdate>(&buffer[..]) {
match update {
ScopeUpdate::AddBreadcrumb(b) => sentry::add_breadcrumb(b),
ScopeUpdate::SetUser(u) => sentry::configure_scope(|scope| {
scope.set_user(u);
}),
ScopeUpdate::SetExtra(k, v) => sentry::configure_scope(|scope| match v {
Some(v) => scope.set_extra(&k, v),
None => scope.remove_extra(&k),
}),
ScopeUpdate::SetTag(k, v) => match v {
Some(v) => sentry::configure_scope(|scope| scope.set_tag(&k, &v)),
None => sentry::configure_scope(|scope| scope.remove_tag(&k)),
},
}
}
});

let child = child.on_minidump(move |buffer, path| {
sentry::with_scope(
|scope| {
// Remove event.process because this event came from the
Expand Down Expand Up @@ -49,5 +109,5 @@ pub fn init(sentry_client: &sentry::Client) -> Result<ClientHandle, Error> {
});
}

child.spawn()
child.spawn().map(|handle| Handle { _handle: handle })
}
25 changes: 20 additions & 5 deletions tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,40 @@ async fn test_example_app() -> Result<(), Box<dyn Error>> {
let envelope_rx = sentry_test_server::server(("127.0.0.1", 8123))?;

// We need to await at some point otherwise the server doesn't seem to start
actix_rt::time::sleep(Duration::from_secs(1)).await;
actix_rt::time::sleep(Duration::from_secs(2)).await;

Command::new("cargo")
.args(["run", "--example", "app"])
.args(["run", "--example", "app", "--all-features"])
.spawn()?
.wait()?;

let env = envelope_rx.recv_timeout(Duration::from_secs(2))?;
let env = envelope_rx.recv_timeout(Duration::from_secs(15))?;

if let Ok(json) = sentry_test_server::to_json_pretty(&env) {
println!("{}", json);
}

let item = env
let env_item = env
.items()
.find(|item| matches!(item, EnvelopeItem::Event(_)))
.expect("envelope should have an event");

let event = match env_item {
EnvelopeItem::Event(event) => event.clone(),
_ => unreachable!("envelope should have an event"),
};

let user = event.user.expect("event should have a user");

assert_eq!(user.email, Some("john@doe.town".into()));
assert_eq!(user.username, Some("john_doe".into()));

let env_item = env
.items()
.find(|item| matches!(item, EnvelopeItem::Attachment(_)))
.expect("envelope should have an attachment");

let attachment = match item {
let attachment = match env_item {
EnvelopeItem::Attachment(attachment) => attachment,
_ => unreachable!("envelope should have an attachment"),
};
Expand Down

0 comments on commit ff271e9

Please sign in to comment.