UNPKG

6.29 kBJavaScriptView Raw
1'use strict';
2
3const path = require('path');
4const _ = require('lodash');
5const Promise = require('bluebird');
6
7const Cache = require('./cache');
8
9const errors = require('./errors');
10const QueueFactory = require('./queue');
11const ErrorReporterFactory = require('./error');
12const MetricsFactory = require('./metrics');
13const LoggerFactory = require('./logger');
14const ApiFactory = require('./api');
15const Express = require('./express');
16const Config = require('./config');
17
18class MicroKit {
19 constructor(options) {
20 const configDir = path.join(process.env.NODE_CONFIG_DIR || process.cwd(), 'config');
21 const env = process.env.NODE_ENV;
22 const defaultOptions = {
23 queue: {name: 'log'},
24 metrics: {name: 'log'},
25 error: {name: 'log'},
26 logger: {name: 'console', formatter: 'pretty'},
27 systemLogger: {name: 'console', formatter: 'pretty'},
28 api: {name: 'swagger'},
29 config: {
30 providers: [{
31 name: 'file',
32 file: path.join(configDir, 'default.json'),
33 ignoreMissing: true
34 }, {
35 name: 'file',
36 file: path.join(configDir, 'default.yaml'),
37 ignoreMissing: true
38 }, {
39 name: 'file',
40 file: path.join(configDir, 'default.js'),
41 ignoreMissing: true
42 }, {
43 name: 'file',
44 file: path.join(configDir, `${env}.json`),
45 ignoreMissing: true
46 }, {
47 name: 'file',
48 file: path.join(configDir, `${env}.yaml`),
49 ignoreMissing: true
50 }, {
51 name: 'file',
52 file: path.join(configDir, `${env}.js`),
53 ignoreMissing: true
54 }, {
55 name: 'env',
56 file: path.join(configDir, 'custom-environment-variables.json'),
57 ignoreMissing: true
58 }, {
59 name: 'env',
60 file: path.join(configDir, 'custom-environment-variables.yaml'),
61 ignoreMissing: true
62 }]
63 },
64 catchGlobal: true,
65 catchSigint: true,
66 restartOnException: false // automatic restart can cause errors
67 };
68
69 this.queueFactory = new QueueFactory();
70 this.errorReporterFactory = new ErrorReporterFactory();
71 this.metricsFactory = new MetricsFactory();
72 this.loggerFactory = new LoggerFactory();
73 this.apiFactory = new ApiFactory();
74
75 this.cache = new Cache();
76 this.runOnExit = [];
77 this.runOnExitInternal = [];
78
79 this.options = _.defaultsDeep(options || {}, defaultOptions);
80 if (this.options.loadConfig) {
81 this.options = _.defaultsDeep(this.config.microkit, this.options);
82 }
83
84 if (!this.options.name) {
85 throw new Error('application name not specified');
86 }
87
88 if (this.options.catchGlobal) {
89 process.once('uncaughtException', err => {
90 this.logger.error('uncaughtException', err);
91
92 return this.error.capture(err, {}, {wait: true}).finally(() => {
93 if (this.options.restartOnException) {
94 return this.exit().then(() => {
95 process.exit(1);
96 });
97 }
98 });
99 });
100
101 process.once('unhandledRejection', err => {
102 this.logger.error('unhandledRejection', err);
103
104 return this.error.capture(err, {}, {wait: true}).finally(() => {
105 if (this.options.restartOnException) {
106 return this.exit().then(() => {
107 process.exit(1);
108 });
109 }
110 });
111 });
112 }
113
114 if (this.options.catchSigint) {
115 process.on('SIGINT', () => {
116 this.exit().then(() => process.exit());
117 });
118 }
119
120 process.on('exit', sig => {
121 this.logger.info('exit', {signal: sig});
122 });
123
124 this.systemLogger = this.loggerFactory.create(
125 this.options.systemLogger.name,
126 _.omit(this.options.systemLogger, 'name')
127 );
128 }
129
130 get name() {
131 return this.options.name;
132 }
133
134 exit() {
135 return Promise.map(this.runOnExitInternal, handler => handler()).then(
136 () => {
137 return Promise.map(this.runOnExit, handler => handler());
138 }
139 );
140 }
141
142 _toDisposal(service) {
143 this.onExit(() => service.dispose(), true);
144 return service;
145 }
146
147 _getContext(component) {
148 return {
149 service: this.options.name,
150 error: this.error,
151 logger: this.systemLogger.create(component)
152 };
153 }
154
155 get queue() {
156 return this.cache('queue', () => this._toDisposal(
157 this.queueFactory.create(
158 this.options.queue.name,
159 _.omit(this.options.queue, 'name'),
160 this._getContext('queue')
161 )
162 ));
163 }
164
165 get logger() {
166 return this.cache('logger', () => this._toDisposal(
167 this.loggerFactory.create(
168 this.options.logger.name,
169 _.omit(this.options.logger, 'name'),
170 this._getContext('logger')
171 )
172 ));
173 }
174
175 get error() {
176 return this.cache('error', () => this.errorReporterFactory.create(
177 this.options.error.name,
178 _.omit(this.options.error, 'name'),
179 {
180 service: this.options.name,
181 logger: this.systemLogger.create('error')
182 }
183 ));
184 }
185
186 get errors() {
187 return errors;
188 }
189
190 get metrics() {
191 return this.cache('metrics', () => this.metricsFactory.create(
192 this.options.metrics.name,
193 _.omit(this.options.metrics, 'name'),
194 this._getContext('metrics')
195 ));
196 }
197
198 get api() {
199 return this.cache('api', () => this.apiFactory.create(
200 this.options.api.name,
201 _.omit(this.options.api, 'name'),
202 this._getContext('api')
203 ));
204 }
205
206 get express() {
207 return this.cache('express', () => this._toDisposal(
208 new Express(this.options.express, this._getContext('express'))
209 ));
210 }
211
212 get config() {
213 return this.cache('config', () => new Config(this.options.config));
214 }
215
216 onExit(handler, internal) {
217 if (internal) {
218 return this.runOnExitInternal.push(handler);
219 }
220
221 this.runOnExit.push(handler);
222 }
223}
224
225module.exports = function (options) {
226 // load microkit options from config
227 if (options.optionsModule) {
228 let extraOptions;
229 const optionsModule = require(options.optionsModule);
230
231 if (_.isFunction(optionsModule)) {
232 extraOptions = optionsModule(options);
233 } else {
234 extraOptions = optionsModule;
235 }
236
237 _.defaultsDeep(options, extraOptions);
238 }
239
240 return new MicroKit(options);
241};