Prepare for marshal error tagging
Extend `assert.error` with optional `errorName` for `console` to show.
MODIFY
packages/ses/NEWS.md
packages/ses/NEWS.md
@@ -2,7 +2,15 @@ User-visible changes in SES:
## Next Release
-- No changes yet
+- Added to `assert.error` an optional options bag with an option named
+ `errorName`. `assert.error` makes error objects with detailed error
+ messages whose unredacted contents appear only in `console` output
+ and are otherwise unobservable. If `errorName` is provided, then
+ it is also used in the console output instead of the normal error name,
+ but is otherwise unobservable. This will rarely be used, but will be
+ used by the `@agoric/marshal` package to name an unserialized error
+ so that it can be traced back to the site that serialized it; and
+ ultimately to its origin.
## Release 0.12.6 (27-Mar-2021)

MODIFY
packages/ses/src/error/assert.js
packages/ses/src/error/assert.js
@@ -153,12 +153,37 @@ const getLogArgs = ({ template, args }) => {
*/
const hiddenMessageLogArgs = new WeakMap();
+// So each error tag will be unique.
+let errorTagNum = 0;
+
+/**
+ * @type {WeakMap<Error, string>}
+ */
+const errorTags = new WeakMap();
+
+/**
+ * @param {Error} err
+ * @param {string=} optErrorName
+ * @returns {string}
+ */
+const tagError = (err, optErrorName = err.name) => {
+ let errorTag = errorTags.get(err);
+ if (errorTag !== undefined) {
+ return errorTag;
+ }
+ errorTagNum += 1;
+ errorTag = `${optErrorName}#${errorTagNum}`;
+ errorTags.set(err, errorTag);
+ return errorTag;
+};
+
/**
* @type {AssertMakeError}
*/
const makeError = (
optDetails = redactedDetails`Assert failed`,
ErrorConstructor = Error,
+ { errorName = undefined } = {},
) => {
if (typeof optDetails === 'string') {
// If it is a string, use it as the literal part of the template so
@@ -172,6 +197,9 @@ const makeError = (
const messageString = getMessageString(hiddenDetails);
const error = new ErrorConstructor(messageString);
hiddenMessageLogArgs.set(error, getLogArgs(hiddenDetails));
+ if (errorName !== undefined) {
+ tagError(error, errorName);
+ }
// The next line is a particularly fruitful place to put a breakpoint.
return error;
};
@@ -256,6 +284,11 @@ const defaultGetStackString = error => {
/** @type {LoggedErrorHandler} */
const loggedErrorHandler = {
getStackString: globalThis.getStackString || defaultGetStackString,
+ tagError: error => tagError(error),
+ resetErrorTagNum: () => {
+ errorTagNum = 0;
+ },
+ getMessageLogArgs: error => hiddenMessageLogArgs.get(error),
takeMessageLogArgs: error => {
const result = hiddenMessageLogArgs.get(error);
hiddenMessageLogArgs.delete(error);

MODIFY
packages/ses/src/error/console.js
packages/ses/src/error/console.js
@@ -108,7 +108,14 @@ export const consoleWhitelist = freeze([
// /////////////////////////////////////////////////////////////////////////////
/** @type {MakeLoggingConsoleKit} */
-const makeLoggingConsoleKit = () => {
+const makeLoggingConsoleKit = (
+ loggedErrorHandler,
+ { shouldResetForDebugging = false } = {},
+) => {
+ if (shouldResetForDebugging) {
+ loggedErrorHandler.resetErrorTagNum();
+ }
+
// Not frozen!
let logArray = [];
@@ -165,34 +172,11 @@ export const BASE_CONSOLE_LEVEL = 'debug';
const makeCausalConsole = (baseConsole, loggedErrorHandler) => {
const {
getStackString,
+ tagError,
takeMessageLogArgs,
takeNoteLogArgsArray,
} = loggedErrorHandler;
- // by "tagged", we mean first sent to the baseConsole as an argument in a
- // console level method call, in which it is shown with an identifying tag
- // number. We number the errors according to the order in
- // which they were first logged to the baseConsole, starting at 1.
- let numErrorsTagged = 0;
- /** @type WeakMap<Error, number> */
- const errorTagOrder = new WeakMap();
-
- /**
- * @param {Error} err
- * @returns {string}
- */
- const tagError = err => {
- let errNum;
- if (errorTagOrder.has(err)) {
- errNum = errorTagOrder.get(err);
- } else {
- numErrorsTagged += 1;
- errorTagOrder.set(err, numErrorsTagged);
- errNum = numErrorsTagged;
- }
- return `${err.name}#${errNum}`;
- };
-
/**
* @param {ReadonlyArray<any>} logArgs
* @param {Array<any>} subErrorsSink

MODIFY
packages/ses/src/error/internal-types.js
packages/ses/src/error/internal-types.js
* hidden information to augment the logging of errors.
*
* @property {GetStackString} getStackString
+ * @property {(error: Error) => string} tagError
+ * @property {() => void} resetErrorTagNum for debugging purposes only
+ * @property {(error: Error) => (LogArgs | undefined)} getMessageLogArgs
* @property {(error: Error) => (LogArgs | undefined)} takeMessageLogArgs
* @property {(error: Error, callback?: NoteCallback) => LogArgs[] } takeNoteLogArgsArray
*/
* @property {() => readonly LogRecord[]} takeLog
*/
+/**
+ * @typedef {Object} MakeLoggingConsoleKitOptions
+ * @property {boolean=} shouldResetForDebugging
+ */
+
/**
* @callback MakeLoggingConsoleKit
*
* consumes these, so later calls to `takeLog()` will only provide a log of
* calls that have happened since then.
*
+ * @param {LoggedErrorHandler} loggedErrorHandler
+ * @param {MakeLoggingConsoleKitOptions=} options
* @returns {LoggingConsoleKit}
*/

MODIFY
packages/ses/src/error/types.js
packages/ses/src/error/types.js
-// Much of this file is duplicated at
-// https://github.com/Agoric/agoric-sdk/blob/master/packages/assert/src/types.js
-// Coordinate edits until we refactor to avoid this duplication
-
// @ts-check
/**
* @returns {asserts flag}
*/
+/**
+ * @typedef {Object} AssertMakeErrorOptions
+ * @property {string=} errorName
+ */
+
/**
* @callback AssertMakeError
*
* @param {Details=} optDetails The details of what was asserted
* @param {ErrorConstructor=} ErrorConstructor An optional alternate error
* constructor to use.
+ * @param {AssertMakeErrorOptions=} options
* @returns {Error}
*/

MODIFY
packages/ses/test/error/test-assert-log.js
packages/ses/test/error/test-assert-log.js
@@ -314,6 +314,35 @@ test('assert error explicit', t => {
);
});
+test('assert error named', t => {
+ const err = assert.error(d`<${'bar'},${q('baz')}>`, URIError, {
+ errorName: 'Foo-Err',
+ });
+ t.is(err.message, '<(a string),"baz">');
+ t.is(err.name, 'URIError');
+ throwsAndLogs(
+ t,
+ () => {
+ throw err;
+ },
+ /<\(a string\),"baz">/,
+ [['log', 'Caught', URIError]],
+ );
+ throwsAndLogs(
+ t,
+ () => {
+ throw err;
+ },
+ /<\(a string\),"baz">/,
+ [
+ ['log', 'Caught', '(Foo-Err#2)'],
+ ['debug', 'Foo-Err#2:', '<', 'bar', ',', 'baz', '>'],
+ ['debug', 'stack of URIError\n'],
+ ],
+ { wrapWithCausal: true },
+ );
+});
+
test('assert q', t => {
throwsAndLogs(
t,

MODIFY
packages/ses/test/error/throws-and-logs.js
packages/ses/test/error/throws-and-logs.js
@@ -79,7 +79,10 @@ const getBogusStackString = error => {
// ```
export const assertLogs = freeze((t, thunk, goldenLog, options = {}) => {
const { checkLogs = true, wrapWithCausal = false } = options;
- const { loggingConsole, takeLog } = makeLoggingConsoleKit();
+ const { loggingConsole, takeLog } = makeLoggingConsoleKit(
+ loggedErrorHandler,
+ { shouldResetForDebugging: true },
+ );
let useConsole = console;
if (checkLogs) {
useConsole = loggingConsole;

Prepare for marshal error tagging (#649)

Extend `assert.error` with optional `errorName` for `console` to show.