UNPKG

next

Version:

The React Framework

417 lines (415 loc) 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 0 && (module.exports = { PROMISE_MARKER: null, UNAVAILABLE_MARKER: null, forwardErrorLog: null, forwardUnhandledError: null, initializeDebugLogForwarding: null, isTerminalLoggingEnabled: null, logQueue: null, logStringify: null, logUnhandledRejection: null, preLogSerializationClone: null }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { PROMISE_MARKER: function() { return PROMISE_MARKER; }, UNAVAILABLE_MARKER: function() { return UNAVAILABLE_MARKER; }, forwardErrorLog: function() { return forwardErrorLog; }, forwardUnhandledError: function() { return forwardUnhandledError; }, initializeDebugLogForwarding: function() { return initializeDebugLogForwarding; }, isTerminalLoggingEnabled: function() { return isTerminalLoggingEnabled; }, logQueue: function() { return logQueue; }, logStringify: function() { return logStringify; }, logUnhandledRejection: function() { return logUnhandledRejection; }, preLogSerializationClone: function() { return preLogSerializationClone; } }); const _safestablestringify = require("next/dist/compiled/safe-stable-stringify"); const _stitchederror = require("./errors/stitched-error"); const _errorsource = require("../../../shared/lib/error-source"); const _terminalloggingconfig = require("./terminal-logging-config"); const _forwardlogsshared = require("../../shared/forward-logs-shared"); const terminalLoggingConfig = (0, _terminalloggingconfig.getTerminalLoggingConfig)(); const PROMISE_MARKER = 'Promise {}'; const UNAVAILABLE_MARKER = '[Unable to view]'; const maximumDepth = typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.depthLimit ? terminalLoggingConfig.depthLimit : 5; const maximumBreadth = typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.edgeLimit ? terminalLoggingConfig.edgeLimit : 100; const stringify = (0, _safestablestringify.configure)({ maximumDepth, maximumBreadth }); const isTerminalLoggingEnabled = (0, _terminalloggingconfig.getIsTerminalLoggingEnabled)(); const methods = [ 'log', 'info', 'warn', 'debug', 'table', 'assert', 'dir', 'dirxml', 'group', 'groupCollapsed', 'groupEnd', 'trace' ]; function preLogSerializationClone(value, seen) { if (seen === void 0) seen = new WeakMap(); if (value === undefined) return _forwardlogsshared.UNDEFINED_MARKER; if (value === null || typeof value !== 'object') return value; if (seen.has(value)) return seen.get(value); try { Object.keys(value); } catch (e) { return UNAVAILABLE_MARKER; } try { if (typeof value.then === 'function') return PROMISE_MARKER; } catch (e) { return UNAVAILABLE_MARKER; } if (Array.isArray(value)) { const out = []; seen.set(value, out); for (const item of value){ try { out.push(preLogSerializationClone(item, seen)); } catch (e) { out.push(UNAVAILABLE_MARKER); } } return out; } const proto = Object.getPrototypeOf(value); if (proto === Object.prototype || proto === null) { const out = {}; seen.set(value, out); for (const key of Object.keys(value)){ try { out[key] = preLogSerializationClone(value[key], seen); } catch (e) { out[key] = UNAVAILABLE_MARKER; } } return out; } return Object.prototype.toString.call(value); } const logStringify = (data)=>{ try { const result = stringify(data); return result != null ? result : '"' + UNAVAILABLE_MARKER + '"'; } catch (e) { return '"' + UNAVAILABLE_MARKER + '"'; } }; const afterThisFrame = (cb)=>{ let timeout; const rafId = requestAnimationFrame(()=>{ timeout = setTimeout(()=>{ cb(); }); }); return ()=>{ cancelAnimationFrame(rafId); clearTimeout(timeout); }; }; let isPatched = false; const serializeEntries = (entries)=>entries.map((clientEntry)=>{ switch(clientEntry.kind){ case 'any-logged-error': case 'console': { return { ...clientEntry, args: clientEntry.args.map(stringifyUserArg) }; } case 'formatted-error': { return clientEntry; } default: { return null; } } }); const logQueue = { entries: [], flushScheduled: false, cancelFlush: null, socket: null, sourceType: undefined, router: null, scheduleLogSend: (entry)=>{ logQueue.entries.push(entry); if (logQueue.flushScheduled) { return; } // safe to deref and use in setTimeout closure since we cancel on new socket const socket = logQueue.socket; if (!socket) { return; } // we probably dont need this logQueue.flushScheduled = true; // non blocking log flush, runs at most once per frame logQueue.cancelFlush = afterThisFrame(()=>{ logQueue.flushScheduled = false; // just incase try { const payload = JSON.stringify({ event: 'browser-logs', entries: serializeEntries(logQueue.entries), router: logQueue.router, // needed for source mapping, we just assign the sourceType from the last error for the whole batch sourceType: logQueue.sourceType }); socket.send(payload); logQueue.entries = []; logQueue.sourceType = undefined; } catch (e) { // error (make sure u don't infinite loop) /* noop */ } }); }, onSocketReady: (socket)=>{ if (socket.readyState !== WebSocket.OPEN) { // invariant return; } // incase an existing timeout was going to run with a stale socket logQueue.cancelFlush == null ? void 0 : logQueue.cancelFlush.call(logQueue); logQueue.socket = socket; try { const payload = JSON.stringify({ event: 'browser-logs', entries: serializeEntries(logQueue.entries), router: logQueue.router, sourceType: logQueue.sourceType }); socket.send(payload); logQueue.entries = []; logQueue.sourceType = undefined; } catch (e) { /** noop just incase */ } } }; const stringifyUserArg = (arg)=>{ if (arg.kind !== 'arg') { return arg; } return { ...arg, data: logStringify(arg.data) }; }; const createErrorArg = (error)=>{ const stack = stackWithOwners(error); return { kind: 'formatted-error-arg', prefix: error.message ? error.name + ": " + error.message : "" + error.name, stack }; }; const createLogEntry = (level, args)=>{ // do not abstract this, it implicitly relies on which functions call it. forcing the inlined implementation makes you think about callers // error capture stack trace maybe const stack = stackWithOwners(new Error()); const stackLines = stack == null ? void 0 : stack.split('\n'); const cleanStack = stackLines == null ? void 0 : stackLines.slice(3).join('\n') // this is probably ignored anyways ; const entry = { kind: 'console', consoleMethodStack: cleanStack != null ? cleanStack : null, method: level, args: args.map((arg)=>{ if (arg instanceof Error) { return createErrorArg(arg); } return { kind: 'arg', data: preLogSerializationClone(arg) }; }) }; logQueue.scheduleLogSend(entry); }; const forwardErrorLog = (args)=>{ const errorObjects = args.filter((arg)=>arg instanceof Error); const first = errorObjects.at(0); if (first) { const source = (0, _errorsource.getErrorSource)(first); if (source) { logQueue.sourceType = source; } } /** * browser shows stack regardless of type of data passed to console.error, so we should do the same * * do not abstract this, it implicitly relies on which functions call it. forcing the inlined implementation makes you think about callers */ const stack = stackWithOwners(new Error()); const stackLines = stack == null ? void 0 : stack.split('\n'); const cleanStack = stackLines == null ? void 0 : stackLines.slice(3).join('\n'); const entry = { kind: 'any-logged-error', method: 'error', consoleErrorStack: cleanStack != null ? cleanStack : '', args: args.map((arg)=>{ if (arg instanceof Error) { return createErrorArg(arg); } return { kind: 'arg', data: preLogSerializationClone(arg) }; }) }; logQueue.scheduleLogSend(entry); }; const createUncaughtErrorEntry = (errorName, errorMessage, fullStack)=>{ const entry = { kind: 'formatted-error', prefix: "Uncaught " + errorName + ": " + errorMessage, stack: fullStack, method: 'error' }; logQueue.scheduleLogSend(entry); }; const stackWithOwners = (error)=>{ let ownerStack = ''; (0, _stitchederror.setOwnerStackIfAvailable)(error); ownerStack = (0, _stitchederror.getOwnerStack)(error) || ''; const stack = (error.stack || '') + ownerStack; return stack; }; function logUnhandledRejection(reason) { if (reason instanceof Error) { createUnhandledRejectionErrorEntry(reason, stackWithOwners(reason)); return; } createUnhandledRejectionNonErrorEntry(reason); } const createUnhandledRejectionErrorEntry = (error, fullStack)=>{ const source = (0, _errorsource.getErrorSource)(error); if (source) { logQueue.sourceType = source; } const entry = { kind: 'formatted-error', prefix: "⨯ unhandledRejection: " + error.name + ": " + error.message, stack: fullStack, method: 'error' }; logQueue.scheduleLogSend(entry); }; const createUnhandledRejectionNonErrorEntry = (reason)=>{ const entry = { kind: 'any-logged-error', // we can't access the stack since the event is dispatched async and creating an inline error would be meaningless consoleErrorStack: '', method: 'error', args: [ { kind: 'arg', data: "⨯ unhandledRejection:", isRejectionMessage: true }, { kind: 'arg', data: preLogSerializationClone(reason) } ] }; logQueue.scheduleLogSend(entry); }; const isHMR = (args)=>{ const firstArg = args[0]; if (typeof firstArg !== 'string') { return false; } if (firstArg.startsWith('[Fast Refresh]')) { return true; } if (firstArg.startsWith('[HMR]')) { return true; } return false; }; const isIgnoredLog = (args)=>{ if (args.length < 3) { return false; } const [format, styles, label] = args; if (typeof format !== 'string' || typeof styles !== 'string' || typeof label !== 'string') { return false; } // kinda hacky, we should define a common format for these strings so we can safely ignore return format.startsWith('%c%s%c') && styles.includes('background:'); }; function forwardUnhandledError(error) { createUncaughtErrorEntry(error.name, error.message, stackWithOwners(error)); } const initializeDebugLogForwarding = (router)=>{ // probably don't need this if (isPatched) { return; } // TODO(rob): why does this break rendering on server, important to know incase the same bug appears in browser if (typeof window === 'undefined') { return; } // better to be safe than sorry try { methods.forEach((method)=>(0, _forwardlogsshared.patchConsoleMethod)(method, function(_) { for(var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++){ args[_key - 1] = arguments[_key]; } if (isHMR(args)) { return; } if (isIgnoredLog(args)) { return; } createLogEntry(method, args); })); } catch (e) {} logQueue.router = router; isPatched = true; }; if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') { Object.defineProperty(exports.default, '__esModule', { value: true }); Object.assign(exports.default, exports); module.exports = exports.default; } //# sourceMappingURL=forward-logs.js.map