diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-11-03 01:33:53 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-11-03 01:33:53 +0100 |
commit | d1291f67551c58168af43698a359cb5ddfd266b0 (patch) | |
tree | 55a13ed29fe1915e3f42f1b1b7038dafa2e975a7 /node_modules/selenium-webdriver/lib/logging.js | |
parent | d0a0695fb5d34996850723f7d4b1b59c3df909c2 (diff) |
node_modules
Diffstat (limited to 'node_modules/selenium-webdriver/lib/logging.js')
-rw-r--r-- | node_modules/selenium-webdriver/lib/logging.js | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/node_modules/selenium-webdriver/lib/logging.js b/node_modules/selenium-webdriver/lib/logging.js new file mode 100644 index 000000000..97f54924c --- /dev/null +++ b/node_modules/selenium-webdriver/lib/logging.js @@ -0,0 +1,670 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC 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. + +'use strict'; + +/** + * @fileoverview Defines WebDriver's logging system. The logging system is + * broken into major components: local and remote logging. + * + * The local logging API, which is anchored by the {@linkplain Logger} class is + * similar to Java's logging API. Loggers, retrieved by + * {@linkplain #getLogger getLogger(name)}, use hierarchical, dot-delimited + * namespaces (e.g. "" > "webdriver" > "webdriver.logging"). Recorded log + * messages are represented by the {@linkplain Entry} class. You can capture log + * records by {@linkplain Logger#addHandler attaching} a handler function to the + * desired logger. For convenience, you can quickly enable logging to the + * console by simply calling {@linkplain #installConsoleHandler + * installConsoleHandler}. + * + * The [remote logging API](https://github.com/SeleniumHQ/selenium/wiki/Logging) + * allows you to retrieve logs from a remote WebDriver server. This API uses the + * {@link Preferences} class to define desired log levels prior to creating + * a WebDriver session: + * + * var prefs = new logging.Preferences(); + * prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); + * + * var caps = Capabilities.chrome(); + * caps.setLoggingPrefs(prefs); + * // ... + * + * Remote log entries, also represented by the {@link Entry} class, may be + * retrieved via {@link webdriver.WebDriver.Logs}: + * + * driver.manage().logs().get(logging.Type.BROWSER) + * .then(function(entries) { + * entries.forEach(function(entry) { + * console.log('[%s] %s', entry.level.name, entry.message); + * }); + * }); + * + * **NOTE:** Only a few browsers support the remote logging API (notably + * Firefox and Chrome). Firefox supports basic logging functionality, while + * Chrome exposes robust + * [performance logging](https://sites.google.com/a/chromium.org/chromedriver/logging) + * options. Remote logging is still considered a non-standard feature, and the + * APIs exposed by this module for it are non-frozen. This module will be + * updated, possibly breaking backwards-compatibility, once logging is + * officially defined by the + * [W3C WebDriver spec](http://www.w3.org/TR/webdriver/). + */ + +/** + * Defines a message level that may be used to control logging output. + * + * @final + */ +class Level { + /** + * @param {string} name the level's name. + * @param {number} level the level's numeric value. + */ + constructor(name, level) { + if (level < 0) { + throw new TypeError('Level must be >= 0'); + } + + /** @private {string} */ + this.name_ = name; + + /** @private {number} */ + this.value_ = level; + } + + /** This logger's name. */ + get name() { + return this.name_; + } + + /** The numeric log level. */ + get value() { + return this.value_; + } + + /** @override */ + toString() { + return this.name; + } +} + +/** + * Indicates no log messages should be recorded. + * @const + */ +Level.OFF = new Level('OFF', Infinity); + + +/** + * Log messages with a level of `1000` or higher. + * @const + */ +Level.SEVERE = new Level('SEVERE', 1000); + + +/** + * Log messages with a level of `900` or higher. + * @const + */ +Level.WARNING = new Level('WARNING', 900); + + +/** + * Log messages with a level of `800` or higher. + * @const + */ +Level.INFO = new Level('INFO', 800); + + +/** + * Log messages with a level of `700` or higher. + * @const + */ +Level.DEBUG = new Level('DEBUG', 700); + + +/** + * Log messages with a level of `500` or higher. + * @const + */ +Level.FINE = new Level('FINE', 500); + + +/** + * Log messages with a level of `400` or higher. + * @const + */ +Level.FINER = new Level('FINER', 400); + + +/** + * Log messages with a level of `300` or higher. + * @const + */ +Level.FINEST = new Level('FINEST', 300); + + +/** + * Indicates all log messages should be recorded. + * @const + */ +Level.ALL = new Level('ALL', 0); + + +const ALL_LEVELS = /** !Set<Level> */new Set([ + Level.OFF, + Level.SEVERE, + Level.WARNING, + Level.INFO, + Level.DEBUG, + Level.FINE, + Level.FINER, + Level.FINEST, + Level.ALL +]); + + +const LEVELS_BY_NAME = /** !Map<string, !Level> */ new Map([ + [Level.OFF.name, Level.OFF], + [Level.SEVERE.name, Level.SEVERE], + [Level.WARNING.name, Level.WARNING], + [Level.INFO.name, Level.INFO], + [Level.DEBUG.name, Level.DEBUG], + [Level.FINE.name, Level.FINE], + [Level.FINER.name, Level.FINER], + [Level.FINEST.name, Level.FINEST], + [Level.ALL.name, Level.ALL] +]); + + +/** + * Converts a level name or value to a {@link Level} value. If the name/value + * is not recognized, {@link Level.ALL} will be returned. + * + * @param {(number|string)} nameOrValue The log level name, or value, to + * convert. + * @return {!Level} The converted level. + */ +function getLevel(nameOrValue) { + if (typeof nameOrValue === 'string') { + return LEVELS_BY_NAME.get(nameOrValue) || Level.ALL; + } + if (typeof nameOrValue !== 'number') { + throw new TypeError('not a string or number'); + } + for (let level of ALL_LEVELS) { + if (nameOrValue >= level.value) { + return level; + } + } + return Level.ALL; +} + + +/** + * Describes a single log entry. + * + * @final + */ +class Entry { + /** + * @param {(!Level|string|number)} level The entry level. + * @param {string} message The log message. + * @param {number=} opt_timestamp The time this entry was generated, in + * milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the + * current time will be used. + * @param {string=} opt_type The log type, if known. + */ + constructor(level, message, opt_timestamp, opt_type) { + this.level = level instanceof Level ? level : getLevel(level); + this.message = message; + this.timestamp = + typeof opt_timestamp === 'number' ? opt_timestamp : Date.now(); + this.type = opt_type || ''; + } + + /** + * @return {{level: string, message: string, timestamp: number, + * type: string}} The JSON representation of this entry. + */ + toJSON() { + return { + 'level': this.level.name, + 'message': this.message, + 'timestamp': this.timestamp, + 'type': this.type + }; + } +} + + +/** @typedef {(string|function(): string)} */ +let Loggable; + + +/** + * An object used to log debugging messages. Loggers use a hierarchical, + * dot-separated naming scheme. For instance, "foo" is considered the parent of + * the "foo.bar" and an ancestor of "foo.bar.baz". + * + * Each logger may be assigned a {@linkplain #setLevel log level}, which + * controls which level of messages will be reported to the + * {@linkplain #addHandler handlers} attached to this instance. If a log level + * is not explicitly set on a logger, it will inherit its parent. + * + * This class should never be directly instantiated. Instead, users should + * obtain logger references using the {@linkplain ./logging.getLogger() + * getLogger()} function. + * + * @final + */ +class Logger { + /** + * @param {string} name the name of this logger. + * @param {Level=} opt_level the initial level for this logger. + */ + constructor(name, opt_level) { + /** @private {string} */ + this.name_ = name; + + /** @private {Level} */ + this.level_ = opt_level || null; + + /** @private {Logger} */ + this.parent_ = null; + + /** @private {Set<function(!Entry)>} */ + this.handlers_ = null; + } + + /** @return {string} the name of this logger. */ + getName() { + return this.name_; + } + + /** + * @param {Level} level the new level for this logger, or `null` if the logger + * should inherit its level from its parent logger. + */ + setLevel(level) { + this.level_ = level; + } + + /** @return {Level} the log level for this logger. */ + getLevel() { + return this.level_; + } + + /** + * @return {!Level} the effective level for this logger. + */ + getEffectiveLevel() { + let logger = this; + let level; + do { + level = logger.level_; + logger = logger.parent_; + } while (logger && !level); + return level || Level.OFF; + } + + /** + * @param {!Level} level the level to check. + * @return {boolean} whether messages recorded at the given level are loggable + * by this instance. + */ + isLoggable(level) { + return level.value !== Level.OFF.value + && level.value >= this.getEffectiveLevel().value; + } + + /** + * Adds a handler to this logger. The handler will be invoked for each message + * logged with this instance, or any of its descendants. + * + * @param {function(!Entry)} handler the handler to add. + */ + addHandler(handler) { + if (!this.handlers_) { + this.handlers_ = new Set; + } + this.handlers_.add(handler); + } + + /** + * Removes a handler from this logger. + * + * @param {function(!Entry)} handler the handler to remove. + * @return {boolean} whether a handler was successfully removed. + */ + removeHandler(handler) { + if (!this.handlers_) { + return false; + } + return this.handlers_.delete(handler); + } + + /** + * Logs a message at the given level. The message may be defined as a string + * or as a function that will return the message. If a function is provided, + * it will only be invoked if this logger's + * {@linkplain #getEffectiveLevel() effective log level} includes the given + * `level`. + * + * @param {!Level} level the level at which to log the message. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + log(level, loggable) { + if (!this.isLoggable(level)) { + return; + } + let message = '[' + this.name_ + '] ' + + (typeof loggable === 'function' ? loggable() : loggable); + let entry = new Entry(level, message, Date.now()); + for (let logger = this; !!logger; logger = logger.parent_) { + if (logger.handlers_) { + for (let handler of logger.handlers_) { + handler(entry); + } + } + } + } + + /** + * Logs a message at the {@link Level.SEVERE} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + severe(loggable) { + this.log(Level.SEVERE, loggable); + } + + /** + * Logs a message at the {@link Level.WARNING} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + warning(loggable) { + this.log(Level.WARNING, loggable); + } + + /** + * Logs a message at the {@link Level.INFO} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + info(loggable) { + this.log(Level.INFO, loggable); + } + + /** + * Logs a message at the {@link Level.DEBUG} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + debug(loggable) { + this.log(Level.DEBUG, loggable); + } + + /** + * Logs a message at the {@link Level.FINE} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + fine(loggable) { + this.log(Level.FINE, loggable); + } + + /** + * Logs a message at the {@link Level.FINER} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + finer(loggable) { + this.log(Level.FINER, loggable); + } + + /** + * Logs a message at the {@link Level.FINEST} log level. + * @param {(string|function(): string)} loggable the message to log, or a + * function that will return the message. + */ + finest(loggable) { + this.log(Level.FINEST, loggable); + } +} + + +/** + * Maintains a collection of loggers. + * + * @final + */ +class LogManager { + constructor() { + /** @private {!Map<string, !Logger>} */ + this.loggers_ = new Map; + this.root_ = new Logger('', Level.OFF); + } + + /** + * Retrieves a named logger, creating it in the process. This function will + * implicitly create the requested logger, and any of its parents, if they + * do not yet exist. + * + * @param {string} name the logger's name. + * @return {!Logger} the requested logger. + */ + getLogger(name) { + if (!name) { + return this.root_; + } + let parent = this.root_; + for (let i = name.indexOf('.'); i != -1; i = name.indexOf('.', i + 1)) { + let parentName = name.substr(0, i); + parent = this.createLogger_(parentName, parent); + } + return this.createLogger_(name, parent); + } + + /** + * Creates a new logger. + * + * @param {string} name the logger's name. + * @param {!Logger} parent the logger's parent. + * @return {!Logger} the new logger. + * @private + */ + createLogger_(name, parent) { + if (this.loggers_.has(name)) { + return /** @type {!Logger} */(this.loggers_.get(name)); + } + let logger = new Logger(name, null); + logger.parent_ = parent; + this.loggers_.set(name, logger); + return logger; + } +} + + +const logManager = new LogManager; + + +/** + * Retrieves a named logger, creating it in the process. This function will + * implicitly create the requested logger, and any of its parents, if they + * do not yet exist. + * + * The log level will be unspecified for newly created loggers. Use + * {@link Logger#setLevel(level)} to explicitly set a level. + * + * @param {string} name the logger's name. + * @return {!Logger} the requested logger. + */ +function getLogger(name) { + return logManager.getLogger(name); +} + + +function pad(n) { + if (n > 10) { + return '' + n; + } else { + return '0' + n; + } +} + + +/** + * Logs all messages to the Console API. + * @param {!Entry} entry the entry to log. + */ +function consoleHandler(entry) { + if (typeof console === 'undefined' || !console) { + return; + } + + var timestamp = new Date(entry.timestamp); + var msg = + '[' + timestamp.getUTCFullYear() + '-' + + pad(timestamp.getUTCMonth() + 1) + '-' + + pad(timestamp.getUTCDate()) + 'T' + + pad(timestamp.getUTCHours()) + ':' + + pad(timestamp.getUTCMinutes()) + ':' + + pad(timestamp.getUTCSeconds()) + 'Z] ' + + '[' + entry.level.name + '] ' + + entry.message; + + var level = entry.level.value; + if (level >= Level.SEVERE.value) { + console.error(msg); + } else if (level >= Level.WARNING.value) { + console.warn(msg); + } else { + console.log(msg); + } +} + + +/** + * Adds the console handler to the given logger. The console handler will log + * all messages using the JavaScript Console API. + * + * @param {Logger=} opt_logger The logger to add the handler to; defaults + * to the root logger. + */ +function addConsoleHandler(opt_logger) { + let logger = opt_logger || logManager.root_; + logger.addHandler(consoleHandler); +} + + +/** + * Removes the console log handler from the given logger. + * + * @param {Logger=} opt_logger The logger to remove the handler from; defaults + * to the root logger. + * @see exports.addConsoleHandler + */ +function removeConsoleHandler(opt_logger) { + let logger = opt_logger || logManager.root_; + logger.removeHandler(consoleHandler); +} + + +/** + * Installs the console log handler on the root logger. + */ +function installConsoleHandler() { + addConsoleHandler(logManager.root_); +} + + +/** + * Common log types. + * @enum {string} + */ +const Type = { + /** Logs originating from the browser. */ + BROWSER: 'browser', + /** Logs from a WebDriver client. */ + CLIENT: 'client', + /** Logs from a WebDriver implementation. */ + DRIVER: 'driver', + /** Logs related to performance. */ + PERFORMANCE: 'performance', + /** Logs from the remote server. */ + SERVER: 'server' +}; + + +/** + * Describes the log preferences for a WebDriver session. + * + * @final + */ +class Preferences { + constructor() { + /** @private {!Map<string, !Level>} */ + this.prefs_ = new Map; + } + + /** + * Sets the desired logging level for a particular log type. + * @param {(string|Type)} type The log type. + * @param {(!Level|string|number)} level The desired log level. + * @throws {TypeError} if `type` is not a `string`. + */ + setLevel(type, level) { + if (typeof type !== 'string') { + throw TypeError('specified log type is not a string: ' + typeof type); + } + this.prefs_.set(type, level instanceof Level ? level : getLevel(level)); + } + + /** + * Converts this instance to its JSON representation. + * @return {!Object<string, string>} The JSON representation of this set of + * preferences. + */ + toJSON() { + let json = {}; + for (let key of this.prefs_.keys()) { + json[key] = this.prefs_.get(key).name; + } + return json; + } +} + + +// PUBLIC API + + +module.exports = { + Entry: Entry, + Level: Level, + LogManager: LogManager, + Logger: Logger, + Preferences: Preferences, + Type: Type, + addConsoleHandler: addConsoleHandler, + getLevel: getLevel, + getLogger: getLogger, + installConsoleHandler: installConsoleHandler, + removeConsoleHandler: removeConsoleHandler +}; |