Skip to content

Commit

Permalink
feat(errors): Use exception list to generate type and message (#25589)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
neilkakkar and github-actions[bot] authored Oct 15, 2024
1 parent 34f3eae commit 2efac4c
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 63 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 18 additions & 5 deletions frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export function ImportingModule(): JSX.Element {
eventProperties={errorProperties({
$exception_type: 'UnhandledRejection',
$exception_message: "Importing module '/static/chunk-PIJHGO7Q.js' is not found.",
$exception_stack_trace_raw: '[]',
$exception_list: [],
$exception_handled: false,
})}
/>
Expand All @@ -136,10 +136,23 @@ export function AnonymousErrorWithStackTrace(): JSX.Element {
return (
<ErrorDisplay
eventProperties={errorProperties({
$exception_type: 'Error',
$exception_message: 'wat',
$exception_stack_trace_raw:
'[{"filename":"<anonymous>","function":"?","in_app":true,"lineno":1,"colno":26}]',
$exception_list: [
{
type: 'Error',
value: 'wat123',
stacktrace: {
frames: [
{
filename: '<anonymous>',
function: '?',
in_app: true,
lineno: 1,
colno: 26,
},
],
},
},
],
})}
/>
)
Expand Down
47 changes: 17 additions & 30 deletions frontend/src/lib/components/Errors/ErrorDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,6 @@ function ActiveFlags({ flags }: { flags: string[] }): JSX.Element {

export function getExceptionPropertiesFrom(eventProperties: Record<string, any>): Record<string, any> {
const {
$exception_type,
$exception_message,
$exception_synthetic,
$lib,
$lib_version,
$browser,
Expand All @@ -138,27 +135,29 @@ export function getExceptionPropertiesFrom(eventProperties: Record<string, any>)
$level,
} = eventProperties

let $exception_stack_trace_raw = eventProperties.$exception_stack_trace_raw
let $exception_type = eventProperties.$exception_type
let $exception_message = eventProperties.$exception_message
let $exception_synthetic = eventProperties.$exception_synthetic
let $exception_list = eventProperties.$exception_list
// exception autocapture sets $exception_stack_trace_raw as a string
// if it isn't present then this is probably a sentry exception.
// try and grab the frames from that
if (!$exception_stack_trace_raw?.length && $sentry_exception) {
if (Array.isArray($sentry_exception.values)) {
const firstException = $sentry_exception.values[0]
if (firstException.stacktrace) {
$exception_stack_trace_raw = JSON.stringify(firstException.stacktrace.frames)
}
}
}
// exception autocapture sets $exception_list for chained exceptions.
// If it's not present, get this list from the sentry_exception

// exception autocapture sets $exception_list for all exceptions.
// If it's not present, then this is probably a sentry exception. Get this list from the sentry_exception
if (!$exception_list?.length && $sentry_exception) {
if (Array.isArray($sentry_exception.values)) {
$exception_list = $sentry_exception.values
}
}

if (!$exception_type) {
$exception_type = $exception_list?.[0]?.type
}
if (!$exception_message) {
$exception_message = $exception_list?.[0]?.value
}
if ($exception_synthetic == undefined) {
$exception_synthetic = $exception_list?.[0]?.mechanism?.synthetic
}

return {
$exception_type,
$exception_message,
Expand All @@ -171,7 +170,6 @@ export function getExceptionPropertiesFrom(eventProperties: Record<string, any>)
$os_version,
$active_feature_flags,
$sentry_url,
$exception_stack_trace_raw,
$exception_list,
$level,
}
Expand All @@ -190,7 +188,6 @@ export function ErrorDisplay({ eventProperties }: { eventProperties: EventType['
$os_version,
$active_feature_flags,
$sentry_url,
$exception_stack_trace_raw,
$exception_list,
$level,
} = getExceptionPropertiesFrom(eventProperties)
Expand Down Expand Up @@ -224,17 +221,7 @@ export function ErrorDisplay({ eventProperties }: { eventProperties: EventType['
<TitledSnack title="browser" value={$browser ? `${$browser} ${$browser_version}` : 'unknown'} />
<TitledSnack title="os" value={$os ? `${$os} ${$os_version}` : 'unknown'} />
</div>
{$exception_list?.length ? (
<ChainedStackTraces exceptionList={$exception_list} />
) : $exception_stack_trace_raw?.length ? (
<>
<LemonDivider dashed={true} />
<div className="flex flex-col gap-1 mt-6">
<h2 className="mb-0">Stack Trace</h2>
<StackTrace rawTrace={$exception_stack_trace_raw} showAllFrames />
</div>
</>
) : null}
{$exception_list?.length ? <ChainedStackTraces exceptionList={$exception_list} /> : null}
<LemonDivider dashed={true} />
<div className="flex flex-col gap-1 mt-6">
<h2 className="mb-0">Active Feature Flags</h2>
Expand Down
78 changes: 74 additions & 4 deletions frontend/src/lib/components/Errors/error-display.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getExceptionPropertiesFrom } from 'lib/components/Errors/ErrorDisplay'

describe('Error Display', () => {
it('can read sentry stack trace when $exception_stack_trace_raw is not present', () => {
it('can read sentry stack trace when $exception_list is not present', () => {
const eventProperties = {
'should not be in the': 'result',
$browser: 'Chrome',
Expand Down Expand Up @@ -53,8 +53,6 @@ describe('Error Display', () => {
$browser: 'Chrome',
$browser_version: '92.0.4515',
$exception_message: 'There was an error creating the support ticket with zendesk.',
$exception_stack_trace_raw:
'[{"colno":220,"filename":"https://app-static-prod.posthog.com/static/chunk-UFQKIDIH.js","function":"submitZendeskTicket","in_app":true,"lineno":25}]',
$exception_list: [
{
mechanism: {
Expand Down Expand Up @@ -118,7 +116,6 @@ describe('Error Display', () => {
$browser: 'Chrome',
$browser_version: '92.0.4515',
$exception_message: 'the message sent into sentry captureMessage',
$exception_stack_trace_raw: undefined,
$exception_synthetic: undefined,
$exception_type: undefined,
$lib: 'posthog-js',
Expand All @@ -130,4 +127,77 @@ describe('Error Display', () => {
'https://sentry.io/organizations/posthog/issues/?project=1899813&query=40e442d79c22473391aeeeba54c82163',
})
})

it('can read exception_list stack trace when $exception_type and message are not present', () => {
const eventProperties = {
'should not be in the': 'result',
$browser: 'Chrome',
$browser_version: '92.0.4515',
$active_feature_flags: ['feature1,feature2'],
$lib: 'posthog-js',
$lib_version: '1.0.0',
$os: 'Windows',
$os_version: '10',
$exception_list: [
{
mechanism: {
handled: true,
type: 'generic',
synthetic: false,
},
stacktrace: {
frames: [
{
colno: 220,
filename: 'https://app-static-prod.posthog.com/static/chunk-UFQKIDIH.js',
function: 'submitZendeskTicket',
in_app: true,
lineno: 25,
},
],
},
type: 'Error',
value: 'There was an error creating the support ticket with zendesk2.',
},
],
$exception_personURL: 'https://app.posthog.com/person/f6kW3HXaha6dAvHZiOmgrcAXK09682P6nNPxvfjqM9c',
}
const result = getExceptionPropertiesFrom(eventProperties)
expect(result).toEqual({
$active_feature_flags: ['feature1,feature2'],
$browser: 'Chrome',
$browser_version: '92.0.4515',
$exception_message: 'There was an error creating the support ticket with zendesk2.',
$exception_synthetic: false,
$exception_type: 'Error',
$lib: 'posthog-js',
$lib_version: '1.0.0',
$level: undefined,
$os: 'Windows',
$os_version: '10',
$sentry_url: undefined,
$exception_list: [
{
mechanism: {
handled: true,
type: 'generic',
synthetic: false,
},
stacktrace: {
frames: [
{
colno: 220,
filename: 'https://app-static-prod.posthog.com/static/chunk-UFQKIDIH.js',
function: 'submitZendeskTicket',
in_app: true,
lineno: 25,
},
],
},
type: 'Error',
value: 'There was an error creating the support ticket with zendesk2.',
},
],
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,21 @@ export function enrichExceptionEventStep(
let type: string | null = null
let message: string | null = null
let firstFunction: string | null = null
let exceptionStack: string | null = null
let exceptionList: any[] | null = null

try {
exceptionStack = event.properties['$exception_stack_trace_raw']
exceptionList = event.properties['$exception_list']
const fingerPrint = event.properties['$exception_fingerprint']
type = event.properties['$exception_type']
message = event.properties['$exception_message']

if (!type && exceptionList && exceptionList.length > 0) {
type = exceptionList[0].type
}
if (!message && exceptionList && exceptionList.length > 0) {
message = exceptionList[0].value
}

if (fingerPrint) {
EXTERNAL_FINGERPRINT_COUNTER.inc()
return Promise.resolve(event)
Expand All @@ -55,12 +60,7 @@ export function enrichExceptionEventStep(
}

try {
if (exceptionStack) {
const parsedStack = JSON.parse(exceptionStack)
if (parsedStack.length > 0) {
firstFunction = parsedStack[0].function
}
} else if (exceptionList && exceptionList.length > 0) {
if (exceptionList && exceptionList.length > 0) {
const firstException = exceptionList[0]
if (firstException.stacktrace) {
// TODO: Should this be the last function instead?, or first in app function?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,28 @@ import { enrichExceptionEventStep } from '../../../../src/worker/ingestion/event

jest.mock('../../../../src/worker/plugins/run')

const aStackTrace =
'[{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"dependenciesChecker","in_app":true,"lineno":721,"colno":42},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"?","in_app":true,"lineno":2474,"colno":40},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"Object.memoized [as tiles]","in_app":true,"lineno":632,"colno":24},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"dependenciesChecker","in_app":true,"lineno":721,"colno":42},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"memoized","in_app":true,"lineno":632,"colno":24},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"dependenciesChecker","in_app":true,"lineno":721,"colno":42},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"logic.selector","in_app":true,"lineno":2517,"colno":18},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"pathSelector","in_app":true,"lineno":2622,"colno":37},{"filename":"<anonymous>","function":"Array.reduce","in_app":true},{"filename":"http://localhost:8234/static/chunk-VDD5ZZ2W.js","function":"?","in_app":true,"lineno":2626,"colno":15}]'
const DEFAULT_EXCEPTION_LIST = [
{
mechanism: {
handled: true,
type: 'generic',
synthetic: false,
},
stacktrace: {
frames: [
{
colno: 220,
filename: 'https://app-static-prod.posthog.com/static/chunk-UFQKIDIH.js',
function: 'submitZendeskTicket',
in_app: true,
lineno: 25,
},
],
},
type: 'Error',
value: 'There was an error creating the support ticket with zendesk.',
},
]

const preIngestionEvent: PreIngestionEvent = {
eventUuid: '018eebf3-cb48-750b-bfad-36409ea6f2b2',
Expand Down Expand Up @@ -42,7 +62,7 @@ describe('enrichExceptionEvent()', () => {

it('ignores non-exception events - even if they have a stack trace', async () => {
event.event = 'not_exception'
event.properties['$exception_stack_trace_raw'] = '[{"some": "data"}]'
event.properties['$exception_list'] = DEFAULT_EXCEPTION_LIST
expect(event.properties['$exception_fingerprint']).toBeUndefined()

const response = await enrichExceptionEventStep(runner, event)
Expand All @@ -51,7 +71,8 @@ describe('enrichExceptionEvent()', () => {

it('use a fingerprint if it is present', async () => {
event.event = '$exception'
event.properties['$exception_stack_trace_raw'] = '[{"some": "data"}]'
event.properties['$exception_list'] = DEFAULT_EXCEPTION_LIST

event.properties['$exception_fingerprint'] = 'some-fingerprint'

const response = await enrichExceptionEventStep(runner, event)
Expand All @@ -62,32 +83,36 @@ describe('enrichExceptionEvent()', () => {
it('uses the message and stack trace as the simplest grouping', async () => {
event.event = '$exception'
event.properties['$exception_message'] = 'some-message'
event.properties['$exception_stack_trace_raw'] = aStackTrace
event.properties['$exception_list'] = DEFAULT_EXCEPTION_LIST

const response = await enrichExceptionEventStep(runner, event)

expect(response.properties['$exception_fingerprint']).toStrictEqual(['some-message', 'dependenciesChecker'])
expect(response.properties['$exception_fingerprint']).toStrictEqual([
'Error',
'some-message',
'submitZendeskTicket',
])
})

it('includes type in stack grouping when present', async () => {
event.event = '$exception'
event.properties['$exception_message'] = 'some-message'
event.properties['$exception_stack_trace_raw'] = aStackTrace
event.properties['$exception_list'] = DEFAULT_EXCEPTION_LIST
event.properties['$exception_type'] = 'UnhandledRejection'

const response = await enrichExceptionEventStep(runner, event)

expect(response.properties['$exception_fingerprint']).toStrictEqual([
'UnhandledRejection',
'some-message',
'dependenciesChecker',
'submitZendeskTicket',
])
})

it('falls back to message and type when no stack trace', async () => {
event.event = '$exception'
event.properties['$exception_message'] = 'some-message'
event.properties['$exception_stack_trace_raw'] = null
event.properties['$exception_list'] = null
event.properties['$exception_type'] = 'UnhandledRejection'

const response = await enrichExceptionEventStep(runner, event)
Expand All @@ -98,11 +123,38 @@ describe('enrichExceptionEvent()', () => {
it('adds no fingerprint if no qualifying properties', async () => {
event.event = '$exception'
event.properties['$exception_message'] = null
event.properties['$exception_stack_trace_raw'] = null
event.properties['$exception_list'] = null
event.properties['$exception_type'] = null

const response = await enrichExceptionEventStep(runner, event)

expect(response.properties['$exception_fingerprint']).toBeUndefined()
})

it('uses exception_list to generate message, type, and fingerprint when not present', async () => {
event.event = '$exception'
event.properties['$exception_list'] = DEFAULT_EXCEPTION_LIST

const response = await enrichExceptionEventStep(runner, event)

expect(response.properties['$exception_fingerprint']).toStrictEqual([
'Error',
'There was an error creating the support ticket with zendesk.',
'submitZendeskTicket',
])
})

it('exception_type overrides exception_list to generate fingerprint when present', async () => {
event.event = '$exception'
event.properties['$exception_list'] = DEFAULT_EXCEPTION_LIST
event.properties['$exception_type'] = 'UnhandledRejection'

const response = await enrichExceptionEventStep(runner, event)

expect(response.properties['$exception_fingerprint']).toStrictEqual([
'UnhandledRejection',
'There was an error creating the support ticket with zendesk.',
'submitZendeskTicket',
])
})
})
Loading

0 comments on commit 2efac4c

Please sign in to comment.