-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: manually capture errors (#1374)
- Loading branch information
Showing
9 changed files
with
139 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,77 @@ | ||
import { start } from '../support/setup' | ||
|
||
describe('Exception autocapture', () => { | ||
beforeEach(() => { | ||
cy.on('uncaught:exception', () => { | ||
// otherwise the exception we throw on purpose causes the test to fail | ||
return false | ||
}) | ||
|
||
describe('Exception capture', () => { | ||
it('manual exception capture', () => { | ||
start({ | ||
decideResponseOverrides: { | ||
autocaptureExceptions: true, | ||
autocaptureExceptions: false, | ||
}, | ||
url: './playground/cypress', | ||
}) | ||
cy.wait('@exception-autocapture-script') | ||
}) | ||
|
||
it('captures exceptions', () => { | ||
cy.get('[data-cy-button-throws-error]').click() | ||
cy.get('[data-cy-exception-button]').click() | ||
|
||
// ugh | ||
cy.wait(1500) | ||
|
||
cy.phCaptures({ full: true }).then((captures) => { | ||
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$autocapture', '$exception']) | ||
expect(captures[2].event).to.be.eql('$exception') | ||
expect(captures[2].properties.$exception_message).to.be.eql('This is an error') | ||
expect(captures[2].properties.$exception_message).to.be.eql('wat even am I') | ||
expect(captures[2].properties.$exception_type).to.be.eql('Error') | ||
expect(captures[2].properties.$exception_source).to.match(/http:\/\/localhost:\d+\/playground\/cypress\//) | ||
expect(captures[2].properties.$exception_personURL).to.match( | ||
/http:\/\/localhost:\d+\/project\/test_token\/person\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/ | ||
) | ||
expect(captures[2].properties.extra_prop).to.be.eql(2) | ||
expect(captures[2].properties.$exception_source).to.eql(undefined) | ||
expect(captures[2].properties.$exception_personURL).to.eql(undefined) | ||
expect(captures[2].properties.$exception_stack_trace_raw).not.to.exist | ||
}) | ||
}) | ||
|
||
describe('Exception autocapture enabled', () => { | ||
beforeEach(() => { | ||
cy.on('uncaught:exception', () => { | ||
// otherwise the exception we throw on purpose causes the test to fail | ||
return false | ||
}) | ||
|
||
start({ | ||
decideResponseOverrides: { | ||
autocaptureExceptions: true, | ||
}, | ||
url: './playground/cypress', | ||
}) | ||
cy.wait('@exception-autocapture-script') | ||
}) | ||
|
||
it('autocaptures exceptions', () => { | ||
cy.get('[data-cy-button-throws-error]').click() | ||
|
||
// ugh | ||
cy.wait(1500) | ||
|
||
cy.phCaptures({ full: true }).then((captures) => { | ||
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$autocapture', '$exception']) | ||
expect(captures[2].event).to.be.eql('$exception') | ||
expect(captures[2].properties.$exception_message).to.be.eql('This is an error') | ||
expect(captures[2].properties.$exception_type).to.be.eql('Error') | ||
expect(captures[2].properties.$exception_source).to.match( | ||
/http:\/\/localhost:\d+\/playground\/cypress\// | ||
) | ||
expect(captures[2].properties.$exception_personURL).to.match( | ||
/http:\/\/localhost:\d+\/project\/test_token\/person\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/ | ||
) | ||
}) | ||
}) | ||
|
||
it('sets stacktrace on manual captures if autocapture enabled', () => { | ||
cy.get('[data-cy-exception-button]').click() | ||
|
||
// ugh | ||
cy.wait(1500) | ||
|
||
cy.phCaptures({ full: true }).then((captures) => { | ||
expect(captures[2].properties.$exception_message).to.be.eql('wat even am I') | ||
expect(captures[2].properties.$exception_stack_trace_raw).to.exist | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { EXCEPTION_CAPTURE_ENDPOINT_SUFFIX } from './constants' | ||
import { PostHog } from './posthog-core' | ||
import { DecideResponse, Properties } from './types' | ||
import { isObject } from './utils/type-utils' | ||
|
||
// TODO: move this to /x/ as default | ||
export const BASE_ERROR_ENDPOINT_SUFFIX = '/e/' | ||
|
||
export class PostHogExceptions { | ||
private _endpointSuffix: string | ||
|
||
constructor(private readonly instance: PostHog) { | ||
// TODO: once BASE_ERROR_ENDPOINT_SUFFIX is no longer /e/ this can be removed | ||
this._endpointSuffix = | ||
this.instance.persistence?.props[EXCEPTION_CAPTURE_ENDPOINT_SUFFIX] || BASE_ERROR_ENDPOINT_SUFFIX | ||
} | ||
|
||
get endpoint() { | ||
// Always respect any api_host set by the client config | ||
return this.instance.requestRouter.endpointFor('api', this._endpointSuffix) | ||
} | ||
|
||
afterDecideResponse(response: DecideResponse) { | ||
const autocaptureExceptionsResponse = response.autocaptureExceptions | ||
|
||
this._endpointSuffix = isObject(autocaptureExceptionsResponse) | ||
? autocaptureExceptionsResponse.endpoint || BASE_ERROR_ENDPOINT_SUFFIX | ||
: BASE_ERROR_ENDPOINT_SUFFIX | ||
|
||
if (this.instance.persistence) { | ||
// when we come to moving the endpoint to not /e/ | ||
// we'll want that to persist between startup and decide response | ||
// TODO: once BASE_ENDPOINT is no longer /e/ this can be removed | ||
this.instance.persistence.register({ | ||
[EXCEPTION_CAPTURE_ENDPOINT_SUFFIX]: this._endpointSuffix, | ||
}) | ||
} | ||
} | ||
|
||
/** | ||
* :TRICKY: Make sure we batch these requests | ||
*/ | ||
sendExceptionEvent(properties: Properties) { | ||
this.instance.capture('$exception', properties, { | ||
_noTruncate: true, | ||
_batchKey: 'exceptionEvent', | ||
_url: this.endpoint, | ||
}) | ||
} | ||
} |