diff --git a/src/crash/sentry_envelope.zig b/src/crash/sentry_envelope.zig index 6b675554c..b0d9f5b70 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, }; } @@ -349,9 +356,191 @@ 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; + + 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( + 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 +/// 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.