1 | /*
|
2 | Licensed to the Apache Software Foundation (ASF) under one
|
3 | or more contributor license agreements. See the NOTICE file
|
4 | distributed with this work for additional information
|
5 | regarding copyright ownership. The ASF licenses this file
|
6 | to you under the Apache License, Version 2.0 (the
|
7 | "License"); you may not use this file except in compliance
|
8 | with the License. You may obtain a copy of the License at
|
9 |
|
10 | http://www.apache.org/licenses/LICENSE-2.0
|
11 |
|
12 | Unless required by applicable law or agreed to in writing,
|
13 | software distributed under the License is distributed on an
|
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
15 | KIND, either express or implied. See the License for the
|
16 | specific language governing permissions and limitations
|
17 | under the License.
|
18 | */
|
19 |
|
20 | var ansi = require('ansi');
|
21 | var EventEmitter = require('events').EventEmitter;
|
22 | var CordovaError = require('./CordovaError/CordovaError');
|
23 | var EOL = require('os').EOL;
|
24 |
|
25 | var INSTANCE;
|
26 |
|
27 | /**
|
28 | * @class CordovaLogger
|
29 | *
|
30 | * Implements logging facility that anybody could use. Should not be
|
31 | * instantiated directly, `CordovaLogger.get()` method should be used instead
|
32 | * to acquire logger instance
|
33 | */
|
34 | function CordovaLogger () {
|
35 | this.levels = {};
|
36 | this.colors = {};
|
37 | this.stdout = process.stdout;
|
38 | this.stderr = process.stderr;
|
39 |
|
40 | this.stdoutCursor = ansi(this.stdout);
|
41 | this.stderrCursor = ansi(this.stderr);
|
42 |
|
43 | this.addLevel('verbose', 1000, 'grey');
|
44 | this.addLevel('normal', 2000);
|
45 | this.addLevel('warn', 2000, 'yellow');
|
46 | this.addLevel('info', 3000, 'blue');
|
47 | this.addLevel('error', 5000, 'red');
|
48 | this.addLevel('results', 10000);
|
49 |
|
50 | this.setLevel('normal');
|
51 | }
|
52 |
|
53 | /**
|
54 | * Static method to create new or acquire existing instance.
|
55 | *
|
56 | * @return {CordovaLogger} Logger instance
|
57 | */
|
58 | CordovaLogger.get = function () {
|
59 | return INSTANCE || (INSTANCE = new CordovaLogger());
|
60 | };
|
61 |
|
62 | CordovaLogger.VERBOSE = 'verbose';
|
63 | CordovaLogger.NORMAL = 'normal';
|
64 | CordovaLogger.WARN = 'warn';
|
65 | CordovaLogger.INFO = 'info';
|
66 | CordovaLogger.ERROR = 'error';
|
67 | CordovaLogger.RESULTS = 'results';
|
68 |
|
69 | /**
|
70 | * Emits log message to process' stdout/stderr depending on message's severity
|
71 | * and current log level. If severity is less than current logger's level,
|
72 | * then the message is ignored.
|
73 | *
|
74 | * @param {String} logLevel The message's log level. The logger should have
|
75 | * corresponding level added (via logger.addLevel), otherwise
|
76 | * `CordovaLogger.NORMAL` level will be used.
|
77 | * @param {String} message The message, that should be logged to process'
|
78 | * stdio
|
79 | *
|
80 | * @return {CordovaLogger} Current instance, to allow calls chaining.
|
81 | */
|
82 | CordovaLogger.prototype.log = function (logLevel, message) {
|
83 | // if there is no such logLevel defined, or provided level has
|
84 | // less severity than active level, then just ignore this call and return
|
85 | if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) {
|
86 | // return instance to allow to chain calls
|
87 | return this;
|
88 | }
|
89 |
|
90 | var isVerbose = this.logLevel === 'verbose';
|
91 | var cursor = this.stdoutCursor;
|
92 |
|
93 | if (message instanceof Error || logLevel === CordovaLogger.ERROR) {
|
94 | message = formatError(message, isVerbose);
|
95 | cursor = this.stderrCursor;
|
96 | }
|
97 |
|
98 | var color = this.colors[logLevel];
|
99 | if (color) {
|
100 | cursor.bold().fg[color]();
|
101 | }
|
102 |
|
103 | cursor.write(message).reset().write(EOL);
|
104 |
|
105 | return this;
|
106 | };
|
107 |
|
108 | /**
|
109 | * Adds a new level to logger instance. This method also creates a shortcut
|
110 | * method to log events with the level provided (i.e. after adding new level
|
111 | * 'debug', the method `debug(message)`, equal to logger.log('debug', message),
|
112 | * will be added to logger instance)
|
113 | *
|
114 | * @param {String} level A log level name. The levels with the following
|
115 | * names added by default to every instance: 'verbose', 'normal', 'warn',
|
116 | * 'info', 'error', 'results'
|
117 | * @param {Number} severity A number that represents level's severity.
|
118 | * @param {String} color A valid color name, that will be used to log
|
119 | * messages with this level. Any CSS color code or RGB value is allowed
|
120 | * (according to ansi documentation:
|
121 | * https://github.com/TooTallNate/ansi.js#features)
|
122 | *
|
123 | * @return {CordovaLogger} Current instance, to allow calls chaining.
|
124 | */
|
125 | CordovaLogger.prototype.addLevel = function (level, severity, color) {
|
126 |
|
127 | this.levels[level] = severity;
|
128 |
|
129 | if (color) {
|
130 | this.colors[level] = color;
|
131 | }
|
132 |
|
133 | // Define own method with corresponding name
|
134 | if (!this[level]) {
|
135 | this[level] = this.log.bind(this, level);
|
136 | }
|
137 |
|
138 | return this;
|
139 | };
|
140 |
|
141 | /**
|
142 | * Sets the current logger level to provided value. If logger doesn't have level
|
143 | * with this name, `CordovaLogger.NORMAL` will be used.
|
144 | *
|
145 | * @param {String} logLevel Level name. The level with this name should be
|
146 | * added to logger before.
|
147 | *
|
148 | * @return {CordovaLogger} Current instance, to allow calls chaining.
|
149 | */
|
150 | CordovaLogger.prototype.setLevel = function (logLevel) {
|
151 | this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL;
|
152 |
|
153 | return this;
|
154 | };
|
155 |
|
156 | /**
|
157 | * Adjusts the current logger level according to the passed options.
|
158 | *
|
159 | * @param {Object|Array} opts An object or args array with options
|
160 | *
|
161 | * @return {CordovaLogger} Current instance, to allow calls chaining.
|
162 | */
|
163 | CordovaLogger.prototype.adjustLevel = function (opts) {
|
164 | if (opts.verbose || (Array.isArray(opts) && opts.indexOf('--verbose') !== -1)) {
|
165 | this.setLevel('verbose');
|
166 | } else if (opts.silent || (Array.isArray(opts) && opts.indexOf('--silent') !== -1)) {
|
167 | this.setLevel('error');
|
168 | }
|
169 |
|
170 | return this;
|
171 | };
|
172 |
|
173 | /**
|
174 | * Attaches logger to EventEmitter instance provided.
|
175 | *
|
176 | * @param {EventEmitter} eventEmitter An EventEmitter instance to attach
|
177 | * logger to.
|
178 | *
|
179 | * @return {CordovaLogger} Current instance, to allow calls chaining.
|
180 | */
|
181 | CordovaLogger.prototype.subscribe = function (eventEmitter) {
|
182 |
|
183 | if (!(eventEmitter instanceof EventEmitter)) { throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); }
|
184 |
|
185 | eventEmitter.on('verbose', this.verbose)
|
186 | .on('log', this.normal)
|
187 | .on('info', this.info)
|
188 | .on('warn', this.warn)
|
189 | .on('warning', this.warn)
|
190 | // Set up event handlers for logging and results emitted as events.
|
191 | .on('results', this.results);
|
192 |
|
193 | return this;
|
194 | };
|
195 |
|
196 | function formatError (error, isVerbose) {
|
197 | var message = '';
|
198 |
|
199 | if (error instanceof CordovaError) {
|
200 | message = error.toString(isVerbose);
|
201 | } else if (error instanceof Error) {
|
202 | if (isVerbose) {
|
203 | message = error.stack;
|
204 | } else {
|
205 | message = error.message;
|
206 | }
|
207 | } else {
|
208 | // Plain text error message
|
209 | message = error;
|
210 | }
|
211 |
|
212 | if (typeof message === 'string' && message.toUpperCase().indexOf('ERROR:') !== 0) {
|
213 | // Needed for backward compatibility with external tools
|
214 | message = 'Error: ' + message;
|
215 | }
|
216 |
|
217 | return message;
|
218 | }
|
219 |
|
220 | module.exports = CordovaLogger;
|