diff --git a/packages/core/engine/kites-instance.spec.ts b/packages/core/engine/kites-instance.spec.ts index 148f6da..08f9a24 100644 --- a/packages/core/engine/kites-instance.spec.ts +++ b/packages/core/engine/kites-instance.spec.ts @@ -1,11 +1,13 @@ import * as appRoot from 'app-root-path'; -import { assert, expect } from 'chai'; +import { assert, expect, should } from 'chai'; import * as fs from 'fs'; import * as path from 'path'; import { IKites, KitesInstance } from './kites-instance'; import * as stdMocks from 'std-mocks'; +import { transports } from 'winston'; import { KitesExtension } from '../extensions/extensions'; +import { DebugTransport } from '../logger'; import { engine } from './kites-factory'; function safeUnlink(fn: string) { @@ -108,15 +110,23 @@ describe('kites logs', () => { stdMocks.restore(); let stdoutContent = stdMocks.flush(); - expect(stdoutContent.stdout.length).eq(0, 'stdout is empty'); + expect(stdoutContent.stdout.length).eq(0, 'stdout must be empty'); - let allTransportAreSilent = Object.keys(app.logger.transports).every((name) => app.logger.transports[name].silent === true); + let allTransportAreSilent = app.logger.transports.every((x) => x.silent === true); expect(allTransportAreSilent).eq(true, 'all transports are silent'); }); + it('should have Debug transport enabled by default', () => { + return engine() + .init() + .then((app) => { + expect(app.logger.transports.some(x => x instanceof transports.Console)).eq(true, 'default transport'); + }); + }); + it('should fail to configure custom transport that does not have enough options', async () => { - await engine({ + return await engine({ logger: { console: { transport: 'console' @@ -130,6 +140,30 @@ describe('kites logs', () => { }); }); + it('should not load disabled transports', async () => { + + return await engine({ + logger: { + console: { + level: 'debug', + transport: 'console' + }, + file: { + enabled: false, + level: 'debug', + transport: 'file', + filename: './test.log' + } + } + }) + .init() + .then((app) => { + expect(app.logger.transports.length).eq(1); + expect(app.logger.transports.some(x => x instanceof transports.Console)).eq(true); + expect(app.logger.transports.some(x => x instanceof transports.File)).eq(false); + }); + }); + }); describe('kites initializeListeners', () => { diff --git a/packages/core/engine/kites-instance.ts b/packages/core/engine/kites-instance.ts index c79a381..57b0350 100644 --- a/packages/core/engine/kites-instance.ts +++ b/packages/core/engine/kites-instance.ts @@ -3,11 +3,11 @@ import * as fs from 'fs'; import * as _ from 'lodash'; // import * as nconf from 'nconf'; import * as path from 'path'; -import { Logger } from 'winston'; +import { Logger, transports } from 'winston'; import { EventEmitter } from 'events'; import { ExtensionsManager } from '../extensions/extensions-manager'; -import createDebugLogger, { DebugTransport } from '../logger'; +import { createLogger } from '../logger'; import { EventCollectionEmitter } from './event-collection'; import { Type } from '@kites/common'; @@ -92,7 +92,7 @@ export class KitesInstance extends EventEmitter implements IKites { this.iocContainer = new Container(); // properties - this.logger = createDebugLogger(this.name); + this.logger = createLogger(this.name, this.options.logger); this.fnAfterConfigLoaded = () => this; this.isReady = new Promise((resolve) => { this.on('initialized', resolve); @@ -255,13 +255,14 @@ export class KitesInstance extends EventEmitter implements IKites { * Kites initialize */ async init() { - this._initOptions(); - this.logger.info(`Initializing ${this.name}@${this.version} in mode "${this.options.env}"${this.options.loadConfig ? ', using configuration file ' + this.options.configFile : ''}`); - + // Keep silent if the option configured if (this.options.logger && this.options.logger.silent === true) { this._silentLogs(this.logger); } + this._initOptions(); + this.logger.info(`Initializing ${this.name}@${this.version} in mode "${this.options.env}"${this.options.loadConfig ? ', using configuration file ' + this.options.configFile : ''}`); + await this.extensionsManager.init(); await this.initializeListeners.fire(); @@ -278,15 +279,13 @@ export class KitesInstance extends EventEmitter implements IKites { this.fnAfterConfigLoaded(this); } - // return this._configureWinstonTransports(this.options.logger); + return this._configureWinstonTransports(this.options.logger); } private _silentLogs(logger: Logger) { - if (logger.transports) { - _.keys(logger.transports).forEach((name) => { - logger.transports[name].silent = true; - }); - } + logger.transports.forEach(x => { + x.silent = true; + }); } private _loadConfig() { @@ -329,4 +328,79 @@ export class KitesInstance extends EventEmitter implements IKites { this.options = nconf.get(); } + private _configureWinstonTransports(options: any) { + options = options || {}; + + var knownTransports: any = { + console: transports.Console, + file: transports.File, + http: transports.Http, + stream: transports.Stream, + }; + + var knownOptions = ['transport', 'module', 'enabled']; + + // tslint:disable-next-line:forin + for (let trName in options) { + var tranOpts = options[trName]; + if (!tranOpts || typeof tranOpts !== 'object' || _.isArray(tranOpts)) { + continue; + } + + if (!tranOpts.transport || typeof tranOpts.transport !== 'string') { + throw new Error(`invalid option for transport object ${trName}, option "transport" is not specified or has an incorrect value, must be a string with a valid value. check your "logger" config`); + } + + if (!tranOpts.level || typeof tranOpts.level !== 'string') { + throw new Error(`invalid option for transport object ${trName}, option "level" is not specified or has an incorrect value, must be a string with a valid value. check your "logger" config`); + } + + if (tranOpts.enabled === false) { + continue; + } + + // add transport + if (knownTransports[tranOpts.transport]) { + if (this.logger.transports.some((x: any) => x.name === trName)) { + continue; + } + const transport = knownTransports[tranOpts.transport] as any; + const opts = _.extend(_.omit(tranOpts, knownOptions), { name: trName }); + this.logger.add(new transport(opts)); + } else { + if (typeof tranOpts.module !== 'string') { + throw new Error(`invalid option for transport object "${trName}", option "module" has an incorrect value, must be a string with a module name. check your "logger" config`); + } + + try { + let transportModule = require(tranOpts.module); + let winstonTransport: any = transports; + if (typeof winstonTransport[tranOpts.transport] === 'function') { + transportModule = winstonTransport[tranOpts.transport]; + } else if (typeof transportModule[tranOpts.transport] === 'function') { + transportModule = transportModule[tranOpts.transport]; + } + + if (typeof transportModule !== 'function') { + throw new Error(`invalid option for transport object "${trName}", option module "${tranOpts.module}" does not export a valid transport. check your "logger" config`); + } + + const opts = _.extend(_.omit(tranOpts, knownOptions), { name: trName }); + + this.logger.add(new transportModule(opts)); + + } catch (err) { + if (err.code === 'MODULE_NOT_FOUND') { + throw new Error( + `invalid option for transport object "${trName}", module "${tranOpts.module}" in "module" option could not be found. are you sure that you have installed it?. check your "logger" config`); + } + + throw err; + } + } + + } + + } + } diff --git a/packages/core/extensions/discover.spec.ts b/packages/core/extensions/discover.spec.ts index 08efa9f..69c798b 100644 --- a/packages/core/extensions/discover.spec.ts +++ b/packages/core/extensions/discover.spec.ts @@ -1,13 +1,13 @@ import { expect } from 'chai'; import { join } from 'path'; -import createDebugLogger from '../logger'; +import { createLogger } from '../logger'; import { discover } from './discover'; describe('Discover extensions', () => { it('should load an extension', async () => { const rootDirectory = join(__dirname, '../test'); let extensions: any = await discover({ - logger: createDebugLogger('discover'), + logger: createLogger('discover'), rootDirectory: rootDirectory }); console.log('rootDirectory: ', rootDirectory); diff --git a/packages/core/extensions/location-cache.spec.ts b/packages/core/extensions/location-cache.spec.ts index ddf51c9..1e448c8 100644 --- a/packages/core/extensions/location-cache.spec.ts +++ b/packages/core/extensions/location-cache.spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import { join, resolve } from 'path'; +import { join } from 'path'; -import createDebugLogger from '../logger'; +import { createLogger } from '../logger'; import * as cache from './location-cache'; describe('Location cache', () => { @@ -13,7 +13,7 @@ describe('Location cache', () => { it('should get one and save it!', async () => { const rootDirectory = join(__dirname, '../test'); let extensions: any = await cache.get({ - logger: createDebugLogger('location-cache'), + logger: createLogger('location-cache'), rootDirectory: rootDirectory }); console.log('Found: ', extensions, rootDirectory); diff --git a/packages/core/logger/debug-transport.ts b/packages/core/logger/debug-transport.ts index 2a85a0f..392bab5 100644 --- a/packages/core/logger/debug-transport.ts +++ b/packages/core/logger/debug-transport.ts @@ -2,12 +2,14 @@ import debug from 'debug'; import Transport from 'winston-transport'; export class DebugTransport extends Transport { + public name: string; private debugger: debug.IDebugger; - constructor(options?: Transport.TransportStreamOptions) { + constructor(options?: Transport.TransportStreamOptions, name?: string) { super(options); this.debugger = debug('kites'); + this.name = name || 'debug'; } public log(info, callback: Function) { diff --git a/packages/core/logger/index.ts b/packages/core/logger/index.ts index b343e92..a3bd4f3 100644 --- a/packages/core/logger/index.ts +++ b/packages/core/logger/index.ts @@ -1,17 +1,27 @@ import * as path from 'path'; -import * as winston from 'winston'; +import { format, Logger, loggers } from 'winston'; import { DebugTransport } from './debug-transport'; -export { DebugTransport } from './debug-transport'; +function createLogger(name: string, options?: any): Logger { + if (!loggers.has(name)) { + // add default Debug transport? + const defaultTransports = Object.keys(options || {}).length > 0 ? [] : [ + new DebugTransport(options, name), + ]; -export default function createDebugLogger(name: string, options?: any): winston.Logger { - if (!winston.loggers.has(name)) { - const debugTransport = new DebugTransport(); - winston.loggers.add(name, { - transports: [debugTransport], + loggers.add(name, { + exitOnError: false, + level: 'info', + format: format.combine( + format.label({ label: name }), + format.colorize(), + format.timestamp(), + format.printf(({ level, message, label, timestamp }) => `${timestamp} [${label}] ${level}: ${message}`) + ), + transports: defaultTransports, }); - winston.loggers.get(name).on('error', (err: any) => { + loggers.get(name).on('error', (err: any) => { if (err.code === 'ENOENT') { let msg = err; if (path.dirname(err.path) === '.') { @@ -28,9 +38,17 @@ export default function createDebugLogger(name: string, options?: any): winston. }); } else { // remove all transports and add default Debug transport - winston.loggers.get(name).clear(); - winston.loggers.get(name).add(new DebugTransport(options)); + loggers.get(name).clear(); + + if (Object.keys(options || {}).length === 0) { + loggers.get(name).add(new DebugTransport(options, name)); + } } - return winston.loggers.get(name); + return loggers.get(name); } + +export { + createLogger, + DebugTransport +};