-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
9b48504
commit 78c8390
Showing
15 changed files
with
526 additions
and
212 deletions.
There are no files selected for viewing
108 changes: 108 additions & 0 deletions
108
packages/core/src/domain/console/consoleObservable.spec.ts
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,108 @@ | ||
/* eslint-disable no-console */ | ||
import { isIE } from '../../tools/browserDetection' | ||
import type { Subscription } from '../../tools/observable' | ||
import type { ConsoleLog } from './consoleObservable' | ||
import { ConsoleApiName, initConsoleObservable } from './consoleObservable' | ||
|
||
// prettier: avoid formatting issue | ||
// cf https://github.com/prettier/prettier/issues/12211 | ||
;[ConsoleApiName.log, ConsoleApiName.info, ConsoleApiName.warn, ConsoleApiName.debug, ConsoleApiName.error].forEach( | ||
(api) => { | ||
describe(`console ${api} observable`, () => { | ||
let consoleStub: jasmine.Spy | ||
let consoleSubscription: Subscription | ||
let notifyLog: jasmine.Spy | ||
|
||
beforeEach(() => { | ||
consoleStub = spyOn(console, api) | ||
notifyLog = jasmine.createSpy('notifyLog') | ||
|
||
consoleSubscription = initConsoleObservable([api]).subscribe(notifyLog) | ||
}) | ||
|
||
afterEach(() => { | ||
consoleSubscription.unsubscribe() | ||
}) | ||
|
||
it(`should notify ${api}`, () => { | ||
console[api]('foo', 'bar') | ||
|
||
const consoleLog = notifyLog.calls.mostRecent().args[0] | ||
|
||
expect(consoleLog).toEqual( | ||
jasmine.objectContaining({ | ||
message: `console ${api}: foo bar`, | ||
api, | ||
}) | ||
) | ||
}) | ||
|
||
it('should keep original behavior', () => { | ||
console[api]('foo', 'bar') | ||
|
||
expect(consoleStub).toHaveBeenCalledWith('foo', 'bar') | ||
}) | ||
|
||
it('should format error instance', () => { | ||
console[api](new TypeError('hello')) | ||
const consoleLog = notifyLog.calls.mostRecent().args[0] | ||
expect(consoleLog.message).toBe(`console ${api}: TypeError: hello`) | ||
}) | ||
|
||
it('should stringify object parameters', () => { | ||
console[api]('Hello', { foo: 'bar' }) | ||
const consoleLog = notifyLog.calls.mostRecent().args[0] | ||
expect(consoleLog.message).toBe(`console ${api}: Hello {\n "foo": "bar"\n}`) | ||
}) | ||
|
||
it('should allow multiple callers', () => { | ||
const notifyOtherCaller = jasmine.createSpy('notifyOtherCaller') | ||
const instrumentedConsoleApi = console[api] | ||
const otherConsoleSubscription = initConsoleObservable([api]).subscribe(notifyOtherCaller) | ||
|
||
console[api]('foo', 'bar') | ||
|
||
expect(instrumentedConsoleApi).toEqual(console[api]) | ||
expect(notifyLog).toHaveBeenCalledTimes(1) | ||
expect(notifyOtherCaller).toHaveBeenCalledTimes(1) | ||
|
||
otherConsoleSubscription.unsubscribe() | ||
}) | ||
}) | ||
} | ||
) | ||
|
||
describe('console error observable', () => { | ||
let consoleSubscription: Subscription | ||
let notifyLog: jasmine.Spy | ||
|
||
beforeEach(() => { | ||
spyOn(console, 'error').and.callFake(() => true) | ||
notifyLog = jasmine.createSpy('notifyLog') | ||
|
||
consoleSubscription = initConsoleObservable([ConsoleApiName.error]).subscribe(notifyLog) | ||
}) | ||
|
||
afterEach(() => { | ||
consoleSubscription.unsubscribe() | ||
}) | ||
|
||
it('should generate a handling stack', () => { | ||
function triggerError() { | ||
console.error('foo', 'bar') | ||
} | ||
triggerError() | ||
const consoleLog = notifyLog.calls.mostRecent().args[0] | ||
expect(consoleLog.handlingStack).toMatch(/^Error:\s+at triggerError (.|\n)*$/) | ||
}) | ||
|
||
it('should extract stack from first error', () => { | ||
console.error(new TypeError('foo'), new TypeError('bar')) | ||
const stack = (notifyLog.calls.mostRecent().args[0] as ConsoleLog).stack | ||
if (!isIE()) { | ||
expect(stack).toMatch(/^TypeError: foo\s+at/) | ||
} else { | ||
expect(stack).toContain('TypeError: foo') | ||
} | ||
}) | ||
}) |
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,82 @@ | ||
import { callMonitored } from '../internalMonitoring' | ||
import { computeStackTrace } from '../tracekit' | ||
import { createHandlingStack, formatErrorMessage, toStackTraceString } from '../../tools/error' | ||
import { mergeObservables, Observable } from '../../tools/observable' | ||
import { find, jsonStringify } from '../../tools/utils' | ||
|
||
export const ConsoleApiName = { | ||
log: 'log', | ||
debug: 'debug', | ||
info: 'info', | ||
warn: 'warn', | ||
error: 'error', | ||
} as const | ||
|
||
export type ConsoleApiName = typeof ConsoleApiName[keyof typeof ConsoleApiName] | ||
|
||
export interface ConsoleLog { | ||
message: string | ||
api: ConsoleApiName | ||
stack?: string | ||
handlingStack?: string | ||
} | ||
|
||
const consoleObservablesByApi: { [k in ConsoleApiName]?: Observable<ConsoleLog> } = {} | ||
|
||
export function initConsoleObservable(apis: ConsoleApiName[]) { | ||
const consoleObservables = apis.map((api) => { | ||
if (!consoleObservablesByApi[api]) { | ||
consoleObservablesByApi[api] = createConsoleObservable(api) | ||
} | ||
return consoleObservablesByApi[api]! | ||
}) | ||
|
||
return mergeObservables<ConsoleLog>(...consoleObservables) | ||
} | ||
|
||
/* eslint-disable no-console */ | ||
function createConsoleObservable(api: ConsoleApiName) { | ||
const observable = new Observable<ConsoleLog>(() => { | ||
const originalConsoleApi = console[api] | ||
|
||
console[api] = (...params: unknown[]) => { | ||
originalConsoleApi.apply(console, params) | ||
const handlingStack = createHandlingStack() | ||
|
||
callMonitored(() => { | ||
observable.notify(buildConsoleLog(params, api, handlingStack)) | ||
}) | ||
} | ||
|
||
return () => { | ||
console[api] = originalConsoleApi | ||
} | ||
}) | ||
|
||
return observable | ||
} | ||
|
||
function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack: string): ConsoleLog { | ||
const log: ConsoleLog = { | ||
message: [`console ${api}:`, ...params].map((param) => formatConsoleParameters(param)).join(' '), | ||
api, | ||
} | ||
|
||
if (api === ConsoleApiName.error) { | ||
const firstErrorParam = find(params, (param: unknown): param is Error => param instanceof Error) | ||
log.stack = firstErrorParam ? toStackTraceString(computeStackTrace(firstErrorParam)) : undefined | ||
log.handlingStack = handlingStack | ||
} | ||
|
||
return log | ||
} | ||
|
||
function formatConsoleParameters(param: unknown) { | ||
if (typeof param === 'string') { | ||
return param | ||
} | ||
if (param instanceof Error) { | ||
return formatErrorMessage(computeStackTrace(param)) | ||
} | ||
return jsonStringify(param, undefined, 2) | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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
Oops, something went wrong.