diff --git a/packages/browser-repl/package.json b/packages/browser-repl/package.json index bad4a20162..1256cdd2a2 100644 --- a/packages/browser-repl/package.json +++ b/packages/browser-repl/package.json @@ -36,6 +36,7 @@ "@leafygreen-ui/palette": "^2.0.0", "@leafygreen-ui/syntax": "^2.2.0", "@mongosh/browser-runtime-core": "0.0.0-dev.0", + "@mongosh/errors": "0.0.0-dev.0", "@mongosh/history": "0.0.0-dev.0", "@mongosh/i18n": "0.0.0-dev.0", "@mongosh/node-runtime-worker-thread": "0.0.0-dev.0", diff --git a/packages/browser-repl/src/components/types/error-output.tsx b/packages/browser-repl/src/components/types/error-output.tsx index 7b43ce735c..5aae48e893 100644 --- a/packages/browser-repl/src/components/types/error-output.tsx +++ b/packages/browser-repl/src/components/types/error-output.tsx @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { isShouldReportAsBugError } from '@mongosh/errors'; import { SimpleTypeOutput } from './simple-type-output'; import { Expandable } from '../utils/expandable'; @@ -26,6 +27,16 @@ export class ErrorOutput extends Component { return this.props.value.stack.split('\n').slice(1).join('\n'); } + formatErrorBugReportInfo(): JSX.Element | undefined { + if (isShouldReportAsBugError(this.props.value)) { + return (
+ This is an error inside mongosh. + Please file a bug report for the MONGOSH project. +
); + } + return undefined; + } + formatErrorInfo(): JSX.Element | undefined { if (this.props.value.errInfo) { return (
@@ -50,6 +61,7 @@ export class ErrorOutput extends Component { return (
{this.renderCollapsed(toggle)}
+ {this.formatErrorBugReportInfo()} {this.formatErrorInfo()} {this.formatErrorResult()}
{this.formatStack()}
diff --git a/packages/cli-repl/src/cli-repl.ts b/packages/cli-repl/src/cli-repl.ts index 6b5853675b..c79174aad4 100644 --- a/packages/cli-repl/src/cli-repl.ts +++ b/packages/cli-repl/src/cli-repl.ts @@ -276,18 +276,21 @@ class CliRepl { } } + get logFilePath(): string { + return this.shellHomeDirectory.localPath(`${this.logId}_log`); + } + /** * Open a writable stream for the current log file. */ async openLogStream(): Promise { - const path = this.shellHomeDirectory.localPath(`${this.logId}_log`); await this.cleanupOldLogfiles(); try { - const stream = createWriteStream(path, { mode: 0o600 }); + const stream = createWriteStream(this.logFilePath, { mode: 0o600 }); await once(stream, 'ready'); return stream; } catch (err) { - this.warnAboutInaccessibleFile(err, path); + this.warnAboutInaccessibleFile(err, this.logFilePath); return new Writable({ write(chunk, enc, cb) { // Just ignore log data if there was an error. @@ -492,6 +495,10 @@ class CliRepl { throw e; } } + + bugReportErrorMessageInfo(): string { + return `Please include the log file for this session (${this.logFilePath}).`; + } } export default CliRepl; diff --git a/packages/cli-repl/src/format-output.ts b/packages/cli-repl/src/format-output.ts index cc2c6f4362..8e84822765 100644 --- a/packages/cli-repl/src/format-output.ts +++ b/packages/cli-repl/src/format-output.ts @@ -7,6 +7,7 @@ import util from 'util'; import stripAnsi from 'strip-ansi'; import clr from './clr'; import { HelpProperties, CollectionNamesWithTypes } from '@mongosh/shell-api'; +import { isShouldReportAsBugError } from '@mongosh/errors'; type EvaluationResult = { value: any; @@ -20,6 +21,7 @@ type FormatOptions = { maxArrayLength?: number; maxStringLength?: number; showStackTraces?: boolean; + bugReportErrorMessageInfo?: string; }; /** @@ -178,6 +180,12 @@ export function formatError(error: Error, options: FormatOptions): string { let result = ''; if (error.name) result += `\r${clr(error.name, ['bold', 'red'], options)}: `; if (error.message) result += error.message; + if (isShouldReportAsBugError(error)) { + result += '\nThis is an error inside mongosh. Please file a bug report for the MONGOSH project here: https://jira.mongodb.org/projects/MONGOSH/issues.'; + if (options.bugReportErrorMessageInfo) { + result += `\n${options.bugReportErrorMessageInfo}`; + } + } if (error.name === 'SyntaxError') { if (!options.colors) { // Babel applies syntax highlighting to its errors by default. diff --git a/packages/cli-repl/src/mongosh-repl.ts b/packages/cli-repl/src/mongosh-repl.ts index ec37cb424d..f7526ce5d5 100644 --- a/packages/cli-repl/src/mongosh-repl.ts +++ b/packages/cli-repl/src/mongosh-repl.ts @@ -31,6 +31,7 @@ export type MongoshIOProvider = Omit, 'validateCon exit(code?: number): Promise; readFileUTF8(filename: string): Promise<{ contents: string, absolutePath: string }>; startMongocryptd(): Promise; + bugReportErrorMessageInfo?(): string | undefined; }; export type MongoshNodeReplOptions = { @@ -599,14 +600,15 @@ class MongoshNodeRepl implements EvaluationListener { return clr(text, style, this.getFormatOptions()); } - getFormatOptions(): { colors: boolean, compact: number | boolean, depth: number, showStackTraces: boolean } { + getFormatOptions(): { colors: boolean, compact: number | boolean, depth: number, showStackTraces: boolean, bugReportErrorMessageInfo?: string } { const output = this.output as WriteStream; return { colors: this._runtimeState?.repl?.useColors ?? (output.isTTY && output.getColorDepth() > 1), compact: this.inspectCompact, depth: this.inspectDepth, - showStackTraces: this.showStackTraces + showStackTraces: this.showStackTraces, + bugReportErrorMessageInfo: this.ioProvider.bugReportErrorMessageInfo?.() }; } diff --git a/packages/cli-repl/test/e2e.spec.ts b/packages/cli-repl/test/e2e.spec.ts index 6f3a5ebb8a..e30280ef8c 100644 --- a/packages/cli-repl/test/e2e.spec.ts +++ b/packages/cli-repl/test/e2e.spec.ts @@ -124,6 +124,12 @@ describe('e2e', function() { shell.writeInputLine('process.exitCode = 42; quit()'); expect(await onExit).to.equal(42); }); + it('decorates internal errors with bug reporting information', async() => { + const err = await shell.executeLine('throw Object.assign(new Error("foo"), { code: "COMMON-90001" })'); + expect(err).to.match(/^Error: foo$/m); + expect(err).to.match(/^This is an error inside mongosh\. Please file a bug report for the MONGOSH project here: https:\/\/jira.mongodb.org\/projects\/MONGOSH\/issues\.$/m); + expect(err).to.match(/^Please include the log file for this session \(.+[/\\][a-f0-9]{24}_log\)\.$/m); + }); }); describe('set db', () => { for (const { mode, dbname, dbnameUri } of [ diff --git a/packages/errors/src/index.spec.ts b/packages/errors/src/index.spec.ts index 5a4ce8eca5..c842c86e39 100644 --- a/packages/errors/src/index.spec.ts +++ b/packages/errors/src/index.spec.ts @@ -26,7 +26,7 @@ describe('errors', () => { const error = new MongoshInternalError('Something went wrong.'); expect(error).to.be.instanceOf(MongoshBaseError); expect(error.name).to.be.equal('MongoshInternalError'); - expect(error.message).to.be.equal('[COMMON-90001] Something went wrong.\nThis is an error inside mongosh. Please file a bug report for the MONGOSH project here: https://jira.mongodb.org/projects/MONGOSH/issues.'); + expect(error.message).to.be.equal('[COMMON-90001] Something went wrong.'); expect(error.code).to.be.equal(CommonErrors.UnexpectedInternalError); expect(error.scope).to.be.equal('COMMON'); }); diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index d03b564779..18bcdfb87b 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -8,6 +8,10 @@ function getScopeFromErrorCode(code: string | null | undefined): string | undefi return !match ? undefined : match[1]; } +function isShouldReportAsBugError(err: Error & { code?: string }): boolean { + return err?.code === CommonErrors.UnexpectedInternalError; +} + abstract class MongoshBaseError extends Error { readonly code: string | undefined; readonly scope: string | undefined; @@ -32,8 +36,7 @@ class MongoshInternalError extends MongoshBaseError { constructor(message: string, metadata?: Object) { super( 'MongoshInternalError', - `${message} -This is an error inside mongosh. Please file a bug report for the MONGOSH project here: https://jira.mongodb.org/projects/MONGOSH/issues.`, + message, CommonErrors.UnexpectedInternalError, metadata ); @@ -82,6 +85,7 @@ class MongoshCommandFailed extends MongoshBaseError { export { getScopeFromErrorCode, + isShouldReportAsBugError, MongoshBaseError, MongoshWarning, MongoshRuntimeError,