From cefd34e03e30b07d51d4a2bb286292362962de56 Mon Sep 17 00:00:00 2001 From: Marco Zanon Date: Fri, 11 Jul 2025 11:56:29 +0200 Subject: [PATCH 1/4] Session class --- src/crash/sentry_envelope.zig | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/crash/sentry_envelope.zig b/src/crash/sentry_envelope.zig index 6b675554c..3c4fce2ea 100644 --- a/src/crash/sentry_envelope.zig +++ b/src/crash/sentry_envelope.zig @@ -349,6 +349,100 @@ pub const EncodedItem = struct { payload: []const u8, }; + +pub const SessionStatus = enum { + // Session is healthy, will never happend crash reporting + ok, + // Session terminated normally, will never happend crash reporting + exited, + // Session crashed + crashed, + // Session exited in an abnormal state that is not crash + abnormal, +}; + +pub const SessionAttrs = struct { + // Sentry release ID (release) + release: []const u8, + + // Sentry environment + // Feel like it should be an enum, but possible values not provided + environment: []const u8, + // Sentry docs does say it's required, but also that is not persisted + ip_address: ?[]const u8, + // Sentry docs does say it's required, but also that is not persisted + user_agent: ?[]const u8, + +}; + + +/// A sentry session +/// +/// https://develop.sentry.dev/sdk/data-model/envelope-items/#session +pub const Session = struct { + + // Session start datetime string, ISO format + started: []const u8, + + // Unique session id generated by the client, it can be + // empty for sessions in exited state + sid: ?[]const u8, + + // Distinct id, should identify a machine or a user. It is an hash + did: ?[]const u8, + + // Sequential number, logical clock, defaults to current unix + // timestamp in milliseconds + seq: ?u64, + + // Timestamp of session change event, not sure of its meaning for crash handling + // Datetime string, ISO format + timestamp: ?[]const u8, + + // Denotes that an event is the first recorded one for a specific session + init: ?bool = false, + + // Represents the length of a session in seconds + duration: ?f64, + + // The current status of the session, tracks reason of + // termination, should not be different from "crashed" + status: ?SessionStatus, + + // Number of errors encountered during the session. The crash is + // always counted as an error + errors: u16, + + // From the docs "The mechanism that caused the session to finish + // with an abnormal status...". If useless can be removed + abnormal_mechanism: ?[] const u8, + + attrs: SessionAttrs, + + pub fn decode( + alloc: Allocator, + encoded: EncodedItem, + ) Item.DecodeError!Session { + _ = alloc; + _ = encoded; + + std.debug.print("Decode requested correctly", .{}); + } + + pub fn encode( + self: *Session, + alloc: Allocator, + ) !EncodedItem { + _ = alloc; + _ = self; + + std.debug.print("Encode requested correctly", .{}); + + return .{}; + } +}; + + /// An arbitrary file attachment. /// /// https://develop.sentry.dev/sdk/envelopes/#attachment From dfca0c87528eab2413ef24114162223b8c6546bf Mon Sep 17 00:00:00 2001 From: Marco Zanon Date: Fri, 11 Jul 2025 11:57:09 +0200 Subject: [PATCH 2/4] Session decode function --- src/crash/sentry_envelope.zig | 90 ++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/crash/sentry_envelope.zig b/src/crash/sentry_envelope.zig index 3c4fce2ea..ab195bd42 100644 --- a/src/crash/sentry_envelope.zig +++ b/src/crash/sentry_envelope.zig @@ -424,9 +424,97 @@ pub const Session = struct { encoded: EncodedItem, ) Item.DecodeError!Session { _ = alloc; - _ = encoded; std.debug.print("Decode requested correctly", .{}); + + var status: ?SessionStatus = null; + + if (encoded.headers.get("status")) |v| { + const temp = switch (v) { + .string => |str| std.meta.stringToEnum( SessionStatus, str ) + orelse return error.InvalidFieldType, + else => return error.InvalidFieldType, + }; + if (temp != SessionStatus.crashed) { + log.debug("Found status different than crashed: {s}", .{ @tagName(temp) }) ; + } + status = temp; + } + + const errors: u16 = if (encoded.headers.get("errors")) |v| switch (v) { + .integer => |n| @truncate( @as(u64, @intCast(n)) ), + else => return error.InvalidFieldType, + } else return error.MissingRequiredField; + + if (errors < 1) { + log.debug("Found error count < 1, while crashing should count as one. Value: {}", .{ errors }); + } + + return .{ + + .started = if (encoded.headers.get("started")) |v| switch (v) { + .string => |str| str, + else => return error.InvalidFieldType, + } else return error.MissingRequiredField, + // + + .sid = if (encoded.headers.get("sid")) |v| switch (v) { + .string => |str| str, + else => return error.InvalidFieldType + } else null, + // + // did: ?[]const u8, + .did = if (encoded.headers.get("did")) |v| switch (v) { + .string => |str| str, + else => return error.InvalidFieldType + } else null, + + .seq = if (encoded.headers.get("seq")) |v| switch (v) { + .integer => |num| @intCast(num), + else => return error.InvalidFieldType + } else null, + + .timestamp = if (encoded.headers.get("timestamp")) |v| switch (v) { + .string => |str| str, + else => return error.InvalidFieldType + } else null, + + // Default to false if not present + .init = if (encoded.headers.get("init")) |v| switch (v) { + .bool => |b| b, + else => return error.InvalidFieldType + } else false, + // duration: ?f64, + .duration = if (encoded.headers.get("duration")) |v| switch (v) { + .float => |f| f, + else => return error.InvalidFieldType + } else null, + + .status = status, + + .errors = errors, + .attrs = if (encoded.headers.get("attrs")) |v| switch (v) { + .object => |obj| .{ + .release = if (obj.get("release")) |u| switch (u) { + .string => |s| s, + else => return error.InvalidFieldType, + } else return error.MissingRequiredField, + .environment = if (obj.get("environment")) |u| switch(u) { + .string => |s| s, + else => return error.InvalidFieldType, + } else return error.MissingRequiredField, + .ip_address = if (obj.get("ip_address")) |u| switch(u) { + .string => |s| s, + else => return error.InvalidFieldType, + } else null, + .user_agent = if (obj.get("user_agent")) |u| switch(u) { + .string => |s| s, + else => return error.InvalidFieldType, + } else null + }, + else => return error.InvalidFieldType, + } else return error.MissingRequiredField, + }; } pub fn encode( From 9599ba5f9e4724d488c2791143f6c1406f5de016 Mon Sep 17 00:00:00 2001 From: Marco Zanon Date: Fri, 11 Jul 2025 12:00:24 +0200 Subject: [PATCH 3/4] Add type to Item tagged union to enable decoding of session object --- src/crash/sentry_envelope.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/crash/sentry_envelope.zig b/src/crash/sentry_envelope.zig index ab195bd42..c12ff5a01 100644 --- a/src/crash/sentry_envelope.zig +++ b/src/crash/sentry_envelope.zig @@ -278,6 +278,7 @@ pub const ItemType = enum { pub const Item = union(enum) { encoded: EncodedItem, attachment: Attachment, + session: Session, /// Convert the item to an encoded item. This modify the item /// in place. @@ -288,6 +289,7 @@ pub const Item = union(enum) { const result: EncodedItem = switch (self.*) { .encoded => |v| return v, .attachment => |*v| try v.encode(alloc), + .session => |*v| try v.encode(alloc), }; self.* = .{ .encoded = result }; return result; @@ -299,6 +301,7 @@ pub const Item = union(enum) { return switch (self) { .encoded => |v| v.type, .attachment => .attachment, + .session => .session, }; } @@ -335,6 +338,10 @@ pub const Item = union(enum) { alloc, encoded, ) }, + .session => .{ .session = try .decode( + alloc, + encoded, + ) }, else => return error.UnsupportedType, }; } From f8c9597a3a0d2c3fd79e716ae7ccc25bdc35a4d3 Mon Sep 17 00:00:00 2001 From: Marco Zanon Date: Fri, 11 Jul 2025 12:00:52 +0200 Subject: [PATCH 4/4] Minor change: updated url of sentry sdk --- src/crash/sentry_envelope.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crash/sentry_envelope.zig b/src/crash/sentry_envelope.zig index c12ff5a01..b0d9f5b70 100644 --- a/src/crash/sentry_envelope.zig +++ b/src/crash/sentry_envelope.zig @@ -540,7 +540,7 @@ pub const Session = struct { /// An arbitrary file attachment. /// -/// https://develop.sentry.dev/sdk/envelopes/#attachment +/// https://develop.sentry.dev/sdk/data-model/envelope-items/#attachment pub const Attachment = struct { /// "filename" field is the name of the uploaded file without /// a path component.