Comparing version
@@ -154,4 +154,4 @@ 'use strict'; | ||
const DEFAULT_LIMIT = 5; | ||
const linkedErrorsIntegration = core.defineIntegration((options = { limit: DEFAULT_LIMIT }) => { | ||
const DEFAULT_LIMIT$1 = 5; | ||
const linkedErrorsIntegration = core.defineIntegration((options = { limit: DEFAULT_LIMIT$1 }) => { | ||
return { | ||
@@ -394,2 +394,172 @@ name: 'LinkedErrors', | ||
/** | ||
* Define an integration function that can be used to create an integration instance. | ||
* Note that this by design hides the implementation details of the integration, as they are considered internal. | ||
* | ||
* Inlined from https://github.com/getsentry/sentry-javascript/blob/develop/packages/core/src/integration.ts#L165 | ||
*/ | ||
function defineIntegration(fn) { | ||
return fn; | ||
} | ||
// Adapted from: https://github.com/getsentry/sentry-javascript/blob/develop/packages/core/src/integrations/zoderrors.ts | ||
const INTEGRATION_NAME = 'ZodErrors'; | ||
const DEFAULT_LIMIT = 10; | ||
function originalExceptionIsZodError(originalException) { | ||
return (utils.isError(originalException) && | ||
originalException.name === 'ZodError' && | ||
Array.isArray(originalException.errors)); | ||
} | ||
/** | ||
* Formats child objects or arrays to a string | ||
* that is preserved when sent to Sentry. | ||
* | ||
* Without this, we end up with something like this in Sentry: | ||
* | ||
* [ | ||
* [Object], | ||
* [Object], | ||
* [Object], | ||
* [Object] | ||
* ] | ||
*/ | ||
function flattenIssue(issue) { | ||
return { | ||
...issue, | ||
path: 'path' in issue && Array.isArray(issue.path) | ||
? issue.path.join('.') | ||
: undefined, | ||
keys: 'keys' in issue ? JSON.stringify(issue.keys) : undefined, | ||
unionErrors: 'unionErrors' in issue ? JSON.stringify(issue.unionErrors) : undefined, | ||
}; | ||
} | ||
/** | ||
* Takes ZodError issue path array and returns a flattened version as a string. | ||
* This makes it easier to display paths within a Sentry error message. | ||
* | ||
* Array indexes are normalized to reduce duplicate entries | ||
* | ||
* @param path ZodError issue path | ||
* @returns flattened path | ||
* | ||
* @example | ||
* flattenIssuePath([0, 'foo', 1, 'bar']) // -> '<array>.foo.<array>.bar' | ||
*/ | ||
function flattenIssuePath(path) { | ||
return path | ||
.map((p) => { | ||
if (typeof p === 'number') { | ||
return '<array>'; | ||
} | ||
else { | ||
return p; | ||
} | ||
}) | ||
.join('.'); | ||
} | ||
/** | ||
* Zod error message is a stringified version of ZodError.issues | ||
* This doesn't display well in the Sentry UI. Replace it with something shorter. | ||
*/ | ||
function formatIssueMessage(zodError) { | ||
const errorKeyMap = new Set(); | ||
for (const iss of zodError.issues) { | ||
const issuePath = flattenIssuePath(iss.path); | ||
if (issuePath.length > 0) { | ||
errorKeyMap.add(issuePath); | ||
} | ||
} | ||
const errorKeys = Array.from(errorKeyMap); | ||
if (errorKeys.length === 0) { | ||
// If there are no keys, then we're likely validating the root | ||
// variable rather than a key within an object. This attempts | ||
// to extract what type it was that failed to validate. | ||
// For example, z.string().parse(123) would return "string" here. | ||
let rootExpectedType = 'variable'; | ||
if (zodError.issues.length > 0) { | ||
const iss = zodError.issues[0]; | ||
if ('expected' in iss && typeof iss.expected === 'string') { | ||
rootExpectedType = iss.expected; | ||
} | ||
} | ||
return `Failed to validate ${rootExpectedType}`; | ||
} | ||
return `Failed to validate keys: ${utils.truncate(errorKeys.join(', '), 100)}`; | ||
} | ||
/** | ||
* Applies ZodError issues to an event context and replaces the error message | ||
*/ | ||
function applyZodErrorsToEvent(limit, event, saveAttachments, hint) { | ||
if (event.exception === undefined || | ||
event.exception.values === undefined || | ||
hint === undefined || | ||
hint.originalException === undefined || | ||
!originalExceptionIsZodError(hint.originalException) || | ||
hint.originalException.issues.length === 0) { | ||
return event; | ||
} | ||
try { | ||
const flattenedIssues = hint.originalException.errors.map(flattenIssue); | ||
if (saveAttachments === true) { | ||
// Add an attachment with all issues (no limits), as well as the default | ||
// flatten format to see if it's preferred over our custom flatten format. | ||
if (!Array.isArray(hint.attachments)) { | ||
hint.attachments = []; | ||
} | ||
hint.attachments.push({ | ||
filename: 'zod_issues.json', | ||
data: JSON.stringify({ | ||
issueDetails: hint.originalException.flatten(flattenIssue), | ||
}), | ||
}); | ||
} | ||
return { | ||
...event, | ||
exception: { | ||
...event.exception, | ||
values: [ | ||
{ | ||
...event.exception.values[0], | ||
value: formatIssueMessage(hint.originalException), | ||
}, | ||
...event.exception.values.slice(1), | ||
], | ||
}, | ||
extra: { | ||
...event.extra, | ||
'zoderror.issues': flattenedIssues.slice(0, limit), | ||
}, | ||
}; | ||
} | ||
catch (e) { | ||
// Hopefully we never throw errors here, but record it | ||
// with the event just in case. | ||
return { | ||
...event, | ||
extra: { | ||
...event.extra, | ||
'zoderrors sentry integration parse error': { | ||
message: `an exception was thrown while processing ZodError within applyZodErrorsToEvent()`, | ||
error: e instanceof Error | ||
? `${e.name}: ${e.message}\n${e.stack}` | ||
: 'unknown', | ||
}, | ||
}, | ||
}; | ||
} | ||
} | ||
/** | ||
* Sentry integration to process Zod errors, making them easier to work with in Sentry. | ||
*/ | ||
const zodErrorsIntegration = defineIntegration((options = {}) => { | ||
const limit = options.limit ?? DEFAULT_LIMIT; | ||
return { | ||
name: INTEGRATION_NAME, | ||
processEvent(originalEvent, hint) { | ||
const processedEvent = applyZodErrorsToEvent(limit, originalEvent, options.saveAttachments, hint); | ||
return processedEvent; | ||
}, | ||
}; | ||
}); | ||
/** | ||
* Installs integrations on the current scope. | ||
@@ -452,6 +622,6 @@ * | ||
name: 'npm:' + 'toucan-js', | ||
version: '4.0.0', | ||
version: '4.1.0', | ||
}, | ||
], | ||
version: '4.0.0', | ||
version: '4.1.0', | ||
}; | ||
@@ -611,2 +781,3 @@ super(options); | ||
linkedErrorsIntegration(), | ||
zodErrorsIntegration(), | ||
]), | ||
@@ -613,0 +784,0 @@ ]; |
@@ -1,3 +0,3 @@ | ||
import { GLOBAL_OBJ, isError, isPlainObject, extractExceptionKeysForMessage, normalizeToSize, addExceptionTypeValue, addExceptionMechanism, isInstanceOf, resolvedSyncPromise, createStackParser, nodeStackLineParser, basename, rejectedSyncPromise, stackParserFromStackParserOptions } from '@sentry/utils'; | ||
import { defineIntegration, ServerRuntimeClient, createTransport, Scope, getIntegrationsToSetup } from '@sentry/core'; | ||
import { GLOBAL_OBJ, isError, isPlainObject, extractExceptionKeysForMessage, normalizeToSize, addExceptionTypeValue, addExceptionMechanism, isInstanceOf, truncate, resolvedSyncPromise, createStackParser, nodeStackLineParser, basename, rejectedSyncPromise, stackParserFromStackParserOptions } from '@sentry/utils'; | ||
import { defineIntegration as defineIntegration$1, ServerRuntimeClient, createTransport, Scope, getIntegrationsToSetup } from '@sentry/core'; | ||
export { dedupeIntegration, extraErrorDataIntegration, rewriteFramesIntegration, sessionTimingIntegration } from '@sentry/core'; | ||
@@ -153,4 +153,4 @@ | ||
const DEFAULT_LIMIT = 5; | ||
const linkedErrorsIntegration = defineIntegration((options = { limit: DEFAULT_LIMIT }) => { | ||
const DEFAULT_LIMIT$1 = 5; | ||
const linkedErrorsIntegration = defineIntegration$1((options = { limit: DEFAULT_LIMIT$1 }) => { | ||
return { | ||
@@ -188,3 +188,3 @@ name: 'LinkedErrors', | ||
}; | ||
const requestDataIntegration = defineIntegration((userOptions = {}) => { | ||
const requestDataIntegration = defineIntegration$1((userOptions = {}) => { | ||
const options = { ...defaultRequestDataOptions, ...userOptions }; | ||
@@ -394,2 +394,172 @@ return { | ||
/** | ||
* Define an integration function that can be used to create an integration instance. | ||
* Note that this by design hides the implementation details of the integration, as they are considered internal. | ||
* | ||
* Inlined from https://github.com/getsentry/sentry-javascript/blob/develop/packages/core/src/integration.ts#L165 | ||
*/ | ||
function defineIntegration(fn) { | ||
return fn; | ||
} | ||
// Adapted from: https://github.com/getsentry/sentry-javascript/blob/develop/packages/core/src/integrations/zoderrors.ts | ||
const INTEGRATION_NAME = 'ZodErrors'; | ||
const DEFAULT_LIMIT = 10; | ||
function originalExceptionIsZodError(originalException) { | ||
return (isError(originalException) && | ||
originalException.name === 'ZodError' && | ||
Array.isArray(originalException.errors)); | ||
} | ||
/** | ||
* Formats child objects or arrays to a string | ||
* that is preserved when sent to Sentry. | ||
* | ||
* Without this, we end up with something like this in Sentry: | ||
* | ||
* [ | ||
* [Object], | ||
* [Object], | ||
* [Object], | ||
* [Object] | ||
* ] | ||
*/ | ||
function flattenIssue(issue) { | ||
return { | ||
...issue, | ||
path: 'path' in issue && Array.isArray(issue.path) | ||
? issue.path.join('.') | ||
: undefined, | ||
keys: 'keys' in issue ? JSON.stringify(issue.keys) : undefined, | ||
unionErrors: 'unionErrors' in issue ? JSON.stringify(issue.unionErrors) : undefined, | ||
}; | ||
} | ||
/** | ||
* Takes ZodError issue path array and returns a flattened version as a string. | ||
* This makes it easier to display paths within a Sentry error message. | ||
* | ||
* Array indexes are normalized to reduce duplicate entries | ||
* | ||
* @param path ZodError issue path | ||
* @returns flattened path | ||
* | ||
* @example | ||
* flattenIssuePath([0, 'foo', 1, 'bar']) // -> '<array>.foo.<array>.bar' | ||
*/ | ||
function flattenIssuePath(path) { | ||
return path | ||
.map((p) => { | ||
if (typeof p === 'number') { | ||
return '<array>'; | ||
} | ||
else { | ||
return p; | ||
} | ||
}) | ||
.join('.'); | ||
} | ||
/** | ||
* Zod error message is a stringified version of ZodError.issues | ||
* This doesn't display well in the Sentry UI. Replace it with something shorter. | ||
*/ | ||
function formatIssueMessage(zodError) { | ||
const errorKeyMap = new Set(); | ||
for (const iss of zodError.issues) { | ||
const issuePath = flattenIssuePath(iss.path); | ||
if (issuePath.length > 0) { | ||
errorKeyMap.add(issuePath); | ||
} | ||
} | ||
const errorKeys = Array.from(errorKeyMap); | ||
if (errorKeys.length === 0) { | ||
// If there are no keys, then we're likely validating the root | ||
// variable rather than a key within an object. This attempts | ||
// to extract what type it was that failed to validate. | ||
// For example, z.string().parse(123) would return "string" here. | ||
let rootExpectedType = 'variable'; | ||
if (zodError.issues.length > 0) { | ||
const iss = zodError.issues[0]; | ||
if ('expected' in iss && typeof iss.expected === 'string') { | ||
rootExpectedType = iss.expected; | ||
} | ||
} | ||
return `Failed to validate ${rootExpectedType}`; | ||
} | ||
return `Failed to validate keys: ${truncate(errorKeys.join(', '), 100)}`; | ||
} | ||
/** | ||
* Applies ZodError issues to an event context and replaces the error message | ||
*/ | ||
function applyZodErrorsToEvent(limit, event, saveAttachments, hint) { | ||
if (event.exception === undefined || | ||
event.exception.values === undefined || | ||
hint === undefined || | ||
hint.originalException === undefined || | ||
!originalExceptionIsZodError(hint.originalException) || | ||
hint.originalException.issues.length === 0) { | ||
return event; | ||
} | ||
try { | ||
const flattenedIssues = hint.originalException.errors.map(flattenIssue); | ||
if (saveAttachments === true) { | ||
// Add an attachment with all issues (no limits), as well as the default | ||
// flatten format to see if it's preferred over our custom flatten format. | ||
if (!Array.isArray(hint.attachments)) { | ||
hint.attachments = []; | ||
} | ||
hint.attachments.push({ | ||
filename: 'zod_issues.json', | ||
data: JSON.stringify({ | ||
issueDetails: hint.originalException.flatten(flattenIssue), | ||
}), | ||
}); | ||
} | ||
return { | ||
...event, | ||
exception: { | ||
...event.exception, | ||
values: [ | ||
{ | ||
...event.exception.values[0], | ||
value: formatIssueMessage(hint.originalException), | ||
}, | ||
...event.exception.values.slice(1), | ||
], | ||
}, | ||
extra: { | ||
...event.extra, | ||
'zoderror.issues': flattenedIssues.slice(0, limit), | ||
}, | ||
}; | ||
} | ||
catch (e) { | ||
// Hopefully we never throw errors here, but record it | ||
// with the event just in case. | ||
return { | ||
...event, | ||
extra: { | ||
...event.extra, | ||
'zoderrors sentry integration parse error': { | ||
message: `an exception was thrown while processing ZodError within applyZodErrorsToEvent()`, | ||
error: e instanceof Error | ||
? `${e.name}: ${e.message}\n${e.stack}` | ||
: 'unknown', | ||
}, | ||
}, | ||
}; | ||
} | ||
} | ||
/** | ||
* Sentry integration to process Zod errors, making them easier to work with in Sentry. | ||
*/ | ||
const zodErrorsIntegration = defineIntegration((options = {}) => { | ||
const limit = options.limit ?? DEFAULT_LIMIT; | ||
return { | ||
name: INTEGRATION_NAME, | ||
processEvent(originalEvent, hint) { | ||
const processedEvent = applyZodErrorsToEvent(limit, originalEvent, options.saveAttachments, hint); | ||
return processedEvent; | ||
}, | ||
}; | ||
}); | ||
/** | ||
* Installs integrations on the current scope. | ||
@@ -452,6 +622,6 @@ * | ||
name: 'npm:' + 'toucan-js', | ||
version: '4.0.0', | ||
version: '4.1.0', | ||
}, | ||
], | ||
version: '4.0.0', | ||
version: '4.1.0', | ||
}; | ||
@@ -611,2 +781,3 @@ super(options); | ||
linkedErrorsIntegration(), | ||
zodErrorsIntegration(), | ||
]), | ||
@@ -613,0 +784,0 @@ ]; |
export * from './linkedErrors'; | ||
export * from './requestData'; | ||
export { zodErrorsIntegration } from './zod/zoderrors'; | ||
export { dedupeIntegration, extraErrorDataIntegration, rewriteFramesIntegration, sessionTimingIntegration, } from '@sentry/core'; | ||
//# sourceMappingURL=index.d.ts.map |
{ | ||
"name": "toucan-js", | ||
"sideEffects": false, | ||
"version": "4.0.0", | ||
"version": "4.1.0", | ||
"description": "Cloudflare Workers client for Sentry", | ||
@@ -37,3 +37,3 @@ "main": "dist/index.cjs.js", | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "25.0.3", | ||
"@rollup/plugin-commonjs": "26.0.1", | ||
"@rollup/plugin-node-resolve": "15.1.0", | ||
@@ -48,3 +48,3 @@ "@rollup/plugin-replace": "5.0.2", | ||
"jest": "29.6.1", | ||
"miniflare": "2.14.0", | ||
"miniflare": "3.20240701.0", | ||
"jest-environment-miniflare": "2.10.0", | ||
@@ -51,0 +51,0 @@ "ts-jest": "29.1.1", |
@@ -63,2 +63,3 @@ <p align="center"> | ||
- [requestDataIntegration](src/integrations/requestData.ts) | ||
- [zodErrorsIntegration](src/integrations/zod/zoderrors.ts) | ||
@@ -65,0 +66,0 @@ ### Custom integration example: |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
91818
21.6%39
18.18%2066
24.98%108
0.93%