// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617/**18* @fileoverview Defines WebDriver's logging system. The logging system is19* broken into major components: local and remote logging.20*21* The local logging API, which is anchored by the22* {@link webdriver.logging.Logger Logger} class, is similar to Java's23* logging API. Loggers, retrieved by {@link webdriver.logging.getLogger}, use24* hierarchical, dot-delimited namespaces25* (e.g. "" > "webdriver" > "webdriver.logging"). Recorded log messages are26* represented by the {@link webdriver.logging.LogRecord LogRecord} class. You27* can capture log records by28* {@linkplain webdriver.logging.Logger#addHandler attaching} a handler function29* to the desired logger. For convenience, you can quickly enable logging to30* the console by simply calling31* {@link webdriver.logging.installConsoleHandler()}.32*33* The [remote logging API](https://github.com/SeleniumHQ/selenium/wiki/Logging)34* allows you to retrieve logs from a remote WebDriver server. This API uses the35* {@link Preferences} class to define desired log levels prior to create a36* WebDriver session:37*38* var prefs = new webdriver.logging.Preferences();39* prefs.setLevel(webdriver.logging.Type.BROWSER,40* webdriver.logging.Level.DEBUG);41*42* var caps = webdriver.Capabilities.chrome();43* caps.setLoggingPrefs(prefs);44* // ...45*46* Remote log entries are represented by the {@link Entry} class and may be47* retrieved via {@link webdriver.WebDriver.Logs}:48*49* driver.manage().logs().get(webdriver.logging.Type.BROWSER)50* .then(function(entries) {51* entries.forEach(function(entry) {52* console.log('[%s] %s', entry.level.name, entry.message);53* });54* });55*56* **NOTE:** Only a few browsers support the remote logging API (notably57* Firefox and Chrome). Firefox supports basic logging functionality, while58* Chrome exposes robust59* [performance logging](https://chromedriver.chromium.org/logging)60* options. Remote logging is still considered a non-standard feature, and the61* APIs exposed by this module for it are non-frozen. Once logging is officially62* defined by the [W3C WebDriver spec](http://www.w3.org/TR/webdriver/), this63* module will be updated to use a consistent API for local and remote logging.64*/6566goog.module('webdriver.logging');67goog.module.declareLegacyNamespace();6869var LogManager = goog.require('goog.debug.LogManager');70var LogRecord = goog.require('goog.debug.LogRecord');71var Logger = goog.require('goog.debug.Logger');72var googString = goog.require('goog.string');7374var padNumber = googString.padNumber;7576/** @const */77exports.LogRecord = LogRecord;787980/** @const */81exports.Logger = Logger;828384/** @const */85exports.Level = Logger.Level;868788/**89* DEBUG is a message level for debugging messages and has the same log level90* as the {@link Logger.Level.CONFIG} message level.91* @const {!Logger.Level}92*/93Logger.Level.DEBUG = new Logger.Level('DEBUG', Logger.Level.CONFIG.value);949596/**97* Finds a named logger.98*99* @param {string=} opt_name The dot-delimited logger name, such as100* "webdriver.logging.Logger". Defaults to the name of the root logger.101* @return {!Logger} The named logger.102*/103function getLogger(opt_name) {104return LogManager.getLogger(opt_name || Logger.ROOT_LOGGER_NAME);105}106exports.getLogger = getLogger;107108109/**110* Logs all messages to the Console API.111*/112function consoleHandler(record) {113if (typeof console === 'undefined' || !console) {114return;115}116record = /** @type {!LogRecord} */(record);117var timestamp = new Date(record.getMillis());118var msg =119'[' + timestamp.getUTCFullYear() + '-' +120padNumber(timestamp.getUTCMonth() + 1, 2) + '-' +121padNumber(timestamp.getUTCDate(), 2) + 'T' +122padNumber(timestamp.getUTCHours(), 2) + ':' +123padNumber(timestamp.getUTCMinutes(), 2) + ':' +124padNumber(timestamp.getUTCSeconds(), 2) + 'Z]' +125'[' + record.getLevel().name + ']' +126'[' + record.getLoggerName() + '] ' +127record.getMessage();128129var level = record.getLevel().value;130if (level >= Logger.Level.SEVERE.value) {131console.error(msg);132} else if (level >= Logger.Level.WARNING.value) {133console.warn(msg);134} else {135console.log(msg);136}137}138139140/**141* Adds the console handler to the given logger. The console handler will log142* all messages using the JavaScript Console API.143*144* @param {Logger=} opt_logger The logger to add the handler to; defaults145* to the root logger.146* @see exports.removeConsoleHandler147*/148exports.addConsoleHandler = function(opt_logger) {149var logger = opt_logger || getLogger();150logger.addHandler(consoleHandler);151};152153154/**155* Installs the console log handler on the root logger.156* @see exports.addConsoleHandler157*/158exports.installConsoleHandler = function() {159exports.addConsoleHandler();160};161162163/**164* Removes the console log handler from the given logger.165*166* @param {Logger=} opt_logger The logger to remove the handler from; defaults167* to the root logger.168* @see exports.addConsoleHandler169*/170exports.removeConsoleHandler = function(opt_logger) {171var logger = opt_logger || getLogger();172logger.removeHandler(consoleHandler);173};174175176/**177* Converts a level name or value to a {@link webdriver.logging.Level} value.178* If the name/value is not recognized, {@link webdriver.logging.Level.ALL}179* will be returned.180* @param {(number|string)} nameOrValue The log level name, or value, to181* convert .182* @return {!Logger.Level} The converted level.183*/184function getLevel(nameOrValue) {185// DEBUG is not a predefined Closure log level, but maps to CONFIG. Since186// DEBUG is a predefined level for the WebDriver protocol, we prefer it over187// CONFIG.188if ('DEBUG' === nameOrValue || Logger.Level.DEBUG.value === nameOrValue) {189return Logger.Level.DEBUG;190} else if (goog.isString(nameOrValue)) {191return Logger.Level.getPredefinedLevel(/** @type {string} */(nameOrValue))192|| Logger.Level.ALL;193} else {194return Logger.Level.getPredefinedLevelByValue(195/** @type {number} */(nameOrValue)) || Logger.Level.ALL;196}197}198exports.getLevel = getLevel;199200201/**202* Normalizes a {@link Logger.Level} to one of the distinct values recognized203* by WebDriver's wire protocol.204* @param {!Logger.Level} level The input level.205* @return {!Logger.Level} The normalized level.206*/207function normalizeLevel(level) {208if (level.value <= Logger.Level.ALL.value) { // ALL is 0.209return Logger.Level.ALL;210211} else if (level.value === Logger.Level.OFF.value) { // OFF is Infinity212return Logger.Level.OFF;213214} else if (level.value < Logger.Level.INFO.value) {215return Logger.Level.DEBUG;216217} else if (level.value < Logger.Level.WARNING.value) {218return Logger.Level.INFO;219220} else if (level.value < Logger.Level.SEVERE.value) {221return Logger.Level.WARNING;222223} else {224return Logger.Level.SEVERE;225}226}227228229/**230* Common log types.231* @enum {string}232*/233var Type = {234/** Logs originating from the browser. */235BROWSER: 'browser',236/** Logs from a WebDriver client. */237CLIENT: 'client',238/** Logs from a WebDriver implementation. */239DRIVER: 'driver',240/** Logs related to performance. */241PERFORMANCE: 'performance',242/** Logs from the remote server. */243SERVER: 'server'244};245exports.Type = Type;246247248/**249* Describes the log preferences for a WebDriver session.250* @final251*/252var Preferences = goog.defineClass(null, {253/** @constructor */254constructor: function() {255/** @private {!Object.<string, Logger.Level>} */256this.prefs_ = {};257},258259/**260* Sets the desired logging level for a particular log type.261* @param {(string|Type)} type The log type.262* @param {!Logger.Level} level The desired log level.263*/264setLevel: function(type, level) {265this.prefs_[type] = normalizeLevel(level);266},267268/**269* Converts this instance to its JSON representation.270* @return {!Object.<string, string>} The JSON representation of this set of271* preferences.272*/273toJSON: function() {274var obj = {};275for (var type in this.prefs_) {276if (this.prefs_.hasOwnProperty(type)) {277obj[type] = this.prefs_[type].name;278}279}280return obj;281}282});283exports.Preferences = Preferences;284285286/**287* A single log entry recorded by a WebDriver component, such as a remote288* WebDriver server.289* @final290*/291var Entry = goog.defineClass(null, {292/**293* @param {(!Logger.Level|string)} level The entry level.294* @param {string} message The log message.295* @param {number=} opt_timestamp The time this entry was generated, in296* milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the297* current time will be used.298* @param {string=} opt_type The log type, if known.299* @constructor300*/301constructor: function(level, message, opt_timestamp, opt_type) {302303/** @type {!Logger.Level} */304this.level = goog.isString(level) ? getLevel(level) : level;305306/** @type {string} */307this.message = message;308309/** @type {number} */310this.timestamp = goog.isNumber(opt_timestamp) ? opt_timestamp : goog.now();311312/** @type {string} */313this.type = opt_type || '';314},315316statics: {317/**318* Converts a {@link goog.debug.LogRecord} into a319* {@link webdriver.logging.Entry}.320* @param {!LogRecord} logRecord The record to convert.321* @param {string=} opt_type The log type.322* @return {!Entry} The converted entry.323*/324fromClosureLogRecord: function(logRecord, opt_type) {325return new Entry(326normalizeLevel(/** @type {!Logger.Level} */(logRecord.getLevel())),327'[' + logRecord.getLoggerName() + '] ' + logRecord.getMessage(),328logRecord.getMillis(),329opt_type);330}331},332333/**334* @return {{level: string, message: string, timestamp: number,335* type: string}} The JSON representation of this entry.336*/337toJSON: function() {338return {339'level': this.level.name,340'message': this.message,341'timestamp': this.timestamp,342'type': this.type343};344}345});346exports.Entry = Entry;347348349