Repository: FHIR/sushi (ts)

Issue:
SUSHI errors when an instance adds an extension on meta.profile without explicitly setting the profile
Given FSH like the following:
```
Instance: PatientExample
InstanceOf: MyPatient
Description: "An example of a patient with a license to krill."
* meta.profile.extension[MyExtension].valueString = "Howdy"
* name
  * given[0] = "James"
  * family = "Pond"
```
SUSHI will emit the following error: `Cannot read properties of undefined (reading 'startsWith')`

This is because the InstanceExporter is trying to check if the `meta.profile` value starts with a particular string -- but in this case, although `meta.profile` is not null, it does not yet have an assigned value. In fact, the code where this is happening is the code that is intended to _set_ the `meta.profile` value based on the `InstanceOf`. Oops.

Pull request description:
Debug log stacks from unexpected errors
This PR debug logs error stacks whenever we log an arbitrary error message (which usually corresponds to unexpected errors). This will allow us to more easily find the root of unexpected problems, especially things like trying to access properties on null/undefined.

This also fixes a log statement that passed in `e.sourceInfo` instead of `instance.sourceInfo` -- which resulted in receiving no source information when an unexpected error arose while exporting an instance.

You can test this using the example project I attached to #1330. This PR does _not_ fix #1330, but I implemented this PR while investigating #1330. Using that example project and SUSHI 3.3.2, you'll get an error with no source information and no stack trace (even when using debug logging). Using this PR's branch and the default logging level, you'll get the source information. Using this PR's branch and the `-l debug` flag, you'll additionally get the stack trace.

Patch:
diff --git a/README.md b/README.md
index eb39ac9..dceb25b 100644
--- a/README.md
+++ b/README.md
@@ -27,14 +27,14 @@ $ sushi help
 Usage: sushi [options] [command]
 
 Options:
-  -v, --version                              print SUSHI version
-  -h, --help                                 display help for command
+  -v, --version                                        print SUSHI version
+  -h, --help                                           display help for command
 
 Commands:
-  build [options] [path-to-fsh-project]      build a SUSHI project
-  init                                       initialize a SUSHI project
-  update-dependencies [path-to-fsh-project]  update FHIR packages in project configuration
-  help [command]                             display help for command
+  build [options] [path-to-fsh-project]                build a SUSHI project
+  init [options]                                       initialize a SUSHI project
+  update-dependencies [options] [path-to-fsh-project]  update FHIR packages in project configuration
+  help [command]                                       display help for command
 ```
 
 To build a SUSHI project, use the `build` command:
diff --git a/src/app.ts b/src/app.ts
index e215c7d..bb35ea5 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);
 }
 
@@ -68,6 +71,7 @@ async function app() {
     )
     .option('-s, --snapshot', 'generate snapshot in Structure Definition output', false)
     .action(async function (projectPath, options) {
+      setLogLevel(options);
       await runBuild(projectPath, options, program.helpInformation()).catch(logUnexpectedError);
     })
     .on('--help', () => {
@@ -97,7 +101,14 @@ async function app() {
   program
     .command('init')
     .description('initialize a SUSHI project')
-    .action(async function () {
+    .addOption(
+      new Option(
+        '-l, --log-level <level>',
+        'specify the level of log messages (default: "info")'
+      ).choices(['error', 'warn', 'info', 'debug'])
+    )
+    .action(async function (options) {
+      setLogLevel(options);
       await init().catch(logUnexpectedError);
       process.exit(0);
     });
@@ -106,7 +117,14 @@ async function app() {
     .command('update-dependencies')
     .description('update FHIR packages in project configuration')
     .argument('[path-to-fsh-project]')
-    .action(async function (projectPath) {
+    .addOption(
+      new Option(
+        '-l, --log-level <level>',
+        'specify the level of log messages (default: "info")'
+      ).choices(['error', 'warn', 'info', 'debug'])
+    )
+    .action(async function (projectPath, options) {
+      setLogLevel(options);
       await runUpdateDependencies(projectPath).catch(logUnexpectedError);
       process.exit(0);
     })
@@ -137,12 +155,6 @@ async function runBuild(input: string, program: OptionValues, helpText: string)
     process.exit(1);
   }
 
-  // Set the log level. If no level is specified, logger defaults to info
-  if (program.logLevel != null) {
-    // program.logLevel has only valid log levels because the CLI sets the choices
-    logger.level = program.logLevel;
-  }
-
   logger.info(`Running ${getVersion()}`);
   logger.info('Arguments:');
   if (program.logLevel) {
@@ -243,6 +255,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);
   }
@@ -310,6 +325,14 @@ function getVersion(): string {
   return 'unknown';
 }
 
+function setLogLevel(options: OptionValues) {
+  // Set the log level. If no level is specified, logger defaults to info
+  if (options.logLevel != null) {
+    // options.logLevel has only valid log levels because the CLI sets the choices
+    logger.level = options.logLevel;
+  }
+}
+
 function printResults(pkg: Package, sushiVersions: any) {
   // NOTE: These variables are creatively names to align well in the strings below while keeping prettier happy
   const profileNum = pad(pkg.profiles.length.toString(), 13);
diff --git a/src/export/CodeSystemExporter.ts b/src/export/CodeSystemExporter.ts
index f837899..21f3d21 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 781c0ba..a7ea7b8 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);
+          }
         }
       }
     });
@@ -852,7 +864,10 @@ export class InstanceExporter implements Fishable {
       try {
         this.exportInstance(instance);
       } catch (e) {
-        logger.error(e.message, e.sourceInfo);
+        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 4779cb6..b5604a5 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 80eafd2..7e0c40c 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 435526c..8db8da7 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 55d7a97..7c6282c 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 339220d..86e230c 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -1513,6 +1513,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(
@@ -1524,8 +1527,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 34c3419..92237fa 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);
+        }
       }
     });
   }
@@ -445,6 +448,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 a2fe6c4..7710b8a 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 f608425..fe21563 100644
--- a/src/utils/PathUtils.ts
+++ b/src/utils/PathUtils.ts
@@ -231,6 +231,9 @@ export function resolveSoftIndexing(rules: Array<Rule | CaretValueRule>, 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<Rule | CaretValueRule>, 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 eb708e6..9c8b26e 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<void> {
       }
     } 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<string> {
     }
   } 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<string | undefined> {
     }
   } catch (e) {
     logger.info(`Unable to determine the latest version of sushi: ${e.message}`);
+    if (e.stack) {
+      logger.debug(e.stack);
+    }
   }
 
   if (latestVer === undefined) {


Test patch:
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index 8fa2cbe..442ef98 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', () => {