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

Commit

Permalink
feat(logger): improve logging methods (#3131)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnishina committed Apr 19, 2016
1 parent 5fa94db commit afdd9d7
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 41 deletions.
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ gulp.task('clang', function() {
});

gulp.task('typings', function(done) {
runSpawn(done, 'node', ['node_modules/typings/dist/bin/typings.js', 'install']);
runSpawn(done, 'node', ['node_modules/typings/dist/typings.js', 'install']);
});

gulp.task('tsc', function(done) {
Expand Down
18 changes: 11 additions & 7 deletions lib/configParser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as path from 'path';
import * as glob from 'glob';

import * as Logger from './logger';
import {Logger} from './logger2';
import {ConfigError} from './exitCodes';

let logger = new Logger('configParser');

// Coffee is required here to enable config files written in coffee-script.
try {
require('coffee-script').register();
Expand Down Expand Up @@ -39,7 +41,7 @@ export interface Config {
suite?: string;
suites?: any;
troubleshoot?: boolean;
exclude?: Array<string>| string;
exclude?: Array<string>|string;
maxSessions?: number;
}

Expand Down Expand Up @@ -75,7 +77,7 @@ export class ConfigParser {
* @return {Array} The resolved file paths.
*/
public static resolveFilePatterns(
patterns: Array<string>| string, opt_omitWarnings?: boolean,
patterns: Array<string>|string, opt_omitWarnings?: boolean,
opt_relativeTo?: string): Array<string> {
let resolvedFiles: Array<string> = [];
let cwd = opt_relativeTo || process.cwd();
Expand All @@ -86,7 +88,7 @@ export class ConfigParser {
for (let fileName of patterns) {
let matches = glob.sync(fileName, {cwd});
if (!matches.length && !opt_omitWarnings) {
Logger.warn('pattern ' + fileName + ' did not match any files.');
logger.warn('pattern ' + fileName + ' did not match any files.');
}
for (let match of matches) {
let resolvedPath = path.resolve(cwd, match);
Expand All @@ -106,9 +108,9 @@ export class ConfigParser {
let specs: Array<string> = [];
if (config.suite) {
config.suite.split(',').forEach((suite) => {
let suiteList = config.suites[suite];
let suiteList = config.suites ? config.suites[suite] : null;
if (suiteList == null) {
throw new Error('Unknown test suite: ' + suite);
throw new ConfigError(logger, 'Unknown test suite: ' + suite);
}
union(specs, makeArray(suiteList));
});
Expand Down Expand Up @@ -163,10 +165,12 @@ export class ConfigParser {
try {
fileConfig = require(filePath).config;
} catch (e) {
throw new ConfigError('failed loading configuration file ' + filename)
throw new ConfigError(
logger, 'failed loading configuration file ' + filename);
}
if (!fileConfig) {
throw new ConfigError(
logger,
'configuration file ' + filename + ' did not export a config object');
}
fileConfig.configDir = path.dirname(filePath);
Expand Down
27 changes: 17 additions & 10 deletions lib/exitCodes.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import * as Logger from './logger';
import {Logger} from './logger2';

export class ProtractorError extends Error {
msg: string;
const CONFIG_ERROR_CODE = 105;

export class ProtractorError {
error: Error;
description: string;
code: number;
constructor(msg: string, code: number) {
super(msg);
this.msg = msg;
stack: string;
constructor(logger: Logger, description: string, code: number) {
this.error = new Error();
this.description = description;
this.code = code;
Logger.error('error code: ' + this.code + ' - ' + this.msg);
logger.error('error code: ' + this.code);
logger.error('description: ' + this.description);
this.stack = this.error.stack;
}
}

const CONFIG_ERROR_CODE = 105;

/**
* Configuration file error
*/
export class ConfigError extends ProtractorError {
constructor(msg: string) { super(msg, CONFIG_ERROR_CODE); }
static CODE = CONFIG_ERROR_CODE;
constructor(logger: Logger, description: string) {
super(logger, description, ConfigError.CODE);
}
}
269 changes: 269 additions & 0 deletions lib/logger2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import * as fs from 'fs';
import * as path from 'path';
import {Config} from './configParser';

// Will use chalk if chalk is available to add color to console logging
let chalk: any;
let printRed: Function;
let printYellow: Function;
let printGray: Function;

try {
chalk = require('chalk');
printRed = chalk.red;
printYellow = chalk.yellow;
printGray = chalk.gray;
} catch (e) {
printRed = printYellow = printGray = (msg: any) => { return msg; };
}

export enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG
}

export enum WriteTo {
CONSOLE,
FILE,
BOTH,
NONE
}

let logFile = 'protractor.log'; // the default log file name

/**
* Logger class adds timestamp output, log levels, and identifiers to help
* when debugging. Also could write to console, file, both, or none.
*/
export class Logger {
static logLevel: LogLevel = LogLevel.INFO;
static showTimestamp: boolean = true;
static showId: boolean = true;
static writeTo: WriteTo = WriteTo.CONSOLE;
static fd: any;
static firstWrite: boolean = false;

/**
* Set up the logging configuration from the protractor configuration file.
* @param config The protractor configuration
*/
static set(config: Config): void {
if (config.troubleshoot) {
Logger.logLevel = LogLevel.DEBUG;
}
}

/**
* Set up the write location. If writing to a file, get the file descriptor.
* @param writeTo The enum for where to write the logs.
* @param opt_logFile An optional parameter to override the log file location.
*/
static setWrite(writeTo: WriteTo, opt_logFile?: string): void {
if (opt_logFile) {
logFile = opt_logFile;
}
Logger.writeTo = writeTo;
if (Logger.writeTo == WriteTo.FILE || Logger.writeTo == WriteTo.BOTH) {
Logger.fd = fs.openSync(path.resolve(logFile), 'a');
Logger.firstWrite = false;
}
}

/**
* Creates a logger instance with an ID for the logger.
* @constructor
*/
constructor(private id: string) {}

/**
* Log INFO
* @param ...msgs multiple arguments to be logged.
*/
info(...msgs: any[]): void { this.log_(LogLevel.INFO, msgs); }

/**
* Log DEBUG
* @param ...msgs multiple arguments to be logged.
*/
debug(...msgs: any[]): void { this.log_(LogLevel.DEBUG, msgs); }

/**
* Log WARN
* @param ...msgs multiple arguments to be logged.
*/
warn(...msgs: any[]): void { this.log_(LogLevel.WARN, msgs); }

/**
* Log ERROR
* @param ...msgs multiple arguments to be logged.
*/
error(...msgs: any[]): void { this.log_(LogLevel.ERROR, msgs); }

/**
* For the log level set, check to see if the messages should be logged.
* @param logLevel The log level of the message.
* @param msgs The messages to be logged
*/
log_(logLevel: LogLevel, msgs: any[]): void {
switch (Logger.logLevel) {
case LogLevel.ERROR:
if (logLevel <= LogLevel.ERROR) {
this.print_(logLevel, msgs);
}
break;
case LogLevel.WARN:
if (logLevel <= LogLevel.WARN) {
this.print_(logLevel, msgs);
}
break;
case LogLevel.INFO:
if (logLevel <= LogLevel.INFO) {
this.print_(logLevel, msgs);
}
break;
case LogLevel.DEBUG:
if (logLevel <= LogLevel.DEBUG) {
this.print_(logLevel, msgs);
}
break;
default:
throw new Error('Log level undefined');
}
}

/**
* Format with timestamp, log level, identifier, and message and log to
* specified medium (console, file, both, none).
* @param logLevel The log level of the message.
* @param msgs The messages to be logged.
*/
print_(logLevel: LogLevel, msgs: any[]): void {
let consoleLog: string = '';
let fileLog: string = '';

if (Logger.showTimestamp) {
consoleLog += Logger.timestamp_(WriteTo.CONSOLE);
fileLog += Logger.timestamp_(WriteTo.FILE);
}
consoleLog += Logger.level_(logLevel, this.id, WriteTo.CONSOLE);
fileLog += Logger.level_(logLevel, this.id, WriteTo.FILE);
if (Logger.showId) {
consoleLog += Logger.id_(logLevel, this.id, WriteTo.CONSOLE);
fileLog += Logger.id_(logLevel, this.id, WriteTo.FILE);
}
consoleLog += ' - ';
fileLog += ' - ';

switch (Logger.writeTo) {
case WriteTo.CONSOLE:
msgs.unshift(consoleLog);
console.log.apply(console, msgs);
break;
case WriteTo.FILE:
// for the first line written to the file, add a space
if (!Logger.firstWrite) {
fs.writeSync(Logger.fd, '\n');
Logger.firstWrite = true;
}
fileLog += ' ' + Logger.msgToFile_(msgs);
fs.writeSync(Logger.fd, fileLog + '\n');
break;
case WriteTo.BOTH:
// for the first line written to the file, add a space
if (!Logger.firstWrite) {
fs.writeSync(Logger.fd, '\n');
Logger.firstWrite = true;
}
fileLog += ' ' + Logger.msgToFile_(msgs);
fs.writeSync(Logger.fd, fileLog + '\n');
msgs.unshift(consoleLog);
console.log.apply(console, msgs);
break;
case WriteTo.NONE:
break;
}
}

/**
* Get a timestamp formatted with [hh:mm:ss]
* @param writeTo The enum for where to write the logs.
* @return The string of the formatted timestamp
*/
static timestamp_(writeTo: WriteTo): string {
let d = new Date();
let ts = '[';
let hours = d.getHours() < 10 ? '0' + d.getHours() : d.getHours();
let minutes = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes();
let seconds = d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds();
if (writeTo == WriteTo.CONSOLE) {
ts += printGray(hours + ':' + minutes + ':' + seconds) + ']';
} else {
ts += hours + ':' + minutes + ':' + seconds + ']';
}
ts += ' ';
return ts;
}

/**
* Get the identifier of the logger as '/<id>'
* @param logLevel The log level of the message.
* @param writeTo The enum for where to write the logs.
* @return The string of the formatted id
*/
static id_(logLevel: LogLevel, id: string, writeTo: WriteTo): string {
let level = LogLevel[logLevel].toString();
if (writeTo === WriteTo.FILE) {
return '/' + id;
} else if (logLevel === LogLevel.ERROR) {
return printRed('/' + id);
} else if (logLevel === LogLevel.WARN) {
return printYellow('/' + id);
} else {
return '/' + id;
}
}

/**
* Get the log level formatted with the first letter. For info, it is I.
* @param logLevel The log level of the message.
* @param writeTo The enum for where to write the logs.
* @return The string of the formatted log level
*/
static level_(logLevel: LogLevel, id: string, writeTo: WriteTo): string {
let level = LogLevel[logLevel].toString();
if (writeTo === WriteTo.FILE) {
return level[0];
} else if (logLevel === LogLevel.ERROR) {
return printRed(level[0]);
} else if (logLevel === LogLevel.WARN) {
return printYellow(level[0]);
} else {
return level[0];
}
}

/**
* Convert the list of messages to a single string message.
* @param msgs The list of messages.
* @return The string of the formatted messages
*/
static msgToFile_(msgs: any[]): string {
let log = '';
for (let pos = 0; pos < msgs.length; pos++) {
let msg = msgs[pos];
let ret: any;
if (typeof msg === 'object') {
ret = JSON.stringify(msg);
} else {
ret = msg;
}
if (pos !== msgs.length - 1) {
ret += ' ';
}
log += ret;
}
return log;
}
}
5 changes: 3 additions & 2 deletions lib/taskLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ export default class TaskLogger {
(capabilities.browserName) ? capabilities.browserName : '';
tag += (capabilities.version) ? (' ' + capabilities.version) : '';
tag += (capabilities.platform) ? (' ' + capabilities.platform) : '';
tag += (capabilities.logName && capabilities.count < 2) ? '' : ' #' +
this.task.taskId;
tag += (capabilities.logName && capabilities.count < 2) ?
'' :
' #' + this.task.taskId;
tag += '] ';

data = data.toString();
Expand Down
Loading

0 comments on commit afdd9d7

Please sign in to comment.