@alterior/logging 3.5.3 → 3.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alterior/logging",
3
- "version": "3.5.3",
3
+ "version": "3.5.6",
4
4
  "description": "Configurable context-aware logging",
5
5
  "author": "The Alterior Project (https://github.com/alterior-mvc)",
6
6
  "license": "MIT",
@@ -38,11 +38,11 @@
38
38
  "docs": "typedoc ."
39
39
  },
40
40
  "dependencies": {
41
- "@alterior/annotations": "^3.4.0",
41
+ "@alterior/annotations": "^3.5.6",
42
42
  "@alterior/common": "^3.4.0",
43
- "@alterior/di": "^3.5.3",
44
- "@alterior/runtime": "^3.5.3",
43
+ "@alterior/di": "^3.5.6",
44
+ "@alterior/runtime": "^3.5.6",
45
45
  "rxjs": "^7.8.0"
46
46
  },
47
- "gitHead": "808d85ec078d91261d8388c717d77c02aefc568a"
47
+ "gitHead": "220e169fc8c855dc0935620bdb5efb1e9a2b86a7"
48
48
  }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './inspect';
2
+ export * from './logger';
3
+ export * from './trace';
4
+
5
+ export * from './logging.module';
package/src/inspect.ts ADDED
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Module exports.
3
+ */
4
+
5
+ import { ConsoleColors } from '@alterior/common';
6
+
7
+ export interface InspectOptions {
8
+ stylize: (str, styleType) => any;
9
+ depth?: number;
10
+ colors?: boolean;
11
+ showHidden?: boolean;
12
+ customInspect?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Echos the value of a value. Trys to print the value out
17
+ * in the best way possible given the different types.
18
+ *
19
+ * @param {Object} obj The object to print out.
20
+ * @param {Object} opts Optional options object that alters the output.
21
+ * @license MIT (© Joyent)
22
+ */
23
+ /* legacy: obj, showHidden, depth, colors*/
24
+
25
+ export function inspect(obj : any, opts? : InspectOptions): string {
26
+ // default options
27
+ interface Context extends InspectOptions {
28
+ seen: any[];
29
+ }
30
+
31
+ var ctx: Context = {
32
+ seen: [],
33
+ stylize: stylizeNothing
34
+ };
35
+
36
+ if (opts)
37
+ Object.assign(ctx, opts);
38
+
39
+ // set default options
40
+ if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
41
+ if (isUndefined(ctx.depth)) ctx.depth = 2;
42
+ if (isUndefined(ctx.colors)) ctx.colors = false;
43
+ if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
44
+ if (ctx.colors) ctx.stylize = stylizeWithConsoleColors;
45
+
46
+ return formatValue(ctx, obj, ctx.depth);
47
+ }
48
+
49
+ // Don't use 'blue' not visible on cmd.exe
50
+ const CONSOLE_COLOR_STYLES = {
51
+ 'special': 'cyan',
52
+ 'number': 'yellow',
53
+ 'boolean': 'yellow',
54
+ 'undefined': 'grey',
55
+ 'null': 'bold',
56
+ 'string': 'green',
57
+ 'date': 'magenta',
58
+ // "name": intentionally not styling
59
+ 'regexp': 'red'
60
+ };
61
+
62
+ /**
63
+ * Pass the string through with no stylization.
64
+ */
65
+ export function stylizeNothing(str, styleType) {
66
+ return str;
67
+ }
68
+
69
+ function isBoolean(arg) {
70
+ return typeof arg === 'boolean';
71
+ }
72
+
73
+ function isUndefined(arg) {
74
+ return arg === void 0;
75
+ }
76
+
77
+ /**
78
+ * Use console colors to style the string. Suitable for output to
79
+ * terminals with ANSI color support.
80
+ */
81
+ export function stylizeWithConsoleColors(str, styleType) {
82
+ var style = CONSOLE_COLOR_STYLES[styleType];
83
+ return style ? ConsoleColors[style](str) : str;
84
+ }
85
+
86
+ function isFunction(arg) {
87
+ return typeof arg === 'function';
88
+ }
89
+
90
+ function isString(arg) {
91
+ return typeof arg === 'string';
92
+ }
93
+
94
+ function isNumber(arg) {
95
+ return typeof arg === 'number';
96
+ }
97
+
98
+ function isNull(arg) {
99
+ return arg === null;
100
+ }
101
+
102
+ function hasOwn(obj, prop) {
103
+ return Object.prototype.hasOwnProperty.call(obj, prop);
104
+ }
105
+
106
+ function isRegExp(re) {
107
+ return isObject(re) && objectToString(re) === '[object RegExp]';
108
+ }
109
+
110
+ function isObject(arg) {
111
+ return typeof arg === 'object' && arg !== null;
112
+ }
113
+
114
+ function isError(e) {
115
+ return isObject(e) &&
116
+ (objectToString(e) === '[object Error]' || e instanceof Error);
117
+ }
118
+
119
+ function isDate(d) {
120
+ return isObject(d) && objectToString(d) === '[object Date]';
121
+ }
122
+
123
+ function objectToString(o) {
124
+ return Object.prototype.toString.call(o);
125
+ }
126
+
127
+ function arrayToHash(array) {
128
+ var hash = {};
129
+
130
+ array.forEach(function (val, idx) {
131
+ hash[val] = true;
132
+ });
133
+
134
+ return hash;
135
+ }
136
+
137
+ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
138
+ var output = [];
139
+ for (var i = 0, l = value.length; i < l; ++i) {
140
+ if (hasOwn(value, String(i))) {
141
+ output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
142
+ String(i), true));
143
+ } else {
144
+ output.push('');
145
+ }
146
+ }
147
+ keys.forEach(function (key) {
148
+ if (!key.match(/^\d+$/)) {
149
+ output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
150
+ key, true));
151
+ }
152
+ });
153
+ return output;
154
+ }
155
+
156
+ function formatError(value) {
157
+ return '[' + Error.prototype.toString.call(value) + ']';
158
+ }
159
+
160
+ function formatValue(ctx, value, recurseTimes) {
161
+ // Provide a hook for user-specified inspect functions.
162
+ // Check that value is an object with an inspect function on it
163
+ if (ctx.customInspect &&
164
+ value &&
165
+ isFunction(value.inspect) &&
166
+ // Filter out the util module, it's inspect function is special
167
+ value.inspect !== inspect &&
168
+ // Also filter out any prototype objects using the circular check.
169
+ !(value.constructor && value.constructor.prototype === value)) {
170
+ var ret = value.inspect(recurseTimes, ctx);
171
+ if (!isString(ret)) {
172
+ ret = formatValue(ctx, ret, recurseTimes);
173
+ }
174
+ return ret;
175
+ }
176
+
177
+ // Primitive types cannot have properties
178
+ var primitive = formatPrimitive(ctx, value);
179
+ if (primitive) {
180
+ return primitive;
181
+ }
182
+
183
+ // Look up the keys of the object.
184
+ var keys = Object.keys(value);
185
+ var visibleKeys = arrayToHash(keys);
186
+
187
+ try {
188
+ if (ctx.showHidden && Object.getOwnPropertyNames) {
189
+ keys = Object.getOwnPropertyNames(value);
190
+ }
191
+ } catch (e) {
192
+ // ignore
193
+ }
194
+
195
+ // IE doesn't make error fields non-enumerable
196
+ // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
197
+ if (isError(value)
198
+ && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
199
+ return formatError(value);
200
+ }
201
+
202
+ // Some type of object without properties can be shortcutted.
203
+ if (keys.length === 0) {
204
+ if (isFunction(value)) {
205
+ var name = value.name ? ': ' + value.name : '';
206
+ return ctx.stylize('[Function' + name + ']', 'special');
207
+ }
208
+ if (isRegExp(value)) {
209
+ return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
210
+ }
211
+ if (isDate(value)) {
212
+ return ctx.stylize(Date.prototype.toString.call(value), 'date');
213
+ }
214
+ if (isError(value)) {
215
+ return formatError(value);
216
+ }
217
+ }
218
+
219
+ var base = '', array = false, braces = ['{', '}'];
220
+
221
+ // Make Array say that they are Array
222
+ if (Array.isArray(value)) {
223
+ array = true;
224
+ braces = ['[', ']'];
225
+ }
226
+
227
+ // Make functions say that they are functions
228
+ if (isFunction(value)) {
229
+ var n = value.name ? ': ' + value.name : '';
230
+ base = ' [Function' + n + ']';
231
+ }
232
+
233
+ // Make RegExps say that they are RegExps
234
+ if (isRegExp(value)) {
235
+ base = ' ' + RegExp.prototype.toString.call(value);
236
+ }
237
+
238
+ // Make dates with properties first say the date
239
+ if (isDate(value)) {
240
+ base = ' ' + Date.prototype.toUTCString.call(value);
241
+ }
242
+
243
+ // Make error with message first say the error
244
+ if (isError(value)) {
245
+ base = ' ' + formatError(value);
246
+ }
247
+
248
+ if (keys.length === 0 && (!array || value.length == 0)) {
249
+ return braces[0] + base + braces[1];
250
+ }
251
+
252
+ if (recurseTimes < 0) {
253
+ if (isRegExp(value)) {
254
+ return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
255
+ } else {
256
+ return ctx.stylize('[Object]', 'special');
257
+ }
258
+ }
259
+
260
+ ctx.seen.push(value);
261
+
262
+ var output;
263
+ if (array) {
264
+ output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
265
+ } else {
266
+ output = keys.map(function (key) {
267
+ return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
268
+ });
269
+ }
270
+
271
+ ctx.seen.pop();
272
+
273
+ return reduceToSingleString(output, base, braces);
274
+ }
275
+
276
+ function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
277
+ var name, str, desc;
278
+ desc = { value: void 0 };
279
+ try {
280
+ // ie6 › navigator.toString
281
+ // throws Error: Object doesn't support this property or method
282
+ desc.value = value[key];
283
+ } catch (e) {
284
+ // ignore
285
+ }
286
+ try {
287
+ // ie10 › Object.getOwnPropertyDescriptor(window.location, 'hash')
288
+ // throws TypeError: Object doesn't support this action
289
+ if (Object.getOwnPropertyDescriptor) {
290
+ desc = Object.getOwnPropertyDescriptor(value, key) || desc;
291
+ }
292
+ } catch (e) {
293
+ // ignore
294
+ }
295
+ if (desc.get) {
296
+ if (desc.set) {
297
+ str = ctx.stylize('[Getter/Setter]', 'special');
298
+ } else {
299
+ str = ctx.stylize('[Getter]', 'special');
300
+ }
301
+ } else {
302
+ if (desc.set) {
303
+ str = ctx.stylize('[Setter]', 'special');
304
+ }
305
+ }
306
+ if (!hasOwn(visibleKeys, key)) {
307
+ name = '[' + key + ']';
308
+ }
309
+ if (!str) {
310
+ if (ctx.seen.indexOf(desc.value) < 0) {
311
+ if (isNull(recurseTimes)) {
312
+ str = formatValue(ctx, desc.value, null);
313
+ } else {
314
+ str = formatValue(ctx, desc.value, recurseTimes - 1);
315
+ }
316
+ if (str.indexOf('\n') > -1) {
317
+ if (array) {
318
+ str = str.split('\n').map(function (line) {
319
+ return ' ' + line;
320
+ }).join('\n').substr(2);
321
+ } else {
322
+ str = '\n' + str.split('\n').map(function (line) {
323
+ return ' ' + line;
324
+ }).join('\n');
325
+ }
326
+ }
327
+ } else {
328
+ str = ctx.stylize('[Circular]', 'special');
329
+ }
330
+ }
331
+ if (isUndefined(name)) {
332
+ if (array && key.match(/^\d+$/)) {
333
+ return str;
334
+ }
335
+ name = JSON.stringify('' + key);
336
+ if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
337
+ name = name.substr(1, name.length - 2);
338
+ name = ctx.stylize(name, 'name');
339
+ } else {
340
+ name = name.replace(/'/g, "\\'")
341
+ .replace(/\\"/g, '"')
342
+ .replace(/(^"|"$)/g, "'");
343
+ name = ctx.stylize(name, 'string');
344
+ }
345
+ }
346
+
347
+ return name + ': ' + str;
348
+ }
349
+
350
+ function formatPrimitive(ctx, value) {
351
+ if (isUndefined(value))
352
+ return ctx.stylize('undefined', 'undefined');
353
+ if (isString(value)) {
354
+ var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
355
+ .replace(/'/g, "\\'")
356
+ .replace(/\\"/g, '"') + '\'';
357
+ return ctx.stylize(simple, 'string');
358
+ }
359
+ if (isNumber(value))
360
+ return ctx.stylize('' + value, 'number');
361
+ if (isBoolean(value))
362
+ return ctx.stylize('' + value, 'boolean');
363
+ // For some reason typeof null is "object", so special case here.
364
+ if (isNull(value))
365
+ return ctx.stylize('null', 'null');
366
+ }
367
+
368
+ function reduceToSingleString(output, base, braces) {
369
+ var numLinesEst = 0;
370
+ var length = output.reduce(function (prev, cur) {
371
+ numLinesEst++;
372
+ if (cur.indexOf('\n') >= 0) numLinesEst++;
373
+ return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
374
+ }, 0);
375
+
376
+ if (length > 60) {
377
+ return braces[0] +
378
+ (base === '' ? '' : base + '\n ') +
379
+ ' ' +
380
+ output.join(',\n ') +
381
+ ' ' +
382
+ braces[1];
383
+ }
384
+
385
+ return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
386
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,417 @@
1
+ import { Injectable, Optional } from '@alterior/di';
2
+ import { ExecutionContext, Application } from '@alterior/runtime';
3
+
4
+ export type LogSeverity = 'debug' | 'info' | 'warning' | 'error' | 'fatal';
5
+
6
+ import * as fs from 'fs';
7
+ import { inspect, stylizeWithConsoleColors } from './inspect';
8
+
9
+ export class LoggingOptionsRef {
10
+ constructor(readonly options : LoggingOptions) {
11
+ }
12
+
13
+ public static get currentRef(): LoggingOptionsRef {
14
+ if (!ExecutionContext.current || !ExecutionContext.current.application)
15
+ return null;
16
+
17
+ return ExecutionContext.current.application.inject(LoggingOptionsRef, null);
18
+ }
19
+
20
+ public static get current(): LoggingOptions {
21
+ let ref = this.currentRef;
22
+ return ref ? ref.options : {};
23
+ }
24
+ }
25
+
26
+ export interface LogOptions {
27
+ severity : LogSeverity;
28
+ }
29
+
30
+ export interface LogEvent {
31
+ type : 'message' | 'inspect';
32
+ message : string;
33
+
34
+ /**
35
+ * Subject being inspected. Defined
36
+ * only for `type == 'inspect'`
37
+ */
38
+ subject? : any;
39
+
40
+ /**
41
+ * Context data, if any
42
+ */
43
+ context? : any;
44
+
45
+ /**
46
+ * Context label, if set
47
+ */
48
+ contextLabel? : string;
49
+
50
+ /**
51
+ * Source label, if set.
52
+ */
53
+ sourceLabel? : string;
54
+
55
+ /**
56
+ * Context summary
57
+ */
58
+ contextSummary? : string;
59
+
60
+ /**
61
+ * Severity of the log event.
62
+ */
63
+ severity : LogSeverity;
64
+
65
+ /**
66
+ * The date when the log event was recorded.
67
+ */
68
+ date : Date;
69
+ }
70
+
71
+ export interface LogListener {
72
+ log(message : LogEvent) : Promise<void>;
73
+ }
74
+
75
+ export interface FormatSegment {
76
+ type : "raw" | "parameter";
77
+ value : string;
78
+ }
79
+
80
+ export class LogFormatter {
81
+ constructor(readonly logFormat : LogFormat) {
82
+ this.compile();
83
+ }
84
+
85
+ private compile() {
86
+ if (typeof this.logFormat === 'function')
87
+ return;
88
+
89
+ let segment = '';
90
+ let segments : FormatSegment[] = [];
91
+
92
+ for (let i = 0, max = this.logFormat.length; i < max; ++i) {
93
+ let char = this.logFormat[i];
94
+
95
+ if (char == '%') {
96
+ let lookAhead = this.logFormat.substr(i + 1);
97
+
98
+ if (lookAhead.includes('%')) {
99
+
100
+ if (segment != '') {
101
+ segments.push({ type: 'raw', value: segment });
102
+ segment = '';
103
+ }
104
+
105
+ let parameterName = lookAhead.replace(/%.*$/, '');
106
+ i += parameterName.length + 1;
107
+
108
+ segments.push({ type: 'parameter', value: parameterName });
109
+
110
+ } else {
111
+ segment += char;
112
+ }
113
+ } else {
114
+ segment += char;
115
+ }
116
+ }
117
+
118
+ if (segment !== '') {
119
+ segments.push({ type: 'raw', value: segment });
120
+ }
121
+
122
+ this.segments = segments;
123
+ }
124
+
125
+ segments : FormatSegment[] = [];
126
+
127
+ private formatParameter(name : string, value : Object) {
128
+ if (value === true || value === false)
129
+ return value ? '«true»' : '«false»';
130
+
131
+ if (value === null)
132
+ return '«null»';
133
+
134
+ if (value === undefined)
135
+ return '«undefined»';
136
+
137
+ if (value instanceof Date)
138
+ return value.toISOString();
139
+
140
+ return value.toString();
141
+ }
142
+
143
+ public format(message : LogEvent) : string {
144
+ if (typeof this.logFormat === 'function')
145
+ return this.logFormat(message);
146
+
147
+ return this.segments.map(x => x.type == 'parameter' ? this.formatParameter(x.value, message[x.value]) : x.value).join('');
148
+ }
149
+ }
150
+
151
+ export class ConsoleLogger implements LogListener {
152
+ constructor(
153
+ readonly format : LogFormat
154
+ ) {
155
+ this.formatter = new LogFormatter(format);
156
+ }
157
+
158
+ private formatter : LogFormatter;
159
+
160
+ async log(message : LogEvent) {
161
+ let messageText = message.message;
162
+
163
+ if (message.type === 'inspect') {
164
+
165
+ // On the web, take advantage of the interactive
166
+ // inspection facilities
167
+
168
+ if (typeof document !== 'undefined') {
169
+ console.dir(message.subject);
170
+ return;
171
+ }
172
+
173
+ // Inspect
174
+
175
+ messageText = inspect(message.subject, {
176
+ stylize: stylizeWithConsoleColors
177
+ });
178
+ }
179
+
180
+ let finalMessage = Object.assign({}, message, { message: messageText });
181
+ let finalMessageStr = this.formatter.format(finalMessage);
182
+ console.log(finalMessageStr);
183
+ }
184
+ }
185
+
186
+ export type LogFormat = string | ((event : LogEvent) => string);
187
+
188
+ export class FileLogger implements LogListener {
189
+ constructor(
190
+ readonly format : LogFormat,
191
+ readonly filename : string
192
+ ) {
193
+ this.formatter = new LogFormatter(format);
194
+ }
195
+
196
+ private formatter : LogFormatter;
197
+ private _fdReady : Promise<number>;
198
+
199
+ get ready() {
200
+ return this._fdReady;
201
+ }
202
+
203
+ async open() {
204
+ if (this._fdReady)
205
+ return await this._fdReady;
206
+
207
+ return await (this._fdReady = new Promise((res, rej) => {
208
+ fs.open(this.filename, 'a', (err, fd) => {
209
+ if (err)
210
+ rej(err);
211
+ else
212
+ res(fd);
213
+ });
214
+ }));
215
+ }
216
+
217
+ async write(str : string) {
218
+ let fd = await this.open();
219
+ await new Promise<void>((res, rej) => {
220
+ fs.write(fd, Buffer.from(str), (err, written, buffer) => {
221
+ if (err) {
222
+ rej(err);
223
+ return;
224
+ }
225
+
226
+ // sync to flush this to disk right away
227
+ fs.fdatasync(fd, (err) => err ? rej(err) : res());
228
+ });
229
+ });
230
+ }
231
+
232
+ async log(message : LogEvent) {
233
+ let formattedMessage = this.formatter.format(message);
234
+ await this.write(`${formattedMessage}\n`);
235
+ }
236
+ }
237
+
238
+ export interface LoggingOptions {
239
+ /**
240
+ * Specify functions which listen to and in some way handle
241
+ * emitted log messages.
242
+ */
243
+ listeners? : LogListener[];
244
+
245
+ /**
246
+ * Whether to enable tracing as provided by @ConsoleTrace.
247
+ */
248
+ tracing?: boolean;
249
+ }
250
+
251
+ export class ZonedLogger {
252
+ constructor(
253
+ @Optional() protected optionsRef : LoggingOptionsRef,
254
+ protected app? : Application,
255
+ sourceLabel? : string
256
+ ) {
257
+ this._sourceLabel = sourceLabel;
258
+ }
259
+
260
+ clone() {
261
+ let logger = new ZonedLogger(this.optionsRef, this.app, this._sourceLabel);
262
+ Object.keys(this).filter(x => typeof this[x] !== 'function').forEach(key => logger[key] = this[key]);
263
+ return logger;
264
+ }
265
+
266
+ protected _sourceLabel : string = undefined;
267
+
268
+ get sourceLabel() {
269
+ return this._sourceLabel;
270
+ }
271
+
272
+ static readonly ZONE_LOCAL_NAME = '@alterior/logger:Logger.current';
273
+
274
+ public static get current(): ZonedLogger {
275
+ return Zone.current.get(Logger.ZONE_LOCAL_NAME)
276
+ ?? ExecutionContext.current?.application?.inject(Logger)
277
+ ?? new ZonedLogger(null, null)
278
+ ;
279
+ }
280
+
281
+ get listeners() {
282
+ if (this.optionsRef?.options?.listeners) {
283
+ return this.optionsRef.options.listeners;
284
+ }
285
+
286
+ if (this.app?.options?.silent)
287
+ return [];
288
+
289
+ return DEFAULT_LISTENERS;
290
+ }
291
+
292
+ /**
293
+ * Run the given function with this logger as the current logger.
294
+ * Calls to Logger.current will yield the logger for this execution
295
+ * context.
296
+ */
297
+ run(func : Function): any {
298
+ return Zone.current.fork({
299
+ name: `LoggerContext`,
300
+ properties: {
301
+ [Logger.ZONE_LOCAL_NAME]: this
302
+ }
303
+ }).run(func);
304
+ }
305
+
306
+ protected createMessage(message : Partial<LogEvent>): LogEvent {
307
+ let contextSummary = this.contextLabel || '';
308
+
309
+ if (this._sourceLabel)
310
+ contextSummary = `${contextSummary} » ${this._sourceLabel}`;
311
+
312
+ return <LogEvent>Object.assign(<Partial<LogEvent>>{
313
+ type: 'message',
314
+ context: this.context,
315
+ date: new Date(),
316
+ message,
317
+ contextLabel: this.contextLabel,
318
+ sourceLabel: this._sourceLabel,
319
+ contextSummary: contextSummary
320
+ }, message);
321
+ }
322
+
323
+ log(message : string, options? : LogOptions) {
324
+ this.emitLog(this.createMessage({
325
+ message,
326
+ severity: (options ? options.severity : undefined) || 'info'
327
+ }));
328
+ }
329
+
330
+ info(message : string, options? : LogOptions) {
331
+ this.log(message, Object.assign({}, options, { severity: 'info' }));
332
+ }
333
+
334
+ debug(message : string, options? : LogOptions) {
335
+ this.log(message, Object.assign({}, options, { severity: 'debug' }));
336
+ }
337
+
338
+ warning(message : string, options? : LogOptions) {
339
+ this.log(message, Object.assign({}, options, { severity: 'warning' }));
340
+ }
341
+
342
+ error(message : string, options? : LogOptions) {
343
+ this.log(message, Object.assign({}, options, { severity: 'error' }));
344
+ }
345
+
346
+ inspect(object : any, options? : LogOptions) {
347
+ this.emitLog(this.createMessage({
348
+ type: 'inspect',
349
+ severity: (options ? options.severity : undefined) || 'info',
350
+ subject: object,
351
+ message: '' + object
352
+ }))
353
+ }
354
+
355
+ private emitLog(message : LogEvent) {
356
+ this.listeners.forEach(listener => listener.log(message));
357
+ }
358
+
359
+ get context() {
360
+ return Zone.current.get('logContext');
361
+ }
362
+
363
+ get contextLabel() {
364
+ return Zone.current.get('logContextLabel');
365
+ }
366
+
367
+ async withContext<T = any>(context : any, label : string, callback : () => T): Promise<T> {
368
+ let zone = Zone.current.fork({
369
+ name: `LogContextZone: ${label}`,
370
+ properties: {
371
+ logContext: context,
372
+ logContextLabel: label
373
+ }
374
+ });
375
+
376
+ return await zone.run<T>(() => callback());
377
+ }
378
+ }
379
+
380
+ @Injectable()
381
+ export class Logger extends ZonedLogger {
382
+ constructor(
383
+ @Optional() optionsRef : LoggingOptionsRef,
384
+ app? : Application
385
+ ) {
386
+ super(optionsRef, app);
387
+ }
388
+
389
+ clone() {
390
+ let logger = new Logger(this.optionsRef);
391
+ Object.keys(this).filter(x => typeof this[x] !== 'function').forEach(key => logger[key] = this[key]);
392
+ return logger;
393
+ }
394
+
395
+ withSource(sourceLabel : string | Object) {
396
+ let logger = this.clone();
397
+
398
+ if (typeof sourceLabel === 'string') {
399
+ logger._sourceLabel = sourceLabel;
400
+ } else if (typeof sourceLabel === 'object') {
401
+ logger._sourceLabel = sourceLabel.constructor.name;
402
+ } else {
403
+ logger._sourceLabel = `${sourceLabel}`;
404
+ }
405
+
406
+ return logger;
407
+ }
408
+ }
409
+
410
+ export const DEFAULT_FORMAT : LogFormat = (event : LogEvent) => {
411
+ if (event.contextSummary)
412
+ return `${event.date.toISOString()} [${event.contextSummary}] ${event.severity}: ${event.message}`;
413
+ else
414
+ return `${event.date.toISOString()} ${event.severity}: ${event.message}`;
415
+ };
416
+
417
+ export const DEFAULT_LISTENERS : LogListener[] = [ new ConsoleLogger(DEFAULT_FORMAT) ];
@@ -0,0 +1,22 @@
1
+ import { Module } from "@alterior/di";
2
+ import { Logger, LoggingOptions, LoggingOptionsRef } from "./logger";
3
+
4
+ @Module({
5
+ providers: [
6
+ Logger
7
+ ]
8
+ })
9
+ export class LoggingModule {
10
+ static configure(options : LoggingOptions = {}) {
11
+ return LoggingModule.forRoot(options);
12
+ }
13
+
14
+ static forRoot(options : LoggingOptions = {}) {
15
+ return {
16
+ $module: LoggingModule,
17
+ providers: [
18
+ { provide: LoggingOptionsRef, useValue: new LoggingOptionsRef(options) }
19
+ ]
20
+ }
21
+ }
22
+ }
package/src/trace.ts ADDED
@@ -0,0 +1,236 @@
1
+ /**
2
+ * (C) 2017-2019 William Lahti
3
+ */
4
+
5
+ import { Mutator } from "@alterior/annotations";
6
+ import { ConsoleColors, indentConsole, coalesce } from '@alterior/common';
7
+ import { LoggingOptionsRef, DEFAULT_LISTENERS, Logger } from "./logger";
8
+
9
+ let ENABLED : boolean = false;
10
+
11
+ export function getTracingEnabled() {
12
+ return ENABLED;
13
+ }
14
+
15
+ export function setTracingEnabled(enabled : boolean) {
16
+ ENABLED = enabled;
17
+ }
18
+
19
+ /**
20
+ * Prints messages related to the execution of the decorated method
21
+ * to the console.
22
+ *
23
+ * - When multiple traced functions are involved, the output is indented
24
+ * appropriately.
25
+ * - Intercepts console messages from within the function to add
26
+ * appropriate indentation.
27
+ * - Records (and rethrows) exceptions thrown from within the function.
28
+ * - For methods returning promises, the promise itself is traced,
29
+ * printing a "Resolved" message upon completion or a "Rejected"
30
+ * message upon rejection.
31
+ *
32
+ * Note that this functionality can be enabled and disabled by configuring
33
+ * the `tracing` option when configuring this module for your application.
34
+ * When @Trace() is used outside of an Alterior execution context, the trace
35
+ * logs are always enabled, and the default log listeners are used.
36
+ *
37
+ * For example, given a class:
38
+ *
39
+ ```
40
+ class Thing {
41
+ @ConsoleTrace()
42
+ static doSomething(data : any, stuff : number) {
43
+ console.log("ALMOST...");
44
+ try {
45
+ this.anotherSomething(stuff);
46
+ } catch (e) {
47
+
48
+ }
49
+ }
50
+ @ConsoleTrace()
51
+ static anotherSomething(stuff : number) {
52
+ console.log("NAH...");
53
+ throw new Error('Uh oh');
54
+ }
55
+ }
56
+ ```
57
+ * When executed, one may see:
58
+ ```sh
59
+ Thing#doSomething({"stuff":321,"other":"nice"}, 12333) {
60
+ ALMOST...
61
+ Thing#anotherSomething(12333) {
62
+ NAH...
63
+ (!!) Exception:
64
+ Error: Uh oh
65
+ } // [Exception] Thing#anotherSomething(12333)
66
+ } // [Done, 6ms] Thing#doSomething({"stuff":321,"other":"nice"}, 12333)
67
+ * ```
68
+ */
69
+ export function Trace() {
70
+ return Mutator.define({
71
+ options: {
72
+ validTargets: ['method']
73
+ },
74
+
75
+ invoke(site) {
76
+
77
+ // - When used outside of Alterior, we will *always trace*.
78
+ // - When used inside an Alterior app, we will not trace by default.
79
+ // - If optionsRef.options.tracing is set to true, we trace.
80
+
81
+ let optionsRef = LoggingOptionsRef.currentRef;
82
+ let options = optionsRef ? (optionsRef.options || {}) : null;
83
+ let enabled = options ? coalesce(options.tracing, false) : true;
84
+ let logger = Logger.current;
85
+
86
+ if (!enabled)
87
+ return;
88
+
89
+ if (!ENABLED)
90
+ return;
91
+
92
+ let originalMethod : Function = site.propertyDescriptor.value;
93
+ site.propertyDescriptor.value = function (...args) {
94
+ let type : Function;
95
+ let isStatic : boolean = false;
96
+
97
+ if (typeof site.target === 'function') {
98
+ type = site.target;
99
+ isStatic = true;
100
+ } else {
101
+ type = site.target.constructor;
102
+ isStatic = false;
103
+ }
104
+
105
+ let typeName : string;
106
+ let sep : string = '.';
107
+
108
+ if (isStatic)
109
+ sep = '#';
110
+
111
+ typeName = type.name;
112
+
113
+ let startedAt = Date.now();
114
+
115
+ let argStrings = [];
116
+
117
+ for (let arg of args) {
118
+ if (arg === null)
119
+ argStrings.push('null');
120
+ else if (arg === undefined)
121
+ argStrings.push('undefined');
122
+ else if (arg === true)
123
+ argStrings.push('true');
124
+ else if (arg === false)
125
+ argStrings.push('false');
126
+ else if (typeof arg === 'string')
127
+ argStrings.push(JSON.stringify(arg));
128
+ else if (typeof arg === 'function')
129
+ argStrings.push(arg.name);
130
+ else if (typeof arg === 'object')
131
+ argStrings.push(JSON.stringify(arg))
132
+ else
133
+ argStrings.push(''+arg);
134
+ }
135
+
136
+ let methodSpec = `${ConsoleColors.cyan(`${typeName}${sep}${site.propertyKey}`)}(${argStrings.join(', ')})`;
137
+ logger.debug(`${methodSpec} {`);
138
+ let value;
139
+
140
+ let finish = (message?) => {
141
+ let time = Date.now() - startedAt;
142
+ let components = [message || ConsoleColors.green(`Done`)];
143
+ let timingColor = m => m;
144
+ let showTiming = time > 3;
145
+
146
+ if (time > 20) {
147
+ timingColor = ConsoleColors.red;
148
+ } else if (time > 5) {
149
+ timingColor = ConsoleColors.yellow;
150
+ }
151
+
152
+ if (showTiming) {
153
+ components.push(timingColor(`${time}ms`));
154
+ }
155
+
156
+ logger.debug(`} // [${components.join(', ')}] ${methodSpec}`);
157
+ };
158
+
159
+ let status = undefined;
160
+
161
+ try {
162
+ indentConsole(4, () => {
163
+ try {
164
+ value = originalMethod.apply(this, args);
165
+ } catch (e) {
166
+ console.error(`${ConsoleColors.red(`(!!)`)} Exception:`);
167
+ indentConsole(6, () => console.error(e));
168
+
169
+ throw e;
170
+ }
171
+ });
172
+ } catch (e) {
173
+ finish(ConsoleColors.red(`Exception`));
174
+ throw e;
175
+ }
176
+
177
+ if (value && value.then) {
178
+ let promise : Promise<any> = value;
179
+ value = promise.then(() => {
180
+ finish(ConsoleColors.green(`Resolved`));
181
+ }).catch(e => {
182
+ finish(ConsoleColors.red(`Rejected (${e})`));
183
+ throw e;
184
+ });
185
+ } else {
186
+ finish();
187
+ }
188
+
189
+ return value;
190
+ };
191
+ }
192
+ });
193
+ }
194
+
195
+ /**
196
+ * If an exception occurs within the target method, report that exception to the console and rethrow.
197
+ */
198
+ export function ReportExceptionsToConsole() {
199
+ return Mutator.define({
200
+ options: {
201
+ validTargets: ['method']
202
+ },
203
+
204
+ invoke(site) {
205
+ let originalMethod : Function = site.propertyDescriptor.value;
206
+ site.propertyDescriptor.value = function (...args) {
207
+ let type : Function;
208
+ let isStatic : boolean = false;
209
+
210
+ if (typeof site.target === 'function') {
211
+ type = site.target;
212
+ isStatic = true;
213
+ } else {
214
+ type = site.target.constructor;
215
+ isStatic = false;
216
+ }
217
+
218
+ let typeName : string;
219
+ let sep : string = '.';
220
+
221
+ if (isStatic)
222
+ sep = '#';
223
+
224
+ typeName = type.name;
225
+
226
+ try {
227
+ return originalMethod.apply(this, args);
228
+ } catch (e) {
229
+ console.error(`Error occurred during ${typeName}${sep}${site.propertyKey}():`);
230
+ console.error(e);
231
+ throw e;
232
+ }
233
+ };
234
+ }
235
+ });
236
+ }
@@ -1,18 +0,0 @@
1
- import { suite } from 'razmin';
2
- import { inspect } from './inspect';
3
- import { expect } from 'chai';
4
- suite(describe => {
5
- describe('inspect', it => {
6
- it('directly stringifies numbers', () => expect(inspect(123)).to.equal('123'));
7
- it('quotes strings', () => expect(inspect('hello')).to.equal("'hello'"));
8
- it('knows false', () => expect(inspect(false)).to.equal("false"));
9
- it('knows true', () => expect(inspect(true)).to.equal("true"));
10
- it('knows null', () => expect(inspect(null)).to.equal("null"));
11
- it('knows undefined', () => expect(inspect(undefined)).to.equal("undefined"));
12
- it('digs into objects', () => expect(inspect({ abc: 123 })).to.equal("{ abc: 123 }"));
13
- it('presents functions', () => expect(inspect(() => 123)).to.equal("[Function]"));
14
- function namedTest() { }
15
- it('presents named functions', () => expect(inspect(namedTest)).to.equal("[Function: namedTest]"));
16
- });
17
- });
18
- //# sourceMappingURL=inspect.test.js.map
@@ -1,204 +0,0 @@
1
- import { __awaiter } from "tslib";
2
- import { suite } from 'razmin';
3
- import { Logger, LoggingOptionsRef, ConsoleLogger, FileLogger, LogFormatter } from './logger';
4
- import { expect } from 'chai';
5
- import * as os from 'os';
6
- import * as path from 'path';
7
- import * as fs from 'fs';
8
- const SAMPLE_LOG_MESSAGE_1 = { type: 'message', message: 'ABCDEF', date: new Date(), severity: 'info', context: null, contextLabel: null, sourceLabel: null };
9
- const SAMPLE_LOG_MESSAGE_2 = { type: 'message', message: '123456', date: new Date(), severity: 'info', context: null, contextLabel: null, sourceLabel: null };
10
- const SAMPLE_LOG_MESSAGE_3 = { type: 'message', message: 'TUVXYZ', date: new Date(), severity: 'info', context: null, contextLabel: null, sourceLabel: null };
11
- function SAMPLE_LOG_MESSAGE(message) {
12
- return {
13
- type: 'message',
14
- message,
15
- date: new Date(),
16
- severity: 'info',
17
- context: null,
18
- contextLabel: null,
19
- sourceLabel: null
20
- };
21
- }
22
- function SAMPLE_LOG_MESSAGE_FATAL(message) {
23
- return {
24
- type: 'message',
25
- message,
26
- date: new Date(),
27
- severity: 'fatal',
28
- context: null,
29
- contextLabel: null,
30
- sourceLabel: null
31
- };
32
- }
33
- suite(describe => {
34
- describe('Logger', it => {
35
- it('calls each listener', () => __awaiter(void 0, void 0, void 0, function* () {
36
- let result = '';
37
- let logger = new Logger(new LoggingOptionsRef({
38
- listeners: [
39
- { log(message) {
40
- return __awaiter(this, void 0, void 0, function* () { result += `a${message.message}`; });
41
- } },
42
- { log(message) {
43
- return __awaiter(this, void 0, void 0, function* () { result += `b${message.message}`; });
44
- } }
45
- ]
46
- }));
47
- yield logger.log('X');
48
- yield logger.log('Y');
49
- expect(result).to.eq('aXbXaYbY');
50
- }));
51
- it('defaults to ConsoleLogger', () => __awaiter(void 0, void 0, void 0, function* () {
52
- let logger = new Logger(undefined);
53
- let logger2 = new Logger(new LoggingOptionsRef({}));
54
- expect(logger.listeners.length).to.eq(1);
55
- expect(logger.listeners[0].constructor).to.equal(ConsoleLogger);
56
- expect(logger2.listeners.length).to.eq(1);
57
- expect(logger2.listeners[0].constructor).to.equal(ConsoleLogger);
58
- }));
59
- it('supports execution-based context', () => __awaiter(void 0, void 0, void 0, function* () {
60
- let logger = new Logger(new LoggingOptionsRef({ listeners: [] }));
61
- expect(logger.context).to.not.exist;
62
- expect(logger.contextLabel).to.not.exist;
63
- logger.withContext({ abc: 123 }, 'contextlabel', () => {
64
- expect(logger.context.abc).to.eq(123);
65
- expect(logger.contextLabel).to.eq('contextlabel');
66
- setTimeout(() => {
67
- expect(logger.context.abc).to.eq(123);
68
- expect(logger.contextLabel).to.eq('contextlabel');
69
- }, 10);
70
- });
71
- expect(logger.context).to.not.exist;
72
- expect(logger.contextLabel).to.not.exist;
73
- logger.withContext({ abc: 321 }, 'contextlabel2', () => {
74
- expect(logger.context.abc).to.eq(321);
75
- expect(logger.contextLabel).to.eq('contextlabel2');
76
- setTimeout(() => {
77
- expect(logger.context.abc).to.eq(321);
78
- expect(logger.contextLabel).to.eq('contextlabel2');
79
- }, 10);
80
- });
81
- expect(logger.context).to.not.exist;
82
- expect(logger.contextLabel).to.not.exist;
83
- }));
84
- it('supports child loggers with a specific source', () => __awaiter(void 0, void 0, void 0, function* () {
85
- let logger = new Logger(new LoggingOptionsRef({ listeners: [] }));
86
- expect(logger.sourceLabel).to.not.exist;
87
- let sublogger = logger.withSource('athing');
88
- expect(sublogger.sourceLabel).to.eq('athing');
89
- expect(logger.sourceLabel).to.not.exist;
90
- }));
91
- });
92
- describe('FileLogger', it => {
93
- it('basically works', () => __awaiter(void 0, void 0, void 0, function* () {
94
- let filename = path.join(os.tmpdir(), `file-logger-test-${Math.floor(Math.random() * 100000)}.txt`);
95
- if (fs.existsSync(filename))
96
- fs.unlinkSync(filename);
97
- let logger = new FileLogger('%severity%|%message%', filename);
98
- yield logger.open();
99
- yield logger.log({
100
- type: 'message',
101
- message: 'TUVXYZ',
102
- date: new Date(),
103
- severity: 'info'
104
- });
105
- let contents2 = fs.readFileSync(filename).toString().split(/\n/g);
106
- yield logger.log({
107
- type: 'message',
108
- message: '123456',
109
- date: new Date(),
110
- severity: 'info'
111
- });
112
- let contents3 = fs.readFileSync(filename).toString().split(/\n/g);
113
- if (fs.existsSync(filename))
114
- fs.unlinkSync(filename);
115
- expect(contents2[0]).to.eq('info|TUVXYZ');
116
- expect(contents3[0]).to.eq('info|TUVXYZ');
117
- expect(contents3[1]).to.eq('info|123456');
118
- // make sure there's nothing extra
119
- expect(contents2.length).to.eq(2);
120
- expect(contents3.length).to.eq(3);
121
- }));
122
- });
123
- describe('LogFormatter', it => {
124
- it('parses an empty string, ergo always produces empty strings', () => {
125
- let formatter = new LogFormatter('');
126
- expect(formatter.format(SAMPLE_LOG_MESSAGE(''))).to.equal('');
127
- expect(formatter.format(SAMPLE_LOG_MESSAGE('abcdef'))).to.equal('');
128
- expect(formatter.format(SAMPLE_LOG_MESSAGE('stuff!'))).to.equal('');
129
- });
130
- it('parses a literal string, ergo always produces the literal string', () => {
131
- let formatter = new LogFormatter('a silly format');
132
- expect(formatter.format(SAMPLE_LOG_MESSAGE(''))).to.equal('a silly format');
133
- expect(formatter.format(SAMPLE_LOG_MESSAGE('abcdef'))).to.equal('a silly format');
134
- expect(formatter.format(SAMPLE_LOG_MESSAGE('stuff!'))).to.equal('a silly format');
135
- });
136
- it('parses variables and substitutes message fields', () => {
137
- let formatter = new LogFormatter('-%severity%- -%message%-');
138
- expect(formatter.format(SAMPLE_LOG_MESSAGE('one silly message'))).to.equal('-info- -one silly message-');
139
- expect(formatter.format(SAMPLE_LOG_MESSAGE_FATAL('another silly message'))).to.equal('-fatal- -another silly message-');
140
- expect(formatter.format(SAMPLE_LOG_MESSAGE(''))).to.equal('-info- --');
141
- });
142
- });
143
- describe('ConsoleLogger', it => {
144
- function patchConsole(buffer) {
145
- let originalLog;
146
- function patch() {
147
- originalLog = console.log;
148
- console.log = message => {
149
- buffer.push(message);
150
- };
151
- }
152
- function unpatch() {
153
- console.log = originalLog;
154
- }
155
- let zone = Zone.current.fork({
156
- name: 'ConsoleLoggerTestZone',
157
- onInvoke(parent, current, target, callback, applyThis, applyArgs, source) {
158
- try {
159
- patch();
160
- parent.invoke(target, callback, applyThis, applyArgs, source);
161
- }
162
- finally {
163
- unpatch();
164
- }
165
- },
166
- onInvokeTask(parent, current, target, task, applyThis, applyArgs) {
167
- try {
168
- patch();
169
- parent.invokeTask(target, task, applyThis, applyArgs);
170
- }
171
- finally {
172
- unpatch();
173
- }
174
- }
175
- });
176
- return zone;
177
- }
178
- it('should obey silly formats', () => {
179
- let buffer = [];
180
- patchConsole(buffer)
181
- .run(() => {
182
- let logger = new ConsoleLogger('ABCDEF');
183
- logger.log(SAMPLE_LOG_MESSAGE_3);
184
- logger.log(SAMPLE_LOG_MESSAGE_2);
185
- });
186
- expect(buffer.length).to.eq(2);
187
- expect(buffer[0]).to.eq('ABCDEF');
188
- expect(buffer[1]).to.eq('ABCDEF');
189
- });
190
- it('should understand format variable substitution', () => {
191
- let buffer = [];
192
- patchConsole(buffer)
193
- .run(() => {
194
- let logger = new ConsoleLogger('%severity%|%message%');
195
- logger.log(SAMPLE_LOG_MESSAGE_3);
196
- logger.log(SAMPLE_LOG_MESSAGE_2);
197
- });
198
- expect(buffer.length).to.eq(2);
199
- expect(buffer[0]).to.eq('info|TUVXYZ');
200
- expect(buffer[1]).to.eq('info|123456');
201
- });
202
- });
203
- });
204
- //# sourceMappingURL=logger.test.js.map
package/dist.esm/test.js DELETED
@@ -1,6 +0,0 @@
1
- import "reflect-metadata";
2
- import { suite } from 'razmin';
3
- suite()
4
- .include(['./**/*.test.js'])
5
- .run();
6
- //# sourceMappingURL=test.js.map
@@ -1,63 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import { suite } from "razmin";
3
- import { Trace, setTracingEnabled, getTracingEnabled } from "./trace";
4
- import { interceptConsole } from "@alterior/common";
5
- import { expect } from 'chai';
6
- suite(describe => {
7
- describe('Trace', it => {
8
- it('reasonably traces a program', () => {
9
- let wasTracingEnabled = getTracingEnabled();
10
- setTracingEnabled(true);
11
- try {
12
- let log = [];
13
- interceptConsole((method, original, console, args) => {
14
- log.push({ method, original, console, args });
15
- }, () => {
16
- class Thing {
17
- static doSomething(data, stuff) {
18
- console.log("Doing something...");
19
- try {
20
- this.doAnotherThing(stuff);
21
- }
22
- catch (e) {
23
- }
24
- }
25
- static doAnotherThing(stuff) {
26
- console.log("Doing another thing...");
27
- throw new Error('Uh oh');
28
- }
29
- }
30
- __decorate([
31
- Trace(),
32
- __metadata("design:type", Function),
33
- __metadata("design:paramtypes", [Object, Number]),
34
- __metadata("design:returntype", void 0)
35
- ], Thing, "doSomething", null);
36
- __decorate([
37
- Trace(),
38
- __metadata("design:type", Function),
39
- __metadata("design:paramtypes", [Number]),
40
- __metadata("design:returntype", void 0)
41
- ], Thing, "doAnotherThing", null);
42
- let thing = new Thing();
43
- Thing.doSomething({ stuff: 321, other: "nice" }, 12333);
44
- });
45
- expect(log.length, `Content was: '${log.map(x => x.args.join(' ')).join("\n")}`).to.eq(8);
46
- expect(log[0].args[0]).to.contain('Thing#doSomething');
47
- expect(log[0].args[0]).to.contain('{');
48
- expect(log[1].args[0]).to.contain('Doing something...');
49
- expect(log[2].args[0]).to.contain('Thing#doAnotherThing');
50
- expect(log[2].args[0]).to.contain('{');
51
- expect(log[3].args[0]).to.contain('Doing another thing...');
52
- expect(log[4].args[0]).to.contain('Exception');
53
- expect(log[5].args[0]).to.contain('Error: Uh oh');
54
- expect(log[6].args[0]).to.contain('}');
55
- expect(log[7].args[0]).to.contain('}');
56
- }
57
- finally {
58
- setTracingEnabled(wasTracingEnabled);
59
- }
60
- });
61
- });
62
- });
63
- //# sourceMappingURL=trace.test.js.map