@pryv/boiler 1.0.8 → 1.2.3

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–2023 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,20 @@
1
+
2
+ module.exports = { 
3
+ init,
4
+ log
5
+ }
6
+
7
+ async function init(settings) {
8
+ console.log('CUSTOM LOGGER initilaized with', settings);
9
+ }
10
+
11
+ /**
12
+ *
13
+ * @param {string} level one of 'debug', 'info', 'warn', 'error'
14
+ * @param {string} key ':' namespaced keys
15
+ * @param {message} message
16
+ * @param {object} meta
17
+ */
18
+ function log(level, key, text, meta) {
19
+ console.log('Custom: ' + JSON.stringify({level, key, text, meta}));
20
+ }
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.3",
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,9 +46,6 @@ const defaults = {
46
46
  rotation: {
47
47
  isActive: false
48
48
  }
49
- },
50
- airbrake: {
51
- active: false
52
49
  }
53
50
  }
54
51
  };
@@ -76,6 +73,7 @@ class Config {
76
73
  * @param {string} appName
77
74
  * @param {string} [learnDirectory] - (optional) if set, all .get() calls will be tracked in this files in this directory
78
75
  * @param {string} [options.baseConfigDir] - (optional) directory to use to look for configs (default, env)
76
+ * @param {string} [options.baseFilesDir] - (optional) directory to use for `file://` relative path
79
77
  * @param {Array<ConfigFile|ConfigPlugin|ConfigData|ConfigRemoteURL|ConfigRemoteURLFromKey>} [options.extras] - (optional) and array of extra files or plugins to load (synchronously or async)
80
78
  * @param {Object} logging
81
79
  * @returns {Config} this
@@ -83,6 +81,7 @@ class Config {
83
81
  initSync(options, logging) {
84
82
  this.appName = options.appName;
85
83
  this.learnDirectoryAndFilename = getLearnFilename(options.appName, options.learnDirectory);
84
+ this.baseFilesDir = options.baseFilesDir || process.cwd();
86
85
 
87
86
  const logger = this.logger = logging.getLogger('config');
88
87
  const store = this.store = new nconf.Provider();
@@ -192,6 +191,7 @@ class Config {
192
191
  const store = this.store;
193
192
  const logger = this.logger;
194
193
  const baseConfigDir = this.baseConfigDir;
194
+ const baseFilesDir = this.baseFilesDir;
195
195
 
196
196
  async function loadUrl(scope, key, url) {
197
197
  if (typeof url === 'undefined' || url === null) {
@@ -201,7 +201,7 @@ class Config {
201
201
 
202
202
  let res = null;
203
203
  if (isFileUrl(url)) {
204
- res = loadFromFile(url);
204
+ res = loadFromFile(url, baseFilesDir);
205
205
  } else {
206
206
  res = await loadFromUrl(url);
207
207
  }
@@ -265,13 +265,13 @@ class Config {
265
265
 
266
266
  /**
267
267
  * Retreive value
268
- * @param {string} key
268
+ * @param {string} [key] if no key is provided all the config is returned
269
269
  */
270
270
  get(key) {
271
271
  if (! this.store) { throw(new Error('Config not yet initialized'))}
272
- learn(this.learnDirectoryAndFilename, key);
273
272
  const value = this.store.get(key);
274
273
  if (typeof value === 'undefined') this.logger.debug('get: [' + key +'] is undefined');
274
+ learn(this.learnDirectoryAndFilename, key);
275
275
  return value;
276
276
  }
277
277
 
@@ -338,17 +338,16 @@ module.exports = Config;
338
338
  const FILE_PROTOCOL = 'file://';
339
339
  const FILE_PROTOCOL_LENGTH = FILE_PROTOCOL.length;
340
340
 
341
- async function loadFromUrl(serviceInfoUrl ) {
342
- const res = await superagent.get(serviceInfoUrl);
341
+ async function loadFromUrl(url) {
342
+ const res = await superagent.get(url);
343
343
  return res.body;
344
344
  }
345
345
 
346
- function loadFromFile(fileUrl ) {
346
+ function loadFromFile(fileUrl, baseFilesDir) {
347
347
  const filePath = stripFileProtocol(fileUrl);
348
348
 
349
349
  if (isRelativePath(filePath)) {
350
- const serviceCorePath = path.resolve(__dirname, '../../../../../'); // assuming /node_modules/@pryv/boiler/src/
351
- fileUrl = path.resolve(serviceCorePath, filePath);
350
+ fileUrl = path.resolve(baseFilesDir, filePath);
352
351
  fileUrl = 'file://' + fileUrl;
353
352
  } else {
354
353
  // absolute path, do nothing.
@@ -390,19 +389,14 @@ function learn(learnDirectoryAndFilename, key) {
390
389
  if (learnDirectoryAndFilename) {
391
390
  const caller_line = (new Error()).stack.split('\n')[3]; // get callee name and line
392
391
  const index = caller_line.indexOf("at ");
393
- str = key + ';' + caller_line.slice(index+3, caller_line.length) + '\n';
392
+ const str = key + ';' + caller_line.slice(index+3, caller_line.length) + '\n';
394
393
  fs.appendFileSync(learnDirectoryAndFilename + '-calls.csv', str);
395
394
  }
396
395
  }
397
396
 
398
397
  function saveConfig(learnDirectoryAndFilename, store) {
399
398
  if (learnDirectoryAndFilename) {
400
- let i = 0;
401
- let filename;
402
- do {
403
- filename =learnDirectoryAndFilename + '-config.json';
404
- i++;
405
- } while(fs.existsSync(filename));
399
+ const filename = learnDirectoryAndFilename + '-config.json';
406
400
  fs.writeFileSync(filename, JSON.stringify({stores: store.stores, config: store.get()}, null, 2));
407
401
  }
408
402
  }
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,7 +23,7 @@ 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
28
  getConfig: getConfig,
35
29
  /**
@@ -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,6 +8,7 @@ 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
 
@@ -73,12 +74,16 @@ function generateFormat(options) {
73
74
  /**
74
75
  * Helper to pass log instructions to winston
75
76
  */
76
- function globalLog(level, text, context) {
77
+ function globalLog(level, key, message, context) {
78
+ const text = `[${key}] ${message}`;
77
79
  if (winstonInstance) {
78
80
  winstonInstance[level](text, context);
79
81
  } else {
80
82
  console.log('Logger not initialized: ', ...arguments);
81
83
  }
84
+ if (customLoggerInstance) {
85
+ customLoggerInstance.log(level, key, message, context);
86
+ }
82
87
  }
83
88
 
84
89
 
@@ -132,6 +137,13 @@ async function initLoggerWithConfig(config) { 
132
137
  }
133
138
 
134
139
  }
140
+
141
+ // custom
142
+ if (config.get('logs:custom:active')) {
143
+ customLoggerInstance = require(config.get('logs:custom:path'));
144
+ await customLoggerInstance.init(config.get('logs:custom:settings'));
145
+ }
146
+
135
147
  rootLogger.debug('Logger Initialized');
136
148
  };
137
149
 
@@ -183,7 +195,7 @@ class Logger {
183
195
 
184
196
  log() {
185
197
  const level = arguments[0];
186
- const text = '[' + this._name() + ']: ' + hideSensitiveValues(arguments[1]);
198
+ const message = hideSensitiveValues(arguments[1]);
187
199
  const context = [];
188
200
 
189
201
  let meta;
@@ -196,7 +208,7 @@ class Logger {
196
208
  } else if (context.length > 1) {
197
209
  meta = {context: context};
198
210
  }
199
- globalLog(level, text, meta);
211
+ globalLog(level, this._name(), message, meta);
200
212
  }
201
213
 
202
214
  info () { this.log('info', ...arguments); }
@@ -212,6 +224,7 @@ class Logger {
212
224
  /**
213
225
  * get a "sub" Logger
214
226
  * @param {Logger} name
227
+ * @returns {Logger}
215
228
  */
216
229
  getLogger (name) {
217
230
  return new Logger(name, this);
@@ -220,6 +233,11 @@ class Logger {
220
233
  inspect() { inspect(...arguments); }
221
234
  }
222
235
 
236
+ /**
237
+ * Get a new logger, or root loggger if no name is provided
238
+ * @param {string} [name]
239
+ * @returns {Logger}
240
+ */
223
241
  function getLogger(name) {
224
242
  if (! rootLogger) {
225
243
  throw new Error('Initalize boiler before using logger')
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
- };