Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.

Commit afdd9d7

Browse files
committed
feat(logger): improve logging methods (#3131)
1 parent 5fa94db commit afdd9d7

9 files changed

+485
-41
lines changed

gulpfile.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ gulp.task('clang', function() {
4848
});
4949

5050
gulp.task('typings', function(done) {
51-
runSpawn(done, 'node', ['node_modules/typings/dist/bin/typings.js', 'install']);
51+
runSpawn(done, 'node', ['node_modules/typings/dist/typings.js', 'install']);
5252
});
5353

5454
gulp.task('tsc', function(done) {

lib/configParser.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as path from 'path';
22
import * as glob from 'glob';
33

4-
import * as Logger from './logger';
4+
import {Logger} from './logger2';
55
import {ConfigError} from './exitCodes';
66

7+
let logger = new Logger('configParser');
8+
79
// Coffee is required here to enable config files written in coffee-script.
810
try {
911
require('coffee-script').register();
@@ -39,7 +41,7 @@ export interface Config {
3941
suite?: string;
4042
suites?: any;
4143
troubleshoot?: boolean;
42-
exclude?: Array<string>| string;
44+
exclude?: Array<string>|string;
4345
maxSessions?: number;
4446
}
4547

@@ -75,7 +77,7 @@ export class ConfigParser {
7577
* @return {Array} The resolved file paths.
7678
*/
7779
public static resolveFilePatterns(
78-
patterns: Array<string>| string, opt_omitWarnings?: boolean,
80+
patterns: Array<string>|string, opt_omitWarnings?: boolean,
7981
opt_relativeTo?: string): Array<string> {
8082
let resolvedFiles: Array<string> = [];
8183
let cwd = opt_relativeTo || process.cwd();
@@ -86,7 +88,7 @@ export class ConfigParser {
8688
for (let fileName of patterns) {
8789
let matches = glob.sync(fileName, {cwd});
8890
if (!matches.length && !opt_omitWarnings) {
89-
Logger.warn('pattern ' + fileName + ' did not match any files.');
91+
logger.warn('pattern ' + fileName + ' did not match any files.');
9092
}
9193
for (let match of matches) {
9294
let resolvedPath = path.resolve(cwd, match);
@@ -106,9 +108,9 @@ export class ConfigParser {
106108
let specs: Array<string> = [];
107109
if (config.suite) {
108110
config.suite.split(',').forEach((suite) => {
109-
let suiteList = config.suites[suite];
111+
let suiteList = config.suites ? config.suites[suite] : null;
110112
if (suiteList == null) {
111-
throw new Error('Unknown test suite: ' + suite);
113+
throw new ConfigError(logger, 'Unknown test suite: ' + suite);
112114
}
113115
union(specs, makeArray(suiteList));
114116
});
@@ -163,10 +165,12 @@ export class ConfigParser {
163165
try {
164166
fileConfig = require(filePath).config;
165167
} catch (e) {
166-
throw new ConfigError('failed loading configuration file ' + filename)
168+
throw new ConfigError(
169+
logger, 'failed loading configuration file ' + filename);
167170
}
168171
if (!fileConfig) {
169172
throw new ConfigError(
173+
logger,
170174
'configuration file ' + filename + ' did not export a config object');
171175
}
172176
fileConfig.configDir = path.dirname(filePath);

lib/exitCodes.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
1-
import * as Logger from './logger';
1+
import {Logger} from './logger2';
22

3-
export class ProtractorError extends Error {
4-
msg: string;
3+
const CONFIG_ERROR_CODE = 105;
4+
5+
export class ProtractorError {
6+
error: Error;
7+
description: string;
58
code: number;
6-
constructor(msg: string, code: number) {
7-
super(msg);
8-
this.msg = msg;
9+
stack: string;
10+
constructor(logger: Logger, description: string, code: number) {
11+
this.error = new Error();
12+
this.description = description;
913
this.code = code;
10-
Logger.error('error code: ' + this.code + ' - ' + this.msg);
14+
logger.error('error code: ' + this.code);
15+
logger.error('description: ' + this.description);
16+
this.stack = this.error.stack;
1117
}
1218
}
1319

14-
const CONFIG_ERROR_CODE = 105;
15-
1620
/**
1721
* Configuration file error
1822
*/
1923
export class ConfigError extends ProtractorError {
20-
constructor(msg: string) { super(msg, CONFIG_ERROR_CODE); }
24+
static CODE = CONFIG_ERROR_CODE;
25+
constructor(logger: Logger, description: string) {
26+
super(logger, description, ConfigError.CODE);
27+
}
2128
}

lib/logger2.ts

+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import {Config} from './configParser';
4+
5+
// Will use chalk if chalk is available to add color to console logging
6+
let chalk: any;
7+
let printRed: Function;
8+
let printYellow: Function;
9+
let printGray: Function;
10+
11+
try {
12+
chalk = require('chalk');
13+
printRed = chalk.red;
14+
printYellow = chalk.yellow;
15+
printGray = chalk.gray;
16+
} catch (e) {
17+
printRed = printYellow = printGray = (msg: any) => { return msg; };
18+
}
19+
20+
export enum LogLevel {
21+
ERROR,
22+
WARN,
23+
INFO,
24+
DEBUG
25+
}
26+
27+
export enum WriteTo {
28+
CONSOLE,
29+
FILE,
30+
BOTH,
31+
NONE
32+
}
33+
34+
let logFile = 'protractor.log'; // the default log file name
35+
36+
/**
37+
* Logger class adds timestamp output, log levels, and identifiers to help
38+
* when debugging. Also could write to console, file, both, or none.
39+
*/
40+
export class Logger {
41+
static logLevel: LogLevel = LogLevel.INFO;
42+
static showTimestamp: boolean = true;
43+
static showId: boolean = true;
44+
static writeTo: WriteTo = WriteTo.CONSOLE;
45+
static fd: any;
46+
static firstWrite: boolean = false;
47+
48+
/**
49+
* Set up the logging configuration from the protractor configuration file.
50+
* @param config The protractor configuration
51+
*/
52+
static set(config: Config): void {
53+
if (config.troubleshoot) {
54+
Logger.logLevel = LogLevel.DEBUG;
55+
}
56+
}
57+
58+
/**
59+
* Set up the write location. If writing to a file, get the file descriptor.
60+
* @param writeTo The enum for where to write the logs.
61+
* @param opt_logFile An optional parameter to override the log file location.
62+
*/
63+
static setWrite(writeTo: WriteTo, opt_logFile?: string): void {
64+
if (opt_logFile) {
65+
logFile = opt_logFile;
66+
}
67+
Logger.writeTo = writeTo;
68+
if (Logger.writeTo == WriteTo.FILE || Logger.writeTo == WriteTo.BOTH) {
69+
Logger.fd = fs.openSync(path.resolve(logFile), 'a');
70+
Logger.firstWrite = false;
71+
}
72+
}
73+
74+
/**
75+
* Creates a logger instance with an ID for the logger.
76+
* @constructor
77+
*/
78+
constructor(private id: string) {}
79+
80+
/**
81+
* Log INFO
82+
* @param ...msgs multiple arguments to be logged.
83+
*/
84+
info(...msgs: any[]): void { this.log_(LogLevel.INFO, msgs); }
85+
86+
/**
87+
* Log DEBUG
88+
* @param ...msgs multiple arguments to be logged.
89+
*/
90+
debug(...msgs: any[]): void { this.log_(LogLevel.DEBUG, msgs); }
91+
92+
/**
93+
* Log WARN
94+
* @param ...msgs multiple arguments to be logged.
95+
*/
96+
warn(...msgs: any[]): void { this.log_(LogLevel.WARN, msgs); }
97+
98+
/**
99+
* Log ERROR
100+
* @param ...msgs multiple arguments to be logged.
101+
*/
102+
error(...msgs: any[]): void { this.log_(LogLevel.ERROR, msgs); }
103+
104+
/**
105+
* For the log level set, check to see if the messages should be logged.
106+
* @param logLevel The log level of the message.
107+
* @param msgs The messages to be logged
108+
*/
109+
log_(logLevel: LogLevel, msgs: any[]): void {
110+
switch (Logger.logLevel) {
111+
case LogLevel.ERROR:
112+
if (logLevel <= LogLevel.ERROR) {
113+
this.print_(logLevel, msgs);
114+
}
115+
break;
116+
case LogLevel.WARN:
117+
if (logLevel <= LogLevel.WARN) {
118+
this.print_(logLevel, msgs);
119+
}
120+
break;
121+
case LogLevel.INFO:
122+
if (logLevel <= LogLevel.INFO) {
123+
this.print_(logLevel, msgs);
124+
}
125+
break;
126+
case LogLevel.DEBUG:
127+
if (logLevel <= LogLevel.DEBUG) {
128+
this.print_(logLevel, msgs);
129+
}
130+
break;
131+
default:
132+
throw new Error('Log level undefined');
133+
}
134+
}
135+
136+
/**
137+
* Format with timestamp, log level, identifier, and message and log to
138+
* specified medium (console, file, both, none).
139+
* @param logLevel The log level of the message.
140+
* @param msgs The messages to be logged.
141+
*/
142+
print_(logLevel: LogLevel, msgs: any[]): void {
143+
let consoleLog: string = '';
144+
let fileLog: string = '';
145+
146+
if (Logger.showTimestamp) {
147+
consoleLog += Logger.timestamp_(WriteTo.CONSOLE);
148+
fileLog += Logger.timestamp_(WriteTo.FILE);
149+
}
150+
consoleLog += Logger.level_(logLevel, this.id, WriteTo.CONSOLE);
151+
fileLog += Logger.level_(logLevel, this.id, WriteTo.FILE);
152+
if (Logger.showId) {
153+
consoleLog += Logger.id_(logLevel, this.id, WriteTo.CONSOLE);
154+
fileLog += Logger.id_(logLevel, this.id, WriteTo.FILE);
155+
}
156+
consoleLog += ' - ';
157+
fileLog += ' - ';
158+
159+
switch (Logger.writeTo) {
160+
case WriteTo.CONSOLE:
161+
msgs.unshift(consoleLog);
162+
console.log.apply(console, msgs);
163+
break;
164+
case WriteTo.FILE:
165+
// for the first line written to the file, add a space
166+
if (!Logger.firstWrite) {
167+
fs.writeSync(Logger.fd, '\n');
168+
Logger.firstWrite = true;
169+
}
170+
fileLog += ' ' + Logger.msgToFile_(msgs);
171+
fs.writeSync(Logger.fd, fileLog + '\n');
172+
break;
173+
case WriteTo.BOTH:
174+
// for the first line written to the file, add a space
175+
if (!Logger.firstWrite) {
176+
fs.writeSync(Logger.fd, '\n');
177+
Logger.firstWrite = true;
178+
}
179+
fileLog += ' ' + Logger.msgToFile_(msgs);
180+
fs.writeSync(Logger.fd, fileLog + '\n');
181+
msgs.unshift(consoleLog);
182+
console.log.apply(console, msgs);
183+
break;
184+
case WriteTo.NONE:
185+
break;
186+
}
187+
}
188+
189+
/**
190+
* Get a timestamp formatted with [hh:mm:ss]
191+
* @param writeTo The enum for where to write the logs.
192+
* @return The string of the formatted timestamp
193+
*/
194+
static timestamp_(writeTo: WriteTo): string {
195+
let d = new Date();
196+
let ts = '[';
197+
let hours = d.getHours() < 10 ? '0' + d.getHours() : d.getHours();
198+
let minutes = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes();
199+
let seconds = d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds();
200+
if (writeTo == WriteTo.CONSOLE) {
201+
ts += printGray(hours + ':' + minutes + ':' + seconds) + ']';
202+
} else {
203+
ts += hours + ':' + minutes + ':' + seconds + ']';
204+
}
205+
ts += ' ';
206+
return ts;
207+
}
208+
209+
/**
210+
* Get the identifier of the logger as '/<id>'
211+
* @param logLevel The log level of the message.
212+
* @param writeTo The enum for where to write the logs.
213+
* @return The string of the formatted id
214+
*/
215+
static id_(logLevel: LogLevel, id: string, writeTo: WriteTo): string {
216+
let level = LogLevel[logLevel].toString();
217+
if (writeTo === WriteTo.FILE) {
218+
return '/' + id;
219+
} else if (logLevel === LogLevel.ERROR) {
220+
return printRed('/' + id);
221+
} else if (logLevel === LogLevel.WARN) {
222+
return printYellow('/' + id);
223+
} else {
224+
return '/' + id;
225+
}
226+
}
227+
228+
/**
229+
* Get the log level formatted with the first letter. For info, it is I.
230+
* @param logLevel The log level of the message.
231+
* @param writeTo The enum for where to write the logs.
232+
* @return The string of the formatted log level
233+
*/
234+
static level_(logLevel: LogLevel, id: string, writeTo: WriteTo): string {
235+
let level = LogLevel[logLevel].toString();
236+
if (writeTo === WriteTo.FILE) {
237+
return level[0];
238+
} else if (logLevel === LogLevel.ERROR) {
239+
return printRed(level[0]);
240+
} else if (logLevel === LogLevel.WARN) {
241+
return printYellow(level[0]);
242+
} else {
243+
return level[0];
244+
}
245+
}
246+
247+
/**
248+
* Convert the list of messages to a single string message.
249+
* @param msgs The list of messages.
250+
* @return The string of the formatted messages
251+
*/
252+
static msgToFile_(msgs: any[]): string {
253+
let log = '';
254+
for (let pos = 0; pos < msgs.length; pos++) {
255+
let msg = msgs[pos];
256+
let ret: any;
257+
if (typeof msg === 'object') {
258+
ret = JSON.stringify(msg);
259+
} else {
260+
ret = msg;
261+
}
262+
if (pos !== msgs.length - 1) {
263+
ret += ' ';
264+
}
265+
log += ret;
266+
}
267+
return log;
268+
}
269+
}

lib/taskLogger.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ export default class TaskLogger {
5656
(capabilities.browserName) ? capabilities.browserName : '';
5757
tag += (capabilities.version) ? (' ' + capabilities.version) : '';
5858
tag += (capabilities.platform) ? (' ' + capabilities.platform) : '';
59-
tag += (capabilities.logName && capabilities.count < 2) ? '' : ' #' +
60-
this.task.taskId;
59+
tag += (capabilities.logName && capabilities.count < 2) ?
60+
'' :
61+
' #' + this.task.taskId;
6162
tag += '] ';
6263

6364
data = data.toString();

0 commit comments

Comments
 (0)