Skip to content

Commit

Permalink
feat(@ngtools/logger): Implement a reactive logger.
Browse files Browse the repository at this point in the history
It is typescript friendly and ultra performant. Using it in e2e tests for a PoC.
  • Loading branch information
hansl committed Dec 29, 2016
1 parent 50d73d2 commit b00464a
Show file tree
Hide file tree
Showing 22 changed files with 668 additions and 82 deletions.
2 changes: 1 addition & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ test_script:
- node --version
- npm --version
- npm test
- node tests\e2e_runner.js
- node tests\run_e2e.js

build: off
12 changes: 6 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ matrix:
allow_failures:
- os: osx
- node_js: "7"
- env: NODE_SCRIPT="tests/e2e_runner.js --nightly"
- env: NODE_SCRIPT="tests/run_e2e.js --nightly"
include:
- node_js: "6"
os: linux
Expand All @@ -22,27 +22,27 @@ matrix:
env: SCRIPT=build
- node_js: "4"
os: linux
env: NODE_SCRIPT=tests/e2e_runner.js
env: NODE_SCRIPT=tests/run_e2e.js
- node_js: "6"
os: linux
env: SCRIPT=test
- node_js: "6"
os: linux
env: NODE_SCRIPT=tests/e2e_runner.js
env: NODE_SCRIPT=tests/run_e2e.js
- node_js: "6"
os: osx
env: NODE_SCRIPT=tests/e2e_runner.js
env: NODE_SCRIPT=tests/run_e2e.js

# Optional builds.
- node_js: "6"
os: osx
env: SCRIPT=test
- node_js: "6"
os: linux
env: NODE_SCRIPT="tests/e2e_runner.js --nightly"
env: NODE_SCRIPT="tests/run_e2e.js --nightly"
- node_js: "7"
os: linux
env: NODE_SCRIPT=tests/e2e_runner.js
env: NODE_SCRIPT=tests/run_e2e.js

before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
Expand Down
9 changes: 2 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
"@types/glob": "^5.0.29",
"@types/jasmine": "^2.2.32",
"@types/lodash": "^4.14.43",
"@types/mock-fs": "3.6.28",
"@types/mock-fs": "^3.6.30",
"@types/node": "^6.0.36",
"@types/request": "0.0.30",
"@types/rimraf": "0.0.25-alpha",
Expand All @@ -151,11 +151,9 @@
"conventional-changelog": "^1.1.0",
"dtsgenerator": "^0.7.1",
"eslint": "^2.8.0",
"exists-sync": "0.0.3",
"express": "^4.14.0",
"jasmine": "^2.4.1",
"jasmine-spec-reporter": "^2.7.0",
"minimatch": "^3.0.3",
"minimist": "^1.2.0",
"mocha": "^2.4.5",
"mock-fs": "3.10.0",
Expand All @@ -166,10 +164,7 @@
"resolve-bin": "^0.4.0",
"rewire": "^2.5.1",
"sinon": "^1.17.3",
"through": "^2.3.8",
"tree-kill": "^1.0.0",
"ts-node": "^1.3.0",
"tslint": "^4.0.2",
"walk-sync": "^0.2.6"
"ts-node": "^1.3.0"
}
}
5 changes: 1 addition & 4 deletions packages/@angular-cli/ast-tools/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,5 @@
"jasmine",
"node"
]
},
"exclude": [
"**/*.spec.ts"
]
}
}
10 changes: 10 additions & 0 deletions packages/@ngtools/logger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@ngtools/logger",
"version": "0.1.0",
"description": "",
"main": "./src/index.js",
"license": "MIT",
"peerDependencies": {
"rxjs": "^5.0.1"
}
}
78 changes: 78 additions & 0 deletions packages/@ngtools/logger/src/console-logger-stack.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {LogEntry, Logger} from './logger';
import {ConsoleLoggerStack} from './console-logger-stack';
import {NullLogger} from './null-logger';


describe('ConsoleLoggerStack', () => {
it('works', (done: DoneFn) => {
const logger = ConsoleLoggerStack.start('test');
logger
.toArray()
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'hello', level: 'debug', name: 'test' }),
jasmine.objectContaining({ message: 'world', level: 'info', name: 'test' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));

console.debug('hello');
console.log('world');
ConsoleLoggerStack.end();
});

it('works as a stack', (done: DoneFn) => {
const oldConsoleLog = console.log;
const logger = ConsoleLoggerStack.start('test');
expect(console.log).not.toBe(oldConsoleLog);
logger
.toArray()
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'red', level: 'info', name: 'test' }),
jasmine.objectContaining({ message: 'blue', level: 'info', name: 'test2' }),
jasmine.objectContaining({ message: 'yellow', level: 'info', name: 'test3' }),
jasmine.objectContaining({ message: 'green', level: 'info', name: 'test2' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));

console.log('red');
ConsoleLoggerStack.push('test2');
console.log('blue');
ConsoleLoggerStack.push('test3');
console.log('yellow');
ConsoleLoggerStack.pop();
console.log('green');
ConsoleLoggerStack.end();
expect(console.log).toBe(oldConsoleLog);
});

it('can push instances or classes', (done: DoneFn) => {
const oldConsoleLog = console.log;
const logger = new Logger('test');
ConsoleLoggerStack.start(logger);
expect(console.log).not.toBe(oldConsoleLog);
logger
.toArray()
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'red', level: 'info', name: 'test' }),
jasmine.objectContaining({ message: 'green', level: 'info', name: 'test2' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));

console.log('red');
ConsoleLoggerStack.push(new NullLogger(logger));
console.log('blue');
ConsoleLoggerStack.pop();
ConsoleLoggerStack.push(new Logger('test2', logger));
console.log('green');
ConsoleLoggerStack.end();
expect(console.log).toBe(oldConsoleLog);
});
});
105 changes: 105 additions & 0 deletions packages/@ngtools/logger/src/console-logger-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {Logger} from './logger';

let globalConsoleStack: Logger[] = null;
let originalConsoleDebug: (message?: any, ...optionalParams: any[]) => void;
let originalConsoleLog: (message?: any, ...optionalParams: any[]) => void;
let originalConsoleWarn: (message?: any, ...optionalParams: any[]) => void;
let originalConsoleError: (message?: any, ...optionalParams: any[]) => void;


function _push(logger: Logger) {
if (globalConsoleStack.length == 0) {
originalConsoleDebug = console.debug;
originalConsoleLog = console.log;
originalConsoleWarn = console.warn;
originalConsoleError = console.error;

console.debug = (msg: string, ...args: any[]) => {
globalConsoleStack[globalConsoleStack.length - 1].debug(msg, { args });
};
console.log = (msg: string, ...args: any[]) => {
globalConsoleStack[globalConsoleStack.length - 1].info(msg, { args });
};
console.warn = (msg: string, ...args: any[]) => {
globalConsoleStack[globalConsoleStack.length - 1].warn(msg, { args });
};
console.error = (msg: string, ...args: any[]) => {
globalConsoleStack[globalConsoleStack.length - 1].error(msg, { args });
};
}
globalConsoleStack.push(logger);

return logger;
}

function _pop() {
globalConsoleStack[globalConsoleStack.length - 1].complete();
globalConsoleStack.pop();
if (globalConsoleStack.length == 0) {
console.log = originalConsoleLog;
console.warn = originalConsoleWarn;
console.error = originalConsoleError;
console.debug = originalConsoleDebug;
globalConsoleStack = null;
}
}


export type LoggerConstructor<T extends Logger> = {
new (...args: any[]): T;
};


export class ConsoleLoggerStack {
static push(name: string): Logger;
static push(logger: Logger): Logger;
static push<T extends Logger>(loggerClass: LoggerConstructor<T>, ...args: any[]): Logger;
static push<T extends Logger>(nameOrLogger: string | Logger | LoggerConstructor<T> = '',
...args: any[]) {
if (typeof nameOrLogger == 'string') {
return _push(new Logger(nameOrLogger as string, this.top()));
} else if (nameOrLogger instanceof Logger) {
const logger = nameOrLogger as Logger;
if (logger.parent !== this.top()) {
throw new Error('Pushing a logger that is not a direct child of the top of the stack.');
}
return _push(logger);
} else {
const klass = nameOrLogger as LoggerConstructor<T>;
return _push(new klass(...args, this.top()));
}
}
static pop(): Logger | null {
_pop();
return this.top();
}

static top(): Logger | null {
return globalConsoleStack && globalConsoleStack[globalConsoleStack.length - 1];
}

static start(name: string): Logger;
static start(logger: Logger): Logger;
static start<T extends Logger>(loggerClass: LoggerConstructor<T>, ...args: any[]): Logger;
static start<T extends Logger>(nameOrLogger: string | Logger | LoggerConstructor<T> = '',
...args: any[]) {
if (globalConsoleStack !== null) {
throw new Error('Cannot start a new console logger stack while one is already going.');
}

globalConsoleStack = [];
if (typeof nameOrLogger == 'string') {
return _push(new Logger(nameOrLogger as string, this.top()));
} else if (nameOrLogger instanceof Logger) {
return _push(nameOrLogger as Logger);
} else {
const klass = nameOrLogger as LoggerConstructor<T>;
return _push(new klass(...args, this.top()));
}
}
static end() {
while (globalConsoleStack !== null) {
this.pop();
}
}
}
33 changes: 33 additions & 0 deletions packages/@ngtools/logger/src/indent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {LogEntry, Logger} from './logger';
import {IndentLogger} from './indent';


describe('IndentSpec', () => {
it('works', (done: DoneFn) => {
const logger = new IndentLogger('test');
logger
.toArray()
.toPromise()
.then((observed: LogEntry[]) => {
expect(observed).toEqual([
jasmine.objectContaining({ message: 'test', level: 'info', name: 'test' }),
jasmine.objectContaining({ message: ' test2', level: 'info', name: 'test2' }),
jasmine.objectContaining({ message: ' test3', level: 'info', name: 'test3' }),
jasmine.objectContaining({ message: ' test4', level: 'info', name: 'test4' }),
jasmine.objectContaining({ message: 'test5', level: 'info', name: 'test' }),
]);
})
.then(() => done(), (err: any) => done.fail(err));
const logger2 = new Logger('test2', logger);
const logger3 = new Logger('test3', logger2);
const logger4 = new Logger('test4', logger);

logger.info('test');
logger2.info('test2');
logger3.info('test3');
logger4.info('test4');
logger.info('test5');

logger.complete();
});
});
37 changes: 37 additions & 0 deletions packages/@ngtools/logger/src/indent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {Logger} from './logger';

import 'rxjs/add/operator/map';


/**
* Keep an map of indentation => array of indentations based on the level.
* This is to optimize calculating the prefix based on the indentation itself. Since most logs
* come from similar levels, and with similar indentation strings, this will be shared by all
* loggers. Also, string concatenation is expensive so performing concats for every log entries
* is expensive; this alleviates it.
*/
const indentationMap: {[indentationType: string]: string[]} = {};


export class IndentLogger extends Logger {
constructor(name: string, parent: Logger | null = null, indentation = ' ') {
super(name, parent);

indentationMap[indentation] = indentationMap[indentation] || [''];
const map = indentationMap[indentation];

this._observable = this._observable.map(entry => {
const l = entry.path.length;
if (l >= map.length) {
let current = map[map.length - 1];
while (l >= map.length) {
current += indentation;
map.push(current);
}
}

entry.message = map[l] + entry.message;
return entry;
});
}
}
6 changes: 6 additions & 0 deletions packages/@ngtools/logger/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

export * from './console-logger-stack';
export * from './indent';
export * from './logger';
export * from './null-logger';
export * from './transform-logger';
Loading

0 comments on commit b00464a

Please sign in to comment.