-
-
Notifications
You must be signed in to change notification settings - Fork 622
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9f6a8a6
commit df92dee
Showing
5 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* @adonisjs/core | ||
* | ||
* (c) Harminder Virk <virk@adonisjs.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
/** | ||
* The binding for the given module is defined inside `providers/AppProvider.ts` | ||
* file. | ||
*/ | ||
declare module '@ioc:Adonis/Core/Application' { | ||
import { ApplicationContract as BaseContract } from '@poppinss/application' | ||
const Application: ApplicationContract | ||
|
||
/** | ||
* Module exports | ||
*/ | ||
export interface ApplicationContract extends BaseContract {} | ||
export default Application | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* @adonisjs/core | ||
* | ||
* (c) Harminder Virk <virk@adonisjs.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
/** | ||
* The binding for the given module is defined inside `providers/AppProvider.ts` | ||
* file. | ||
*/ | ||
declare module '@ioc:Adonis/Core/HealtCheck' { | ||
export type Checker = string | (() => Promise<HealthReportEntry>) | ||
|
||
/** | ||
* Shape of health report entry. Each checker must | ||
* return an object with similar shape. | ||
*/ | ||
export type HealthReportEntry = { | ||
health: { | ||
healthy: boolean, | ||
message?: string, | ||
}, | ||
meta?: any, | ||
} | ||
|
||
/** | ||
* The shape of entire report | ||
*/ | ||
export type HealthReport = { | ||
[service: string]: HealthReportEntry, | ||
} | ||
|
||
/** | ||
* Shape of health check contract | ||
*/ | ||
export interface HealthCheckContract { | ||
addChecker (service: string, checker: Checker): void, | ||
isLive (): Promise<boolean>, | ||
isReady (): boolean, | ||
report (): Promise<{ healthy: boolean, report: HealthReport }>, | ||
} | ||
|
||
const HealthCheck: HealthCheckContract | ||
export default HealthCheck | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* @adonisjs/core | ||
* | ||
* (c) Harminder Virk <virk@adonisjs.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
import { ApplicationContract } from '@ioc:Adonis/Core/Application' | ||
import { parseIocReference, callIocReference } from '@poppinss/utils' | ||
import { HealthCheckContract, Checker, HealthReport, HealthReportEntry } from '@ioc:Adonis/Core/HealtCheck' | ||
|
||
/** | ||
* The module exposes the API to find the health, liveliness and readiness of | ||
* the system. You can also add your own checkers. | ||
*/ | ||
export class HealthCheck implements HealthCheckContract { | ||
/** | ||
* A copy of custom checkers | ||
*/ | ||
private _healthCheckers: { [service: string]: Checker } = {} | ||
|
||
constructor (private _application: ApplicationContract) {} | ||
|
||
/** | ||
* Invokes a given checker to collect the report metrics. | ||
*/ | ||
private async _invokeChecker ( | ||
service: string, | ||
reportSheet: HealthReport, | ||
): Promise<boolean> { | ||
const checker = this._healthCheckers[service] | ||
let report: HealthReportEntry | ||
|
||
try { | ||
if (typeof (checker) === 'function') { | ||
report = await checker() | ||
} else { | ||
report = await callIocReference(parseIocReference(`${checker}.report`), []) | ||
} | ||
} catch (error) { | ||
report = { | ||
health: { healthy: false, message: error.message }, | ||
meta: { fatal: true }, | ||
} | ||
} | ||
|
||
reportSheet[service] = report | ||
return report.health.healthy | ||
} | ||
|
||
/** | ||
* A boolean to know, if all health checks have passed | ||
* or not. | ||
*/ | ||
public async isLive (): Promise<boolean> { | ||
if (!this.isReady()) { | ||
return false | ||
} | ||
|
||
const { healthy } = await this.report() | ||
return healthy | ||
} | ||
|
||
/** | ||
* Add a custom checker to check a given service connectivity | ||
* with the server | ||
*/ | ||
public addChecker (service: string, checker: Checker) { | ||
this._healthCheckers[service] = checker | ||
} | ||
|
||
/** | ||
* Ensure that application is ready and is not shutting | ||
* down. This relies on the application module. | ||
*/ | ||
public isReady () { | ||
return this._application.isReady && !this._application.isShuttingDown | ||
} | ||
|
||
/** | ||
* Returns the health check reports. The health checks are performed when | ||
* this method is invoked. | ||
*/ | ||
public async report (): Promise<{ healthy: boolean, report: HealthReport }> { | ||
const report: HealthReport = {} | ||
await Promise.all(Object.keys(this._healthCheckers).map((service) => { | ||
return this._invokeChecker(service, report) | ||
})) | ||
|
||
/** | ||
* Finding unhealthy service to know if system is healthy or not | ||
*/ | ||
const unhealthyService = Object.keys(report).find((service) => !report[service].health.healthy) | ||
return { healthy: !unhealthyService, report } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
* @adonisjs/core | ||
* | ||
* (c) Harminder Virk <virk@adonisjs.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
/// <reference path="../adonis-typings/application.ts" /> | ||
/// <reference path="../adonis-typings/health-check.ts" /> | ||
|
||
import * as test from 'japa' | ||
import { Ioc } from '@adonisjs/fold' | ||
import { Application } from '@poppinss/application' | ||
import { HealthCheck } from '../src/HealthCheck' | ||
|
||
test.group('HealthCheck', () => { | ||
test('use application isReady state to find if application is ready', (assert) => { | ||
const application = new Application(__dirname, new Ioc(), {}, {}) | ||
const healthCheck = new HealthCheck(application) | ||
|
||
assert.isFalse(healthCheck.isReady()) | ||
|
||
application.isReady = true | ||
assert.isTrue(healthCheck.isReady()) | ||
|
||
application.isShuttingDown = true | ||
assert.isFalse(healthCheck.isReady()) | ||
}) | ||
|
||
test('get health checks report', async (assert) => { | ||
const application = new Application(__dirname, new Ioc(), {}, {}) | ||
const healthCheck = new HealthCheck(application) | ||
|
||
healthCheck.addChecker('event-loop', async () => { | ||
return { | ||
health: { | ||
healthy: true, | ||
}, | ||
} | ||
}) | ||
|
||
const report = await healthCheck.report() | ||
assert.deepEqual(report, { | ||
healthy: true, | ||
report: { | ||
'event-loop': { | ||
health: { | ||
healthy: true, | ||
}, | ||
}, | ||
}, | ||
}) | ||
}) | ||
|
||
test('handle exceptions raised within the checker', async (assert) => { | ||
const application = new Application(__dirname, new Ioc(), {}, {}) | ||
const healthCheck = new HealthCheck(application) | ||
|
||
healthCheck.addChecker('event-loop', async () => { | ||
throw new Error('boom') | ||
}) | ||
|
||
const report = await healthCheck.report() | ||
assert.deepEqual(report, { | ||
healthy: false, | ||
report: { | ||
'event-loop': { | ||
health: { | ||
healthy: false, | ||
message: 'boom', | ||
}, | ||
meta: { | ||
fatal: true, | ||
}, | ||
}, | ||
}, | ||
}) | ||
}) | ||
|
||
test('set healthy to false when any of the checker fails', async (assert) => { | ||
const application = new Application(__dirname, new Ioc(), {}, {}) | ||
const healthCheck = new HealthCheck(application) | ||
|
||
healthCheck.addChecker('database', async () => { | ||
return { | ||
health: { | ||
healthy: true, | ||
}, | ||
} | ||
}) | ||
|
||
healthCheck.addChecker('event-loop', async () => { | ||
throw new Error('boom') | ||
}) | ||
|
||
const report = await healthCheck.report() | ||
assert.deepEqual(report, { | ||
healthy: false, | ||
report: { | ||
'event-loop': { | ||
health: { | ||
healthy: false, | ||
message: 'boom', | ||
}, | ||
meta: { | ||
fatal: true, | ||
}, | ||
}, | ||
'database': { | ||
health: { | ||
healthy: true, | ||
}, | ||
}, | ||
}, | ||
}) | ||
}) | ||
|
||
test('define checker as IoC container binding', async (assert) => { | ||
const ioc = new Ioc() | ||
const application = new Application(__dirname, ioc, {}, {}) | ||
const healthCheck = new HealthCheck(application) | ||
|
||
class DbChecker { | ||
public async report () { | ||
return { | ||
health: { | ||
healthy: true, | ||
}, | ||
} | ||
} | ||
} | ||
|
||
ioc.bind('App/Checkers/Db', () => { | ||
return new DbChecker() | ||
}) | ||
|
||
global[Symbol.for('ioc.make')] = ioc.make.bind(ioc) | ||
global[Symbol.for('ioc.call')] = ioc.call.bind(ioc) | ||
|
||
healthCheck.addChecker('database', 'App/Checkers/Db') | ||
|
||
const report = await healthCheck.report() | ||
assert.deepEqual(report, { | ||
healthy: true, | ||
report: { | ||
'database': { | ||
health: { | ||
healthy: true, | ||
}, | ||
}, | ||
}, | ||
}) | ||
}) | ||
}) |