Skip to content

Commit

Permalink
retrieve fingerprint from console.error calls
Browse files Browse the repository at this point in the history
  • Loading branch information
bcaudan committed Apr 25, 2023
1 parent 36d95f7 commit 51c4202
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 5 deletions.
25 changes: 25 additions & 0 deletions packages/core/src/domain/console/consoleObservable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,29 @@ describe('console error observable', () => {
expect(stack).toContain('TypeError: foo')
}
})

it('should retrieve fingerprint from error', () => {
interface DatadogError extends Error {
dd_fingerprint?: string
}
const error = new Error('foo')
;(error as DatadogError).dd_fingerprint = 'my-fingerprint'

// eslint-disable-next-line no-console
console.error(error)

const consoleLog = notifyLog.calls.mostRecent().args[0]
expect(consoleLog.fingerprint).toBe('my-fingerprint')
})

it('should sanitize error fingerprint', () => {
const error = new Error('foo')
;(error as any).dd_fingerprint = 2

// eslint-disable-next-line no-console
console.error(error)

const consoleLog = notifyLog.calls.mostRecent().args[0]
expect(consoleLog.fingerprint).toBe('2')
})
})
12 changes: 10 additions & 2 deletions packages/core/src/domain/console/consoleObservable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { computeStackTrace } from '../tracekit'
import { createHandlingStack, formatErrorMessage, toStackTraceString } from '../error/error'
import { createHandlingStack, formatErrorMessage, toStackTraceString, isErrorWithFingerprint } from '../error/error'
import { mergeObservables, Observable } from '../../tools/observable'
import { ConsoleApiName } from '../../tools/display'
import { callMonitored } from '../../tools/monitor'
Expand All @@ -12,9 +12,10 @@ export interface ConsoleLog {
api: ConsoleApiName
stack?: string
handlingStack?: string
fingerprint?: string
}

const consoleObservablesByApi: { [k in ConsoleApiName]?: Observable<ConsoleLog> } = {}
let consoleObservablesByApi: { [k in ConsoleApiName]?: Observable<ConsoleLog> } = {}

export function initConsoleObservable(apis: ConsoleApiName[]) {
const consoleObservables = apis.map((api) => {
Expand All @@ -27,6 +28,10 @@ export function initConsoleObservable(apis: ConsoleApiName[]) {
return mergeObservables<ConsoleLog>(...consoleObservables)
}

export function resetConsoleObservable() {
consoleObservablesByApi = {}
}

/* eslint-disable no-console */
function createConsoleObservable(api: ConsoleApiName) {
const observable = new Observable<ConsoleLog>(() => {
Expand All @@ -53,10 +58,12 @@ function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack:
// Todo: remove console error prefix in the next major version
let message = params.map((param) => formatConsoleParameters(param)).join(' ')
let stack
let fingerprint

if (api === ConsoleApiName.error) {
const firstErrorParam = find(params, (param: unknown): param is Error => param instanceof Error)
stack = firstErrorParam ? toStackTraceString(computeStackTrace(firstErrorParam)) : undefined
fingerprint = isErrorWithFingerprint(firstErrorParam) ? String(firstErrorParam.dd_fingerprint) : undefined
message = `console error: ${message}`
}

Expand All @@ -65,6 +72,7 @@ function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack:
message,
stack,
handlingStack,
fingerprint,
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/domain/error/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function hasUsableStack(isErrorInstance: boolean, stackTrace?: StackTrace): stac
return stackTrace.stack.length > 0 && (stackTrace.stack.length > 1 || stackTrace.stack[0].url !== undefined)
}

function isErrorWithFingerprint(error: unknown): error is { dd_fingerprint: unknown } {
export function isErrorWithFingerprint(error: unknown): error is { dd_fingerprint: unknown } {
return error instanceof Error && 'dd_fingerprint' in error
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export { initFetchObservable, FetchResolveContext, FetchStartContext, FetchConte
export { createPageExitObservable, PageExitEvent, PageExitReason, isPageExitReason } from './browser/pageExitObservable'
export * from './browser/addEventListener'
export * from './tools/timer'
export { initConsoleObservable, ConsoleLog } from './domain/console/consoleObservable'
export { initConsoleObservable, resetConsoleObservable, ConsoleLog } from './domain/console/consoleObservable'
export { BoundedBuffer } from './tools/boundedBuffer'
export { catchUserErrors } from './tools/catchUserErrors'
export { createContextManager, ContextManager } from './tools/serialisation/contextManager'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ describe('console collection', () => {
expect(rawLogsEvents[0].rawLogsEvent.error).toEqual({
origin: ErrorSource.CONSOLE,
stack: undefined,
fingerprint: undefined,
})
})

it('should retrieve fingerprint from console error', () => {
;({ stop: stopConsoleCollection } = startConsoleCollection(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardErrorsToLogs: true })!,
lifeCycle
))
interface DatadogError extends Error {
dd_fingerprint?: string
}
const error = new Error('foo')
;(error as DatadogError).dd_fingerprint = 'my-fingerprint'

// eslint-disable-next-line no-console
console.error(error)

expect(rawLogsEvents[0].rawLogsEvent.error).toEqual({
origin: ErrorSource.CONSOLE,
stack: jasmine.any(String),
fingerprint: 'my-fingerprint',
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function startConsoleCollection(configuration: LogsConfiguration, lifeCyc
? {
origin: ErrorSource.CONSOLE, // Todo: Remove in the next major release
stack: log.stack,
fingerprint: log.fingerprint,
}
: undefined,
status: LogStatusForApi[log.api],
Expand Down
1 change: 1 addition & 0 deletions packages/logs/src/rawLogsEvent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Error = {
kind?: string
origin: ErrorSource // Todo: Remove in the next major release
stack?: string
fingerprint?: string
[k: string]: unknown
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RawError, Subscription } from '@datadog/browser-core'
import { ErrorHandling, ErrorSource, Observable, clocksNow } from '@datadog/browser-core'
import { ErrorHandling, ErrorSource, Observable, clocksNow, resetConsoleObservable } from '@datadog/browser-core'
import type { Clock } from '@datadog/browser-core/test'
import { mockClock } from '@datadog/browser-core/test'
import { trackConsoleError } from './trackConsoleError'
Expand All @@ -20,6 +20,7 @@ describe('trackConsoleError', () => {
})

afterEach(() => {
resetConsoleObservable()
subscription.unsubscribe()
clock.cleanup()
})
Expand All @@ -32,9 +33,31 @@ describe('trackConsoleError', () => {
startClocks: clocksNow(),
message: jasmine.any(String),
stack: jasmine.any(String),
fingerprint: undefined,
source: ErrorSource.CONSOLE,
handling: ErrorHandling.HANDLED,
handlingStack: jasmine.any(String),
})
})

it('should retrieve fingerprint from console error', () => {
interface DatadogError extends Error {
dd_fingerprint?: string
}
const error = new Error('foo')
;(error as DatadogError).dd_fingerprint = 'my-fingerprint'

// eslint-disable-next-line no-console
console.error(error)

expect(notifyLog).toHaveBeenCalledWith({
startClocks: clocksNow(),
message: jasmine.any(String),
stack: jasmine.any(String),
source: ErrorSource.CONSOLE,
handling: ErrorHandling.HANDLED,
handlingStack: jasmine.any(String),
fingerprint: 'my-fingerprint',
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export function trackConsoleError(errorObservable: Observable<RawError>) {
startClocks: clocksNow(),
message: consoleError.message,
stack: consoleError.stack,
fingerprint: consoleError.fingerprint,
source: ErrorSource.CONSOLE,
handling: ErrorHandling.HANDLED,
handlingStack: consoleError.handlingStack,
Expand Down

0 comments on commit 51c4202

Please sign in to comment.