Repository: cordova-lib Updated Branches: refs/heads/master b1bdd5569 -> 1fd2dd857
CB-10176 Adds CordovaLogger class, based on logger module from cordova-cli Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/1fd2dd85 Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/1fd2dd85 Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/1fd2dd85 Branch: refs/heads/master Commit: 1fd2dd857dca026483d869660e79784f392921d0 Parents: b1bdd55 Author: Vladimir Kotikov <[email protected]> Authored: Wed Feb 3 17:47:24 2016 +0300 Committer: Vladimir Kotikov <[email protected]> Committed: Wed Feb 3 17:48:06 2016 +0300 ---------------------------------------------------------------------- cordova-common/cordova-common.js | 1 + cordova-common/package.json | 87 +++++------ cordova-common/spec/CordovaLogger.spec.js | 164 ++++++++++++++++++++ cordova-common/src/CordovaLogger.js | 203 +++++++++++++++++++++++++ 4 files changed, 412 insertions(+), 43 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1fd2dd85/cordova-common/cordova-common.js ---------------------------------------------------------------------- diff --git a/cordova-common/cordova-common.js b/cordova-common/cordova-common.js index 59b52fc..22e90a7 100644 --- a/cordova-common/cordova-common.js +++ b/cordova-common/cordova-common.js @@ -26,6 +26,7 @@ exports = module.exports = { ActionStack: require('./src/ActionStack'), CordovaError: require('./src/CordovaError/CordovaError'), + CordovaLogger: require('./src/CordovaLogger'), CordovaExternalToolErrorContext: require('./src/CordovaError/CordovaExternalToolErrorContext'), PlatformJson: require('./src/PlatformJson'), ConfigParser: require('./src/ConfigParser/ConfigParser.js'), http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1fd2dd85/cordova-common/package.json ---------------------------------------------------------------------- diff --git a/cordova-common/package.json b/cordova-common/package.json index 40ed07c..a9d9fc0 100644 --- a/cordova-common/package.json +++ b/cordova-common/package.json @@ -1,45 +1,46 @@ { - "author": "Apache Software Foundation", - "name": "cordova-common", - "description": "Apache Cordova tools and platforms shared routines", - "license": "Apache-2.0", - "version": "1.1.0-dev", - "repository": { - "type": "git", - "url": "git://git-wip-us.apache.org/repos/asf/cordova-common.git" - }, - "bugs": { - "url": "https://issues.apache.org/jira/browse/CB", - "email": "[email protected]" - }, - "main": "cordova-common.js", - "engines": { - "node": ">=0.9.9" - }, - "scripts": { - "test": "npm run jshint && npm run jasmine", - "jshint": "node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint spec", - "jasmine": "node node_modules/jasmine-node/bin/jasmine-node --captureExceptions --color spec", - "cover": "node node_modules/istanbul/lib/cli.js cover --root src --print detail node_modules/jasmine-node/bin/jasmine-node -- spec" - }, - "engineStrict": true, - "dependencies": { - "bplist-parser": "^0.1.0", - "cordova-registry-mapper": "^1.1.8", - "elementtree": "^0.1.6", - "glob": "^5.0.13", - "osenv": "^0.1.3", - "plist": "^1.2.0", - "q": "^1.4.1", - "semver": "^5.0.1", - "shelljs": "^0.5.1", - "underscore": "^1.8.3", - "unorm": "^1.3.3" - }, - "devDependencies": { - "istanbul": "^0.3.17", - "jasmine-node": "^1.14.5", - "jshint": "^2.8.0" - }, - "contributors": [] + "author": "Apache Software Foundation", + "name": "cordova-common", + "description": "Apache Cordova tools and platforms shared routines", + "license": "Apache-2.0", + "version": "1.1.0-dev", + "repository": { + "type": "git", + "url": "git://git-wip-us.apache.org/repos/asf/cordova-common.git" + }, + "bugs": { + "url": "https://issues.apache.org/jira/browse/CB", + "email": "[email protected]" + }, + "main": "cordova-common.js", + "engines": { + "node": ">=0.9.9" + }, + "scripts": { + "test": "npm run jshint && npm run jasmine", + "jshint": "node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint spec", + "jasmine": "node node_modules/jasmine-node/bin/jasmine-node --captureExceptions --color spec", + "cover": "node node_modules/istanbul/lib/cli.js cover --root src --print detail node_modules/jasmine-node/bin/jasmine-node -- spec" + }, + "engineStrict": true, + "dependencies": { + "ansi": "^0.3.1", + "bplist-parser": "^0.1.0", + "cordova-registry-mapper": "^1.1.8", + "elementtree": "^0.1.6", + "glob": "^5.0.13", + "osenv": "^0.1.3", + "plist": "^1.2.0", + "q": "^1.4.1", + "semver": "^5.0.1", + "shelljs": "^0.5.1", + "underscore": "^1.8.3", + "unorm": "^1.3.3" + }, + "devDependencies": { + "istanbul": "^0.3.17", + "jasmine-node": "^1.14.5", + "jshint": "^2.8.0" + }, + "contributors": [] } http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1fd2dd85/cordova-common/spec/CordovaLogger.spec.js ---------------------------------------------------------------------- diff --git a/cordova-common/spec/CordovaLogger.spec.js b/cordova-common/spec/CordovaLogger.spec.js new file mode 100644 index 0000000..fd96488 --- /dev/null +++ b/cordova-common/spec/CordovaLogger.spec.js @@ -0,0 +1,164 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +var CordovaError = require('../src/CordovaError/CordovaError'); +var CordovaLogger = require('../src/CordovaLogger'); +var EventEmitter = require('events').EventEmitter; + +var DEFAULT_LEVELS = ['verbose', 'normal', 'warn', 'info', 'error', 'results']; + +describe('CordovaLogger class', function() { + it('should be constructable', function () { + expect(new CordovaLogger()).toEqual(jasmine.any(CordovaLogger)); + }); + + it('should expose default levels as constants', function () { + DEFAULT_LEVELS.forEach(function (level) { + var constant = level.toUpperCase(); + expect(CordovaLogger[constant]).toBeDefined(); + expect(CordovaLogger[constant]).toBe(level); + }); + }); + + it('should return the same instance via "get" method', function () { + expect(CordovaLogger.get()).toBeDefined(); + expect(CordovaLogger.get()).toBe(CordovaLogger.get()); + expect(CordovaLogger.get()).toEqual(jasmine.any(CordovaLogger)); + }); + + describe('instance', function () { + + var logger; + + beforeEach(function () { + logger = new CordovaLogger(); + }); + + it('should have defaults levels', function () { + DEFAULT_LEVELS.forEach(function (level) { + expect(logger.levels[level]).toBeDefined(); + expect(logger.levels[level]).toEqual(jasmine.any(Number)); + expect(logger[level]).toBeDefined(); + expect(logger[level]).toEqual(jasmine.any(Function)); + expect(logger[level].length).toBe(1); + }); + }); + + describe('addLevel method', function () { + it('should add a new level and a corresponding shortcut method', function () { + spyOn(logger, 'log'); + logger.addLevel('debug', 100000, 'grey'); + expect(logger.levels.debug).toBe(100000); + expect(logger.debug).toEqual(jasmine.any(Function)); + + logger.debug('debug message'); + expect(logger.log).toHaveBeenCalledWith('debug', 'debug message'); + }); + + it('should not add a shortcut method fi the property with the same name already exists', function () { + var logMethod = logger.log; + logger.addLevel('log', 500); + expect(logger.log).toBe(logMethod); // "log" method remains unchanged + }); + }); + + describe('setLevel method', function () { + it('should set logger\'s level to \'NORMAL\' if provided level does not exist', function () { + logger.setLevel('debug'); + expect(logger.logLevel).toBe(CordovaLogger.NORMAL); // default value + }); + }); + + describe('subscribe method', function () { + it('should throw if called without EventEmitter instance', function () { + expect(function () { logger.subscribe(); }).toThrow(); + expect(function () { logger.subscribe(123); }).toThrow(); + }); + + it('should attach corresponding listeners to supplied emitter', function () { + + var eventNamesExclusions = { + log: 'normal', + warning: 'warn' + }; + + var listenerSpy = jasmine.createSpy('listenerSpy') + .andCallFake(function (eventName) { + eventName = eventNamesExclusions[eventName] || eventName; + expect(logger.levels[eventName]).toBeDefined(); + }); + + var emitter = new EventEmitter().on('newListener', listenerSpy); + logger.subscribe(emitter); + }); + }); + + describe('log method', function () { + + function CursorSpy (name) { + var cursorMethods = ['reset', 'write']; + var spy = jasmine.createSpyObj(name, cursorMethods); + + // Make spy methods chainable, as original Cursor acts + cursorMethods.forEach(function (method) { spy[method].andReturn(spy); }); + + return spy; + } + + beforeEach(function () { + // Empty colors table to make it easier to mock + logger.colors = {}; + logger.stdoutCursor = new CursorSpy('stdoutCursor'); + logger.stderrCursor = new CursorSpy('stderrCursor'); + }); + + it('should ignore message if severity is less than logger\'s level', function () { + logger.setLevel('error').log('verbose', 'some_messgge'); + expect(logger.stdoutCursor.write).not.toHaveBeenCalled(); + expect(logger.stderrCursor.write).not.toHaveBeenCalled(); + }); + + it('should log everything except error messages to stdout', function () { + logger.setLevel('verbose'); + DEFAULT_LEVELS.forEach(function (level) { + logger.log(level, 'message'); + }); + + // Multiply calls number to 2 because 'write' method is get called twice (with message and EOL) + expect(logger.stdoutCursor.write.calls.length).toBe((DEFAULT_LEVELS.length - 1) * 2); + expect(logger.stderrCursor.write.calls.length).toBe(1 * 2); + }); + + it('should log Error objects to stderr despite of loglevel', function () { + logger.setLevel('verbose').log('verbose', new Error()); + expect(logger.stdoutCursor.write).not.toHaveBeenCalled(); + expect(logger.stderrCursor.write).toHaveBeenCalled(); + }); + + it('should handle CordovaError instances separately from Error ones', function () { + var errorMock = new CordovaError(); + spyOn(errorMock, 'toString').andReturn('error_message'); + + logger.setLevel('verbose').log('verbose', errorMock); + expect(errorMock.toString).toHaveBeenCalled(); + expect(logger.stderrCursor.write.calls[0].args[0]).toBe('Error: error_message'); + }); + }); + }); +}); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/1fd2dd85/cordova-common/src/CordovaLogger.js ---------------------------------------------------------------------- diff --git a/cordova-common/src/CordovaLogger.js b/cordova-common/src/CordovaLogger.js new file mode 100644 index 0000000..38bfe69 --- /dev/null +++ b/cordova-common/src/CordovaLogger.js @@ -0,0 +1,203 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ + +var ansi = require('ansi'); +var EventEmitter = require('events').EventEmitter; +var CordovaError = require('./CordovaError/CordovaError'); +var EOL = require('os').EOL; + +var INSTANCE; + +/** + * @class CordovaLogger + * + * Implements logging facility that anybody could use. Should not be + * instantiated directly, `CordovaLogger.get()` method should be used instead + * to acquire logger instance + */ +function CordovaLogger () { + this.levels = {}; + this.colors = {}; + this.stdout = process.stdout; + this.stderr = process.stderr; + + this.stdoutCursor = ansi(this.stdout); + this.stderrCursor = ansi(this.stderr); + + this.addLevel('verbose', 1000, 'grey'); + this.addLevel('normal' , 2000); + this.addLevel('warn' , 2000, 'yellow'); + this.addLevel('info' , 3000, 'blue'); + this.addLevel('error' , 5000, 'red'); + this.addLevel('results' , 10000); + + this.setLevel('normal'); +} + +/** + * Static method to create new or acquire existing instance. + * + * @return {CordovaLogger} Logger instance + */ +CordovaLogger.get = function () { + return INSTANCE || (INSTANCE = new CordovaLogger()); +}; + +CordovaLogger.VERBOSE = 'verbose'; +CordovaLogger.NORMAL = 'normal'; +CordovaLogger.WARN = 'warn'; +CordovaLogger.INFO = 'info'; +CordovaLogger.ERROR = 'error'; +CordovaLogger.RESULTS = 'results'; + +/** + * Emits log message to process' stdout/stderr depending on message's severity + * and current log level. If severity is less than current logger's level, + * then the message is ignored. + * + * @param {String} logLevel The message's log level. The logger should have + * corresponding level added (via logger.addLevel), otherwise + * `CordovaLogger.NORMAL` level will be used. + * @param {String} message The message, that should be logged to process' + * stdio + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.log = function (logLevel, message) { + // if there is no such logLevel defined, or provided level has + // less severity than active level, then just ignore this call and return + if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) + // return instance to allow to chain calls + return this; + + var isVerbose = this.logLevel === 'verbose'; + var cursor = this.stdoutCursor; + + if(message instanceof Error || logLevel === CordovaLogger.ERROR) { + message = formatError(message, isVerbose); + cursor = this.stderrCursor; + } + + var color = this.colors[logLevel]; + if (color) { + cursor.bold().fg[color](); + } + + cursor.write(message).reset().write(EOL); + + return this; +}; + +/** + * Adds a new level to logger instance. This method also creates a shortcut + * method to log events with the level provided (i.e. after adding new level + * 'debug', the method `debug(message)`, equal to logger.log('debug', message), + * will be added to logger instance) + * + * @param {String} level A log level name. The levels with the following + * names added by default to every instance: 'verbose', 'normal', 'warn', + * 'info', 'error', 'results' + * @param {Number} severity A number that represents level's severity. + * @param {String} color A valid color name, that will be used to log + * messages with this level. Any CSS color code or RGB value is allowed + * (according to ansi documentation: + * https://github.com/TooTallNate/ansi.js#features) + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.addLevel = function (level, severity, color) { + + this.levels[level] = severity; + + if (color) { + this.colors[level] = color; + } + + // Define own method with corresponding name + if (!this[level]) { + this[level] = this.log.bind(this, level); + } + + return this; +}; + +/** + * Sets the current logger level to provided value. If logger doesn't have level + * with this name, `CordovaLogger.NORMAL` will be used. + * + * @param {String} logLevel Level name. The level with this name should be + * added to logger before. + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.setLevel = function (logLevel) { + this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL; + + return this; +}; + +/** + * Attaches logger to EventEmitter instance provided. + * + * @param {EventEmitter} eventEmitter An EventEmitter instance to attach + * logger to. + * + * @return {CordovaLogger} Current instance, to allow calls chaining. + */ +CordovaLogger.prototype.subscribe = function (eventEmitter) { + + if (!(eventEmitter instanceof EventEmitter)) + throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); + + eventEmitter.on('verbose', this.verbose) + .on('log', this.normal) + .on('info', this.info) + .on('warn', this.warn) + .on('warning', this.warn) + // Set up event handlers for logging and results emitted as events. + .on('results', this.results); + + return this; +}; + +function formatError(error, isVerbose) { + var message = ''; + + if(error instanceof CordovaError) { + message = error.toString(isVerbose); + } else if(error instanceof Error) { + if(isVerbose) { + message = error.stack; + } else { + message = error.message; + } + } else { + // Plain text error message + message = error; + } + + if(message.toUpperCase().indexOf('ERROR:') !== 0) { + // Needed for backward compatibility with external tools + message = 'Error: ' + message; + } + + return message; +} + +module.exports = CordovaLogger; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
