Skip to content

Commit

Permalink
feat(core): introduce Logger service
Browse files Browse the repository at this point in the history
  • Loading branch information
DzmitryShylovich committed Feb 15, 2017
1 parent d3f174a commit a3279d9
Show file tree
Hide file tree
Showing 9 changed files with 394 additions and 2 deletions.
19 changes: 18 additions & 1 deletion modules/@angular/core/src/application_module.ts
Expand Up @@ -8,13 +8,14 @@

import {AnimationQueue} from './animation/animation_queue';
import {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
import {ApplicationRef, ApplicationRef_} from './application_ref';
import {ApplicationRef, ApplicationRef_, isDevMode} from './application_ref';
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
import {Inject, Optional, SkipSelf} from './di/metadata';
import {LOCALE_ID} from './i18n/tokens';
import {Compiler} from './linker/compiler';
import {ViewUtils} from './linker/view_utils';
import {ConsoleLogger, LOGGER_OPTIONS, Logger, LoggerOptions, NoOpLogger} from './logger';
import {NgModule} from './metadata';
import {initServicesIfNeeded} from './view/index';

Expand All @@ -34,6 +35,17 @@ export function _initViewEngine() {
initServicesIfNeeded();
}

export function _loggerFactory(options?: LoggerOptions): Logger {
options = options || {};
const enabled = options.enabled != null ? options.enabled : isDevMode();
if (enabled) {
const _console: Console = typeof console === 'object' ? console : <any>{};
const debug = options.debug != null ? options.debug : true;
return new ConsoleLogger(_console, debug);
}
return new NoOpLogger();
}

/**
* This module includes the providers of @angular/core that are needed
* to bootstrap components via `ApplicationRef`.
Expand All @@ -51,6 +63,11 @@ export function _initViewEngine() {
AnimationQueue,
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
{
provide: Logger,
useFactory: _loggerFactory,
deps: [[new Inject(LOGGER_OPTIONS), new Optional()]]
},
{
provide: LOCALE_ID,
useFactory: _localeFactory,
Expand Down
1 change: 1 addition & 0 deletions modules/@angular/core/src/core.ts
Expand Up @@ -39,3 +39,4 @@ export {AnimationStyles} from './animation/animation_styles';
export {AnimationKeyframe} from './animation/animation_keyframe';
export {Sanitizer, SecurityContext} from './security';
export {TransitionFactory, TransitionInstruction, Trigger} from './triggers';
export {Logger, LOGGER_OPTIONS, LoggerOptions} from './logger';
111 changes: 111 additions & 0 deletions modules/@angular/core/src/logger.ts
@@ -0,0 +1,111 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {InjectionToken} from './di/injection_token';

/** @experimental */
export interface LoggerOptions {
enabled?: boolean;
debug?: boolean;
}

/**
* @whatItDoes Is used in DI to configure the {@link Logger}.
* @experimental
*/
export const LOGGER_OPTIONS = new InjectionToken<LoggerOptions>('Logger Options');

/**
* Simple service for logging.
*
* @experimental
*/
export abstract class Logger {
/** Write a log message. */
abstract log(...args: any[]): void;

/** Write an information message. */
abstract info(...args: any[]): void;

/** Write a warning message. */
abstract warn(...args: any[]): void;

/** Write an error message. */
abstract error(...args: any[]): void;

/** Write a debug message. */
abstract debug(...args: any[]): void;

/** Create a new inline group. */
abstract group(groupTitle?: string): void;

/** Exit the current inline group. */
abstract groupEnd(): void;
}

const noop = (): any => undefined;

/**
* Default implementation of {@link Logger} that safely writes the message into the console.
*
* @experimental
*/
export class ConsoleLogger implements Logger {
constructor(private _console: Console, private _debugEnabled: boolean = true) {}

log(...args: any[]): void { this._invokeConsoleMethod('log', args); }

info(...args: any[]): void { this._invokeConsoleMethod('info', args); }

warn(...args: any[]): void { this._invokeConsoleMethod('warn', args); }

error(...args: any[]): void { this._invokeConsoleMethod('error', args); }

debug(...args: any[]): void {
if (this._debugEnabled) this._invokeConsoleMethod('debug', args);
}

group(groupTitle?: string): void {
const args = groupTitle != null ? [groupTitle] : [];
this._invokeConsoleMethod('group', args);
}

groupEnd(): void { this._invokeConsoleMethod('groupEnd'); }

private _invokeConsoleMethod(type: string, args?: any[]): void {
let logFn: Function = (<any>this._console)[type] || this._console.log || noop;

// console methods in IE9 don't have 'apply' method, polyfill it
if (!logFn.apply) {
logFn = Function.prototype.bind.call(logFn, this._console);
}

logFn.apply(this._console, args);
}
}

/**
* No op implementation of {@link Logger}.
*
* @experimental
*/
export class NoOpLogger implements Logger {
log(): void {}

info(): void {}

warn(): void {}

error(): void {}

debug(): void {}

group(): void {}

groupEnd(): void {}
}
37 changes: 36 additions & 1 deletion modules/@angular/core/test/application_module_spec.ts
Expand Up @@ -6,12 +6,47 @@
* found in the LICENSE file at https://angular.io/license
*/

import {LOCALE_ID} from '@angular/core';
import {Injectable, LOCALE_ID} from '@angular/core';
import {TestBed} from '@angular/core/testing';

import {_loggerFactory} from '../src/application_module';
import {ConsoleLogger, Logger, NoOpLogger} from '../src/logger';
import {describe, expect, inject, it} from '../testing/testing_internal';

export function main() {
describe('Application module', () => {
it('should set the default locale to "en-US"',
inject([LOCALE_ID], (defaultLocale: string) => { expect(defaultLocale).toEqual('en-US'); }));

describe('Logger', () => {
describe('factory', () => {
it('should return NoOpLogger when disabled', () => {
const logger = _loggerFactory({enabled: false});
expect(logger instanceof NoOpLogger).toBe(true);
});

it('should return ConsoleLogger when enabled', () => {
const logger = _loggerFactory({enabled: true});
expect(logger instanceof ConsoleLogger).toBe(true);
});

it('should use isDevMode without options', () => {
const logger = _loggerFactory();
expect(logger instanceof ConsoleLogger).toBe(true);
});
});

describe('should inject Logger service', () => {
@Injectable()
class DependsOnLogger {
constructor(public logger: Logger) {}
}

beforeEach(() => TestBed.configureTestingModule({providers: [DependsOnLogger]}));

it('works',
() => expect(TestBed.get(DependsOnLogger).logger instanceof ConsoleLogger).toBe(true));
});
});
});
}
131 changes: 131 additions & 0 deletions modules/@angular/core/test/logger_spec.ts
@@ -0,0 +1,131 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {ConsoleLogger, Logger, NoOpLogger} from '../src/logger';

interface TestConsole {
log?: (arg?: any) => string;
warn?: (arg?: any) => string;
info?: (arg?: any) => string;
error?: (arg?: any) => string;
debug?: (arg?: any) => string;
group?: (arg?: any) => string;
groupEnd?: (arg?: any) => string;
}

export function main() {
describe('Logger', () => {
let logBuffer: string;
let _console: TestConsole|any;

beforeEach(() => {
logBuffer = '';
_console = {
log: (arg: any = '') => logBuffer += `log${arg};`,
warn: (arg: any = '') => logBuffer += `warn${arg};`,
info: (arg: any = '') => logBuffer += `info${arg};`,
error: (arg: any = '') => logBuffer += `error${arg};`,
debug: (arg: any = '') => logBuffer += `debug${arg};`,
group: (arg: any = '') => logBuffer += `group${arg};`,
groupEnd: () => logBuffer += 'groupEnd;',
};
});

describe('No op', () => {
it('should not log anything', () => {
const logger: Logger = new NoOpLogger();
logger.info();
logger.log();
logger.debug();
logger.error();
logger.warn();
logger.group();
logger.groupEnd();
expect(logBuffer).toEqual('');
});
});

describe('Console', () => {
it('should use console if present', () => {
const logger: Logger = new ConsoleLogger(_console);
logger.info('$');
logger.log('$');
logger.debug('$');
logger.error('$');
logger.warn('$');
logger.group('$');
logger.groupEnd();
expect(logBuffer).toEqual('info$;log$;debug$;error$;warn$;group$;groupEnd;');
});

it('should use console.log() if other not present', () => {
const _console: any = {log: () => logBuffer += 'log;'};
const logger: Logger = new ConsoleLogger(_console);
logger.info();
logger.log();
logger.debug();
logger.error();
logger.warn();
logger.group();
logger.groupEnd();
expect(logBuffer).toEqual('log;log;log;log;log;log;log;');
});

it('should use noop if no console', () => {
const logger: Logger = new ConsoleLogger(<any>{});
logger.info();
logger.log();
logger.debug();
logger.error();
logger.warn();
logger.group();
logger.groupEnd();
expect(logBuffer).toEqual('');
});

describe('debug', () => {
it('should skip debugging output if disabled', () => {
const logger: Logger = new ConsoleLogger(_console, false);
logger.info();
logger.log();
logger.debug();
logger.error();
logger.warn();
logger.group();
logger.groupEnd();
expect(logBuffer).toEqual('info;log;error;warn;group;groupEnd;');
});
});

describe('IE logging', () => {
it(`should work in IE where console methods don't have 'apply' method`, () => {
removeApplyFunctionForIE(_console);
const logger: Logger = new ConsoleLogger(_console);
logger.info('$');
logger.log('$');
logger.debug('$');
logger.error('$');
logger.warn('$');
logger.group('$');
logger.groupEnd();
expect(logBuffer).toEqual('info$;log$;debug$;error$;warn$;group$;groupEnd;');
});

function removeApplyFunctionForIE(console: TestConsole): void {
console.log.apply = null;
console.warn.apply = null;
console.info.apply = null;
console.error.apply = null;
console.debug.apply = null;
console.group.apply = null;
console.groupEnd.apply = null;
}
});
});
});
}
1 change: 1 addition & 0 deletions modules/@angular/core/testing/index.ts
Expand Up @@ -19,3 +19,4 @@ export * from './test_bed';
export * from './testing';
export * from './metadata_override';
export * from './private_export_testing';
export * from './mock_logger';

0 comments on commit a3279d9

Please sign in to comment.