1 | 'use strict';
|
2 |
|
3 | const path = require('path');
|
4 | const _ = require('lodash');
|
5 | const Promise = require('bluebird');
|
6 |
|
7 | const Cache = require('./cache');
|
8 |
|
9 | const errors = require('./errors');
|
10 | const QueueFactory = require('./queue');
|
11 | const ErrorReporterFactory = require('./error');
|
12 | const MetricsFactory = require('./metrics');
|
13 | const LoggerFactory = require('./logger');
|
14 | const ApiFactory = require('./api');
|
15 | const Express = require('./express');
|
16 | const Config = require('./config');
|
17 |
|
18 | class 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
|
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 |
|
225 | module.exports = function (options) {
|
226 |
|
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 | };
|