Skip to content

Commit b4ff6d9

Browse files
authored
refactor: separate forward browser logs utils (#84151)
1 parent 33e4a5b commit b4ff6d9

File tree

9 files changed

+138
-152
lines changed

9 files changed

+138
-152
lines changed

packages/next/src/client/dev/hot-reloader/app/web-socket.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import {
1212
processMessage,
1313
type StaticIndicatorState,
1414
} from './hot-reloader-app'
15-
import {
16-
isTerminalLoggingEnabled,
17-
logQueue,
18-
} from '../../../../next-devtools/userspace/app/forward-logs'
15+
import { logQueue } from '../../../../next-devtools/userspace/app/forward-logs'
1916
import { InvariantError } from '../../../../shared/lib/invariant-error'
2017
import { WEB_SOCKET_MAX_RECONNECTIONS } from '../../../../lib/constants'
2118

@@ -57,9 +54,7 @@ export function createWebSocket(
5754
newWebSocket.binaryType = 'arraybuffer'
5855

5956
function handleOnline() {
60-
if (isTerminalLoggingEnabled) {
61-
logQueue.onSocketReady(newWebSocket)
62-
}
57+
logQueue.onSocketReady(newWebSocket)
6358

6459
reconnections = 0
6560
window.console.log('[HMR] connected')

packages/next/src/client/dev/hot-reloader/pages/websocket.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {
2-
isTerminalLoggingEnabled,
3-
logQueue,
4-
} from '../../../../next-devtools/userspace/app/forward-logs'
1+
import { logQueue } from '../../../../next-devtools/userspace/app/forward-logs'
52
import {
63
HMR_MESSAGE_SENT_TO_BROWSER,
74
type HmrMessageSentToBrowser,
@@ -35,9 +32,7 @@ export function connectHMR(options: { path: string; assetPrefix: string }) {
3532
if (source) source.close()
3633

3734
function handleOnline() {
38-
if (isTerminalLoggingEnabled) {
39-
logQueue.onSocketReady(source)
40-
}
35+
logQueue.onSocketReady(source)
4136
reconnections = 0
4237
window.console.log('[HMR] connected')
4338
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import { patchConsoleError } from './errors/intercept-console-error'
22
import { handleGlobalErrors } from './errors/use-error-handler'
3-
import {
4-
initializeDebugLogForwarding,
5-
isTerminalLoggingEnabled,
6-
} from './forward-logs'
3+
import { initializeDebugLogForwarding } from './forward-logs'
74

85
handleGlobalErrors()
96
patchConsoleError()
107

11-
if (isTerminalLoggingEnabled) {
12-
initializeDebugLogForwarding('app')
13-
}
8+
initializeDebugLogForwarding('app')

packages/next/src/next-devtools/userspace/app/errors/intercept-console-error.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import isError from '../../../../lib/is-error'
22
import { isNextRouterError } from '../../../../client/components/is-next-router-error'
33
import { handleConsoleError } from './use-error-handler'
44
import { parseConsoleArgs } from '../../../../client/lib/console'
5-
import { forwardErrorLog, isTerminalLoggingEnabled } from '../forward-logs'
5+
import { forwardErrorLog } from '../forward-logs'
66

77
export const originConsoleError = globalThis.console.error
88

@@ -37,9 +37,7 @@ export function patchConsoleError() {
3737
args
3838
)
3939
}
40-
if (isTerminalLoggingEnabled) {
41-
forwardErrorLog(args)
42-
}
40+
forwardErrorLog(args)
4341

4442
originConsoleError.apply(window.console, args)
4543
}

packages/next/src/next-devtools/userspace/app/errors/use-error-handler.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@ import {
77
import isError from '../../../../lib/is-error'
88
import { createConsoleError } from '../../../shared/console-error'
99
import { coerceError, setOwnerStackIfAvailable } from './stitched-error'
10-
import {
11-
forwardUnhandledError,
12-
isTerminalLoggingEnabled,
13-
logUnhandledRejection,
14-
} from '../forward-logs'
10+
import { forwardUnhandledError, logUnhandledRejection } from '../forward-logs'
1511

1612
const queueMicroTask =
1713
globalThis.queueMicrotask || ((cb: () => void) => Promise.resolve().then(cb))
@@ -100,9 +96,7 @@ function onUnhandledError(event: WindowEventMap['error']): void | boolean {
10096
const error = coerceError(thrownValue)
10197
setOwnerStackIfAvailable(error)
10298
handleClientError(error)
103-
if (isTerminalLoggingEnabled) {
104-
forwardUnhandledError(error)
105-
}
99+
forwardUnhandledError(error)
106100
}
107101
}
108102

@@ -121,9 +115,7 @@ function onUnhandledRejection(ev: WindowEventMap['unhandledrejection']): void {
121115
handler(error)
122116
}
123117

124-
if (isTerminalLoggingEnabled) {
125-
logUnhandledRejection(reason)
126-
}
118+
logUnhandledRejection(reason)
127119
}
128120

129121
export function handleGlobalErrors() {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { configure } from 'next/dist/compiled/safe-stable-stringify'
2+
import { getTerminalLoggingConfig } from './terminal-logging-config'
3+
import { UNDEFINED_MARKER } from '../../shared/forward-logs-shared'
4+
5+
const terminalLoggingConfig = getTerminalLoggingConfig()
6+
7+
const PROMISE_MARKER = 'Promise {}'
8+
const UNAVAILABLE_MARKER = '[Unable to view]'
9+
10+
const maximumDepth =
11+
typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.depthLimit
12+
? terminalLoggingConfig.depthLimit
13+
: 5
14+
const maximumBreadth =
15+
typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.edgeLimit
16+
? terminalLoggingConfig.edgeLimit
17+
: 100
18+
19+
const stringify = configure({
20+
maximumDepth,
21+
maximumBreadth,
22+
})
23+
24+
/**
25+
* allows us to:
26+
* - revive the undefined log in the server as it would look in the browser
27+
* - not read/attempt to serialize promises (next will console error if you do that, and will cause this program to infinitely recurse)
28+
* - if we read a proxy that throws (no way to detect if something is a proxy), explain to the user we can't read this data
29+
*/
30+
export function preLogSerializationClone<T>(
31+
value: T,
32+
seen = new WeakMap()
33+
): any {
34+
if (value === undefined) return UNDEFINED_MARKER
35+
if (value === null || typeof value !== 'object') return value
36+
if (seen.has(value as object)) return seen.get(value as object)
37+
38+
try {
39+
Object.keys(value as object)
40+
} catch {
41+
return UNAVAILABLE_MARKER
42+
}
43+
44+
try {
45+
if (typeof (value as any).then === 'function') return PROMISE_MARKER
46+
} catch {
47+
return UNAVAILABLE_MARKER
48+
}
49+
50+
if (Array.isArray(value)) {
51+
const out: any[] = []
52+
seen.set(value, out)
53+
for (const item of value) {
54+
try {
55+
out.push(preLogSerializationClone(item, seen))
56+
} catch {
57+
out.push(UNAVAILABLE_MARKER)
58+
}
59+
}
60+
return out
61+
}
62+
63+
const proto = Object.getPrototypeOf(value)
64+
if (proto === Object.prototype || proto === null) {
65+
const out: Record<string, unknown> = {}
66+
seen.set(value as object, out)
67+
for (const key of Object.keys(value as object)) {
68+
try {
69+
out[key] = preLogSerializationClone((value as any)[key], seen)
70+
} catch {
71+
out[key] = UNAVAILABLE_MARKER
72+
}
73+
}
74+
return out
75+
}
76+
77+
return Object.prototype.toString.call(value)
78+
}
79+
80+
// only safe if passed safeClone data
81+
export const logStringify = (data: unknown): string => {
82+
try {
83+
const result = stringify(data)
84+
return result ?? `"${UNAVAILABLE_MARKER}"`
85+
} catch {
86+
return `"${UNAVAILABLE_MARKER}"`
87+
}
88+
}

packages/next/src/next-devtools/userspace/app/forward-logs.test.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import { UNDEFINED_MARKER } from '../../shared/forward-logs-shared'
2-
import {
3-
preLogSerializationClone,
4-
PROMISE_MARKER,
5-
UNAVAILABLE_MARKER,
6-
logStringify,
7-
} from './forward-logs'
1+
import { preLogSerializationClone, logStringify } from './forward-logs-utils'
82

93
const safeStringify = (data: unknown) =>
104
logStringify(preLogSerializationClone(data))
@@ -16,7 +10,9 @@ describe('forward-logs serialization', () => {
1610
expect(preLogSerializationClone('hello')).toBe('hello')
1711
expect(preLogSerializationClone(true)).toBe(true)
1812
expect(preLogSerializationClone(null)).toBe(null)
19-
expect(preLogSerializationClone(undefined)).toBe(UNDEFINED_MARKER)
13+
expect(preLogSerializationClone(undefined)).toBe(
14+
'__next_tagged_undefined'
15+
)
2016
})
2117

2218
it('should handle circular references', () => {
@@ -29,19 +25,19 @@ describe('forward-logs serialization', () => {
2925

3026
it('should handle promises', () => {
3127
const promise = Promise.resolve(42)
32-
expect(preLogSerializationClone(promise)).toBe(PROMISE_MARKER)
28+
expect(preLogSerializationClone(promise)).toBe('Promise {}')
3329
})
3430

3531
it('should handle arrays', () => {
3632
const arr = [1, 'test', undefined, null]
3733
const cloned = preLogSerializationClone(arr)
38-
expect(cloned).toEqual([1, 'test', UNDEFINED_MARKER, null])
34+
expect(cloned).toEqual([1, 'test', '__next_tagged_undefined', null])
3935
})
4036

4137
it('should handle plain objects', () => {
4238
const obj = { a: 1, b: undefined, c: 'test' }
4339
const cloned = preLogSerializationClone(obj)
44-
expect(cloned).toEqual({ a: 1, b: UNDEFINED_MARKER, c: 'test' })
40+
expect(cloned).toEqual({ a: 1, b: '__next_tagged_undefined', c: 'test' })
4541
})
4642

4743
it('should handle objects with getters that throw', () => {
@@ -54,7 +50,7 @@ describe('forward-logs serialization', () => {
5450

5551
const cloned = preLogSerializationClone(obj)
5652
expect(cloned.normalProp).toBe('works')
57-
expect(cloned.throwingGetter).toBe(UNAVAILABLE_MARKER)
53+
expect(cloned.throwingGetter).toBe('[Unable to view]')
5854
})
5955

6056
it('should handle non-plain objects as toString', () => {
@@ -80,7 +76,7 @@ describe('forward-logs serialization', () => {
8076
const arr = [1, throwingProxy, 'normal']
8177
const cloned = preLogSerializationClone(arr)
8278

83-
expect(cloned).toEqual([1, UNAVAILABLE_MARKER, 'normal'])
79+
expect(cloned).toEqual([1, '[Unable to view]', 'normal'])
8480
})
8581
})
8682

@@ -89,7 +85,7 @@ describe('forward-logs serialization', () => {
8985
expect(safeStringify(42)).toBe('42')
9086
expect(safeStringify('hello')).toBe('"hello"')
9187
expect(safeStringify(null)).toBe('null')
92-
expect(safeStringify(undefined)).toBe(`"${UNDEFINED_MARKER}"`)
88+
expect(safeStringify(undefined)).toBe(`"__next_tagged_undefined"`)
9389
})
9490

9591
it('should handle objects with circular references', () => {
@@ -108,7 +104,7 @@ describe('forward-logs serialization', () => {
108104
}
109105

110106
const result = safeStringify(problematicData)
111-
expect(result).toBe(`"${UNAVAILABLE_MARKER}"`)
107+
expect(result).toBe(`"[Unable to view]"`)
112108
})
113109
})
114110
})

0 commit comments

Comments
 (0)