Skip to content

Commit f879d45

Browse files
MitchDickinsongonzaloriestra
authored andcommitted
Merge pull request #6446 from Shopify/09-26-add_slice_name_to_error_handler
Add slice_name to error handler
1 parent 0f46337 commit f879d45

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

packages/cli-kit/src/public/node/error-handler.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {errorHandler, cleanStackFrameFilePath, addBugsnagMetadata, sendErrorToBugsnag} from './error-handler.js'
2+
import * as metadata from './metadata.js'
23
import {ciPlatform, cloudEnvironment, isUnitTest, macAddress} from './context/local.js'
34
import {mockAndCaptureOutput} from './testing/output.js'
45
import * as error from './error.js'
@@ -8,16 +9,23 @@ import {settings} from '@oclif/core'
89
import {beforeEach, describe, expect, test, vi} from 'vitest'
910

1011
const onNotify = vi.fn()
12+
let lastBugsnagEvent: {addMetadata: ReturnType<typeof vi.fn>} | undefined
1113

1214
vi.mock('process')
1315
vi.mock('@bugsnag/js', () => {
1416
return {
1517
default: {
1618
notify: (reportedError: any, args: any, callback: any) => {
1719
onNotify(reportedError)
20+
if (typeof args === 'function') {
21+
const event = {addMetadata: vi.fn()}
22+
lastBugsnagEvent = event as any
23+
args(event)
24+
}
1825
callback(null)
1926
},
2027
isStarted: () => true,
28+
addOnError: vi.fn(),
2129
},
2230
}
2331
})
@@ -39,6 +47,7 @@ beforeEach(() => {
3947
vi.mocked(hashString).mockReturnValue('hashed-macaddress')
4048
vi.mocked(isUnitTest).mockReturnValue(true)
4149
onNotify.mockClear()
50+
lastBugsnagEvent = undefined
4251
vi.mocked(settings).debug = false
4352
vi.mocked(isLocalEnvironment).mockReturnValue(false)
4453
})
@@ -207,4 +216,39 @@ describe('sends errors to Bugsnag', () => {
207216
expect(res.error).toEqual(toThrow)
208217
expect(mockOutput.debug()).toMatch('Error reporting to Bugsnag: Error: Bugsnag is down')
209218
})
219+
220+
test('attaches custom metadata with allowed slice_name when startCommand is present', async () => {
221+
await metadata.addSensitiveMetadata(() => ({
222+
commandStartOptions: {startTime: Date.now(), startCommand: 'app dev', startArgs: []},
223+
}))
224+
225+
await sendErrorToBugsnag(new Error('boom'), 'unexpected_error')
226+
227+
expect(lastBugsnagEvent).toBeDefined()
228+
expect(lastBugsnagEvent!.addMetadata).toHaveBeenCalledWith('custom', {slice_name: 'app'})
229+
})
230+
231+
test('does not attach custom slice_name when startCommand is missing', async () => {
232+
await metadata.addSensitiveMetadata(() => ({
233+
commandStartOptions: {startTime: Date.now(), startCommand: undefined as unknown as string, startArgs: []},
234+
}))
235+
236+
await sendErrorToBugsnag(new Error('boom'), 'unexpected_error')
237+
238+
expect(lastBugsnagEvent).toBeDefined()
239+
const calls = (lastBugsnagEvent!.addMetadata as any).mock.calls as any[]
240+
const customCall = calls.find(([section]: [string]) => section === 'custom')
241+
expect(customCall).toBeUndefined()
242+
})
243+
244+
test('defaults slice_name to cli when first word not allowed', async () => {
245+
await metadata.addSensitiveMetadata(() => ({
246+
commandStartOptions: {startTime: Date.now(), startCommand: 'version', startArgs: []},
247+
}))
248+
249+
await sendErrorToBugsnag(new Error('boom'), 'unexpected_error')
250+
251+
expect(lastBugsnagEvent).toBeDefined()
252+
expect(lastBugsnagEvent!.addMetadata).toHaveBeenCalledWith('custom', {slice_name: 'cli'})
253+
})
210254
})

packages/cli-kit/src/public/node/error-handler.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import StackTracey from 'stacktracey'
2121
import Bugsnag, {Event} from '@bugsnag/js'
2222
import {realpath} from 'fs/promises'
2323

24+
// Allowed slice names for error analytics grouping.
25+
// Hardcoded list per product slices to keep analytics consistent.
26+
const ALLOWED_SLICE_NAMES = new Set<string>(['app', 'theme', 'hydrogen', 'store'])
27+
2428
export async function errorHandler(
2529
error: Error & {exitCode?: number | undefined},
2630
config?: Interfaces.Config,
@@ -126,6 +130,14 @@ export async function sendErrorToBugsnag(
126130
const eventHandler = (event: Event) => {
127131
event.severity = 'error'
128132
event.unhandled = unhandled
133+
// Attach command metadata so we know which CLI command triggered the error
134+
const {commandStartOptions} = metadata.getAllSensitiveMetadata()
135+
const {startCommand} = commandStartOptions ?? {}
136+
if (startCommand) {
137+
const firstWord = startCommand.trim().split(/\s+/)[0] ?? 'cli'
138+
const sliceName = ALLOWED_SLICE_NAMES.has(firstWord) ? firstWord : 'cli'
139+
event.addMetadata('custom', {slice_name: sliceName})
140+
}
129141
}
130142
const errorHandler = (error: unknown) => {
131143
if (error) {

0 commit comments

Comments
 (0)