Skip to content

Commit

Permalink
feat: Pass scope via IPC (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
timfish authored Feb 25, 2024
1 parent b9b08bf commit 06d78e5
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 16 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
11 changes: 8 additions & 3 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.4"
version = "0.7.0"

[dependencies]
minidumper-child = "0.2"
sentry = "0.31"
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"]
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ your application code.

```toml
[dependencies]
sentry = "0.31"
sentry-rust-minidump = "0.6"
sentry = "0.32"
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://[email protected]: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 {
username: Some("john_doe".into()),
email: Some("[email protected]".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("[email protected]".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 06d78e5

Please sign in to comment.