@pryv/boiler 1.0.8

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/src/logging.js ADDED
@@ -0,0 +1,286 @@
1
+ /**
2
+ * @license
3
+ * [BSD-3-Clause](https://github.com/pryv/pryv-boiler/blob/master/LICENSE)
4
+ */
5
+ const util = require('util');
6
+ const winston = require('winston');
7
+ require('winston-daily-rotate-file');
8
+ const debugModule = require('debug');
9
+ let winstonInstance = null;
10
+ let rootLogger = null;
11
+
12
+ // ------ winston formating
13
+
14
+ /**
15
+ *
16
+ * @param {Object} options
17
+ * @param {boolean} options.color - set to true to have colors
18
+ * @param {boolean} options.time - set to true to for timestamp
19
+ * @param {boolean} options.align - set to true to allign logs items
20
+ */
21
+ function generateFormat(options) {
22
+ const formats = [];
23
+ if (options.color) {
24
+ formats.push(winston.format.colorize());
25
+ }
26
+ if (options.time) {
27
+ formats.push(winston.format.timestamp());
28
+ }
29
+ if (options.align) {
30
+ formats.push(winston.format.align());
31
+ }
32
+
33
+ function printf(info) {
34
+ const {
35
+ timestamp, level, message, ...args
36
+ } = info;
37
+
38
+ let items = info[Symbol.for('splat')] || {};
39
+
40
+ let itemStr = '';
41
+ if (items.length > 0) {
42
+ let skip = false;
43
+ if (items.length === 1) {
44
+ if (typeof items[0] === 'undefined') {
45
+ skip = true;
46
+ } else {
47
+ if (items[0] && items[0].context) {
48
+ items = items[0].context;
49
+ }
50
+ }
51
+ }
52
+ if (! skip)
53
+ itemStr = util.inspect(items, {depth: 10, colors: true});
54
+ }
55
+
56
+
57
+
58
+ const line = `[${level}]: ${message} ${itemStr}`;
59
+
60
+ if (options.time) {
61
+ const ts = timestamp.slice(0, 19).replace('T', ' ');
62
+ return ts + ' ' + line;
63
+ } else {
64
+ return line;
65
+ }
66
+ }
67
+ formats.push(winston.format.printf(printf));
68
+ return winston.format.combine(...formats);
69
+ }
70
+
71
+
72
+
73
+ /**
74
+ * Helper to pass log instructions to winston
75
+ */
76
+ function globalLog(level, text, context) {
77
+ if (winstonInstance) {
78
+ winstonInstance[level](text, context);
79
+ } else {
80
+ console.log('Logger not initialized: ', ...arguments);
81
+ }
82
+ }
83
+
84
+
85
+ /**
86
+ * Config initialize Logger right after beeing loaded
87
+ * This is done by config Only
88
+ */
89
+ async function initLoggerWithConfig(config) { 
90
+ if (winstonInstance) {
91
+ throw new Error("Logger was already initialized");
92
+ }
93
+ // console
94
+ winstonInstance = winston.createLogger({ });
95
+ const logConsole = config.get('logs:console');
96
+ let isSilent = ! config.get('logs:console:active');
97
+
98
+ // LOGS env var can override settings
99
+ if (process.env.LOGS) {
100
+ logConsole.level = process.env.LOGS;
101
+ isSilent = false;
102
+ }
103
+
104
+
105
+ const format = generateFormat(logConsole.format)
106
+ const myconsole = new winston.transports.Console({ format: format , level: logConsole.level, silent: isSilent});
107
+ winstonInstance.add(myconsole);
108
+
109
+ rootLogger.debug((isSilent ? '** silent ** ' : '') + 'Console with level: ', logConsole.level);
110
+
111
+ // file
112
+ const logFile = config.get('logs:file');
113
+ if (config.get('logs:file:active')) {
114
+ rootLogger.debug('File active: ' + logFile.path);
115
+ if (logFile.rotation.isActive) {
116
+ const transport = new winston.transports.DailyRotateFile({
117
+ filename: logFile.path + '.%DATE%',
118
+ datePattern: 'YYYY-MM-DD',
119
+ zippedArchive: true,
120
+ maxFiles: logFile.rotation.days ? logFile.rotation.days + 'd' : null,
121
+ });
122
+ } else {
123
+ const files = new winston.transports.File({
124
+ filename: logFile.path,
125
+ level: logFile.level,
126
+ maxsize: logFile.maxFileBytes,
127
+ maxFiles: logFile.maxNbFiles,
128
+ timestamp: true,
129
+ json: false
130
+ });
131
+ winstonInstance.add(files);
132
+ }
133
+
134
+ }
135
+ rootLogger.debug('Logger Initialized');
136
+ };
137
+
138
+
139
+
140
+ // --------------- debug utils
141
+
142
+ /**
143
+ * Dump objects with file and line
144
+ */
145
+ function inspect() {
146
+ let line = '';
147
+ try {
148
+ throw new Error();
149
+ } catch (e) {
150
+ line = e.stack.split(' at ')[2].trim();
151
+ }
152
+ let res = '\n * dump at: ' + line;
153
+ for (var i = 0; i < arguments.length; i++) {
154
+ res += '\n' + i + ' ' + util.inspect(arguments[i], true, 10, true) + '\n';
155
+ }
156
+ return res;
157
+ };
158
+
159
+
160
+ function setGlobalName(name) {
161
+ // create root logger
162
+ rootLogger = new Logger(name, null);
163
+ rootLogger.debug('setGlobalName: ' + name);
164
+ }
165
+
166
+
167
+ class Logger {
168
+ parent; // eventual parent
169
+ debugInstance; // debug instance
170
+
171
+ constructor(name, parent) {
172
+ this.name = name;
173
+ this.parent = parent;
174
+ this.debugInstance = debugModule('pryv:' + this._name());
175
+ }
176
+ /**
177
+ * Private
178
+ */
179
+ _name() {
180
+ if (this.parent) return this.parent._name() + ':' + this.name;
181
+ return this.name;
182
+ }
183
+
184
+ log() {
185
+ const level = arguments[0];
186
+ const text = '[' + this._name() + ']: ' + hideSensitiveValues(arguments[1]);
187
+ const context = [];
188
+
189
+ let meta;
190
+ // Security measure: We do not want any sensitive value to appear in logs
191
+ for (let i = 2; i < arguments.length; i++) {
192
+ context.push(inspectAndHide(arguments[i]));
193
+ }
194
+ if (context.length === 1) {
195
+ meta = {context: context[0]};
196
+ } else if (context.length > 1) {
197
+ meta = {context: context};
198
+ }
199
+ globalLog(level, text, meta);
200
+ }
201
+
202
+ info () { this.log('info', ...arguments); }
203
+ warn () { this.log('warn', ...arguments); }
204
+ error () { this.log('error', ...arguments); }
205
+ debug () {
206
+ if (winstonInstance) {
207
+ this.log('debug', ...arguments);
208
+ }
209
+ this.debugInstance(...arguments);
210
+ }
211
+
212
+ /**
213
+ * get a "sub" Logger
214
+ * @param {Logger} name
215
+ */
216
+ getLogger (name) {
217
+ return new Logger(name, this);
218
+ }
219
+
220
+ inspect() { inspect(...arguments); }
221
+ }
222
+
223
+ function getLogger(name) {
224
+ if (! rootLogger) {
225
+ throw new Error('Initalize boiler before using logger')
226
+ }
227
+ if(! name) {
228
+ return rootLogger;
229
+ }
230
+ return rootLogger.getLogger(name);
231
+ }
232
+
233
+ module.exports = {
234
+ getLogger: getLogger,
235
+ setGlobalName: setGlobalName,
236
+ initLoggerWithConfig: initLoggerWithConfig
237
+ }
238
+
239
+ // ----------------- Hide sensite data -------------------- //
240
+
241
+ function inspectAndHide(o) {
242
+ if (typeof o === 'undefined') return o;
243
+ if (o instanceof Error) return o;
244
+ return _inspectAndHide(JSON.parse(JSON.stringify(o))); // clone and remove circular
245
+ }
246
+
247
+ function _inspectAndHide(o) {
248
+ if (typeof o === 'string') {
249
+ return hideSensitiveValues(o);
250
+ }
251
+ if (o !== null && typeof o === 'object') {
252
+ if (Array.isArray(o)) {
253
+ const res = [];
254
+ for (let item of o) {
255
+ res.push(inspectAndHide(item));
256
+ }
257
+ return res;
258
+ }
259
+
260
+ const res = {};
261
+ for (let key of Object.keys(o)) {
262
+ if (['password', 'passwordHash', 'newPassword'].includes(key)) {
263
+ res[key] = '(hidden password)';
264
+ } else {
265
+ res[key] = inspectAndHide(o[key]);
266
+ }
267
+ }
268
+ return res;
269
+ }
270
+ return o;
271
+ }
272
+
273
+
274
+ // Hides sensitive values (auth tokens and passwords) in log messages
275
+ function hideSensitiveValues (msg) {
276
+ if (typeof msg !== 'string') return msg;
277
+ const tokenRegexp = /auth\=c([a-z0-9-]*)/g;
278
+ const passwordRegexp = /"(password|passwordHash|newPassword)"[:=]"([^"]*)"/g;
279
+ const mask = '(hidden)';
280
+
281
+ const res = msg
282
+ .replace(tokenRegexp, 'auth='+mask)
283
+ .replace(passwordRegexp, '$1='+mask);
284
+
285
+ return res;
286
+ }