UNPKG

7.25 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const Decorators_1 = require("../ioc/Decorators");
4const LogManager_1 = require("../log/LogManager");
5const NewrelicUtil_1 = require("../newrelic/NewrelicUtil");
6const LOGGER = LogManager_1.LogManager.getLogger(__filename);
7exports.HEALTH_CHECK_GROUP = 'inceptum:health';
8function RegisterAsHealthCheck(target) {
9 Decorators_1.RegisterInGroup(exports.HEALTH_CHECK_GROUP)(target);
10 Decorators_1.Lazy(false)(target);
11}
12exports.RegisterAsHealthCheck = RegisterAsHealthCheck;
13var HealthCheckStatus;
14(function (HealthCheckStatus) {
15 HealthCheckStatus["OK"] = "OK";
16 HealthCheckStatus["NOT_READY"] = "NOT_READY";
17 HealthCheckStatus["WARNING"] = "WARNING";
18 HealthCheckStatus["CRITICAL"] = "CRITICAL";
19 HealthCheckStatus["STOPPING"] = "STOPPING";
20})(HealthCheckStatus = exports.HealthCheckStatus || (exports.HealthCheckStatus = {}));
21var HealthCheckType;
22(function (HealthCheckType) {
23 HealthCheckType[HealthCheckType["CONTEXT"] = 0] = "CONTEXT";
24 HealthCheckType[HealthCheckType["DEPENDENCY"] = 1] = "DEPENDENCY";
25 HealthCheckType[HealthCheckType["GROUP"] = 2] = "GROUP";
26})(HealthCheckType = exports.HealthCheckType || (exports.HealthCheckType = {}));
27function getStatusIndex(status) {
28 return [HealthCheckStatus.OK, HealthCheckStatus.NOT_READY, HealthCheckStatus.WARNING, HealthCheckStatus.CRITICAL, HealthCheckStatus.STOPPING].indexOf(status);
29}
30class HealthCheckResult {
31 constructor(status, message, timestamp, data) {
32 this.status = status;
33 this.message = message;
34 this.data = data;
35 this.timestamp = timestamp || new Date().getTime();
36 }
37 static getInitialResult() {
38 return new HealthCheckResult(HealthCheckStatus.NOT_READY, 'First check not run yet');
39 }
40 static staleResult(healthCheck, lastResult) {
41 return new HealthCheckResult(HealthCheckStatus.CRITICAL, `Stale check result for check ${healthCheck.checkName}. Last result on ${lastResult.timestamp}`);
42 }
43}
44exports.HealthCheckResult = HealthCheckResult;
45class HealthCheck {
46 constructor(checkName, sleepMillis = 60000, staleNumFails = 2, runImmediately = false) {
47 this.checkName = checkName;
48 this.sleepMillis = sleepMillis;
49 this.staleNumFails = staleNumFails;
50 this.runImmediately = runImmediately;
51 this.started = false;
52 this.lastResult = HealthCheckResult.getInitialResult();
53 this.checkRunning = false;
54 }
55 getCheckName() {
56 return this.checkName;
57 }
58 getSleepMillis() {
59 return this.sleepMillis;
60 }
61 start() {
62 if (this.started) {
63 return;
64 }
65 this.started = true;
66 this.doStart();
67 if (this.runImmediately) {
68 this.runCheck();
69 }
70 }
71 async runCheck() {
72 if (!this.checkRunning) {
73 this.checkRunning = true;
74 try {
75 this.lastResult = await this.doCheck();
76 }
77 catch (e) {
78 this.lastResult = new HealthCheckResult(HealthCheckStatus.CRITICAL, `There was an error running check ${this.getCheckName()}: ${e.message}`);
79 }
80 if (this.lastResult.status === HealthCheckStatus.CRITICAL) {
81 LOGGER.error(`Health check ${this.getCheckName()} failed with status CRITICAL`, { status: this.lastResult.status, message: this.lastResult.message, data: this.lastResult.data });
82 }
83 this.checkRunning = false;
84 }
85 }
86 doStart() {
87 this.timer = setInterval(async () => { await this.runCheck(); }, this.getSleepMillis());
88 }
89 stop() {
90 if (this.started) {
91 this.doStop();
92 }
93 }
94 doStop() {
95 if (this.timer) {
96 clearInterval(this.timer);
97 }
98 }
99 isStarted() {
100 return this.started;
101 }
102 getLastResult() {
103 const threshold = (new Date().getTime()) - this.getSleepMillis() * this.staleNumFails;
104 if (this.lastResult.timestamp < threshold) {
105 return HealthCheckResult.staleResult(this, this.lastResult);
106 }
107 return this.lastResult;
108 }
109}
110exports.HealthCheck = HealthCheck;
111class HealthCheckGroup extends HealthCheck {
112 constructor(groupName) {
113 super(`Group: ${groupName}`, 0, 0);
114 this.healthChecks = new Map();
115 this.groupKey = groupName;
116 }
117 addCheck(healthCheck, asName) {
118 this.addCheckAs(healthCheck, asName || healthCheck.getCheckName());
119 }
120 addCheckAs(healthCheck, asName) {
121 if (asName.indexOf('.') >= 0) {
122 const parts = asName.split('.', 2);
123 const firstPart = parts[0];
124 const remainderPart = asName.substr(firstPart.length + 1);
125 const group = this.healthChecks.get(firstPart);
126 if (group && (group instanceof HealthCheckGroup)) {
127 group.addCheckAs(healthCheck, remainderPart);
128 }
129 else if (group) {
130 throw new Error(`A health check with name ${firstPart} is already defined in this group`);
131 }
132 else {
133 const newGroup = new HealthCheckGroup(firstPart);
134 newGroup.addCheckAs(healthCheck, remainderPart);
135 this.addCheck(newGroup, firstPart);
136 }
137 }
138 else {
139 this.healthChecks.set(asName, healthCheck);
140 if (this.isStarted()) {
141 healthCheck.start();
142 }
143 }
144 }
145 doStart() {
146 this.healthChecks.forEach((healthCheck) => {
147 healthCheck.start();
148 });
149 }
150 doStop() {
151 this.healthChecks.forEach((healthCheck) => {
152 healthCheck.stop();
153 });
154 }
155 async doCheck() {
156 throw new Error('This shouldn\'t be called. We override getLastResult to get up-to-date info');
157 }
158 getLastResult() {
159 const resp = new HealthCheckResult(HealthCheckStatus.OK, 'OK', new Date().getTime(), {});
160 this.healthChecks.forEach((healthCheck, key) => {
161 // all health check results are included in response.
162 const lastResult = healthCheck.getLastResult();
163 resp.data[key] = lastResult;
164 // although health status is ignore in /health, notify newrelic.
165 if (this.ignoreHealthCheckStatus(healthCheck, lastResult.status)) {
166 NewrelicUtil_1.NewrelicUtil.noticeError(new Error(lastResult.message), lastResult);
167 }
168 else {
169 if (getStatusIndex(lastResult.status) > getStatusIndex(resp.status)) {
170 resp.status = lastResult.status;
171 resp.message = `Check ${healthCheck.getCheckName()} returned ${resp.status}`;
172 }
173 }
174 });
175 return resp;
176 }
177 /**
178 * Ignore dependencies CRITICAL status. It is not considered
179 * to be part of App's health.
180 */
181 ignoreHealthCheckStatus(hc, status) {
182 return status === HealthCheckStatus.CRITICAL && hc.getType() === HealthCheckType.DEPENDENCY;
183 }
184 getType() {
185 return HealthCheckType.GROUP;
186 }
187}
188exports.HealthCheckGroup = HealthCheckGroup;
189//# sourceMappingURL=HealthCheck.js.map
\No newline at end of file