-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(nuxt): Add server error hook #12796
Changes from 7 commits
dcadf22
85359f8
d77c2e5
a374ce5
795d1ea
cea2347
9a6f43c
1af2673
165cf5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { captureException } from '@sentry/node'; | ||
import { H3Error } from 'h3'; | ||
import { defineNitroPlugin } from 'nitropack/runtime'; | ||
import { extractErrorContext } from '../utils'; | ||
|
||
export default defineNitroPlugin(nitroApp => { | ||
nitroApp.hooks.hook('error', (error, errorContext) => { | ||
// Do not handle 404 and 422 | ||
if (error instanceof H3Error) { | ||
if (error.statusCode === 404 || error.statusCode === 422) { | ||
return; | ||
} | ||
} | ||
|
||
if (errorContext) { | ||
const structuredContext = extractErrorContext(errorContext); | ||
|
||
captureException(error, { | ||
captureContext: { contexts: { nuxt: structuredContext } }, | ||
mechanism: { handled: false }, | ||
}); | ||
} else { | ||
captureException(error, { mechanism: { handled: false } }); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l (feel free to disregard): WDYT about calling |
||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type { Context } from '@sentry/types'; | ||
import { dropUndefinedKeys } from '@sentry/utils'; | ||
import type { CapturedErrorContext } from 'nitropack'; | ||
|
||
/** | ||
* Extracts the relevant context information from the error context (H3Event in Nitro Error) | ||
* and created a structured context object. | ||
*/ | ||
export function extractErrorContext(errorContext: CapturedErrorContext): Context { | ||
const structuredContext: Context = { | ||
method: undefined, | ||
path: undefined, | ||
tags: undefined, | ||
}; | ||
|
||
if (errorContext) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l: do we need this guard? i.e. can |
||
if (errorContext.event) { | ||
structuredContext.method = errorContext.event._method || undefined; | ||
structuredContext.path = errorContext.event._path || undefined; | ||
} | ||
|
||
if (Array.isArray(errorContext.tags)) { | ||
structuredContext.tags = errorContext.tags || undefined; | ||
} | ||
} | ||
|
||
return dropUndefinedKeys(structuredContext); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { describe, expect, it } from 'vitest'; | ||
import { extractErrorContext } from '../../../src/runtime/utils'; | ||
|
||
describe('extractErrorContext', () => { | ||
it('returns empty object for undefined or empty context', () => { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(extractErrorContext(undefined)).toEqual({}); | ||
expect(extractErrorContext({})).toEqual({}); | ||
}); | ||
|
||
it('extracts properties from errorContext and drops them if missing', () => { | ||
const context = { | ||
event: { | ||
_method: 'GET', | ||
_path: '/test', | ||
}, | ||
tags: ['tag1', 'tag2'], | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(extractErrorContext(context)).toEqual({ | ||
method: 'GET', | ||
path: '/test', | ||
tags: ['tag1', 'tag2'], | ||
}); | ||
|
||
const partialContext = { | ||
event: { | ||
_path: '/test', | ||
}, | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(extractErrorContext(partialContext)).toEqual({ path: '/test' }); | ||
}); | ||
|
||
it('handles errorContext.tags correctly, including when absent or of unexpected type', () => { | ||
const contextWithTags = { | ||
tags: ['tag1', 'tag2'], | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(extractErrorContext(contextWithTags)).toEqual({ | ||
tags: ['tag1', 'tag2'], | ||
}); | ||
|
||
const contextWithoutTags = { | ||
event: {}, | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(extractErrorContext(contextWithoutTags)).toEqual({}); | ||
|
||
const contextWithInvalidTags = { | ||
event: {}, | ||
tags: 'not-an-array', | ||
}; | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(extractErrorContext(contextWithInvalidTags)).toEqual({}); | ||
}); | ||
|
||
it('gracefully handles unexpected context structure without throwing errors', () => { | ||
const weirdContext1 = { | ||
unexpected: 'value', | ||
}; | ||
const weirdContext2 = ['value']; | ||
const weirdContext3 = 123; | ||
|
||
expect(() => extractErrorContext(weirdContext1)).not.toThrow(); | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(() => extractErrorContext(weirdContext2)).not.toThrow(); | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
expect(() => extractErrorContext(weirdContext3)).not.toThrow(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l/m: We could arguably skip all 4xxs (also 3xxs if that can ever happen).