diff --git a/src/app.ts b/src/app.ts index e215c7db9..10aa63e7d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -38,6 +38,9 @@ const FSH_VERSION = '3.0.0-ballot'; function logUnexpectedError(e: Error) { logger.error(`SUSHI encountered the following unexpected error: ${e.message}`); + if (e.stack) { + logger.debug(e.stack); + } process.exit(1); } @@ -243,6 +246,9 @@ async function runBuild(input: string, program: OptionValues, helpText: string) // If no errors have been logged yet, log this exception so the user knows why we're exiting if (stats.numError === 0) { logger.error(`An unexpected error occurred: ${e.message ?? e}`); + if (e.stack) { + logger.debug(e.stack); + } } process.exit(1); } diff --git a/src/export/CodeSystemExporter.ts b/src/export/CodeSystemExporter.ts index f8378993c..21f3d218b 100644 --- a/src/export/CodeSystemExporter.ts +++ b/src/export/CodeSystemExporter.ts @@ -111,6 +111,9 @@ export class CodeSystemExporter { } } catch (e) { logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } }); resolveSoftIndexing(successfulRules); @@ -147,6 +150,9 @@ export class CodeSystemExporter { assignInstanceFromRawValue(codeSystem, rule, instanceExporter, this.fisher, originalErr); } else { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } } } @@ -246,6 +252,9 @@ export class CodeSystemExporter { this.exportCodeSystem(cs); } catch (e) { logger.error(e.message, cs.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } if (codeSystems.length > 0) { diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts index 3b04152e7..a7ea7b84f 100644 --- a/src/export/InstanceExporter.ts +++ b/src/export/InstanceExporter.ts @@ -208,19 +208,31 @@ export class InstanceExporter implements Fishable { const instanceToAssign = this.fishForFHIR(rule.rawValue); if (instanceToAssign == null) { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } else { try { doRuleValidation(instanceToAssign); } catch (instanceErr) { if (instanceErr instanceof MismatchedTypeError) { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } else { logger.error(instanceErr.message, rule.sourceInfo); + if (instanceErr.stack) { + logger.debug(instanceErr.stack); + } } } } } else { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } } }); @@ -853,6 +865,9 @@ export class InstanceExporter implements Fishable { this.exportInstance(instance); } catch (e) { logger.error(e.message, instance.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } this.warnOnInstanceOfCustomResource(); diff --git a/src/export/MappingExporter.ts b/src/export/MappingExporter.ts index 4779cb673..b5604a596 100644 --- a/src/export/MappingExporter.ts +++ b/src/export/MappingExporter.ts @@ -50,6 +50,9 @@ export class MappingExporter { element.applyMapping(fshDefinition.id, rule.map, rule.comment, rule.language); } catch (e) { logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } else { logger.error( @@ -131,6 +134,9 @@ export class MappingExporter { this.exportMapping(mapping); } catch (e) { logger.error(e.message, mapping.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } // The mappings on each Structure Definition should have a unique id diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts index 80eafd258..7e0c40c6c 100644 --- a/src/export/StructureDefinitionExporter.ts +++ b/src/export/StructureDefinitionExporter.ts @@ -738,6 +738,9 @@ export class StructureDefinitionExporter implements Fishable { } } catch (e) { logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } continue; } @@ -848,6 +851,9 @@ export class StructureDefinitionExporter implements Fishable { element.addSlice(item.name); } catch (e) { logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } }); } @@ -926,6 +932,9 @@ export class StructureDefinitionExporter implements Fishable { } } catch (e) { logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } else { logger.error( @@ -961,13 +970,22 @@ export class StructureDefinitionExporter implements Fishable { } catch (e) { if (e instanceof MismatchedTypeError && originalErr != null) { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } else { logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } } else { if (originalErr != null) { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } else { logger.error(`Could not find a resource named ${rule.value}`, rule.sourceInfo); } @@ -1020,11 +1038,17 @@ export class StructureDefinitionExporter implements Fishable { const slice = element.getSlices().find(el => el.sliceName === item.name); if (slice?.type[0]?.profile?.some(p => p === extension.url)) { logger.warn(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } return; } } // Otherwise it is a conflicting duplicate extension or some other error logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } // check if we have used modifier extensions correctly const isModifier = isModifierExtension(extension); @@ -1061,6 +1085,9 @@ export class StructureDefinitionExporter implements Fishable { // as inline extensions require further definition outside of the contains rule, so // there is more likely to be conflict, and detecting such conflict is more difficult. logger.error(e.message, rule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } }); @@ -1350,6 +1377,9 @@ export class StructureDefinitionExporter implements Fishable { this.exportStructDef(sd); } catch (e) { logger.error(e.message, e.sourceInfo || sd.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } }); this.warnOnNonConformantResourceDefinitions(); diff --git a/src/export/ValueSetExporter.ts b/src/export/ValueSetExporter.ts index 435526cba..8db8da715 100644 --- a/src/export/ValueSetExporter.ts +++ b/src/export/ValueSetExporter.ts @@ -205,6 +205,9 @@ export class ValueSetExporter { assignInstanceFromRawValue(valueSet, rule, instanceExporter, this.fisher, originalErr); } else { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } } } @@ -293,6 +296,9 @@ export class ValueSetExporter { this.exportValueSet(valueSet); } catch (e) { logger.error(e.message, valueSet.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } if (valueSets.length > 0) { diff --git a/src/fhirdefs/load.ts b/src/fhirdefs/load.ts index 55d7a97bb..7c6282c41 100644 --- a/src/fhirdefs/load.ts +++ b/src/fhirdefs/load.ts @@ -78,6 +78,9 @@ export function loadCustomResources( continue; } logger.error(`Loading ${file} failed with the following error:\n${e.message}`); + if (e.stack) { + logger.debug(e.stack); + } continue; } // All resources are added to the predefined map, so that this map can later be used to @@ -125,5 +128,8 @@ export async function loadSupplementalFHIRPackage( .then((def: FHIRDefinitions) => defs.addSupplementalFHIRDefinitions(fhirPackage, def)) .catch((e: Error) => { logger.error(`Failed to load supplemental FHIR package ${fhirPackage}: ${e.message}`); + if (e.stack) { + logger.debug(e.stack); + } }); } diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts index 83b61fcde..12011fce9 100644 --- a/src/fhirtypes/common.ts +++ b/src/fhirtypes/common.ts @@ -1491,6 +1491,9 @@ export function assignInstanceFromRawValue( const instance = instanceExporter.fishForFHIR(rule.rawValue); if (instance == null) { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } else { try { setPropertyOnDefinitionInstance( @@ -1502,8 +1505,14 @@ export function assignInstanceFromRawValue( } catch (instanceErr) { if (instanceErr instanceof MismatchedTypeError) { logger.error(originalErr.message, rule.sourceInfo); + if (originalErr.stack) { + logger.debug(originalErr.stack); + } } else { logger.error(instanceErr.message, rule.sourceInfo); + if (instanceErr.stack) { + logger.debug(instanceErr.stack); + } } } } diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts index da607adfe..fac0e8144 100644 --- a/src/import/FSHImporter.ts +++ b/src/import/FSHImporter.ts @@ -247,6 +247,9 @@ export class FSHImporter extends FSHVisitor { } catch (err) { const sourceInfo = { location: this.extractStartStop(e), file: this.currentFile }; logger.error(`Error in parsing: ${err.message}`, sourceInfo); + if (err.stack) { + logger.debug(err.stack); + } } }); } @@ -444,6 +447,9 @@ export class FSHImporter extends FSHVisitor { this.currentDoc.instances.set(instance.name, instance); } catch (e) { logger.error(e.message, instance.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } } } diff --git a/src/import/importConfiguration.ts b/src/import/importConfiguration.ts index a2fe6c4dd..7710b8a18 100644 --- a/src/import/importConfiguration.ts +++ b/src/import/importConfiguration.ts @@ -79,6 +79,9 @@ export function importConfiguration(yaml: YAMLConfiguration | string, file: stri parsed = YAML.parse(yaml); } catch (e) { logger.error(`Error parsing configuration: ${e.message}.`, { file }); + if (e.stack) { + logger.debug(e.stack); + } throw new Error('Invalid configuration YAML'); } if (typeof parsed !== 'object' || parsed === null) { diff --git a/src/utils/PathUtils.ts b/src/utils/PathUtils.ts index f6084255c..fe21563e0 100644 --- a/src/utils/PathUtils.ts +++ b/src/utils/PathUtils.ts @@ -231,6 +231,9 @@ export function resolveSoftIndexing(rules: Array, strict } } catch (e) { logger.error(e.message, originalRule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } }); originalRule.path = assembleFSHPath(parsedRule.path); // Assembling the separated rule path back into a normal string @@ -257,6 +260,9 @@ export function resolveSoftIndexing(rules: Array, strict } } catch (e) { logger.error(e.message, originalRule.sourceInfo); + if (e.stack) { + logger.debug(e.stack); + } } }); diff --git a/src/utils/Processing.ts b/src/utils/Processing.ts index eb708e698..9c8b26eda 100644 --- a/src/utils/Processing.ts +++ b/src/utils/Processing.ts @@ -169,6 +169,9 @@ export function ensureOutputDir(input: string, output: string): string { logger.error( `Unable to empty existing fsh-generated folder because of the following error: ${e.message}` ); + if (e.stack) { + logger.debug(e.stack); + } } } return outDir; @@ -369,6 +372,9 @@ export async function loadAutomaticDependencies( message += CERTIFICATE_MESSAGE; } logger.warn(message); + if (e.stack) { + logger.debug(e.stack); + } } } } @@ -417,6 +423,9 @@ async function loadConfiguredDependencies( message += CERTIFICATE_MESSAGE; } logger.error(message); + if (e.stack) { + logger.debug(e.stack); + } }); } } @@ -703,6 +712,9 @@ export async function init(): Promise { } } catch (e) { logger.error(`Unable to download ${script} from ${url}: ${e.message}`); + if (e.stack) { + logger.debug(e.stack); + } } } const maxLength = 32; @@ -760,6 +772,9 @@ async function getLatestSushiVersionFallback(): Promise { } } catch (e) { logger.warn(`Unable to determine the latest version of sushi: ${e.message}`); + if (e.stack) { + logger.debug(e.stack); + } } } @@ -774,6 +789,9 @@ export async function getLatestSushiVersion(): Promise { } } catch (e) { logger.info(`Unable to determine the latest version of sushi: ${e.message}`); + if (e.stack) { + logger.debug(e.stack); + } } if (latestVer === undefined) { diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts index 8fa2cbe26..442ef980d 100644 --- a/test/export/StructureDefinitionExporter.test.ts +++ b/test/export/StructureDefinitionExporter.test.ts @@ -3087,7 +3087,12 @@ describe('StructureDefinitionExporter R4', () => { expect(baseCard.max).toBe('1'); expect(changedCard.min).toBe(1); expect(changedCard.max).toBe('1'); - expect(loggerSpy.getLastMessage()).toMatch(/File: Wrong\.fsh.*Line: 5\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Wrong\.fsh.*Line: 5\D*/s); + + // This is one of the spots where we debug log a stack trace just in case the thrown error is an unexpected one + expect(loggerSpy.getLastMessage('debug')).toMatch( + /at ElementDefinition\.constrainCardinality/s + ); }); it('should apply a card rule with only min specified', () => { @@ -3154,7 +3159,7 @@ describe('StructureDefinitionExporter R4', () => { expect(baseCard.max).toBe('1'); expect(changedCard.min).toBe(1); expect(changedCard.max).toBe('1'); // Neither card changes - expect(loggerSpy.getLastMessage()).toMatch(/File: BadCard\.fsh.*Line: 3\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: BadCard\.fsh.*Line: 3\D*/s); }); it('should not apply an incorrect max only card rule', () => { @@ -3177,7 +3182,7 @@ describe('StructureDefinitionExporter R4', () => { expect(baseCard.max).toBe('1'); expect(changedCard.min).toBe(1); expect(changedCard.max).toBe('1'); // Neither card changes - expect(loggerSpy.getLastMessage()).toMatch(/File: BadCard\.fsh.*Line: 3\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: BadCard\.fsh.*Line: 3\D*/s); }); it('should not apply a card rule with no sides specified', () => { @@ -3466,7 +3471,7 @@ describe('StructureDefinitionExporter R4', () => { const changedElement = sd.findElement('Observation.note'); expect(baseElement.binding).toBeUndefined(); expect(changedElement.binding).toBeUndefined(); - expect(loggerSpy.getLastMessage()).toMatch(/File: Codeless\.fsh.*Line: 6\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Codeless\.fsh.*Line: 6\D*/s); }); it('should not override a binding with a less strict binding', () => { @@ -3493,7 +3498,7 @@ describe('StructureDefinitionExporter R4', () => { 'http://hl7.org/fhir/ValueSet/observation-category' ); expect(changedElement.binding.strength).toBe('preferred'); - expect(loggerSpy.getLastMessage()).toMatch(/File: Strict\.fsh.*Line: 9\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Strict\.fsh.*Line: 9\D*/s); }); }); @@ -4677,7 +4682,7 @@ describe('StructureDefinitionExporter R4', () => { expect(baseValue.type).toHaveLength(11); expect(constrainedValue.type).toHaveLength(11); - expect(loggerSpy.getLastMessage()).toMatch(/File: Only\.fsh.*Line: 10\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Only\.fsh.*Line: 10\D*/s); }); it('should log an error when a type constraint implicitly removes a choice created in the current StructureDefinition', () => { @@ -5816,7 +5821,7 @@ describe('StructureDefinitionExporter R4', () => { expect(baseCode.patternCodeableConcept).toBeUndefined(); expect(assignedCode.patternCodeableConcept).toBeUndefined(); // Code remains unset - expect(loggerSpy.getLastMessage()).toMatch(/File: Assigned\.fsh.*Line: 4\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Assigned\.fsh.*Line: 4\D*/s); }); it('should not apply an AssignmentRule when the value is refers to an Instance that is not found', () => { @@ -6960,7 +6965,7 @@ describe('StructureDefinitionExporter R4', () => { expect(sd.elements.length).toBe(baseStructDef.elements.length); expect(barSlice).toBeUndefined(); - expect(loggerSpy.getLastMessage()).toMatch(/File: NoSlice\.fsh.*Line: 6\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: NoSlice\.fsh.*Line: 6\D*/s); }); // Since previous versions of SUSHI used the slicename as a type lookup as well, we used to issue a warning when we @@ -7321,7 +7326,7 @@ describe('StructureDefinitionExporter R4', () => { const baseStatus = baseStructDef.findElement('Observation.status'); expect(status.short).toBe(baseStatus.short); - expect(loggerSpy.getLastMessage()).toMatch(/File: InvalidValue\.fsh.*Line: 6\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: InvalidValue\.fsh.*Line: 6\D*/s); }); it('should apply a CaretValueRule on the parent element', () => { @@ -7442,7 +7447,7 @@ describe('StructureDefinitionExporter R4', () => { const sd = pkg.profiles[0]; expect(sd.description).toBeUndefined(); - expect(loggerSpy.getLastMessage()).toMatch(/File: InvalidValue\.fsh.*Line: 6\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: InvalidValue\.fsh.*Line: 6\D*/s); }); it('should apply a CaretValueRule on an extension element without a path', () => {