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(unreal): Add unreal crash reporting properties #715

Merged
merged 11 commits into from
Aug 27, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

**Features**:

- Add support for attaching Sentry event payloads in Unreal crash reports by adding `__sentry` game data entries. ([#715](https://github.com/getsentry/relay/pull/715))
- Support chunked formdata keys for event payloads on the Minidump endpoint. Since crashpad has a limit for the length of custom attributes, the sentry event payload can be split up into `sentry__1`, `sentry__2`, etc. ([#721](https://github.com/getsentry/relay/pull/721))
- Add periodic re-authentication with the upstream server, previously there was only one initial authentication. ([#731](https://github.com/getsentry/relay/pull/731))

Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion relay-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ sentry-actix = "0.18.0"
serde = { version = "1.0.114", features = ["derive"] }
serde_json = "1.0.55"
smallvec = "1.4.0"
symbolic = { version = "7.4.0", optional = true, default-features=false, features=["unreal-serde"] }
symbolic = { version = "7.5.0", optional = true, default-features=false, features=["unreal-serde"] }
tokio-timer = "0.2.13"
url = { version = "2.1.1", features = ["serde"] }
uuid = { version = "0.8.1", features = ["v5"] }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ expression: event.to_json_pretty().unwrap()
"crash_guid": "UE4CC-Windows-63456D684167A2659DE73EA3517BEDC4_0000",
"crash_reporter_client_version": "1.0",
"crash_type": "Crash",
"custom": {
"CommandLine": "",
"CrashDumpMode": "0",
"CrashVersion": "3",
"DeploymentName": "",
"EngineModeEx": "Unset",
"GameSessionID": "",
"IsPerforceBuild": "false",
"MemoryStats.AvailablePhysical": "0",
"MemoryStats.AvailableVirtual": "0",
"MemoryStats.OOMAllocationAlignment": "0",
"MemoryStats.OOMAllocationSize": "0",
"MemoryStats.PeakUsedPhysical": "0",
"MemoryStats.PeakUsedVirtual": "0",
"MemoryStats.UsedPhysical": "0",
"MemoryStats.UsedVirtual": "0",
"MemoryStats.bIsOOM": "0",
"PlatformFullName": "Win64 [Windows 10 64b]",
"PlatformNameIni": "Windows",
"SourceContext": "",
"UserActivityHint": ""
},
"engine_mode": "Game",
"engine_version": "4.20.3-4369336+++UE4+Release-4.20",
"executable_name": "YetAnother",
Expand Down
42 changes: 41 additions & 1 deletion relay-server/src/utils/unreal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ use crate::envelope::{AttachmentType, ContentType, Envelope, Item, ItemType};
/// Maximum number of unreal logs to parse for breadcrumbs.
const MAX_NUM_UNREAL_LOGS: usize = 40;

const SENTRY_PAYLOAD_KEY: &str = "__sentry";

fn get_event_item(data: &[u8]) -> Result<Option<Item>, Unreal4Error> {
let mut context = Unreal4Context::parse(data)?;
let json = match context.game_data.remove(SENTRY_PAYLOAD_KEY) {
Some(json) if !json.is_empty() => json,
_ => return Ok(None),
};

log::trace!("adding event payload from unreal context");
let mut item = Item::new(ItemType::Event);
item.set_payload(ContentType::Json, json);
Ok(Some(item))
}

/// Expands Unreal 4 items inside an envelope.
///
/// If the envelope does NOT contain an `UnrealReport` item, it doesn't do anything. If the envelope
Expand All @@ -32,6 +47,10 @@ pub fn expand_unreal_envelope(
let payload = unreal_item.payload();
let crash = Unreal4Crash::parse(&payload)?;

let mut has_event = envelope
.get_item_by(|item| item.ty() == ItemType::Event)
.is_some();

for file in crash.files() {
let (content_type, attachment_type) = match file.ty() {
Unreal4FileType::Minidump => (ContentType::Minidump, AttachmentType::Minidump),
Expand All @@ -41,14 +60,21 @@ pub fn expand_unreal_envelope(
Unreal4FileType::Log => (ContentType::Text, AttachmentType::UnrealLogs),
Unreal4FileType::Config => (ContentType::OctetStream, AttachmentType::Attachment),
Unreal4FileType::Context => (ContentType::Xml, AttachmentType::UnrealContext),
Unreal4FileType::Unknown => match file.name() {
_ => match file.name() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad man

self::ITEM_NAME_EVENT => (ContentType::MsgPack, AttachmentType::EventPayload),
self::ITEM_NAME_BREADCRUMBS1 => (ContentType::MsgPack, AttachmentType::Breadcrumbs),
self::ITEM_NAME_BREADCRUMBS2 => (ContentType::MsgPack, AttachmentType::Breadcrumbs),
_ => (ContentType::OctetStream, AttachmentType::Attachment),
},
};

if !has_event && attachment_type == AttachmentType::UnrealContext {
if let Some(event_item) = get_event_item(file.data())? {
envelope.add_item(event_item);
has_event = true;
}
}

let mut item = Item::new(ItemType::Attachment);
item.set_filename(file.name());
item.set_payload(content_type, file.data());
Expand Down Expand Up @@ -197,6 +223,20 @@ fn merge_unreal_context(event: &mut Event, context: Unreal4Context) {
// modules not used just remove it from runtime props
runtime_props.modules.take();

// promote all game data (except the special __sentry key) into a context.
if !context.game_data.is_empty() {
let game_context = contexts.get_or_insert_with("game", || Context::Other(Object::new()));
if let Context::Other(game_context) = game_context {
let filtered_keys = context
.game_data
.into_iter()
.filter(|(key, _)| key != SENTRY_PAYLOAD_KEY)
.map(|(key, value)| (key, Annotated::new(Value::String(value))));

game_context.extend(filtered_keys);
}
}

if let Ok(Some(Value::Object(props))) = types::to_value(&runtime_props) {
let unreal_context =
contexts.get_or_insert_with("unreal", || Context::Other(Object::new()));
Expand Down
Binary file not shown.
87 changes: 87 additions & 0 deletions tests/integration/test_unreal.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,90 @@ def test_unreal_apple_crash_with_processing(
apple_crash_report_marker_found = True

assert apple_crash_report_marker_found


def test_unreal_minidump_with_config_and_processing(
mini_sentry, relay_with_processing, attachments_consumer, events_consumer
):
project_id = 42
options = {"processing": {"attachment_chunk_size": "1.23 GB"}}
relay = relay_with_processing(options)
attachments_consumer = attachments_consumer()

mini_sentry.project_configs[project_id] = mini_sentry.full_project_config()
unreal_content = _load_dump_file("unreal_crash_with_config")

relay.send_unreal_request(project_id, unreal_content)

attachments = {}

while True:
raw_message, message = attachments_consumer.get_message()
if message is None or message["type"] != "attachment_chunk":
event = message
break
attachments[message["id"]] = message

assert event
assert event["type"] == "event"

project_id = event["project_id"]
event_id = event["event_id"]

assert len(event["attachments"]) == 4
assert len(attachments) == 4

logs_file_found = False
mini_dump_found = False
crash_report_ini_found = False
unreal_context_found = False

for attachment_entry in event["attachments"]:
# check that the attachment is registered in the event
attachment_id = attachment_entry["id"]
# check that we didn't get the messages chunked
assert attachment_entry["chunks"] == 1

entry_name = attachment_entry["name"]

if entry_name == "UE4Minidump.dmp":
mini_dump_found = True
elif entry_name == "MyProject.log":
logs_file_found = True
elif entry_name == "CrashContext.runtime-xml":
unreal_context_found = True
elif entry_name == "CrashReportClient.ini":
crash_report_ini_found = True

attachment = attachments.get(attachment_id)
assert attachment is not None
assert attachment["event_id"] == event_id
assert attachment["project_id"] == project_id

assert mini_dump_found
assert logs_file_found
assert unreal_context_found
assert crash_report_ini_found

# check the created event
event_data = json.loads(event["payload"])
assert event_data["release"] == "foo-bar@1.0.0"

assert event_data["event_id"] == event_id

exception = event_data.get("exception")
assert exception is not None
values = exception["values"]
assert values is not None

mini_dump_process_marker_found = False

for value in values:
if value == {
"type": "Minidump",
"value": "Invalid Minidump",
"mechanism": {"type": "minidump", "synthetic": True, "handled": False},
}:
mini_dump_process_marker_found = True

assert mini_dump_process_marker_found