// 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. /** * @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 * {@link webdriver.logging.Logger Logger} class, is similar to Java's * logging API. Loggers, retrieved by {@link webdriver.logging.getLogger}, use * hierarchical, dot-delimited namespaces * (e.g. "" > "webdriver" > "webdriver.logging"). Recorded log messages are * represented by the {@link webdriver.logging.LogRecord LogRecord} class. You * can capture log records by * {@linkplain webdriver.logging.Logger#addHandler attaching} a handler function * to the desired logger. For convenience, you can quickly enable logging to * the console by simply calling * {@link webdriver.logging.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 create a * WebDriver session: * * var prefs = new webdriver.logging.Preferences(); * prefs.setLevel(webdriver.logging.Type.BROWSER, * webdriver.logging.Level.DEBUG); * * var caps = webdriver.Capabilities.chrome(); * caps.setLoggingPrefs(prefs); * // ... * * Remote log entries are represented by the {@link Entry} class and may be * retrieved via {@link webdriver.WebDriver.Logs}: * * driver.manage().logs().get(webdriver.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://chromedriver.chromium.org/logging) * options. Remote logging is still considered a non-standard feature, and the * APIs exposed by this module for it are non-frozen. Once logging is officially * defined by the [W3C WebDriver spec](http://www.w3.org/TR/webdriver/), this * module will be updated to use a consistent API for local and remote logging. */ goog.module('webdriver.logging'); goog.module.declareLegacyNamespace(); var LogManager = goog.require('goog.debug.LogManager'); var LogRecord = goog.require('goog.debug.LogRecord'); var Logger = goog.require('goog.debug.Logger'); var googString = goog.require('goog.string'); var padNumber = googString.padNumber; /** @const */ exports.LogRecord = LogRecord; /** @const */ exports.Logger = Logger; /** @const */ exports.Level = Logger.Level; /** * DEBUG is a message level for debugging messages and has the same log level * as the {@link Logger.Level.CONFIG} message level. * @const {!Logger.Level} */ Logger.Level.DEBUG = new Logger.Level('DEBUG', Logger.Level.CONFIG.value); /** * Finds a named logger. * * @param {string=} opt_name The dot-delimited logger name, such as * "webdriver.logging.Logger". Defaults to the name of the root logger. * @return {!Logger} The named logger. */ function getLogger(opt_name) { return LogManager.getLogger(opt_name || Logger.ROOT_LOGGER_NAME); } exports.getLogger = getLogger; /** * Logs all messages to the Console API. */ function consoleHandler(record) { if (typeof console === 'undefined' || !console) { return; } record = /** @type {!LogRecord} */(record); var timestamp = new Date(record.getMillis()); var msg = '[' + timestamp.getUTCFullYear() + '-' + padNumber(timestamp.getUTCMonth() + 1, 2) + '-' + padNumber(timestamp.getUTCDate(), 2) + 'T' + padNumber(timestamp.getUTCHours(), 2) + ':' + padNumber(timestamp.getUTCMinutes(), 2) + ':' + padNumber(timestamp.getUTCSeconds(), 2) + 'Z]' + '[' + record.getLevel().name + ']' + '[' + record.getLoggerName() + '] ' + record.getMessage(); var level = record.getLevel().value; if (level >= Logger.Level.SEVERE.value) { console.error(msg); } else if (level >= Logger.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. * @see exports.removeConsoleHandler */ exports.addConsoleHandler = function(opt_logger) { var logger = opt_logger || getLogger(); logger.addHandler(consoleHandler); }; /** * Installs the console log handler on the root logger. * @see exports.addConsoleHandler */ exports.installConsoleHandler = function() { exports.addConsoleHandler(); }; /** * 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 */ exports.removeConsoleHandler = function(opt_logger) { var logger = opt_logger || getLogger(); logger.removeHandler(consoleHandler); }; /** * Converts a level name or value to a {@link webdriver.logging.Level} value. * If the name/value is not recognized, {@link webdriver.logging.Level.ALL} * will be returned. * @param {(number|string)} nameOrValue The log level name, or value, to * convert . * @return {!Logger.Level} The converted level. */ function getLevel(nameOrValue) { // DEBUG is not a predefined Closure log level, but maps to CONFIG. Since // DEBUG is a predefined level for the WebDriver protocol, we prefer it over // CONFIG. if ('DEBUG' === nameOrValue || Logger.Level.DEBUG.value === nameOrValue) { return Logger.Level.DEBUG; } else if (goog.isString(nameOrValue)) { return Logger.Level.getPredefinedLevel(/** @type {string} */(nameOrValue)) || Logger.Level.ALL; } else { return Logger.Level.getPredefinedLevelByValue( /** @type {number} */(nameOrValue)) || Logger.Level.ALL; } } exports.getLevel = getLevel; /** * Normalizes a {@link Logger.Level} to one of the distinct values recognized * by WebDriver's wire protocol. * @param {!Logger.Level} level The input level. * @return {!Logger.Level} The normalized level. */ function normalizeLevel(level) { if (level.value <= Logger.Level.ALL.value) { // ALL is 0. return Logger.Level.ALL; } else if (level.value === Logger.Level.OFF.value) { // OFF is Infinity return Logger.Level.OFF; } else if (level.value < Logger.Level.INFO.value) { return Logger.Level.DEBUG; } else if (level.value < Logger.Level.WARNING.value) { return Logger.Level.INFO; } else if (level.value < Logger.Level.SEVERE.value) { return Logger.Level.WARNING; } else { return Logger.Level.SEVERE; } } /** * Common log types. * @enum {string} */ var 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' }; exports.Type = Type; /** * Describes the log preferences for a WebDriver session. * @final */ var Preferences = goog.defineClass(null, { /** @constructor */ constructor: function() { /** @private {!Object.} */ this.prefs_ = {}; }, /** * Sets the desired logging level for a particular log type. * @param {(string|Type)} type The log type. * @param {!Logger.Level} level The desired log level. */ setLevel: function(type, level) { this.prefs_[type] = normalizeLevel(level); }, /** * Converts this instance to its JSON representation. * @return {!Object.} The JSON representation of this set of * preferences. */ toJSON: function() { var obj = {}; for (var type in this.prefs_) { if (this.prefs_.hasOwnProperty(type)) { obj[type] = this.prefs_[type].name; } } return obj; } }); exports.Preferences = Preferences; /** * A single log entry recorded by a WebDriver component, such as a remote * WebDriver server. * @final */ var Entry = goog.defineClass(null, { /** * @param {(!Logger.Level|string)} 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 */ constructor: function(level, message, opt_timestamp, opt_type) { /** @type {!Logger.Level} */ this.level = goog.isString(level) ? getLevel(level) : level; /** @type {string} */ this.message = message; /** @type {number} */ this.timestamp = goog.isNumber(opt_timestamp) ? opt_timestamp : goog.now(); /** @type {string} */ this.type = opt_type || ''; }, statics: { /** * Converts a {@link goog.debug.LogRecord} into a * {@link webdriver.logging.Entry}. * @param {!LogRecord} logRecord The record to convert. * @param {string=} opt_type The log type. * @return {!Entry} The converted entry. */ fromClosureLogRecord: function(logRecord, opt_type) { return new Entry( normalizeLevel(/** @type {!Logger.Level} */(logRecord.getLevel())), '[' + logRecord.getLoggerName() + '] ' + logRecord.getMessage(), logRecord.getMillis(), opt_type); } }, /** * @return {{level: string, message: string, timestamp: number, * type: string}} The JSON representation of this entry. */ toJSON: function() { return { 'level': this.level.name, 'message': this.message, 'timestamp': this.timestamp, 'type': this.type }; } }); exports.Entry = Entry;