@pryv/boiler 1.0.8 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2020–2022 Pryv S.A https://pryv.com
1
+ Copyright (c) 2020–2024 Pryv S.A https://pryv.com
2
2
 
3
3
  Redistribution and use in source and binary forms, with or without
4
4
  modification, are permitted provided that the following conditions are met:
package/README.md CHANGED
@@ -11,6 +11,7 @@ The "boiler" must be initialized with the application name and configuration fil
11
11
  ```js
12
12
  require('@pryv/boiler').init({
13
13
  appName: 'my-app', // This will will be prefixed to any log messages
14
+ baseFilesDir: path.resolve(__dirname, '..'), // use for file:// relative path if not give cwd() will be used
14
15
  baseConfigDir: path.resolve(__dirname, '../config'),
15
16
  extraConfigs: [{
16
17
  scope: 'extra-config',
@@ -99,6 +100,14 @@ config.get('foo'); // {bar: 'hello'}
99
100
  // Note: for 'test' scope there is a "sugar" function with config.injectTestConfig(object)
100
101
  ```
101
102
 
103
+ Finding out from which scope a key applies:
104
+ As nconf is hierachical sometimes you migth want to search from which scope the value of a key is issued.
105
+
106
+ ```javascript
107
+ config.getScopeAndValue('foo');
108
+ // returns {value: 'bar', scope: 'scopeName'; info: 'From <file> or Type <env, '}
109
+ ```
110
+
102
111
  #### "Learn" mode
103
112
 
104
113
  To help detect unused configuration settings, a "learn" mode can be activated to track all calls to `config.get()` in files.
@@ -108,7 +117,7 @@ Example when running tests:
108
117
  export CONFIG_LEARN_DIR="{absolute path}/service-core/learn-config"
109
118
  yarn test
110
119
  ```
111
-
120
+ Note, if CONFIG_LEARN_DIR is not given `{process.cwd()}/learn-config` will be used
112
121
 
113
122
  ### Logging
114
123
 
@@ -135,6 +144,12 @@ Set the `DEBUG` environment variable. For example: `DEBUG="*" node app.js` will
135
144
 
136
145
  As "debug" is a widely used package, you might get way more debug lines than expected, so you can use the `appName` property to only output messages from your application code: `DEBUG="<appName>*" node app.js`
137
146
 
147
+ #### Using a custom logger
148
+
149
+ A custom logger can be used by providing `logs:custom` information to the configuration. A working sample of custom Logger is provided in `./examples/customLogger`.
150
+
151
+ The module must implement `async init(settings)` and `log(level, key, message, meta)`
152
+
138
153
  #### Log configuration sample
139
154
 
140
155
  ```javascript
@@ -151,10 +166,20 @@ logs: {
151
166
  file: {
152
167
  active: true,
153
168
  path: 'application.log'
169
+ },
170
+ custom: { 
171
+ active: true,
172
+ path 'path/to/node/package',
173
+ settings: { /* settings passed to the custom logger */}
154
174
  }
155
175
  }
156
176
  ```
157
177
 
178
+ ## TODO
179
+
180
+ - Make config an eventEmitter ? // to track when read or if config changes
181
+ - FIX realtive PATH logic for config.loadFromFile()
182
+
158
183
 
159
184
  ## Contributing
160
185
 
@@ -4,4 +4,11 @@ foo:
4
4
  foo: 'Hello Foo'
5
5
  service:
6
6
  name: 'To be overriden by Async Service load'
7
- default-yaml: 'default-yaml loaded'
7
+ default-yaml: 'default-yaml loaded'
8
+
9
+ logs:
10
+ custom:
11
+ active: true
12
+ path: '../examples/customLogger'
13
+ settings:
14
+ dummy: 'bob'
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "customlogger",
3
+ "version": "1.0.0",
4
+ "description": "Sample Custom Logger",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "",
10
+ "license": "BSD-3-Clause"
11
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @license
3
+ * [BSD-3-Clause](https://github.com/pryv/pryv-boiler/blob/master/LICENSE)
4
+ */
5
+
6
+ module.exports = {
7
+ init,
8
+ log
9
+ };
10
+
11
+ async function init (settings) {
12
+ console.log('CUSTOM LOGGER initilaized with', settings);
13
+ }
14
+
15
+ /**
16
+ *
17
+ * @param {string} level one of 'debug', 'info', 'warn', 'error'
18
+ * @param {string} key ':' namespaced keys
19
+ * @param {message} message
20
+ * @param {object} meta
21
+ */
22
+ function log (level, key, text, meta) {
23
+ console.log('Custom: ' + JSON.stringify({ level, key, text, meta }));
24
+ }
package/examples/index.js CHANGED
@@ -6,18 +6,9 @@ const path = require('path');
6
6
  const boiler = require('../src');
7
7
  const { getConfigUnsafe, getLogger, getConfig } = require('../src').init({
8
8
  appName: 'sample',
9
+ baseFilesDir: path.resolve(__dirname, '../'),
9
10
  baseConfigDir: path.resolve(__dirname, './configs'),
10
11
  extraConfigs: [{
11
- scope: 'airbrake',
12
- key: 'logs',
13
- data: {
14
- airbrake: {
15
- active: false,
16
- projectId: 319858,
17
- key: '44ca9a107f4546505c7e24c8c598b0c7'
18
- }
19
- }
20
- }, {
21
12
  scope: 'extra1',
22
13
  file: path.resolve(__dirname, './configs/extra-config.yml')
23
14
  }, {
@@ -88,7 +79,6 @@ indexLogger.info('hide stuff auth=c08r0xs95xlb1xgssmp6tr7c0000gp', { password: '
88
79
 
89
80
  (async () => {
90
81
  await getConfig();
91
- await boiler.notifyAirbrake('Hello');
92
82
  indexLogger.info('pryv.li serial: ', config.get('serial'));
93
83
  indexLogger.info('pryv.me name: ', config.get('service:name'));
94
84
  indexLogger.info('Favicon: ', config.get('definitions:favicon:default:url'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pryv/boiler",
3
- "version": "1.0.8",
3
+ "version": "1.2.4",
4
4
  "private": false,
5
5
  "description": "Logging and config boilerplate library for Node.js apps and services at Pryv",
6
6
  "keywords": [
@@ -24,14 +24,15 @@
24
24
  "lint": "semistandard"
25
25
  },
26
26
  "dependencies": {
27
- "@airbrake/node": "^1.4.2",
28
27
  "debug": "^4.3.4",
29
- "js-yaml": "^4.0.0",
28
+ "js-yaml": "^4.1.0",
30
29
  "nconf": "^0.12.0",
31
- "semistandard": "^16.0.1",
32
- "source-licenser": "^2.0.4",
33
- "superagent": "^7.1.2",
34
- "winston": "^3.7.2",
35
- "winston-daily-rotate-file": "^4.6.1"
30
+ "superagent": "^8.0.9",
31
+ "winston": "^3.9.0",
32
+ "winston-daily-rotate-file": "^4.7.1"
33
+ },
34
+ "devDependencies": {
35
+ "semistandard": "^17.0.0",
36
+ "source-licenser": "^2.0.5"
36
37
  }
37
38
  }
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  const path = require('path');
10
- const learnDir = process.env.CONFIG_LEARN_DIR || path.resolve(__dirname, '../../../learn-config');
10
+ const learnDir = process.env.CONFIG_LEARN_DIR || path.resolve(process.cwd(), 'learn-config');
11
11
  console.log('Looking for learning files in: ' + learnDir);
12
12
  const fs = require('fs');
13
13
 
package/src/config.js CHANGED
@@ -46,15 +46,10 @@ const defaults = {
46
46
  rotation: {
47
47
  isActive: false
48
48
  }
49
- },
50
- airbrake: {
51
- active: false
52
49
  }
53
50
  }
54
51
  };
55
52
 
56
-
57
-
58
53
  /**
59
54
  * Config manager
60
55
  */
@@ -65,7 +60,7 @@ class Config {
65
60
  baseConfigDir;
66
61
  learnDirectoryAndFilename;
67
62
 
68
- constructor() {
63
+ constructor () {
69
64
  this.extraAsync = [];
70
65
  }
71
66
 
@@ -76,13 +71,15 @@ class Config {
76
71
  * @param {string} appName
77
72
  * @param {string} [learnDirectory] - (optional) if set, all .get() calls will be tracked in this files in this directory
78
73
  * @param {string} [options.baseConfigDir] - (optional) directory to use to look for configs (default, env)
74
+ * @param {string} [options.baseFilesDir] - (optional) directory to use for `file://` relative path
79
75
  * @param {Array<ConfigFile|ConfigPlugin|ConfigData|ConfigRemoteURL|ConfigRemoteURLFromKey>} [options.extras] - (optional) and array of extra files or plugins to load (synchronously or async)
80
76
  * @param {Object} logging
81
77
  * @returns {Config} this
82
78
  */
83
- initSync(options, logging) {
79
+ initSync (options, logging) {
84
80
  this.appName = options.appName;
85
81
  this.learnDirectoryAndFilename = getLearnFilename(options.appName, options.learnDirectory);
82
+ this.baseFilesDir = options.baseFilesDir || process.cwd();
86
83
 
87
84
  const logger = this.logger = logging.getLogger('config');
88
85
  const store = this.store = new nconf.Provider();
@@ -104,12 +101,12 @@ class Config {
104
101
  // memory must come first for config.set() to work without loading config files
105
102
  // 3. `process.env`
106
103
  // 4. `process.argv`
107
- store.argv({parseValues: true}).env({parseValues: true, separator: '__'});
104
+ store.argv({ parseValues: true }).env({ parseValues: true, separator: '__' });
108
105
 
109
106
  // 5. Values in `${NODE_ENV}-config.yml` or from --config parameter
110
107
  let configFile;
111
108
  if (store.get('config')) {
112
- configFile = store.get('config')
109
+ configFile = store.get('config');
113
110
  } else if (store.get('NODE_ENV')) {
114
111
  configFile = path.resolve(baseConfigDir, store.get('NODE_ENV') + '-config.yml');
115
112
  }
@@ -123,7 +120,7 @@ class Config {
123
120
 
124
121
  // load extra config files & plugins
125
122
  if (options.extras) {
126
- for (let extra of options.extras) {
123
+ for (const extra of options.extras) {
127
124
  if (extra.file) {
128
125
  loadFile(extra.scope, extra.file);
129
126
  continue;
@@ -134,7 +131,7 @@ class Config {
134
131
  continue;
135
132
  }
136
133
  if (extra.data) {
137
- const conf = extra.key ? {[extra.key]: extra.data} : extra.data;
134
+ const conf = extra.key ? { [extra.key]: extra.data } : extra.data;
138
135
  store.use(extra.scope, { type: 'literal', store: conf });
139
136
  logger.debug('Loaded [' + extra.scope + '] from DATA: ' + (extra.key ? ' under [' + extra.key + ']' : ''));
140
137
  continue;
@@ -142,7 +139,7 @@ class Config {
142
139
  if (extra.url || extra.urlFromKey || extra.fileAsync) {
143
140
  // register scope in the chain to keep order of configs
144
141
  store.use(extra.scope, { type: 'literal', store: {} });
145
- logger.debug('Booked [' + extra.scope +'] for async Loading ');
142
+ logger.debug('Booked [' + extra.scope + '] for async Loading ');
146
143
  this.extraAsync.push(extra);
147
144
  continue;
148
145
  }
@@ -155,7 +152,6 @@ class Config {
155
152
  }
156
153
  }
157
154
 
158
-
159
155
  // .end-1 load default and custom config from configs/default-config.json
160
156
  loadFile('default-file', path.resolve(baseConfigDir, 'default-config.yml'));
161
157
 
@@ -168,50 +164,49 @@ class Config {
168
164
 
169
165
  // --- helpers --/
170
166
 
171
- function loadFile(scope, filePath) {
172
-
167
+ function loadFile (scope, filePath) {
173
168
  if (fs.existsSync(filePath)) {
174
-
175
- if (filePath.endsWith('.js')) { // JS file
169
+ if (filePath.endsWith('.js')) { // JS file
176
170
  const conf = require(filePath);
177
171
  store.use(scope, { type: 'literal', store: conf });
178
- } else {  // JSON or YAML
179
- const options = { file: filePath }
180
- if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) { options.format = nconf.formats.yaml }
172
+ } else { // JSON or YAML
173
+ const options = { file: filePath };
174
+ if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) { options.format = nconf.formats.yaml; }
181
175
  store.file(scope, options);
182
176
  }
183
177
 
184
- logger.debug('Loaded [' + scope + '] from file: ' + filePath)
178
+ logger.debug('Loaded [' + scope + '] from file: ' + filePath);
185
179
  } else {
186
180
  logger.debug('Cannot find file: ' + filePath + ' for scope [' + scope + ']');
187
181
  }
188
182
  }
189
183
  }
190
184
 
191
- async initASync() {
185
+ async initASync () {
192
186
  const store = this.store;
193
187
  const logger = this.logger;
194
188
  const baseConfigDir = this.baseConfigDir;
189
+ const baseFilesDir = this.baseFilesDir;
195
190
 
196
- async function loadUrl(scope, key, url) {
191
+ async function loadUrl (scope, key, url) {
197
192
  if (typeof url === 'undefined' || url === null) {
198
- logger.warn('Null or Undefined Url for [' + scope +']');
199
- return;
193
+ logger.warn('Null or Undefined Url for [' + scope + ']');
194
+ return;
200
195
  }
201
196
 
202
197
  let res = null;
203
198
  if (isFileUrl(url)) {
204
- res = loadFromFile(url);
199
+ res = loadFromFile(url, baseFilesDir);
205
200
  } else {
206
201
  res = await loadFromUrl(url);
207
202
  }
208
- const conf = key ? {[key]: res} : res;
203
+ const conf = key ? { [key]: res } : res;
209
204
  store.add(scope, { type: 'literal', store: conf });
210
205
  logger.debug('Loaded [' + scope + '] from URL: ' + url + (key ? ' under [' + key + ']' : ''));
211
206
  }
212
207
 
213
208
  // load remote config files
214
- for (let extra of this.extraAsync) {
209
+ for (const extra of this.extraAsync) {
215
210
  if (extra.url) {
216
211
  await loadUrl(extra.scope, extra.key, extra.url);
217
212
  continue;
@@ -231,11 +226,11 @@ class Config {
231
226
  if (extra.fileAsync) {
232
227
  const filePath = path.resolve(baseConfigDir, extra.fileAsync);
233
228
 
234
- if (! fs.existsSync(filePath)) {
229
+ if (!fs.existsSync(filePath)) {
235
230
  logger.warn('Cannot find file: ' + filePath + ' for scope [' + extra.scope + ']');
236
231
  continue;
237
232
  }
238
- if (! filePath.endsWith('.js')) {
233
+ if (!filePath.endsWith('.js')) {
239
234
  logger.warn('Cannot only load .js file: ' + filePath + ' for scope [' + extra.scope + ']');
240
235
  continue;
241
236
  }
@@ -257,21 +252,21 @@ class Config {
257
252
  * @param {string} key
258
253
  * @returns {boolean}
259
254
  */
260
- has(key) {
261
- if (! this.store) { throw(new Error('Config not yet initialized'))}
255
+ has (key) {
256
+ if (!this.store) { throw (new Error('Config not yet initialized')); }
262
257
  const value = this.store.get(key);
263
258
  return (typeof value !== 'undefined');
264
259
  }
265
260
 
266
261
  /**
267
262
  * Retreive value
268
- * @param {string} key
263
+ * @param {string} [key] if no key is provided all the config is returned
269
264
  */
270
- get(key) {
271
- if (! this.store) { throw(new Error('Config not yet initialized'))}
272
- learn(this.learnDirectoryAndFilename, key);
265
+ get (key) {
266
+ if (!this.store) { throw (new Error('Config not yet initialized')); }
273
267
  const value = this.store.get(key);
274
- if (typeof value === 'undefined') this.logger.debug('get: [' + key +'] is undefined');
268
+ if (typeof value === 'undefined') this.logger.debug('get: [' + key + '] is undefined');
269
+ learn(this.learnDirectoryAndFilename, key);
275
270
  return value;
276
271
  }
277
272
 
@@ -279,20 +274,20 @@ class Config {
279
274
  * Retreive value and store info that applies
280
275
  * @param {string} key
281
276
  */
282
- getScopeAndValue(key) {
283
- if (! this.store) { throw(new Error('Config not yet initialized'))};
284
- for (let scopeName of Object.keys(this.store.stores)) {
277
+ getScopeAndValue (key) {
278
+ if (!this.store) { throw (new Error('Config not yet initialized')); }
279
+ for (const scopeName of Object.keys(this.store.stores)) {
285
280
  const store = this.store.stores[scopeName];
286
281
  const value = store.get(key);
287
282
  if (typeof value !== 'undefined') {
288
283
  const res = {
289
- value: value,
284
+ value,
290
285
  scope: scopeName
291
- }
286
+ };
292
287
  if (store.type === 'file') {
293
- res.info = 'From file: ' + store.file
288
+ res.info = 'From file: ' + store.file;
294
289
  } else {
295
- info = 'Type: ' + store.type
290
+ info = 'Type: ' + store.type;
296
291
  }
297
292
  return res;
298
293
  }
@@ -305,8 +300,8 @@ class Config {
305
300
  * @param {string} key
306
301
  * @param {Object} value
307
302
  */
308
- set(key, value) {
309
- if (! this.store) { throw(new Error('Config not yet initialized'))}
303
+ set (key, value) {
304
+ if (!this.store) { throw (new Error('Config not yet initialized')); }
310
305
  this.store.set(key, value);
311
306
  }
312
307
 
@@ -314,7 +309,7 @@ class Config {
314
309
  * Inject Test Config and override any other option
315
310
  * @param {Object} configObject;
316
311
  */
317
- injectTestConfig(configObject) {
312
+ injectTestConfig (configObject) {
318
313
  this.replaceScopeConfig('test', configObject);
319
314
  }
320
315
 
@@ -323,12 +318,11 @@ class Config {
323
318
  * @param {string} scope;
324
319
  * @param {Object} configObject;
325
320
  */
326
- replaceScopeConfig(scope, configObject) {
327
- if (! this.store) { throw(new Error('Config not yet initialized'))}
328
- this.logger.debug('Replace ['+ scope + '] with: ', configObject);
329
- this.store.add(scope, {type: 'literal', store: configObject});
321
+ replaceScopeConfig (scope, configObject) {
322
+ if (!this.store) { throw (new Error('Config not yet initialized')); }
323
+ this.logger.debug('Replace [' + scope + '] with: ', configObject);
324
+ this.store.add(scope, { type: 'literal', store: configObject });
330
325
  }
331
-
332
326
  }
333
327
 
334
328
  module.exports = Config;
@@ -338,17 +332,16 @@ module.exports = Config;
338
332
  const FILE_PROTOCOL = 'file://';
339
333
  const FILE_PROTOCOL_LENGTH = FILE_PROTOCOL.length;
340
334
 
341
- async function loadFromUrl(serviceInfoUrl ) {
342
- const res = await superagent.get(serviceInfoUrl);
335
+ async function loadFromUrl (url) {
336
+ const res = await superagent.get(url);
343
337
  return res.body;
344
338
  }
345
339
 
346
- function loadFromFile(fileUrl ) {
340
+ function loadFromFile (fileUrl, baseFilesDir) {
347
341
  const filePath = stripFileProtocol(fileUrl);
348
342
 
349
343
  if (isRelativePath(filePath)) {
350
- const serviceCorePath = path.resolve(__dirname, '../../../../../'); // assuming /node_modules/@pryv/boiler/src/
351
- fileUrl = path.resolve(serviceCorePath, filePath);
344
+ fileUrl = path.resolve(baseFilesDir, filePath);
352
345
  fileUrl = 'file://' + fileUrl;
353
346
  } else {
354
347
  // absolute path, do nothing.
@@ -359,74 +352,65 @@ function loadFromFile(fileUrl ) {
359
352
  return res;
360
353
  }
361
354
 
362
-
363
- function isFileUrl(filePath) {
355
+ function isFileUrl (filePath) {
364
356
  return filePath.startsWith(FILE_PROTOCOL);
365
357
  }
366
358
 
367
- function isRelativePath(filePath) {
359
+ function isRelativePath (filePath) {
368
360
  return !path.isAbsolute(filePath);
369
361
  }
370
362
 
371
- function stripFileProtocol(filePath) {
363
+ function stripFileProtocol (filePath) {
372
364
  return filePath.substring(FILE_PROTOCOL_LENGTH);
373
365
  }
374
366
 
375
-
376
367
  // -------- learning mode ------- //
377
368
 
378
- function getLearnFilename(appName, learnDirectory) {
379
- if (! learnDirectory) return;
369
+ function getLearnFilename (appName, learnDirectory) {
370
+ if (!learnDirectory) return;
380
371
  let i = 0;
381
372
  let res;
382
373
  do {
383
- res = path.join(learnDirectory, appName + i );
374
+ res = path.join(learnDirectory, appName + i);
384
375
  i++;
385
- } while(fs.existsSync(res + '-config.json'));
376
+ } while (fs.existsSync(res + '-config.json'));
386
377
  return res;
387
378
  }
388
379
 
389
- function learn(learnDirectoryAndFilename, key) {
380
+ function learn (learnDirectoryAndFilename, key) {
390
381
  if (learnDirectoryAndFilename) {
391
382
  const caller_line = (new Error()).stack.split('\n')[3]; // get callee name and line
392
- const index = caller_line.indexOf("at ");
393
- str = key + ';' + caller_line.slice(index+3, caller_line.length) + '\n';
383
+ const index = caller_line.indexOf('at ');
384
+ const str = key + ';' + caller_line.slice(index + 3, caller_line.length) + '\n';
394
385
  fs.appendFileSync(learnDirectoryAndFilename + '-calls.csv', str);
395
386
  }
396
387
  }
397
388
 
398
- function saveConfig(learnDirectoryAndFilename, store) {
389
+ function saveConfig (learnDirectoryAndFilename, store) {
399
390
  if (learnDirectoryAndFilename) {
400
- let i = 0;
401
- let filename;
402
- do {
403
- filename =learnDirectoryAndFilename + '-config.json';
404
- i++;
405
- } while(fs.existsSync(filename));
406
- fs.writeFileSync(filename, JSON.stringify({stores: store.stores, config: store.get()}, null, 2));
391
+ const filename = learnDirectoryAndFilename + '-config.json';
392
+ fs.writeFileSync(filename, JSON.stringify({ stores: store.stores, config: store.get() }, null, 2));
407
393
  }
408
394
  }
409
395
 
410
-
411
396
  /**
412
397
  * @typedef ConfigFile
413
398
  * @property {string} scope - scope for nconf hierachical load
414
399
  * @property {string} file - the config file (.yml, .json, .js)
415
400
  */
416
401
 
417
- /**
402
+ /**
418
403
  * @typedef ConfigPlugin
419
404
  * @property {Object} plugin
420
405
  * @property {Function} plugin.load - a function that takes the "nconf store" as argument and returns the "name" of the plugin
421
406
  */
422
407
 
423
- /**
408
+ /**
424
409
  * @typedef ConfigData
425
410
  * @property {string} scope - scope for nconf hierachical load
426
411
  * @property {string} [key] - (optional) key to load result of url. If null loaded at root of the config
427
412
  * @property {object} data - the data to load
428
413
 
429
-
430
414
  /**
431
415
  * @typedef ConfigRemoteURL
432
416
  * @property {string} scope - scope for nconf hierachical load
package/src/index.js CHANGED
@@ -4,23 +4,17 @@
4
4
  */
5
5
 
6
6
  /**
7
- * Pryv Boiler module.
8
- * @module boiler
9
- */
7
+ * Pryv Boiler module.
8
+ * @module boiler
9
+ */
10
10
 
11
11
  const Config = require('./config');
12
12
  const logging = require('./logging');
13
- const airbrake = require('./airbrake');
14
13
 
14
+ /** @type {Config} */
15
15
  const config = new Config();
16
16
 
17
17
  const boiler = {
18
- /**
19
- * notify Airbrake.
20
- * If initalize, arguments will be passed to airbrake.notify()
21
- */
22
- notifyAirbrake: airbrake.notifyAirbrake,
23
-
24
18
  /**
25
19
  * get a Logger
26
20
  * @param {string} name
@@ -29,16 +23,16 @@ const boiler = {
29
23
  getLogger: logging.getLogger,
30
24
  /**
31
25
  * Prefered way to get the configuration
32
- * @returns {Promise}
26
+ * @returns {Promise<Config>}
33
27
  */
34
- getConfig: getConfig,
28
+ getConfig,
35
29
  /**
36
30
  * get the configuration.
37
31
  * If the configuration is not fully initialized throw an error
38
32
  * @param {boolean} warnOnly - Only warns about potential misuse of config
39
33
  * @returns {Config}
40
34
  */
41
- getConfigUnsafe: getConfigUnsafe,
35
+ getConfigUnsafe,
42
36
 
43
37
  /**
44
38
  * Init Boiler, should be called just once when starting an APP
@@ -48,7 +42,7 @@ const boiler = {
48
42
  * @param {Array<ConfigFile|ConfigRemoteURL|ConfigRemoteURLFromKey|ConfigPlugin>} [options.extraConfigs] - (optional) and array of extra files to load
49
43
  * @param {Function} [fullyLoadedCallback] - (optional) called when the config is fully loaded
50
44
  */
51
- init: init
45
+ init
52
46
  };
53
47
 
54
48
  let logger;
@@ -69,18 +63,16 @@ function init (options, fullyLoadedCallback) {
69
63
  configInitCalledWithName = options.appName;
70
64
  config.initSync({
71
65
  baseConfigDir: options.baseConfigDir,
66
+ baseFilesDir: options.baseFilesDir,
72
67
  extras: options.extraConfigs,
73
68
  appName: options.appNameWithoutPostfix,
74
69
  learnDirectory: process.env.CONFIG_LEARN_DIR
75
70
  }, logging);
76
71
 
77
72
  logger = logging.getLogger('boiler');
78
- airbrake.setUpAirbrakeIfNeeded(config, logger);
79
73
 
80
74
  config.initASync().then((config) => {
81
75
  configInitialized = true;
82
- // airbrake config might come from async settings, so we try twice.
83
- airbrake.setUpAirbrakeIfNeeded(config, logger);
84
76
  if (fullyLoadedCallback) fullyLoadedCallback(config);
85
77
  });
86
78
 
package/src/logging.js CHANGED
@@ -8,21 +8,22 @@ require('winston-daily-rotate-file');
8
8
  const debugModule = require('debug');
9
9
  let winstonInstance = null;
10
10
  let rootLogger = null;
11
+ let customLoggerInstance = null;
11
12
 
12
13
  // ------ winston formating
13
14
 
14
15
  /**
15
- *
16
- * @param {Object} options
16
+ *
17
+ * @param {Object} options
17
18
  * @param {boolean} options.color - set to true to have colors
18
19
  * @param {boolean} options.time - set to true to for timestamp
19
20
  * @param {boolean} options.align - set to true to allign logs items
20
21
  */
21
- function generateFormat(options) {
22
+ function generateFormat (options) {
22
23
  const formats = [];
23
24
  if (options.color) {
24
25
  formats.push(winston.format.colorize());
25
- }
26
+ }
26
27
  if (options.time) {
27
28
  formats.push(winston.format.timestamp());
28
29
  }
@@ -30,13 +31,13 @@ function generateFormat(options) {
30
31
  formats.push(winston.format.align());
31
32
  }
32
33
 
33
- function printf(info) {
34
+ function printf (info) {
34
35
  const {
35
36
  timestamp, level, message, ...args
36
37
  } = info;
37
-
38
- let items = info[Symbol.for('splat')] || {};
39
-
38
+
39
+ let items = info[Symbol.for('splat')] || {};
40
+
40
41
  let itemStr = '';
41
42
  if (items.length > 0) {
42
43
  let skip = false;
@@ -44,17 +45,14 @@ function generateFormat(options) {
44
45
  if (typeof items[0] === 'undefined') {
45
46
  skip = true;
46
47
  } else {
47
- if (items[0] && items[0].context) {
48
+ if (items[0] && items[0].context) {
48
49
  items = items[0].context;
49
- }
50
+ }
50
51
  }
51
52
  }
52
- if (! skip)
53
- itemStr = util.inspect(items, {depth: 10, colors: true});
53
+ if (!skip) { itemStr = util.inspect(items, { depth: 10, colors: true }); }
54
54
  }
55
55
 
56
-
57
-
58
56
  const line = `[${level}]: ${message} ${itemStr}`;
59
57
 
60
58
  if (options.time) {
@@ -68,81 +66,100 @@ function generateFormat(options) {
68
66
  return winston.format.combine(...formats);
69
67
  }
70
68
 
71
-
72
-
73
69
  /**
74
70
  * Helper to pass log instructions to winston
75
71
  */
76
- function globalLog(level, text, context) {
72
+ function globalLog (level, key, message, context) {
73
+ const text = `[${key}] ${message}`;
77
74
  if (winstonInstance) {
78
75
  winstonInstance[level](text, context);
79
76
  } else {
80
77
  console.log('Logger not initialized: ', ...arguments);
81
78
  }
79
+ if (customLoggerInstance) {
80
+ customLoggerInstance.log(level, key, message, context);
81
+ }
82
82
  }
83
83
 
84
-
85
84
  /**
86
85
  * Config initialize Logger right after beeing loaded
87
86
  * This is done by config Only
88
- */
89
- async function initLoggerWithConfig(config) { 
87
+ */
88
+ async function initLoggerWithConfig (config) {
90
89
  if (winstonInstance) {
91
- throw new Error("Logger was already initialized");
90
+ throw new Error('Logger was already initialized');
92
91
  }
93
92
  // console
94
93
  winstonInstance = winston.createLogger({ });
95
94
  const logConsole = config.get('logs:console');
96
- let isSilent = ! config.get('logs:console:active');
95
+ let isSilent = !config.get('logs:console:active');
97
96
 
98
97
  // LOGS env var can override settings
99
98
  if (process.env.LOGS) {
100
99
  logConsole.level = process.env.LOGS;
101
100
  isSilent = false;
102
- }
103
-
101
+ }
104
102
 
105
- const format = generateFormat(logConsole.format)
106
- const myconsole = new winston.transports.Console({ format: format , level: logConsole.level, silent: isSilent});
103
+ const consoleFormat = generateFormat(logConsole.format);
104
+ const myconsole = new winston.transports.Console({ format: consoleFormat, level: logConsole.level, silent: isSilent });
107
105
  winstonInstance.add(myconsole);
108
-
109
- rootLogger.debug((isSilent ? '** silent ** ' : '') + 'Console with level: ', logConsole.level);
106
+
107
+ rootLogger.debug((isSilent ? '** silent ** ' : '') + 'Console with level: ', logConsole.level);
110
108
 
111
109
  // file
112
110
  const logFile = config.get('logs:file');
113
111
  if (config.get('logs:file:active')) {
112
+ const fileFormat = winston.format.combine(
113
+ winston.format.timestamp(),
114
+ winston.format.json()
115
+ );
116
+
114
117
  rootLogger.debug('File active: ' + logFile.path);
115
118
  if (logFile.rotation.isActive) {
116
- const transport = new winston.transports.DailyRotateFile({
119
+ const rotatedFiles = new winston.transports.DailyRotateFile({
117
120
  filename: logFile.path + '.%DATE%',
118
121
  datePattern: 'YYYY-MM-DD',
119
122
  zippedArchive: true,
123
+ level: logFile.level,
120
124
  maxFiles: logFile.rotation.days ? logFile.rotation.days + 'd' : null,
125
+ format: fileFormat
121
126
  });
127
+ winstonInstance.add(rotatedFiles);
122
128
  } else {
123
- const files = new winston.transports.File({
129
+ const files = new winston.transports.File({
124
130
  filename: logFile.path,
125
131
  level: logFile.level,
126
- maxsize: logFile.maxFileBytes,
127
- maxFiles: logFile.maxNbFiles,
128
- timestamp: true,
129
- json: false
132
+ maxSize: logFile.maxFileBytes || '10m',
133
+ maxFiles: logFile.maxNbFiles || '14d',
134
+ format: fileFormat
130
135
  });
131
136
  winstonInstance.add(files);
132
137
  }
133
-
134
138
  }
135
- rootLogger.debug('Logger Initialized');
136
- };
137
139
 
140
+ // custom
141
+ if (config.get('logs:custom:active')) {
142
+ customLoggerInstance = require(config.get('logs:custom:path'));
143
+ await customLoggerInstance.init(config.get('logs:custom:settings'));
144
+ }
138
145
 
146
+ // catch all errors.
147
+ if (!config.get('logs:skipUncaughtException')) {
148
+ process.on('uncaughtException', function (err) {
149
+ rootLogger.error('UncaughtException', { message: err.message, name: err.name, stack: err.stack });
150
+ throw err;
151
+ });
152
+ }
139
153
 
140
- // --------------- debug utils
154
+ rootLogger.debug('Logger Initialized');
155
+ }
156
+
157
+ // --------------- debug utils
141
158
 
142
159
  /**
143
160
  * Dump objects with file and line
144
161
  */
145
- function inspect() {
162
+ function inspect () {
146
163
  let line = '';
147
164
  try {
148
165
  throw new Error();
@@ -150,40 +167,39 @@ function inspect() {
150
167
  line = e.stack.split(' at ')[2].trim();
151
168
  }
152
169
  let res = '\n * dump at: ' + line;
153
- for (var i = 0; i < arguments.length; i++) {
170
+ for (let i = 0; i < arguments.length; i++) {
154
171
  res += '\n' + i + ' ' + util.inspect(arguments[i], true, 10, true) + '\n';
155
172
  }
156
173
  return res;
157
- };
158
-
174
+ }
159
175
 
160
- function setGlobalName(name) {
176
+ function setGlobalName (name) {
161
177
  // create root logger
162
178
  rootLogger = new Logger(name, null);
163
179
  rootLogger.debug('setGlobalName: ' + name);
164
180
  }
165
181
 
166
-
167
182
  class Logger {
168
183
  parent; // eventual parent
169
184
  debugInstance; // debug instance
170
185
 
171
- constructor(name, parent) {
186
+ constructor (name, parent) {
172
187
  this.name = name;
173
188
  this.parent = parent;
174
- this.debugInstance = debugModule('pryv:' + this._name());
189
+ this.debugInstance = debugModule('pryv:' + this._name());
175
190
  }
191
+
176
192
  /**
177
193
  * Private
178
194
  */
179
- _name() {
195
+ _name () {
180
196
  if (this.parent) return this.parent._name() + ':' + this.name;
181
197
  return this.name;
182
198
  }
183
199
 
184
- log() {
200
+ log () {
185
201
  const level = arguments[0];
186
- const text = '[' + this._name() + ']: ' + hideSensitiveValues(arguments[1]);
202
+ const message = hideSensitiveValues(arguments[1]);
187
203
  const context = [];
188
204
 
189
205
  let meta;
@@ -192,73 +208,79 @@ class Logger {
192
208
  context.push(inspectAndHide(arguments[i]));
193
209
  }
194
210
  if (context.length === 1) {
195
- meta = {context: context[0]};
211
+ meta = { context: context[0] };
196
212
  } else if (context.length > 1) {
197
- meta = {context: context};
213
+ meta = { context };
198
214
  }
199
- globalLog(level, text, meta);
215
+ globalLog(level, this._name(), message, meta);
200
216
  }
201
217
 
202
218
  info () { this.log('info', ...arguments); }
203
219
  warn () { this.log('warn', ...arguments); }
204
220
  error () { this.log('error', ...arguments); }
205
- debug () {
221
+ debug () {
206
222
  if (winstonInstance) {
207
- this.log('debug', ...arguments);
223
+ this.log('debug', ...arguments);
208
224
  }
209
- this.debugInstance(...arguments);
210
- }
225
+ this.debugInstance(...arguments);
226
+ }
211
227
 
212
228
  /**
213
229
  * get a "sub" Logger
214
- * @param {Logger} name
230
+ * @param {Logger} name
231
+ * @returns {Logger}
215
232
  */
216
233
  getLogger (name) {
217
234
  return new Logger(name, this);
218
235
  }
219
236
 
220
- inspect() { inspect(...arguments); }
237
+ inspect () { inspect(...arguments); }
221
238
  }
222
239
 
223
- function getLogger(name) {
224
- if (! rootLogger) {
225
- throw new Error('Initalize boiler before using logger')
240
+ /**
241
+ * Get a new logger, or root loggger if no name is provided
242
+ * @param {string} [name]
243
+ * @returns {Logger}
244
+ */
245
+ function getLogger (name) {
246
+ if (!rootLogger) {
247
+ throw new Error('Initalize boiler before using logger');
226
248
  }
227
- if(! name) {
249
+ if (!name) {
228
250
  return rootLogger;
229
251
  }
230
252
  return rootLogger.getLogger(name);
231
253
  }
232
254
 
233
255
  module.exports = {
234
- getLogger: getLogger,
235
- setGlobalName: setGlobalName,
236
- initLoggerWithConfig: initLoggerWithConfig
237
- }
256
+ getLogger,
257
+ setGlobalName,
258
+ initLoggerWithConfig
259
+ };
238
260
 
239
261
  // ----------------- Hide sensite data -------------------- //
240
262
 
241
- function inspectAndHide(o) {
263
+ function inspectAndHide (o) {
242
264
  if (typeof o === 'undefined') return o;
243
265
  if (o instanceof Error) return o;
244
266
  return _inspectAndHide(JSON.parse(JSON.stringify(o))); // clone and remove circular
245
267
  }
246
268
 
247
- function _inspectAndHide(o) {
269
+ function _inspectAndHide (o) {
248
270
  if (typeof o === 'string') {
249
271
  return hideSensitiveValues(o);
250
272
  }
251
273
  if (o !== null && typeof o === 'object') {
252
274
  if (Array.isArray(o)) {
253
275
  const res = [];
254
- for (let item of o) {
276
+ for (const item of o) {
255
277
  res.push(inspectAndHide(item));
256
278
  }
257
279
  return res;
258
280
  }
259
281
 
260
282
  const res = {};
261
- for (let key of Object.keys(o)) {
283
+ for (const key of Object.keys(o)) {
262
284
  if (['password', 'passwordHash', 'newPassword'].includes(key)) {
263
285
  res[key] = '(hidden password)';
264
286
  } else {
@@ -270,7 +292,6 @@ function _inspectAndHide(o) {
270
292
  return o;
271
293
  }
272
294
 
273
-
274
295
  // Hides sensitive values (auth tokens and passwords) in log messages
275
296
  function hideSensitiveValues (msg) {
276
297
  if (typeof msg !== 'string') return msg;
@@ -279,8 +300,8 @@ function hideSensitiveValues (msg) {
279
300
  const mask = '(hidden)';
280
301
 
281
302
  const res = msg
282
- .replace(tokenRegexp, 'auth='+mask)
283
- .replace(passwordRegexp, '$1='+mask);
284
-
303
+ .replace(tokenRegexp, 'auth=' + mask)
304
+ .replace(passwordRegexp, '$1=' + mask);
305
+
285
306
  return res;
286
- }
307
+ }
package/src/airbrake.js DELETED
@@ -1,52 +0,0 @@
1
- /**
2
- * @license
3
- * [BSD-3-Clause](https://github.com/pryv/pryv-boiler/blob/master/LICENSE)
4
- */
5
-
6
- const { Notifier } = require('@airbrake/node');
7
-
8
- let airbrake;
9
- let logger;
10
-
11
- function setUpAirbrakeIfNeeded (config, rootLogger) {
12
- if (airbrake) {
13
- rootLogger.debug('Skipping airBrake setup (already done)');
14
- return;
15
- }
16
- logger = rootLogger;
17
-
18
- const airBrakeSettings = config.get('logs:airbrake');
19
- if (airBrakeSettings.active) {
20
- airbrake = new Notifier({
21
- projectId: airBrakeSettings.projectId,
22
- projectKey: airBrakeSettings.key,
23
- environment: 'production'
24
- });
25
- logger.debug('Airbrake active with projectId: ', airBrakeSettings);
26
- }
27
-
28
- // Catch uncaught Promise rejections
29
- process.on('unhandledRejection', (reason) => {
30
- throw reason;
31
- });
32
-
33
- // Catch uncaught Exceptions
34
- process.on('uncaughtException', async (error) => {
35
- logger.error('uncaughtException', error);
36
- await notifyAirbrake(error);
37
- if (process.env.NODE_ENV !== 'test') process.exit(1);
38
- });
39
- }
40
-
41
- async function notifyAirbrake () {
42
- if (airbrake != null && typeof airbrake.notify === 'function') {
43
- await airbrake.notify(...arguments);
44
- } else {
45
- logger.debug('Skipping notifyAirbake', ...arguments);
46
- }
47
- }
48
-
49
- module.exports = {
50
- setUpAirbrakeIfNeeded: setUpAirbrakeIfNeeded,
51
- notifyAirbrake: notifyAirbrake
52
- };