diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /shared/logger/node_modules/@sentry/utils/esm/normalize.js | |
init commit
Diffstat (limited to 'shared/logger/node_modules/@sentry/utils/esm/normalize.js')
| -rw-r--r-- | shared/logger/node_modules/@sentry/utils/esm/normalize.js | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/shared/logger/node_modules/@sentry/utils/esm/normalize.js b/shared/logger/node_modules/@sentry/utils/esm/normalize.js new file mode 100644 index 0000000..5d52c58 --- /dev/null +++ b/shared/logger/node_modules/@sentry/utils/esm/normalize.js @@ -0,0 +1,263 @@ +import { isNaN, isSyntheticEvent } from './is.js'; +import { memoBuilder } from './memo.js'; +import { convertToPlainObject } from './object.js'; +import { getFunctionName } from './stacktrace.js'; + +/** + * Recursively normalizes the given object. + * + * - Creates a copy to prevent original input mutation + * - Skips non-enumerable properties + * - When stringifying, calls `toJSON` if implemented + * - Removes circular references + * - Translates non-serializable values (`undefined`/`NaN`/functions) to serializable format + * - Translates known global objects/classes to a string representations + * - Takes care of `Error` object serialization + * - Optionally limits depth of final output + * - Optionally limits number of properties/elements included in any single object/array + * + * @param input The object to be normalized. + * @param depth The max depth to which to normalize the object. (Anything deeper stringified whole.) + * @param maxProperties The max number of elements or properties to be included in any single array or + * object in the normallized output. + * @returns A normalized version of the object, or `"**non-serializable**"` if any errors are thrown during normalization. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function normalize(input, depth = 100, maxProperties = +Infinity) { + try { + // since we're at the outermost level, we don't provide a key + return visit('', input, depth, maxProperties); + } catch (err) { + return { ERROR: `**non-serializable** (${err})` }; + } +} + +/** JSDoc */ +function normalizeToSize( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + object, + // Default Node.js REPL depth + depth = 3, + // 100kB, as 200kB is max payload size, so half sounds reasonable + maxSize = 100 * 1024, +) { + const normalized = normalize(object, depth); + + if (jsonSize(normalized) > maxSize) { + return normalizeToSize(object, depth - 1, maxSize); + } + + return normalized ; +} + +/** + * Visits a node to perform normalization on it + * + * @param key The key corresponding to the given node + * @param value The node to be visited + * @param depth Optional number indicating the maximum recursion depth + * @param maxProperties Optional maximum number of properties/elements included in any single object/array + * @param memo Optional Memo class handling decycling + */ +function visit( + key, + value, + depth = +Infinity, + maxProperties = +Infinity, + memo = memoBuilder(), +) { + const [memoize, unmemoize] = memo; + + // Get the simple cases out of the way first + if ( + value == null || // this matches null and undefined -> eqeq not eqeqeq + (['number', 'boolean', 'string'].includes(typeof value) && !isNaN(value)) + ) { + return value ; + } + + const stringified = stringifyValue(key, value); + + // Anything we could potentially dig into more (objects or arrays) will have come back as `"[object XXXX]"`. + // Everything else will have already been serialized, so if we don't see that pattern, we're done. + if (!stringified.startsWith('[object ')) { + return stringified; + } + + // From here on, we can assert that `value` is either an object or an array. + + // Do not normalize objects that we know have already been normalized. As a general rule, the + // "__sentry_skip_normalization__" property should only be used sparingly and only should only be set on objects that + // have already been normalized. + if ((value )['__sentry_skip_normalization__']) { + return value ; + } + + // We can set `__sentry_override_normalization_depth__` on an object to ensure that from there + // We keep a certain amount of depth. + // This should be used sparingly, e.g. we use it for the redux integration to ensure we get a certain amount of state. + const remainingDepth = + typeof (value )['__sentry_override_normalization_depth__'] === 'number' + ? ((value )['__sentry_override_normalization_depth__'] ) + : depth; + + // We're also done if we've reached the max depth + if (remainingDepth === 0) { + // At this point we know `serialized` is a string of the form `"[object XXXX]"`. Clean it up so it's just `"[XXXX]"`. + return stringified.replace('object ', ''); + } + + // If we've already visited this branch, bail out, as it's circular reference. If not, note that we're seeing it now. + if (memoize(value)) { + return '[Circular ~]'; + } + + // If the value has a `toJSON` method, we call it to extract more information + const valueWithToJSON = value ; + if (valueWithToJSON && typeof valueWithToJSON.toJSON === 'function') { + try { + const jsonValue = valueWithToJSON.toJSON(); + // We need to normalize the return value of `.toJSON()` in case it has circular references + return visit('', jsonValue, remainingDepth - 1, maxProperties, memo); + } catch (err) { + // pass (The built-in `toJSON` failed, but we can still try to do it ourselves) + } + } + + // At this point we know we either have an object or an array, we haven't seen it before, and we're going to recurse + // because we haven't yet reached the max depth. Create an accumulator to hold the results of visiting each + // property/entry, and keep track of the number of items we add to it. + const normalized = (Array.isArray(value) ? [] : {}) ; + let numAdded = 0; + + // Before we begin, convert`Error` and`Event` instances into plain objects, since some of each of their relevant + // properties are non-enumerable and otherwise would get missed. + const visitable = convertToPlainObject(value ); + + for (const visitKey in visitable) { + // Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration. + if (!Object.prototype.hasOwnProperty.call(visitable, visitKey)) { + continue; + } + + if (numAdded >= maxProperties) { + normalized[visitKey] = '[MaxProperties ~]'; + break; + } + + // Recursively visit all the child nodes + const visitValue = visitable[visitKey]; + normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo); + + numAdded++; + } + + // Once we've visited all the branches, remove the parent from memo storage + unmemoize(value); + + // Return accumulated values + return normalized; +} + +/* eslint-disable complexity */ +/** + * Stringify the given value. Handles various known special values and types. + * + * Not meant to be used on simple primitives which already have a string representation, as it will, for example, turn + * the number 1231 into "[Object Number]", nor on `null`, as it will throw. + * + * @param value The value to stringify + * @returns A stringified representation of the given value + */ +function stringifyValue( + key, + // this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for + // our internal use, it'll do + value, +) { + try { + if (key === 'domain' && value && typeof value === 'object' && (value )._events) { + return '[Domain]'; + } + + if (key === 'domainEmitter') { + return '[DomainEmitter]'; + } + + // It's safe to use `global`, `window`, and `document` here in this manner, as we are asserting using `typeof` first + // which won't throw if they are not present. + + if (typeof global !== 'undefined' && value === global) { + return '[Global]'; + } + + // eslint-disable-next-line no-restricted-globals + if (typeof window !== 'undefined' && value === window) { + return '[Window]'; + } + + // eslint-disable-next-line no-restricted-globals + if (typeof document !== 'undefined' && value === document) { + return '[Document]'; + } + + // React's SyntheticEvent thingy + if (isSyntheticEvent(value)) { + return '[SyntheticEvent]'; + } + + if (typeof value === 'number' && value !== value) { + return '[NaN]'; + } + + if (typeof value === 'function') { + return `[Function: ${getFunctionName(value)}]`; + } + + if (typeof value === 'symbol') { + return `[${String(value)}]`; + } + + // stringified BigInts are indistinguishable from regular numbers, so we need to label them to avoid confusion + if (typeof value === 'bigint') { + return `[BigInt: ${String(value)}]`; + } + + // Now that we've knocked out all the special cases and the primitives, all we have left are objects. Simply casting + // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as + // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class), + // we can make sure that only plain objects come out that way. + const objName = getConstructorName(value); + + // Handle HTML Elements + if (/^HTML(\w*)Element$/.test(objName)) { + return `[HTMLElement: ${objName}]`; + } + + return `[object ${objName}]`; + } catch (err) { + return `**non-serializable** (${err})`; + } +} +/* eslint-enable complexity */ + +function getConstructorName(value) { + const prototype = Object.getPrototypeOf(value); + + return prototype ? prototype.constructor.name : 'null prototype'; +} + +/** Calculates bytes size of input string */ +function utf8Length(value) { + // eslint-disable-next-line no-bitwise + return ~-encodeURI(value).split(/%..|./).length; +} + +/** Calculates bytes size of input object */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function jsonSize(value) { + return utf8Length(JSON.stringify(value)); +} + +export { normalize, normalizeToSize, visit as walk }; +//# sourceMappingURL=normalize.js.map |
