-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathconsoleObservable.ts
More file actions
117 lines (96 loc) · 3.71 KB
/
Copy pathconsoleObservable.ts
File metadata and controls
117 lines (96 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { clocksNow } from '@datadog/js-core/time'
import { ConsoleApiName, globalConsole, jsonStringify } from '@datadog/js-core/util'
import { isError, computeRawError } from '../error/error'
import { Observable, mergeObservables } from '../../tools/observable'
import { callMonitored } from '../../tools/monitor'
import { sanitize } from '../../tools/serialisation/sanitize'
import type { RawError } from '../error/error.types'
import { ErrorHandling, ErrorSource, NonErrorPrefix } from '../error/error.types'
import { computeStackTrace } from '../../tools/stackTrace/computeStackTrace'
import { createHandlingStack, formatErrorMessage } from '../../tools/stackTrace/handlingStack'
export type ConsoleLog = NonErrorConsoleLog | ErrorConsoleLog
interface NonErrorConsoleLog extends ConsoleLogBase {
api: Exclude<ConsoleApiName, typeof ConsoleApiName.error>
error: undefined
}
export interface ErrorConsoleLog extends ConsoleLogBase {
api: typeof ConsoleApiName.error
error: RawError
}
interface ConsoleLogBase {
message: string
api: ConsoleApiName
handlingStack: string
}
type ConsoleLogForApi<T extends ConsoleApiName> = T extends typeof ConsoleApiName.error
? ErrorConsoleLog
: NonErrorConsoleLog
let consoleObservablesByApi: { [K in ConsoleApiName]?: Observable<ConsoleLogForApi<K>> } = {}
export function initConsoleObservable<T extends ConsoleApiName[]>(apis: T): Observable<ConsoleLogForApi<T[number]>> {
const consoleObservables = apis.map((api) => {
if (!consoleObservablesByApi[api]) {
consoleObservablesByApi[api] = createConsoleObservable(api) as any // we are sure that the observable created for this api will yield the expected ConsoleLog type
}
return consoleObservablesByApi[api] as unknown as Observable<ConsoleLogForApi<T[number]>>
})
return mergeObservables(...consoleObservables)
}
export function resetConsoleObservable() {
consoleObservablesByApi = {}
}
function createConsoleObservable(api: ConsoleApiName) {
return new Observable<ConsoleLog>((observable) => {
const originalConsoleApi = globalConsole[api]
globalConsole[api] = (...params: unknown[]) => {
originalConsoleApi.apply(console, params)
const handlingStack = createHandlingStack('console error')
callMonitored(() => {
observable.notify(buildConsoleLog(params, api, handlingStack))
})
}
return () => {
globalConsole[api] = originalConsoleApi
}
})
}
function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack: string): ConsoleLog {
const message = params.map((param) => formatConsoleParameters(param)).join(' ')
if (api === ConsoleApiName.error) {
const firstErrorParam = params.find(isError)
const rawError = computeRawError({
originalError: firstErrorParam,
handlingStack,
startClocks: clocksNow(),
source: ErrorSource.CONSOLE,
handling: ErrorHandling.HANDLED,
nonErrorPrefix: NonErrorPrefix.PROVIDED,
// if no good stack is computed from the error, let's not use the fallback stack message
// advising the user to use an instance of Error, as console.error is commonly used without an
// Error instance.
useFallbackStack: false,
})
// Use the full log message as the error message instead of just the error instance message.
rawError.message = message
return {
api,
message,
handlingStack,
error: rawError,
}
}
return {
api,
message,
error: undefined,
handlingStack,
}
}
function formatConsoleParameters(param: unknown) {
if (typeof param === 'string') {
return sanitize(param)
}
if (isError(param)) {
return formatErrorMessage(computeStackTrace(param))
}
return jsonStringify(sanitize(param), undefined, 2)
}