diff --git a/src/run/FshToFhir.ts b/src/run/FshToFhir.ts index fd3ca4e83..95c889846 100644 --- a/src/run/FshToFhir.ts +++ b/src/run/FshToFhir.ts @@ -29,76 +29,83 @@ export async function fshToFhir( // track errors and warnings, and determine log level from options errorsAndWarnings.reset(); errorsAndWarnings.shouldTrack = true; - if (options.logLevel == 'silent') { - logger.transports[0].silent = true; - } else if (options.logLevel != null) { - if (!isLevel(options.logLevel)) { - return { - fhir: null, - errors: [ - { - message: `Invalid logLevel: ${options.logLevel}. Valid levels include: ${levels.join( - ', ' - )}.` - } - ], - warnings: [] - }; + const originalSilent = logger.transports[0].silent; + const originalLevel = logger.level; + try { + if (options.logLevel == 'silent') { + logger.transports[0].silent = true; + } else if (options.logLevel != null) { + if (!isLevel(options.logLevel)) { + return { + fhir: null, + errors: [ + { + message: `Invalid logLevel: ${options.logLevel}. Valid levels include: ${levels.join( + ', ' + )}.` + } + ], + warnings: [] + }; + } + logger.level = options.logLevel; } - logger.level = options.logLevel; - } - const snapshot = options.snapshot ?? false; + const snapshot = options.snapshot ?? false; - // set up a config so that sushi can run - const config = { - canonical: options.canonical ?? 'http://example.org', - FSHOnly: true, - fhirVersion: [options.fhirVersion ?? '4.0.1'], - dependencies: options.dependencies, - version: options.version - }; + // set up a config so that sushi can run + const config = { + canonical: options.canonical ?? 'http://example.org', + FSHOnly: true, + fhirVersion: [options.fhirVersion ?? '4.0.1'], + dependencies: options.dependencies, + version: options.version + }; - // load dependencies - const defs = await createFHIRDefinitions(); - await loadExternalDependencies(defs, config); + // load dependencies + const defs = await createFHIRDefinitions(); + await loadExternalDependencies(defs, config); - // load FSH text into memory - const rawFSHes: RawFSH[] = []; - if (Array.isArray(input)) { - input.forEach((input, i) => { - rawFSHes.push(new RawFSH(input, `Input_${i}`)); - }); - } else { - rawFSHes.push(new RawFSH(input)); - } - const tank = fillTank(rawFSHes, config); - tank.checkDuplicateNameEntities(); + // load FSH text into memory + const rawFSHes: RawFSH[] = []; + if (Array.isArray(input)) { + input.forEach((input, i) => { + rawFSHes.push(new RawFSH(input, `Input_${i}`)); + }); + } else { + rawFSHes.push(new RawFSH(input)); + } + const tank = fillTank(rawFSHes, config); + tank.checkDuplicateNameEntities(); - // process FSH text into FHIR - const outPackage = exportFHIR(tank, defs); - const fhir: any[] = []; - ( - [ - 'profiles', - 'extensions', - 'instances', - 'valueSets', - 'codeSystems', - 'logicals', - 'resources' - ] as const - ).forEach(artifactType => { - outPackage[artifactType].forEach((artifact: { toJSON: (snapshot: boolean) => any }) => { - fhir.push(artifact.toJSON(snapshot)); + // process FSH text into FHIR + const outPackage = exportFHIR(tank, defs); + const fhir: any[] = []; + ( + [ + 'profiles', + 'extensions', + 'instances', + 'valueSets', + 'codeSystems', + 'logicals', + 'resources' + ] as const + ).forEach(artifactType => { + outPackage[artifactType].forEach((artifact: { toJSON: (snapshot: boolean) => any }) => { + fhir.push(artifact.toJSON(snapshot)); + }); }); - }); - return { - fhir, - errors: errorsAndWarnings.errors, - warnings: errorsAndWarnings.warnings - }; + return { + fhir, + errors: errorsAndWarnings.errors, + warnings: errorsAndWarnings.warnings + }; + } finally { + logger.transports[0].silent = originalSilent; + logger.level = originalLevel; + } } // *** WARNING *** diff --git a/test/run/FshToFhir.test.ts b/test/run/FshToFhir.test.ts index fd9d3f3ba..71ce8edac 100644 --- a/test/run/FshToFhir.test.ts +++ b/test/run/FshToFhir.test.ts @@ -34,6 +34,11 @@ describe('#FshToFhir', () => { }); it('should use a higher logging level when specified', async () => { + loadSpy.mockImplementationOnce(() => { + expect(logger.level).toBe('error'); + expect(logger.transports[0].silent).toBeFalsy(); + return Promise.resolve(); + }); const results = await fshToFhir('Bad FSH', { logLevel: 'error' }); expect(results.errors).toHaveLength(1); expect(results.errors[0].location).toEqual({ @@ -45,10 +50,15 @@ describe('#FshToFhir', () => { expect(results.errors[0].message).toMatch(/mismatched input 'Bad'/); expect(results.warnings).toHaveLength(0); expect(results.fhir).toEqual([]); - expect(logger.level).toBe('error'); + expect(logger.level).toBe('info'); + expect(logger.transports[0].silent).toBeFalsy(); }); it('should mute the logger when "silent" is specified', async () => { + loadSpy.mockImplementationOnce(() => { + expect(logger.transports[0].silent).toBe(true); + return Promise.resolve(); + }); const results = await fshToFhir('Bad FSH', { logLevel: 'silent' }); expect(results.errors).toHaveLength(1); expect(results.errors[0].location).toEqual({ @@ -61,7 +71,7 @@ describe('#FshToFhir', () => { expect(results.errors[0].message).toMatch(/mismatched input 'Bad'/); expect(results.warnings).toHaveLength(0); expect(results.fhir).toEqual([]); - expect(logger.transports[0].silent).toBe(true); + expect(logger.transports[0].silent).toBeFalsy(); }); it('should quit and return an error when an invalid logLevel is specified', async () => { @@ -75,6 +85,43 @@ describe('#FshToFhir', () => { expect(results.fhir).toBeNull(); }); + describe('#LoggerIsolation', () => { + it('should not stay silent when a later call uses a non-silent logLevel', async () => { + await fshToFhir('', { logLevel: 'silent' }); + expect(logger.transports[0].silent).toBeFalsy(); + + loadSpy.mockImplementationOnce(() => { + expect(logger.transports[0].silent).toBeFalsy(); + expect(logger.level).toBe('error'); + return Promise.resolve(); + }); + await fshToFhir('', { logLevel: 'error' }); + expect(logger.transports[0].silent).toBeFalsy(); + expect(logger.level).toBe('info'); + }); + + it('should not keep a previous logLevel when a later call omits logLevel', async () => { + await fshToFhir('', { logLevel: 'error' }); + + loadSpy.mockImplementationOnce(() => { + expect(logger.level).toBe('info'); + expect(logger.transports[0].silent).toBeFalsy(); + return Promise.resolve(); + }); + await fshToFhir(''); + expect(logger.level).toBe('info'); + }); + + it('should restore the logger when processing fails', async () => { + loadSpy.mockImplementationOnce(() => { + throw new Error('dependency load failed'); + }); + await expect(fshToFhir('', { logLevel: 'silent' })).rejects.toThrow('dependency load failed'); + expect(logger.transports[0].silent).toBeFalsy(); + expect(logger.level).toBe('info'); + }); + }); + it('should replace configuration options when specified', async () => { await expect( fshToFhir('', {