Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Pass scope via IPC #13

Merged
merged 3 commits into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading