To utilize the functionalities in this answer, Lodash is required. However, you can easily replace that code with their equivalent JavaScript functions.
LogService.ts
import {FactoryProvider, Injectable, InjectionToken, Optional} from '@angular/core';
import * as _ from 'lodash';
const PREFIX_SEPARATOR = ':';
@Injectable()
export class LogService {
/**
* Global logger
*/
public static log: LogService = new LogService();
/**
* Creates an injectable logger for module use.
*/
public static provide(token: InjectionToken<LogService>): FactoryProvider {
return {
provide: token,
useFactory: (log: LogService) => {
let prefix = token.toString().replace(/^InjectionToken\s/, '').replace(/Logger$/, '').toUpperCase();
return log.prefix(prefix);
},
deps: [LogService]
};
}
/**
* Retrieves stack trace if available when browser adds stack property.
*/
private static getStackTrace(): string[] | undefined {
try {
let ex = new Error() as Object;
if (ex.hasOwnProperty('stack') && _.isString(ex['stack'])) {
return ex['stack'].split('\n');
}
} catch (err) {
// do nothing
}
return void 0;
}
/**
* Obtains constructor name from call stack, specific to Chrome browsers.
*/
private static getConstructorName(depth: number): string | undefined {
if (_.isUndefined(window['chrome'])) {
return void 0;
}
let trace = LogService.getStackTrace();
if (!trace || trace.length <= depth) {
return void 0;
}
let str = trace[depth];
if (!/\s+at\snew\s/.test(str)) {
return void 0;
}
let match = /new ([$A-Z_][0-9A-Z_$]*)/i.exec(str);
if (!match || match.length < 2) {
return void 0;
}
return match[1].replace(/(Component|Directive|Service|Factory|Pipe|Resource|Module|Resolver|Provider)$/, '');
}
/**
* Output prefix
*/
private prefixName: string;
/**
* Flag for debugging activation.
*/
private _debug: boolean = process.env.ENV && process.env.ENV === 'development';
/**
* Enables optional prefix provision.
*/
public constructor(@Optional() prefix?: string) {
this.prefixName = prefix;
}
/**
* Generates a logger with automated prefix.
*/
public withPrefix(): LogService {
if (!this._debug) {
return this;
}
return this.prefix(LogService.getConstructorName(4));
}
/**
* Generates a new logger with specified prefix for all output messages.
*/
public prefix(prefix?: string): LogService {
return prefix
? new LogService((this.prefixName || '') + prefix + PREFIX_SEPARATOR)
: this;
}
public get info(): Function {
if (!console || !console.info) {
return _.noop;
}
return this.prefixName
? console.info.bind(console, this.prefixName)
: console.info.bind(console);
}
public get debug(): Function {
if (!this._debug || !console || !console.log) {
return _.noop;
}
return this.prefixName
? console.log.bind(console, this.prefixName)
: console.log.bind(console);
}
public get warn(): Function {
if (!console || !console.warn) {
return _.noop;
}
return this.prefixName
? console.warn.bind(console, this.prefixName)
: console.warn.bind(console);
}
public get error(): Function {
if (!console || !console.error) {
return _.noop;
}
return this.prefixName
? console.error.bind(console, this.prefixName)
: console.error.bind(console);
}
}
LogService.module.ts
In your ngModule
, export this service as a singleton and then invoke forRoot()
in your main module. Import this module normally in other modules.
@NgModule({
})
export class LogModule {
public static forRoot() {
return {
ngModule: LogModule,
providers: [
LogService
]
};
}
}
Creating loggers per module
You can create loggers with module-specific prefixes by establishing an injectable token for each module.
This example pertains to a UI module.
export const UI_LOGGER = new InjectionToken<LogService>('UILogger');
@NgModule({
providers: [
LogService.provide(UI_LOGGER)
]
})
export class UIModule {
}
You can now inject this new logger into your UI components.
@Component({})
export class MyComponent {
private log: LogService; // logger with constructor name
public constructor(@Inject(UI_LOGGER) log: LogService) {
this.log = log.withPrefix();
}
}
By calling log.withPrefix()
, you create a fresh logger with the constructor name in addition to the module name.