diff --git a/build.zig b/build.zig index 06e257c2f..3f1a6a5f5 100644 --- a/build.zig +++ b/build.zig @@ -1004,6 +1004,7 @@ fn addDeps( const sentry_dep = b.dependency("sentry", .{ .target = target, .optimize = optimize, + .backend = .inproc, }); const zlib_dep = b.dependency("zlib", .{ .target = target, diff --git a/pkg/sentry/envelope.zig b/pkg/sentry/envelope.zig index 38253f290..d40a8d9fb 100644 --- a/pkg/sentry/envelope.zig +++ b/pkg/sentry/envelope.zig @@ -7,4 +7,12 @@ pub const Envelope = opaque { pub fn deinit(self: *Envelope) void { c.sentry_envelope_free(@ptrCast(self)); } + + pub fn writeToFile(self: *Envelope, path: []const u8) !void { + if (c.sentry_envelope_write_to_file_n( + @ptrCast(self), + path.ptr, + path.len, + ) != 0) return error.WriteFailed; + } }; diff --git a/pkg/sentry/uuid.zig b/pkg/sentry/uuid.zig index d1e234dda..3f1c06704 100644 --- a/pkg/sentry/uuid.zig +++ b/pkg/sentry/uuid.zig @@ -6,6 +6,10 @@ const c = @import("c.zig").c; pub const UUID = struct { value: c.sentry_uuid_t, + pub fn init() UUID { + return .{ .value = c.sentry_uuid_new_v4() }; + } + pub fn isNil(self: UUID) bool { return c.sentry_uuid_is_nil(&self.value) != 0; } diff --git a/src/os/xdg.zig b/src/os/xdg.zig index e810f441e..6c7655c22 100644 --- a/src/os/xdg.zig +++ b/src/os/xdg.zig @@ -37,6 +37,15 @@ pub fn cache(alloc: Allocator, opts: Options) ![]u8 { }); } +/// Get the XDG state directory. The returned value is allocated. +pub fn state(alloc: Allocator, opts: Options) ![]u8 { + return try dir(alloc, opts, .{ + .env = "XDG_STATE_HOME", + .windows_env = "LOCALAPPDATA", + .default_subdir = ".local/state", + }); +} + const InternalOptions = struct { env: []const u8, windows_env: []const u8, diff --git a/src/sentry.zig b/src/sentry.zig index feb14c787..c54049e43 100644 --- a/src/sentry.zig +++ b/src/sentry.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const build_config = @import("build_config.zig"); const sentry = @import("sentry"); const internal_os = @import("os/main.zig"); +const state = &@import("global.zig").state; const log = std.log.scoped(.sentry); @@ -61,10 +62,42 @@ pub fn deinit() void { } pub const Transport = struct { - pub fn send(envelope: *sentry.Envelope, state: ?*anyopaque) callconv(.C) void { - _ = state; + pub fn send(envelope: *sentry.Envelope, ud: ?*anyopaque) callconv(.C) void { + _ = ud; defer envelope.deinit(); - log.warn("sending envelope", .{}); + // Call our internal impl. If it fails there is nothing we can do + // but log to the user. + sendInternal(envelope) catch |err| { + log.warn("failed to persist crash report err={}", .{err}); + }; + } + + /// Implementation of send but we can use Zig errors. + fn sendInternal(envelope: *sentry.Envelope) !void { + var arena = std.heap.ArenaAllocator.init(state.alloc); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Generate a UUID for this envelope. The envelope DOES have an event_id + // header but I don't think there is any public API way to get it + // afaict so we generate a new UUID for the filename just so we don't + // conflict. + const uuid = sentry.UUID.init(); + + // Get our XDG state directory where we'll store the crash reports. + // This directory must exist for writing to work. + const crash_dir = try internal_os.xdg.state(alloc, .{ .subdir = "ghostty/crash" }); + try std.fs.cwd().makePath(crash_dir); + + // Build our final path and write to it. + const path = try std.fs.path.join(alloc, &.{ + crash_dir, + try std.fmt.allocPrint(alloc, "{s}.ghosttycrash", .{uuid.string()}), + }); + log.debug("writing crash report to disk path={s}", .{path}); + try envelope.writeToFile(path); + + log.warn("crash report written to disk path={s}", .{path}); } };