diff --git a/packages/core/environment/core-environment-server-internal/src/environment_service.test.ts b/packages/core/environment/core-environment-server-internal/src/environment_service.test.ts index 42063306652f6f..47e0380e9111fc 100644 --- a/packages/core/environment/core-environment-server-internal/src/environment_service.test.ts +++ b/packages/core/environment/core-environment-server-internal/src/environment_service.test.ts @@ -8,7 +8,7 @@ import { BehaviorSubject } from 'rxjs'; -import type { CoreContext } from '@kbn/core-base-server-internal'; +import { type CoreContext, CriticalError } from '@kbn/core-base-server-internal'; import type { AnalyticsServicePreboot } from '@kbn/core-analytics-server'; import { EnvironmentService } from './environment_service'; @@ -127,36 +127,58 @@ describe('UuidService', () => { warning.name = 'DeprecationWarning'; process.emit('warning', warning); - expect(logger.get('process').warn).not.toHaveBeenCalled(); + expect(logger.get('environment').warn).not.toHaveBeenCalled(); }); }); - // TODO: From Nodejs v16 emitting an unhandledRejection will kill the process - describe.skip('unhandledRejection warnings', () => { - it('logs warn for an unhandeld promise rejected with an Error', async () => { + describe('unhandledRejection warnings', () => { + it('logs warn for an unhandled promise rejected with an Error', async () => { await service.preboot({ analytics }); const err = new Error('something went wrong'); - process.emit('unhandledRejection', err, new Promise((res, rej) => rej(err))); + process.emit('unhandledRejection', err, new Promise((res, rej) => {})); - expect(logger.get('process').warn).toHaveBeenCalledTimes(1); + expect(logger.get('environment').warn).toHaveBeenCalledTimes(1); expect(loggingSystemMock.collect(logger).warn[0][0]).toMatch( /Detected an unhandled Promise rejection: Error: something went wrong\n.*at / ); }); - it('logs warn for an unhandeld promise rejected with a string', async () => { + it('logs warn for an unhandled promise rejected with a string', async () => { await service.preboot({ analytics }); const err = 'something went wrong'; - process.emit('unhandledRejection', err, new Promise((res, rej) => rej(err))); + process.emit('unhandledRejection', err, new Promise((res, rej) => {})); - expect(logger.get('process').warn).toHaveBeenCalledTimes(1); + expect(logger.get('environment').warn).toHaveBeenCalledTimes(1); expect(loggingSystemMock.collect(logger).warn[0][0]).toMatch( /Detected an unhandled Promise rejection: "something went wrong"/ ); }); }); + + describe('uncaughtException warnings', () => { + it('logs warn for an uncaught exception with an Error', async () => { + await service.preboot({ analytics }); + + const err = new Error('something went wrong'); + process.emit('uncaughtExceptionMonitor', err); // Types won't allow me to provide the `origin` + + expect(logger.get('environment').warn).toHaveBeenCalledTimes(1); + expect(loggingSystemMock.collect(logger).warn[0][0]).toMatch( + /Detected an undefined: Error: something went wrong\n.*at / + ); + }); + + it('does not log warn for an uncaught exception with a CriticalError', async () => { + await service.preboot({ analytics }); + + const err = new CriticalError('something went wrong', 'ERROR_CODE', 1234); + process.emit('uncaughtExceptionMonitor', err); // Types won't allow me to provide the `origin` + + expect(logger.get('environment').warn).toHaveBeenCalledTimes(0); + }); + }); }); describe('#setup()', () => { diff --git a/packages/core/environment/core-environment-server-internal/src/environment_service.ts b/packages/core/environment/core-environment-server-internal/src/environment_service.ts index 26e328e2aad71c..486297bffb14a6 100644 --- a/packages/core/environment/core-environment-server-internal/src/environment_service.ts +++ b/packages/core/environment/core-environment-server-internal/src/environment_service.ts @@ -10,7 +10,7 @@ import { firstValueFrom, of } from 'rxjs'; import { PathConfigType, config as pathConfigDef } from '@kbn/utils'; import type { Logger } from '@kbn/logging'; import type { IConfigService } from '@kbn/config'; -import { CoreContext, coreConfigPaths } from '@kbn/core-base-server-internal'; +import { CoreContext, coreConfigPaths, CriticalError } from '@kbn/core-base-server-internal'; import type { AnalyticsServicePreboot } from '@kbn/core-analytics-server'; import { HttpConfigType } from './types'; import { PidConfigType, pidConfig as pidConfigDef } from './pid_config'; @@ -56,7 +56,7 @@ export class EnvironmentService { public async preboot({ analytics }: EnvironmentServicePrebootDeps) { // IMPORTANT: This code is based on the assumption that none of the configuration values used - // here is supposed to change during preboot phase and it's safe to read them only once. + // here is supposed to change during preboot phase, and it's safe to read them only once. const [pathConfig, serverConfig, pidConfig] = await Promise.all([ firstValueFrom(this.configService.atPath(pathConfigDef.path)), firstValueFrom(this.configService.atPath(coreConfigPaths.http)), @@ -68,6 +68,14 @@ export class EnvironmentService { const message = (reason as Error)?.stack ?? JSON.stringify(reason); this.log.warn(`Detected an unhandled Promise rejection: ${message}`); }); + // Log uncaughtExceptions in our logger before crashing the process: https://github.com/elastic/kibana/issues/183182 + process.on('uncaughtExceptionMonitor', (error, origin) => { + // CriticalErrors are handled in a different path + if (!(error instanceof CriticalError)) { + const message = error?.stack ?? JSON.stringify(error); + this.log.warn(`Detected an ${origin}: ${message}`); + } + }); await createDataFolder({ pathConfig, logger: this.log }); await writePidFile({ pidConfig, logger: this.log });