mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
chore(developer): improve kmc sentry reporting on fatal build errors
kmc already reported unhandled exceptions, but any handled fatal errors
were captured and only reported to the user. It is better to report
these to Sentry as these are still unexpected.
I have refactored all the fatal exception messages in various kmc
modules to use a common mechanism, keeping all the Sentry integration in
kmc, now passing exception data up in the `CompilerEvent.exceptionVar`
property.
* I took the opportunity to rename messages.ts to
infrastructureMessages.ts
* @types/chai was missing which gave intellisense errors in vscode
* normal exit of kmc now provides an opportunity for error reports to
Sentry to be finalized
* Added a unit test for fatal errors in kmc
* Added a manual test pathway with `SENTRY_CLIENT_TEST_BUILD_EXCEPTION`
env var to trip the build fatal error mechanism and verify that it
looks ok; the following shows test runs demonstrate how fatal build
errors are reported:
```
mcdurdin@THARK MINGW64 /c/Projects/keyman/app/developer/src/kmc (chore/developer/report-fatal-compiler-errors-to-sentry)
$ SENTRY_CLIENT_TEST_BUILD_EXCEPTION=1 node . --error-reporting build
fatal KM05001: Unexpected exception: Error: Test exception from SENTRY_CLIENT_TEST_BUILD_EXCEPTION
Call stack:
Error: Test exception from SENTRY_CLIENT_TEST_BUILD_EXCEPTION
at build (file:///C:/Projects/keyman/app/developer/src/kmc/build/src/commands/build.js:78:19)
at Command.<anonymous> (file:///C:/Projects/keyman/app/developer/src/kmc/build/src/commands/build.js:66:24)
at Command.listener [as _actionHandler] (C:\Projects\keyman\app\node_modules\commander\lib\command.js:482:17)
at C:\Projects\keyman\app\node_modules\commander\lib\command.js:1283:65
at Command._chainOrCall (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1177:12)
at Command._parseCommand (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1283:27)
at C:\Projects\keyman\app\node_modules\commander\lib\command.js:1081:27
at Command._chainOrCall (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1177:12)
at Command._dispatchSubcommand (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1077:23)
at Command._parseCommand (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1248:19)
This error has been automatically reported to the Keyman team.
Identifier: 6f0fca1a26694c22b03f02b2463d39c5
Application: Keyman Developer
Reported at: https://sentry.io/organizations/keyman/projects/keyman-developer/events/6f0fca1a26694c22b03f02b2463d39c5/
mcdurdin@THARK MINGW64 /c/Projects/keyman/app/developer/src/kmc (chore/developer/report-fatal-compiler-errors-to-sentry)
$ SENTRY_CLIENT_TEST_BUILD_EXCEPTION=1 node . --no-error-reporting build
fatal KM05001: Unexpected exception: Error: Test exception from SENTRY_CLIENT_TEST_BUILD_EXCEPTION
Call stack:
Error: Test exception from SENTRY_CLIENT_TEST_BUILD_EXCEPTION
at build (file:///C:/Projects/keyman/app/developer/src/kmc/build/src/commands/build.js:78:19)
at Command.<anonymous> (file:///C:/Projects/keyman/app/developer/src/kmc/build/src/commands/build.js:66:24)
at Command.listener [as _actionHandler] (C:\Projects\keyman\app\node_modules\commander\lib\command.js:482:17)
at C:\Projects\keyman\app\node_modules\commander\lib\command.js:1283:65
at Command._chainOrCall (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1177:12)
at Command._parseCommand (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1283:27)
at C:\Projects\keyman\app\node_modules\commander\lib\command.js:1081:27
at Command._chainOrCall (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1177:12)
at Command._dispatchSubcommand (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1077:23)
at Command._parseCommand (C:\Projects\keyman\app\node_modules\commander\lib\command.js:1248:19)
```
This commit is contained in:
parent
574e8505fd
commit
fa19eea873
23 changed files with 123 additions and 80 deletions
|
|
@ -6,6 +6,11 @@ export interface CompilerEvent {
|
|||
line?: number;
|
||||
code: number;
|
||||
message: string;
|
||||
/**
|
||||
* an internal error occurred that should be captured with a stack trace
|
||||
* e.g. to the Keyman sentry instance by kmc
|
||||
*/
|
||||
exceptionVar?: any;
|
||||
};
|
||||
|
||||
export enum CompilerErrorSeverity {
|
||||
|
|
@ -399,7 +404,13 @@ export const defaultCompilerOptions: CompilerOptions = {
|
|||
* @param message
|
||||
* @returns
|
||||
*/
|
||||
export const CompilerMessageSpec = (code: number, message: string) : CompilerEvent => { return { code, message } };
|
||||
export const CompilerMessageSpec = (code: number, message: string, exceptionVar?: any) : CompilerEvent => ({
|
||||
code,
|
||||
message: exceptionVar
|
||||
? (message ?? `Unexpected exception`) + `: ${exceptionVar.toString()}\n\nCall stack:\n${(exceptionVar instanceof Error ? exceptionVar.stack : (new Error()).stack)}` :
|
||||
message,
|
||||
exceptionVar
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated use `CompilerError.exceptionToString` instead
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, compilerExceptionToString as exc } from "@keymanapp/common-types";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.Analyzer;
|
||||
const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
|
|
@ -8,7 +8,7 @@ const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
|||
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
export class AnalyzerMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, `Unexpected exception: ${exc(o.e)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
|
||||
static Info_ScanningFile = (o:{type: string, name: string}) => m(this.INFO_ScanningFile,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ const SevError = CompilerErrorSeverity.Error | Namespace;
|
|||
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
export class KeyboardInfoCompilerMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException,
|
||||
`Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
|
||||
static Error_FileDoesNotExist = (o:{filename: string}) => m(this.ERROR_FileDoesNotExist, `File ${o.filename} does not exist.`);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, compilerExceptionToString as exc } from "@keymanapp/common-types";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m } from "@keymanapp/common-types";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.KmnCompiler;
|
||||
const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
|
|
@ -46,20 +46,21 @@ export const enum KmnCompilerMessageRanges {
|
|||
are reserved for kmcmplib messages.
|
||||
*/
|
||||
export class CompilerMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, `Unexpected exception: ${exc(o.e)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x900;
|
||||
|
||||
static Fatal_MissingWasmModule = (o:{e?: any}) => m(this.FATAL_MissingWasmModule, `Could not instantiate WASM compiler module or initialization failed: ${exc(o.e)}`);
|
||||
static Fatal_MissingWasmModule = (o:{e?: any}) => m(this.FATAL_MissingWasmModule,
|
||||
`Could not instantiate WASM compiler module or initialization failed`, o.e ?? 'unknown error');
|
||||
static FATAL_MissingWasmModule = SevFatal | 0x901;
|
||||
|
||||
// TODO: Is this now deprecated?
|
||||
static Fatal_UnableToSetCompilerOptions = () => m(this.FATAL_UnableToSetCompilerOptions, `Unable to set compiler options`);
|
||||
static Fatal_UnableToSetCompilerOptions = () => m(this.FATAL_UnableToSetCompilerOptions, null, `Unable to set compiler options`);
|
||||
static FATAL_UnableToSetCompilerOptions = SevFatal | 0x902;
|
||||
|
||||
static Fatal_CallbacksNotSet = () => m(this.FATAL_CallbacksNotSet, `Callbacks were not set with init`);
|
||||
static Fatal_CallbacksNotSet = () => m(this.FATAL_CallbacksNotSet, null, `Callbacks were not set with init`);
|
||||
static FATAL_CallbacksNotSet = SevFatal | 0x903;
|
||||
|
||||
static Fatal_UnicodeSetOutOfRange = () => m(this.FATAL_UnicodeSetOutOfRange, `UnicodeSet buffer was too small`);
|
||||
static Fatal_UnicodeSetOutOfRange = () => m(this.FATAL_UnicodeSetOutOfRange, null, `UnicodeSet buffer was too small`);
|
||||
static FATAL_UnicodeSetOutOfRange = SevFatal | 0x904;
|
||||
|
||||
static Error_UnicodeSetHasStrings = () => m(this.ERROR_UnicodeSetHasStrings, `UnicodeSet contains strings, not allowed`);
|
||||
|
|
@ -72,7 +73,7 @@ export class CompilerMessages {
|
|||
static ERROR_UnicodeSetSyntaxError = SevError | 0x907;
|
||||
|
||||
static Error_InvalidKvksFile = (o:{filename: string, e: any}) => m(this.ERROR_InvalidKvksFile,
|
||||
`Error encountered parsing ${o.filename}: ${o.e}`);
|
||||
`Error encountered parsing ${o.filename}: ${o.e ?? 'unknown error'}`); // Note, not fatal, not reporting to Sentry
|
||||
static ERROR_InvalidKvksFile = SevError | 0x908;
|
||||
|
||||
static Warn_InvalidVkeyInKvksFile = (o:{filename: string, invalidVkey: string}) => m(this.WARN_InvalidVkeyInKvksFile,
|
||||
|
|
@ -80,11 +81,11 @@ export class CompilerMessages {
|
|||
static WARN_InvalidVkeyInKvksFile = SevWarn | 0x909;
|
||||
|
||||
static Error_InvalidDisplayMapFile = (o:{filename: string, e: any}) => m(this.ERROR_InvalidDisplayMapFile,
|
||||
`Error encountered parsing display map ${o.filename}: ${o.e}`);
|
||||
`Error encountered parsing display map ${o.filename}: ${o.e ?? 'unknown error'}`); // Note, not fatal, not reporting to Sentry
|
||||
static ERROR_InvalidDisplayMapFile = SevError | 0x90A;
|
||||
|
||||
static Error_InvalidKvkFile = (o:{filename: string, e: any}) => m(this.ERROR_InvalidKvkFile,
|
||||
`Error encountered loading ${o.filename}: ${o.e}`);
|
||||
`Error encountered loading ${o.filename}: ${o.e ?? 'unknown error'}`); // Note, not fatal, not reporting to Sentry
|
||||
static ERROR_InvalidKvkFile = SevError | 0x90B;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export class CompilerMessages {
|
|||
static ERROR_MustBeAtLeastOneLayerElement = SevError | 0x000E;
|
||||
|
||||
static Fatal_SectionCompilerFailed = (o:{sect: string}) =>
|
||||
m(this.FATAL_SectionCompilerFailed, `The compiler for '${o.sect}' failed unexpectedly.`);
|
||||
m(this.FATAL_SectionCompilerFailed, null, `The compiler for '${o.sect}' failed unexpectedly.`);
|
||||
static FATAL_SectionCompilerFailed = SevFatal | 0x000F;
|
||||
|
||||
static Error_DisplayIsRepeated = (o:{to: string}) =>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ const SevError = CompilerErrorSeverity.Error | Namespace;
|
|||
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
export class ModelInfoCompilerMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException,
|
||||
`Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
|
||||
static Error_FileDoesNotExist = (o:{filename: string}) => m(this.ERROR_FileDoesNotExist, `File ${o.filename} does not exist.`);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent } from "@keymanapp/common-types";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec } from "@keymanapp/common-types";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.ModelCompiler;
|
||||
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
|
|
@ -7,12 +7,11 @@ const SevWarn = CompilerErrorSeverity.Warn | Namespace;
|
|||
const SevError = CompilerErrorSeverity.Error | Namespace;
|
||||
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
const m = (code: number, message: string) : CompilerEvent => { return {
|
||||
const m = (code: number, message: string, exceptionVar?: any) : CompilerEvent => ({
|
||||
...CompilerMessageSpec(code, message, exceptionVar),
|
||||
line: ModelCompilerMessageContext.line,
|
||||
filename: ModelCompilerMessageContext.filename,
|
||||
code,
|
||||
message
|
||||
} };
|
||||
});
|
||||
|
||||
export class ModelCompilerMessageContext {
|
||||
// Context added to all messages
|
||||
|
|
@ -22,8 +21,7 @@ export class ModelCompilerMessageContext {
|
|||
|
||||
export class ModelCompilerMessages {
|
||||
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException,
|
||||
`Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
|
||||
static Warn_MixedNormalizationForms = (o:{wordform: string}) => m(this.WARN_MixedNormalizationForms,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ const SevError = CompilerErrorSeverity.Error | Namespace;
|
|||
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
export class CompilerMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException,
|
||||
`Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
|
||||
static Warn_AbsolutePath = (o:{filename: string}) => m(this.WARN_AbsolutePath, `File ${o.filename} has an absolute path, which is not portable.`);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|||
import * as path from 'path';
|
||||
import { Command, Option } from 'commander';
|
||||
import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js';
|
||||
import { InfrastructureMessages } from '../messages/messages.js';
|
||||
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
|
||||
import { CompilerCallbacks, CompilerLogLevel } from '@keymanapp/common-types';
|
||||
import { AnalyzeOskCharacterUse, AnalyzeOskRewritePua } from '@keymanapp/kmc-analyze';
|
||||
import { BaseOptions } from '../util/baseOptions.js';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Command } from 'commander';
|
|||
import { buildActivities } from './buildClasses/buildActivities.js';
|
||||
import { BuildProject } from './buildClasses/BuildProject.js';
|
||||
import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js';
|
||||
import { InfrastructureMessages } from '../messages/messages.js';
|
||||
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
|
||||
import { CompilerFileCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { BaseOptions } from '../util/baseOptions.js';
|
||||
import { expandFileLists } from '../util/fileLists.js';
|
||||
|
|
@ -80,13 +80,18 @@ If no input file is supplied, kmc will build the current folder.`)
|
|||
}
|
||||
|
||||
async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, options: CompilerOptions): Promise<boolean> {
|
||||
|
||||
if(!fs.existsSync(filename)) {
|
||||
parentCallbacks.reportMessage(InfrastructureMessages.Error_FileDoesNotExist({filename}));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// TEST: allow command-line simulation of infrastructure fatal errors, and
|
||||
// also for unit tests
|
||||
if(process.env.SENTRY_CLIENT_TEST_BUILD_EXCEPTION == '1') {
|
||||
throw new Error('Test exception from SENTRY_CLIENT_TEST_BUILD_EXCEPTION');
|
||||
}
|
||||
|
||||
if(!fs.existsSync(filename)) {
|
||||
parentCallbacks.reportMessage(InfrastructureMessages.Error_FileDoesNotExist({filename}));
|
||||
return false;
|
||||
}
|
||||
|
||||
let builder = null;
|
||||
|
||||
// If infile is a directory, then we treat that as a project and build it
|
||||
|
|
@ -136,3 +141,10 @@ async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, o
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* these are exported only for unit tests, do not use
|
||||
*/
|
||||
export const unitTestEndpoints = {
|
||||
build
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { BuildActivity } from './BuildActivity.js';
|
|||
import { CompilerCallbacks, CompilerOptions, KeymanDeveloperProject, KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { KeyboardInfoCompiler } from '@keymanapp/kmc-keyboard-info';
|
||||
import { loadProject } from '../../util/projectLoader.js';
|
||||
import { InfrastructureMessages } from '../../messages/messages.js';
|
||||
import { InfrastructureMessages } from '../../messages/infrastructureMessages.js';
|
||||
import { KmpCompiler } from '@keymanapp/kmc-package';
|
||||
import { calculateSourcePath } from '../../util/calculateSourcePath.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/
|
|||
import { writeMergedModelMetadataFile } from '@keymanapp/kmc-model-info';
|
||||
import { KmpCompiler } from '@keymanapp/kmc-package';
|
||||
import { loadProject } from '../../util/projectLoader.js';
|
||||
import { InfrastructureMessages } from '../../messages/messages.js';
|
||||
import { InfrastructureMessages } from '../../messages/infrastructureMessages.js';
|
||||
import { calculateSourcePath } from '../../util/calculateSourcePath.js';
|
||||
|
||||
export class BuildModelInfo extends BuildActivity {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import * as fs from 'fs';
|
|||
import { CompilerCallbacks, CompilerFileCallbacks, CompilerOptions, KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanFileTypes } from '@keymanapp/common-types';
|
||||
import { BuildActivity } from './BuildActivity.js';
|
||||
import { buildActivities } from './buildActivities.js';
|
||||
import { InfrastructureMessages } from '../../messages/messages.js';
|
||||
import { InfrastructureMessages } from '../../messages/infrastructureMessages.js';
|
||||
import { loadProject } from '../../util/projectLoader.js';
|
||||
|
||||
export class BuildProject extends BuildActivity {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ try {
|
|||
KeymanSentry.captureException(e);
|
||||
}
|
||||
|
||||
// Ensure any messages reported to Sentry have had time to be uploaded before we
|
||||
// exit. In most cases, this will be a no-op so should not affect performance.
|
||||
await KeymanSentry.close();
|
||||
|
||||
async function run() {
|
||||
/* Arguments */
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ const SevError = CompilerErrorSeverity.Error | Namespace;
|
|||
const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
export class InfrastructureMessages {
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException,
|
||||
`Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`);
|
||||
static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, null, o.e ?? 'unknown error');
|
||||
static FATAL_UnexpectedException = SevFatal | 0x0001;
|
||||
|
||||
// For this message, we override the filename with the passed-in file. A bit of a hack but does the job
|
||||
|
|
@ -89,18 +89,34 @@ export class KeymanSentry {
|
|||
isInit = true;
|
||||
}
|
||||
|
||||
private static writeSentryMessage(eventId: string) {
|
||||
process.stderr.write(`
|
||||
This error has been automatically reported to the Keyman team.
|
||||
Identifier: ${eventId}
|
||||
Application: Keyman Developer
|
||||
Reported at: https://sentry.io/organizations/keyman/projects/keyman-developer/events/${eventId}/
|
||||
`);
|
||||
}
|
||||
|
||||
static async reportException(e: any, silent: boolean = true) {
|
||||
if(isInit) {
|
||||
const eventId = await Sentry.captureException(e);
|
||||
if(!silent) {
|
||||
this.writeSentryMessage(eventId);
|
||||
}
|
||||
return eventId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static async captureException(e: any) {
|
||||
if(isInit) {
|
||||
const eventId = Sentry.captureException(e);
|
||||
process.stderr.write(`
|
||||
Fatal error: ${(e??'').toString()}
|
||||
|
||||
This error has been automatically reported to the Keyman team.
|
||||
Identifier: ${eventId}
|
||||
Application: Keyman Developer
|
||||
Reported at: https://sentry.io/organizations/keyman/projects/keyman-developer/events/${eventId}/
|
||||
`);
|
||||
await Sentry.close(2000);
|
||||
Fatal error: ${(e??'').toString()}
|
||||
`);
|
||||
this.writeSentryMessage(eventId);
|
||||
this.close();
|
||||
|
||||
// For local development, we don't want to bury the trace; we need the cast to avoid
|
||||
// TS2367 (comparison appears to be unintentional)
|
||||
|
|
@ -112,4 +128,10 @@ This error has been automatically reported to the Keyman team.
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
static async close() {
|
||||
if(isInit) {
|
||||
await Sentry.close(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,10 @@ import { CompilerCallbacks, CompilerEvent,
|
|||
CompilerError,
|
||||
CompilerCallbackOptions,
|
||||
CompilerFileCallbacks} from '@keymanapp/common-types';
|
||||
import { InfrastructureMessages } from '../messages/messages.js';
|
||||
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
|
||||
import chalk from 'chalk';
|
||||
import supportsColor from 'supports-color';
|
||||
import { KeymanSentry } from './KeymanSentry.js';
|
||||
|
||||
const color = chalk.default;
|
||||
const severityColors: {[value in CompilerErrorSeverity]: chalk.Chalk} = {
|
||||
|
|
@ -121,6 +122,14 @@ export class NodeCompilerCallbacks implements CompilerCallbacks {
|
|||
|
||||
this.messages.push({...event});
|
||||
|
||||
// report fatal errors to Sentry, but don't display; note, it won't be
|
||||
// reported if user has disabled the Sentry setting
|
||||
if(CompilerError.severity(event.code) == CompilerErrorSeverity.Fatal) {
|
||||
// this is async so returns a Promise, we'll let it resolve
|
||||
// in its own time, and it can print its message then
|
||||
KeymanSentry.reportException(event.exceptionVar, false);
|
||||
}
|
||||
|
||||
if(CompilerError.severity(event.code) < compilerLogLevelToSeverity[this.options.logLevel]) {
|
||||
// collect messages but don't print to console
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { CompilerCallbacks } from "@keymanapp/common-types";
|
||||
import { InfrastructureMessages } from "../messages/messages.js";
|
||||
import { InfrastructureMessages } from "../messages/infrastructureMessages.js";
|
||||
|
||||
/**
|
||||
* Replaces each entry starting with `@` with the content of the file, with one
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { CompilerCallbacks, KeymanDeveloperProject, KeymanFileTypes, KPJFileReader } from "@keymanapp/common-types";
|
||||
import { InfrastructureMessages } from "../messages/messages.js";
|
||||
import { InfrastructureMessages } from "../messages/infrastructureMessages.js";
|
||||
|
||||
export const isProject = (filename: string): boolean =>
|
||||
fs.existsSync(filename) && (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { InfrastructureMessages } from '../src/messages/messages.js';
|
||||
import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js';
|
||||
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { NodeCompilerCallbacks } from '../src/util/NodeCompilerCallbacks.js';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/common-types';
|
||||
import { unitTestEndpoints } from '../src/commands/build.js';
|
||||
|
||||
describe('InfrastructureMessages', function () {
|
||||
it('should have a valid InfrastructureMessages object', function() {
|
||||
|
|
@ -15,34 +16,20 @@ describe('InfrastructureMessages', function () {
|
|||
// Message tests
|
||||
//
|
||||
|
||||
/*
|
||||
TODO:
|
||||
// FATAL_UnexpectedException
|
||||
|
||||
let callbacks = new TestCompilerCallbacks();
|
||||
|
||||
async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number) {
|
||||
context.timeout(10000);
|
||||
|
||||
callbacks.clear();
|
||||
|
||||
const builder = new BuildKmnKeyboard();
|
||||
const path = makePathToFixture(...fixture);
|
||||
let result = await builder.build(path, callbacks, {
|
||||
compilerVersion: false,
|
||||
compilerWarningsAsErrors: true,
|
||||
debug: false,
|
||||
warnDeprecatedCode: true,
|
||||
it('should generate FATAL_UnexpectedException if an exception is raised', async function() {
|
||||
const ncb = new NodeCompilerCallbacks({logLevel: 'silent'});
|
||||
process.env.SENTRY_CLIENT_TEST_BUILD_EXCEPTION = '1';
|
||||
await unitTestEndpoints.build(null, ncb, {});
|
||||
delete process.env.SENTRY_CLIENT_TEST_BUILD_EXCEPTION;
|
||||
assert.isTrue(ncb.hasMessage(InfrastructureMessages.FATAL_UnexpectedException),
|
||||
`FATAL_UnexpectedException not generated, instead got: `+JSON.stringify(ncb.messages,null,2));
|
||||
assert.lengthOf(ncb.messages, 1);
|
||||
assert.instanceOf<Error>(ncb.messages[0].exceptionVar, Error);
|
||||
});
|
||||
|
||||
if(messageId) {
|
||||
assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
|
||||
assert.lengthOf(callbacks.messages, 1);
|
||||
} else {
|
||||
assert.lengthOf(callbacks.messages, 0, `messages should be empty, but instead got: `+JSON.stringify(callbacks.messages,null,2));
|
||||
assert.isTrue(result);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// ERROR_FileDoesNotExist
|
||||
|
||||
it('should generate ERROR_FileDoesNotExist if a file does not exist', async function() {
|
||||
|
|
@ -3,7 +3,7 @@ import { assert } from 'chai';
|
|||
import 'mocha';
|
||||
import { BuildProject } from '../src/commands/buildClasses/BuildProject.js';
|
||||
import { makePathToFixture } from './helpers/index.js';
|
||||
import { InfrastructureMessages } from '../src/messages/messages.js';
|
||||
import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js';
|
||||
|
||||
const callbacks = new TestCompilerCallbacks();
|
||||
|
||||
|
|
|
|||
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -35,6 +35,7 @@
|
|||
"@keymanapp/ldml-keyboard-constants": "file:core/include/ldml"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||
"chai": "^4.3.4",
|
||||
"esbuild": "^0.15.16",
|
||||
|
|
@ -3649,9 +3650,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz",
|
||||
"integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/component-emitter": {
|
||||
"version": "1.2.11",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"name": "root",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||
"chai": "^4.3.4",
|
||||
"esbuild": "^0.15.16",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue