Skip to content

Commit ab2a395

Browse files
hanslMRHarrison
authored andcommitted
feat(@ngtools/logger): Implement a reactive logger. (angular#3774)
It is typescript friendly and ultra performant. Using it in e2e tests for a PoC.
1 parent 92bd44f commit ab2a395

22 files changed

+668
-82
lines changed

.appveyor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ test_script:
1414
- node --version
1515
- npm --version
1616
- npm test
17-
- node tests\e2e_runner.js
17+
- node tests\run_e2e.js
1818

1919
build: off

.travis.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ matrix:
1212
allow_failures:
1313
- os: osx
1414
- node_js: "7"
15-
- env: NODE_SCRIPT="tests/e2e_runner.js --nightly"
15+
- env: NODE_SCRIPT="tests/run_e2e.js --nightly"
1616
include:
1717
- node_js: "6"
1818
os: linux
@@ -22,27 +22,27 @@ matrix:
2222
env: SCRIPT=build
2323
- node_js: "4"
2424
os: linux
25-
env: NODE_SCRIPT=tests/e2e_runner.js
25+
env: NODE_SCRIPT=tests/run_e2e.js
2626
- node_js: "6"
2727
os: linux
2828
env: SCRIPT=test
2929
- node_js: "6"
3030
os: linux
31-
env: NODE_SCRIPT=tests/e2e_runner.js
31+
env: NODE_SCRIPT=tests/run_e2e.js
3232
- node_js: "6"
3333
os: osx
34-
env: NODE_SCRIPT=tests/e2e_runner.js
34+
env: NODE_SCRIPT=tests/run_e2e.js
3535

3636
# Optional builds.
3737
- node_js: "6"
3838
os: osx
3939
env: SCRIPT=test
4040
- node_js: "6"
4141
os: linux
42-
env: NODE_SCRIPT="tests/e2e_runner.js --nightly"
42+
env: NODE_SCRIPT="tests/run_e2e.js --nightly"
4343
- node_js: "7"
4444
os: linux
45-
env: NODE_SCRIPT=tests/e2e_runner.js
45+
env: NODE_SCRIPT=tests/run_e2e.js
4646

4747
before_install:
4848
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi

package.json

+2-7
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
"@types/glob": "^5.0.29",
139139
"@types/jasmine": "^2.2.32",
140140
"@types/lodash": "^4.14.43",
141-
"@types/mock-fs": "3.6.28",
141+
"@types/mock-fs": "^3.6.30",
142142
"@types/node": "^6.0.36",
143143
"@types/request": "0.0.30",
144144
"@types/rimraf": "0.0.25-alpha",
@@ -149,11 +149,9 @@
149149
"conventional-changelog": "^1.1.0",
150150
"dtsgenerator": "^0.7.1",
151151
"eslint": "^2.8.0",
152-
"exists-sync": "0.0.3",
153152
"express": "^4.14.0",
154153
"jasmine": "^2.4.1",
155154
"jasmine-spec-reporter": "^2.7.0",
156-
"minimatch": "^3.0.3",
157155
"minimist": "^1.2.0",
158156
"mocha": "^2.4.5",
159157
"mock-fs": "3.10.0",
@@ -164,10 +162,7 @@
164162
"resolve-bin": "^0.4.0",
165163
"rewire": "^2.5.1",
166164
"sinon": "^1.17.3",
167-
"through": "^2.3.8",
168165
"tree-kill": "^1.0.0",
169-
"ts-node": "^1.3.0",
170-
"tslint": "^4.0.2",
171-
"walk-sync": "^0.2.6"
166+
"ts-node": "^1.3.0"
172167
}
173168
}

packages/@angular-cli/ast-tools/tsconfig.json

+1-4
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,5 @@
2020
"jasmine",
2121
"node"
2222
]
23-
},
24-
"exclude": [
25-
"**/*.spec.ts"
26-
]
23+
}
2724
}

packages/@ngtools/logger/package.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@ngtools/logger",
3+
"version": "0.1.0",
4+
"description": "",
5+
"main": "./src/index.js",
6+
"license": "MIT",
7+
"peerDependencies": {
8+
"rxjs": "^5.0.1"
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {LogEntry, Logger} from './logger';
2+
import {ConsoleLoggerStack} from './console-logger-stack';
3+
import {NullLogger} from './null-logger';
4+
5+
6+
describe('ConsoleLoggerStack', () => {
7+
it('works', (done: DoneFn) => {
8+
const logger = ConsoleLoggerStack.start('test');
9+
logger
10+
.toArray()
11+
.toPromise()
12+
.then((observed: LogEntry[]) => {
13+
expect(observed).toEqual([
14+
jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }),
15+
jasmine.objectContaining({ message: 'world', level: 'info', name: 'test' }),
16+
]);
17+
})
18+
.then(() => done(), (err: any) => done.fail(err));
19+
20+
console.debug('hello');
21+
console.log('world');
22+
ConsoleLoggerStack.end();
23+
});
24+
25+
it('works as a stack', (done: DoneFn) => {
26+
const oldConsoleLog = console.log;
27+
const logger = ConsoleLoggerStack.start('test');
28+
expect(console.log).not.toBe(oldConsoleLog);
29+
logger
30+
.toArray()
31+
.toPromise()
32+
.then((observed: LogEntry[]) => {
33+
expect(observed).toEqual([
34+
jasmine.objectContaining({ message: 'red', level: 'info', name: 'test' }),
35+
jasmine.objectContaining({ message: 'blue', level: 'info', name: 'test2' }),
36+
jasmine.objectContaining({ message: 'yellow', level: 'info', name: 'test3' }),
37+
jasmine.objectContaining({ message: 'green', level: 'info', name: 'test2' }),
38+
]);
39+
})
40+
.then(() => done(), (err: any) => done.fail(err));
41+
42+
console.log('red');
43+
ConsoleLoggerStack.push('test2');
44+
console.log('blue');
45+
ConsoleLoggerStack.push('test3');
46+
console.log('yellow');
47+
ConsoleLoggerStack.pop();
48+
console.log('green');
49+
ConsoleLoggerStack.end();
50+
expect(console.log).toBe(oldConsoleLog);
51+
});
52+
53+
it('can push instances or classes', (done: DoneFn) => {
54+
const oldConsoleLog = console.log;
55+
const logger = new Logger('test');
56+
ConsoleLoggerStack.start(logger);
57+
expect(console.log).not.toBe(oldConsoleLog);
58+
logger
59+
.toArray()
60+
.toPromise()
61+
.then((observed: LogEntry[]) => {
62+
expect(observed).toEqual([
63+
jasmine.objectContaining({ message: 'red', level: 'info', name: 'test' }),
64+
jasmine.objectContaining({ message: 'green', level: 'info', name: 'test2' }),
65+
]);
66+
})
67+
.then(() => done(), (err: any) => done.fail(err));
68+
69+
console.log('red');
70+
ConsoleLoggerStack.push(new NullLogger(logger));
71+
console.log('blue');
72+
ConsoleLoggerStack.pop();
73+
ConsoleLoggerStack.push(new Logger('test2', logger));
74+
console.log('green');
75+
ConsoleLoggerStack.end();
76+
expect(console.log).toBe(oldConsoleLog);
77+
});
78+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {Logger} from './logger';
2+
3+
let globalConsoleStack: Logger[] = null;
4+
let originalConsoleDebug: (message?: any, ...optionalParams: any[]) => void;
5+
let originalConsoleLog: (message?: any, ...optionalParams: any[]) => void;
6+
let originalConsoleWarn: (message?: any, ...optionalParams: any[]) => void;
7+
let originalConsoleError: (message?: any, ...optionalParams: any[]) => void;
8+
9+
10+
function _push(logger: Logger) {
11+
if (globalConsoleStack.length == 0) {
12+
originalConsoleDebug = console.debug;
13+
originalConsoleLog = console.log;
14+
originalConsoleWarn = console.warn;
15+
originalConsoleError = console.error;
16+
17+
console.debug = (msg: string, ...args: any[]) => {
18+
globalConsoleStack[globalConsoleStack.length - 1].debug(msg, { args });
19+
};
20+
console.log = (msg: string, ...args: any[]) => {
21+
globalConsoleStack[globalConsoleStack.length - 1].info(msg, { args });
22+
};
23+
console.warn = (msg: string, ...args: any[]) => {
24+
globalConsoleStack[globalConsoleStack.length - 1].warn(msg, { args });
25+
};
26+
console.error = (msg: string, ...args: any[]) => {
27+
globalConsoleStack[globalConsoleStack.length - 1].error(msg, { args });
28+
};
29+
}
30+
globalConsoleStack.push(logger);
31+
32+
return logger;
33+
}
34+
35+
function _pop() {
36+
globalConsoleStack[globalConsoleStack.length - 1].complete();
37+
globalConsoleStack.pop();
38+
if (globalConsoleStack.length == 0) {
39+
console.log = originalConsoleLog;
40+
console.warn = originalConsoleWarn;
41+
console.error = originalConsoleError;
42+
console.debug = originalConsoleDebug;
43+
globalConsoleStack = null;
44+
}
45+
}
46+
47+
48+
export type LoggerConstructor<T extends Logger> = {
49+
new (...args: any[]): T;
50+
};
51+
52+
53+
export class ConsoleLoggerStack {
54+
static push(name: string): Logger;
55+
static push(logger: Logger): Logger;
56+
static push<T extends Logger>(loggerClass: LoggerConstructor<T>, ...args: any[]): Logger;
57+
static push<T extends Logger>(nameOrLogger: string | Logger | LoggerConstructor<T> = '',
58+
...args: any[]) {
59+
if (typeof nameOrLogger == 'string') {
60+
return _push(new Logger(nameOrLogger as string, this.top()));
61+
} else if (nameOrLogger instanceof Logger) {
62+
const logger = nameOrLogger as Logger;
63+
if (logger.parent !== this.top()) {
64+
throw new Error('Pushing a logger that is not a direct child of the top of the stack.');
65+
}
66+
return _push(logger);
67+
} else {
68+
const klass = nameOrLogger as LoggerConstructor<T>;
69+
return _push(new klass(...args, this.top()));
70+
}
71+
}
72+
static pop(): Logger | null {
73+
_pop();
74+
return this.top();
75+
}
76+
77+
static top(): Logger | null {
78+
return globalConsoleStack && globalConsoleStack[globalConsoleStack.length - 1];
79+
}
80+
81+
static start(name: string): Logger;
82+
static start(logger: Logger): Logger;
83+
static start<T extends Logger>(loggerClass: LoggerConstructor<T>, ...args: any[]): Logger;
84+
static start<T extends Logger>(nameOrLogger: string | Logger | LoggerConstructor<T> = '',
85+
...args: any[]) {
86+
if (globalConsoleStack !== null) {
87+
throw new Error('Cannot start a new console logger stack while one is already going.');
88+
}
89+
90+
globalConsoleStack = [];
91+
if (typeof nameOrLogger == 'string') {
92+
return _push(new Logger(nameOrLogger as string, this.top()));
93+
} else if (nameOrLogger instanceof Logger) {
94+
return _push(nameOrLogger as Logger);
95+
} else {
96+
const klass = nameOrLogger as LoggerConstructor<T>;
97+
return _push(new klass(...args, this.top()));
98+
}
99+
}
100+
static end() {
101+
while (globalConsoleStack !== null) {
102+
this.pop();
103+
}
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {LogEntry, Logger} from './logger';
2+
import {IndentLogger} from './indent';
3+
4+
5+
describe('IndentSpec', () => {
6+
it('works', (done: DoneFn) => {
7+
const logger = new IndentLogger('test');
8+
logger
9+
.toArray()
10+
.toPromise()
11+
.then((observed: LogEntry[]) => {
12+
expect(observed).toEqual([
13+
jasmine.objectContaining({ message: 'test', level: 'info', name: 'test' }),
14+
jasmine.objectContaining({ message: ' test2', level: 'info', name: 'test2' }),
15+
jasmine.objectContaining({ message: ' test3', level: 'info', name: 'test3' }),
16+
jasmine.objectContaining({ message: ' test4', level: 'info', name: 'test4' }),
17+
jasmine.objectContaining({ message: 'test5', level: 'info', name: 'test' }),
18+
]);
19+
})
20+
.then(() => done(), (err: any) => done.fail(err));
21+
const logger2 = new Logger('test2', logger);
22+
const logger3 = new Logger('test3', logger2);
23+
const logger4 = new Logger('test4', logger);
24+
25+
logger.info('test');
26+
logger2.info('test2');
27+
logger3.info('test3');
28+
logger4.info('test4');
29+
logger.info('test5');
30+
31+
logger.complete();
32+
});
33+
});
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {Logger} from './logger';
2+
3+
import 'rxjs/add/operator/map';
4+
5+
6+
/**
7+
* Keep an map of indentation => array of indentations based on the level.
8+
* This is to optimize calculating the prefix based on the indentation itself. Since most logs
9+
* come from similar levels, and with similar indentation strings, this will be shared by all
10+
* loggers. Also, string concatenation is expensive so performing concats for every log entries
11+
* is expensive; this alleviates it.
12+
*/
13+
const indentationMap: {[indentationType: string]: string[]} = {};
14+
15+
16+
export class IndentLogger extends Logger {
17+
constructor(name: string, parent: Logger | null = null, indentation = ' ') {
18+
super(name, parent);
19+
20+
indentationMap[indentation] = indentationMap[indentation] || [''];
21+
const map = indentationMap[indentation];
22+
23+
this._observable = this._observable.map(entry => {
24+
const l = entry.path.length;
25+
if (l >= map.length) {
26+
let current = map[map.length - 1];
27+
while (l >= map.length) {
28+
current += indentation;
29+
map.push(current);
30+
}
31+
}
32+
33+
entry.message = map[l] + entry.message;
34+
return entry;
35+
});
36+
}
37+
}

packages/@ngtools/logger/src/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
export * from './console-logger-stack';
3+
export * from './indent';
4+
export * from './logger';
5+
export * from './null-logger';
6+
export * from './transform-logger';

0 commit comments

Comments
 (0)