diff --git a/build.zig b/build.zig index bc829d59e..9087dacb4 100644 --- a/build.zig +++ b/build.zig @@ -1001,6 +1001,10 @@ fn addDeps( .target = target, .optimize = optimize, }); + const sentry_dep = b.dependency("sentry", .{ + .target = target, + .optimize = optimize, + }); const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize, @@ -1104,6 +1108,7 @@ fn addDeps( step.root_module.addImport("xev", libxev_dep.module("xev")); step.root_module.addImport("opengl", opengl_dep.module("opengl")); step.root_module.addImport("pixman", pixman_dep.module("pixman")); + step.root_module.addImport("sentry", sentry_dep.module("sentry")); step.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); step.root_module.addImport("vaxis", vaxis_dep.module("vaxis")); @@ -1199,6 +1204,10 @@ fn addDeps( // Fontconfig step.linkLibrary(fontconfig_dep.artifact("fontconfig")); } + + // Sentry + step.linkLibrary(sentry_dep.artifact("sentry")); + try static_libs.append(sentry_dep.artifact("sentry").getEmittedBin()); } if (!lib) { diff --git a/build.zig.zon b/build.zig.zon index 798d2612a..08ed9cd71 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -37,6 +37,7 @@ .oniguruma = .{ .path = "./pkg/oniguruma" }, .opengl = .{ .path = "./pkg/opengl" }, .pixman = .{ .path = "./pkg/pixman" }, + .sentry = .{ .path = "./pkg/sentry" }, .simdutf = .{ .path = "./pkg/simdutf" }, .utfcpp = .{ .path = "./pkg/utfcpp" }, .zlib = .{ .path = "./pkg/zlib" }, diff --git a/src/global.zig b/src/global.zig index 0ee4914a3..df24c129f 100644 --- a/src/global.zig +++ b/src/global.zig @@ -7,6 +7,7 @@ const fontconfig = @import("fontconfig"); const glslang = @import("glslang"); const harfbuzz = @import("harfbuzz"); const oni = @import("oniguruma"); +const sentry = @import("sentry.zig"); const renderer = @import("renderer.zig"); const xev = @import("xev"); @@ -117,6 +118,18 @@ pub const GlobalState = struct { // First things first, we fix our file descriptors internal_os.fixMaxFiles(); + // Initialize our crash reporting. + try sentry.init(); + + // const sentrylib = @import("sentry"); + // if (sentrylib.captureEvent(sentrylib.Value.initMessageEvent( + // .info, + // null, + // "hello, world", + // ))) |uuid| { + // std.log.warn("uuid={s}", .{uuid.string()}); + // } else std.log.warn("failed to capture event", .{}); + // We need to make sure the process locale is set properly. Locale // affects a lot of behaviors in a shell. try internal_os.ensureLocale(self.alloc); @@ -138,6 +151,9 @@ pub const GlobalState = struct { pub fn deinit(self: *GlobalState) void { if (self.resources_dir) |dir| self.alloc.free(dir); + // Flush our crash logs + sentry.deinit(); + if (self.gpa) |*value| { // We want to ensure that we deinit the GPA because this is // the point at which it will output if there were safety violations. diff --git a/src/sentry.zig b/src/sentry.zig new file mode 100644 index 000000000..5c91085c1 --- /dev/null +++ b/src/sentry.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const assert = std.debug.assert; +const build_config = @import("build_config.zig"); +const sentry = @import("sentry"); + +const log = std.log.scoped(.sentry); + +/// Process-wide initialization of our Sentry client. +/// +/// PRIVACY NOTE: I want to make it very clear that Ghostty by default does +/// NOT send any data over the network. We use the Sentry native SDK to collect +/// crash reports and logs, but we only store them locally (see Transport). +/// It is up to the user to grab the logs and manually send them to us +/// (or they own Sentry instance) if they want to. +pub fn init() !void { + const transport = sentry.Transport.init(&Transport.send); + errdefer transport.deinit(); + + const opts = sentry.c.sentry_options_new(); + errdefer sentry.c.sentry_options_free(opts); + sentry.c.sentry_options_set_release_n( + opts, + build_config.version_string, + build_config.version_string.len, + ); + sentry.c.sentry_options_set_transport(opts, @ptrCast(transport)); + + // Debug logging for Sentry + sentry.c.sentry_options_set_debug(opts, @intFromBool(true)); + + if (sentry.c.sentry_init(opts) != 0) { + log.warn("failed to initialize sentry", .{}); + } +} + +/// Process-wide deinitialization of our Sentry client. This ensures all +/// our data is flushed. +pub fn deinit() void { + _ = sentry.c.sentry_close(); +} + +pub const Transport = struct { + pub fn send(envelope: *sentry.Envelope, state: ?*anyopaque) callconv(.C) void { + _ = state; + defer envelope.deinit(); + + log.warn("sending envelope", .{}); + } +};