Upgrade Sentry: tracing, breadcrumbs, dSYM upload (#366)
* Upgrade Sentry: tracing, breadcrumbs, dSYM upload - Enhanced Sentry SDK init with performance tracing (10% sample), explicit app hang timeout, stack trace attachment, and HTTP failure capture - Added breadcrumbs for key user actions: workspace switch/create/close, split creation, command palette open/close, app focus — these give context to hang/crash reports - Added dSYM upload step to nightly and release CI workflows so hang stacks are fully symbolicated (requires SENTRY_AUTH_TOKEN secret) - Created SentryHelper.swift with lightweight breadcrumb helper Closes https://github.com/manaflow-ai/cmux/issues/365 * Remove command palette breadcrumbs Not useful for hang diagnosis — keep only workspace/tab/split/focus breadcrumbs that correlate with heavy operations.
This commit is contained in:
parent
396942c7e4
commit
53ef6a5f7d
6 changed files with 58 additions and 0 deletions
13
.github/workflows/nightly.yml
vendored
13
.github/workflows/nightly.yml
vendored
|
|
@ -294,6 +294,19 @@ jobs:
|
|||
# by appcast URLs to prevent signature/asset mismatch races.
|
||||
cp "$DMG_RELEASE" "$NIGHTLY_DMG_IMMUTABLE"
|
||||
|
||||
- name: Upload dSYMs to Sentry
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: manaflow
|
||||
SENTRY_PROJECT: cmuxterm-macos
|
||||
run: |
|
||||
if [ -z "$SENTRY_AUTH_TOKEN" ]; then
|
||||
echo "SENTRY_AUTH_TOKEN not set, skipping dSYM upload"
|
||||
exit 0
|
||||
fi
|
||||
brew install getsentry/tools/sentry-cli || true
|
||||
sentry-cli debug-files upload --include-sources build/Build/Products/Release/
|
||||
|
||||
- name: Generate Sparkle appcast (nightly)
|
||||
env:
|
||||
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
|
||||
|
|
|
|||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -250,6 +250,20 @@ jobs:
|
|||
xcrun stapler staple "$DMG_RELEASE"
|
||||
xcrun stapler validate "$DMG_RELEASE"
|
||||
|
||||
- name: Upload dSYMs to Sentry
|
||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: manaflow
|
||||
SENTRY_PROJECT: cmuxterm-macos
|
||||
run: |
|
||||
if [ -z "$SENTRY_AUTH_TOKEN" ]; then
|
||||
echo "SENTRY_AUTH_TOKEN not set, skipping dSYM upload"
|
||||
exit 0
|
||||
fi
|
||||
brew install getsentry/tools/sentry-cli || true
|
||||
sentry-cli debug-files upload --include-sources build/Build/Products/Release/
|
||||
|
||||
- name: Generate Sparkle appcast
|
||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
A5001500 /* CmuxWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001510 /* CmuxWebView.swift */; };
|
||||
A5001501 /* UITestRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001511 /* UITestRecorder.swift */; };
|
||||
A5001226 /* SocketControlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001225 /* SocketControlSettings.swift */; };
|
||||
A5001601 /* SentryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001600 /* SentryHelper.swift */; };
|
||||
A5001400 /* Panel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001410 /* Panel.swift */; };
|
||||
A5001401 /* TerminalPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001411 /* TerminalPanel.swift */; };
|
||||
A5001402 /* BrowserPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001412 /* BrowserPanel.swift */; };
|
||||
|
|
@ -146,6 +147,7 @@
|
|||
A5001017 /* ghostty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ghostty.h; sourceTree = "<group>"; };
|
||||
A5001018 /* cmux-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "cmux-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
A5001019 /* TerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalController.swift; sourceTree = "<group>"; };
|
||||
A5001600 /* SentryHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryHelper.swift; sourceTree = "<group>"; };
|
||||
A5001510 /* CmuxWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/CmuxWebView.swift; sourceTree = "<group>"; };
|
||||
A5001511 /* UITestRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestRecorder.swift; sourceTree = "<group>"; };
|
||||
A5001520 /* PostHogAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostHogAnalytics.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -322,6 +324,7 @@
|
|||
A5001019 /* TerminalController.swift */,
|
||||
A5001541 /* PortScanner.swift */,
|
||||
A5001225 /* SocketControlSettings.swift */,
|
||||
A5001600 /* SentryHelper.swift */,
|
||||
A5001090 /* AppDelegate.swift */,
|
||||
A5001091 /* NotificationsPage.swift */,
|
||||
A5001092 /* TerminalNotificationStore.swift */,
|
||||
|
|
@ -551,6 +554,7 @@
|
|||
A5001007 /* TerminalController.swift in Sources */,
|
||||
A5001540 /* PortScanner.swift in Sources */,
|
||||
A5001226 /* SocketControlSettings.swift in Sources */,
|
||||
A5001601 /* SentryHelper.swift in Sources */,
|
||||
A5001093 /* AppDelegate.swift in Sources */,
|
||||
A5001094 /* NotificationsPage.swift in Sources */,
|
||||
A5001095 /* TerminalNotificationStore.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -699,6 +699,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
|
|||
options.debug = false
|
||||
#endif
|
||||
options.sendDefaultPii = true
|
||||
|
||||
// Performance tracing (10% of transactions)
|
||||
options.tracesSampleRate = 0.1
|
||||
// App hang timeout (default is 2s, be explicit)
|
||||
options.appHangTimeoutInterval = 2.0
|
||||
// Attach stack traces to all events
|
||||
options.attachStacktrace = true
|
||||
// Capture failed HTTP requests
|
||||
options.enableCaptureFailedRequests = true
|
||||
}
|
||||
|
||||
if !isRunningUnderXCTest {
|
||||
|
|
@ -804,6 +813,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
|
|||
#endif
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
sentryBreadcrumb("app.didBecomeActive", category: "lifecycle", data: [
|
||||
"tabCount": tabManager?.tabs.count ?? 0
|
||||
])
|
||||
let env = ProcessInfo.processInfo.environment
|
||||
if !isRunningUnderXCTest(env) {
|
||||
PostHogAnalytics.shared.trackDailyActive(reason: "didBecomeActive")
|
||||
|
|
|
|||
9
Sources/SentryHelper.swift
Normal file
9
Sources/SentryHelper.swift
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import Sentry
|
||||
|
||||
/// Add a Sentry breadcrumb for user-action context in hang/crash reports.
|
||||
func sentryBreadcrumb(_ message: String, category: String = "ui", data: [String: Any]? = nil) {
|
||||
let crumb = Breadcrumb(level: .info, category: category)
|
||||
crumb.message = message
|
||||
crumb.data = data
|
||||
SentrySDK.addBreadcrumb(crumb)
|
||||
}
|
||||
|
|
@ -567,6 +567,9 @@ class TabManager: ObservableObject {
|
|||
@Published var selectedTabId: UUID? {
|
||||
didSet {
|
||||
guard selectedTabId != oldValue else { return }
|
||||
sentryBreadcrumb("workspace.switch", data: [
|
||||
"tabCount": tabs.count
|
||||
])
|
||||
let previousTabId = oldValue
|
||||
if let previousTabId,
|
||||
let previousPanelId = focusedPanelId(for: previousTabId) {
|
||||
|
|
@ -752,6 +755,7 @@ class TabManager: ObservableObject {
|
|||
|
||||
@discardableResult
|
||||
func addWorkspace(workingDirectory overrideWorkingDirectory: String? = nil, select: Bool = true) -> Workspace {
|
||||
sentryBreadcrumb("workspace.create", data: ["tabCount": tabs.count + 1])
|
||||
let workingDirectory = normalizedWorkingDirectory(overrideWorkingDirectory) ?? preferredWorkingDirectoryForNewTab()
|
||||
let inheritedConfig = inheritedTerminalConfigForNewWorkspace()
|
||||
let ordinal = Self.nextPortOrdinal
|
||||
|
|
@ -963,6 +967,7 @@ class TabManager: ObservableObject {
|
|||
|
||||
func closeWorkspace(_ workspace: Workspace) {
|
||||
guard tabs.count > 1 else { return }
|
||||
sentryBreadcrumb("workspace.close", data: ["tabCount": tabs.count - 1])
|
||||
|
||||
AppDelegate.shared?.notificationStore?.clearNotifications(forTabId: workspace.id)
|
||||
unwireClosedBrowserTracking(for: workspace)
|
||||
|
|
@ -1725,6 +1730,7 @@ class TabManager: ObservableObject {
|
|||
guard let selectedTabId,
|
||||
let tab = tabs.first(where: { $0.id == selectedTabId }),
|
||||
let focusedPanelId = tab.focusedPanelId else { return }
|
||||
sentryBreadcrumb("split.create", data: ["direction": String(describing: direction)])
|
||||
_ = newSplit(tabId: selectedTabId, surfaceId: focusedPanelId, direction: direction)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue