mirror of
https://github.com/videojs/video.js.git
synced 2025-09-16 09:26:56 +02:00
feat: Log Levels (#3853)
Add a log levels and history api: `videojs.log.level()` and `videojs.log.history()`. `.level()` will return the current level and you can also set it to be one of: `all`, `error`, `off`, or `warn`. `.history()` will return a list of all things logged since videojs loaded. It can be disabled via `videojs.log.history.disable()` (and re-enabled with `enable()`) as well as cleared with `videojs.log.history.clear()`.
This commit is contained in:
committed by
Gary Katsevman
parent
b07143d276
commit
844e4f0107
13
docs/faq.md
13
docs/faq.md
@@ -19,6 +19,7 @@
|
||||
* [Q: How can I autoplay a video on a mobile device?](#q-how-can-i-autoplay-a-video-on-a-mobile-device)
|
||||
* [Q: How can I play RTMP video in video.js?](#q-how-can-i-play-rtmp-video-in-videojs)
|
||||
* [Q: How can I hide the links to my video/subtitles/audio/tracks?](#q-how-can-i-hide-the-links-to-my-videosubtitlesaudiotracks)
|
||||
* [Q: Can I turn off video.js logging?](#q-can-i-turn-off-videojs-logging)
|
||||
* [Q: What is a plugin?](#q-what-is-a-plugin)
|
||||
* [Q: How do I make a plugin for video.js?](#q-how-do-i-make-a-plugin-for-videojs)
|
||||
* [Q: Where can I find a list of video.js plugins?](#q-where-can-i-find-a-list-of-videojs-plugins)
|
||||
@@ -169,6 +170,16 @@ help but are outside of the scope of video.js.
|
||||
|
||||
For content that must be highly secure [videojs-contrib-eme][eme] adds DRM support.
|
||||
|
||||
## Q: Can I turn off video.js logging?
|
||||
|
||||
Yes! This can be achieved by adding the following code _after_ including Video.js, but _before_ creating any player(s):
|
||||
|
||||
```js
|
||||
videojs.log.level('off');
|
||||
```
|
||||
|
||||
For more information, including which logging levels are available, check out the [debugging guide][debug-guide].
|
||||
|
||||
## Q: What is a plugin?
|
||||
|
||||
A plugin is a group of reusable functionality that can be re-used by others. For instance a plugin could add
|
||||
@@ -307,3 +318,5 @@ Yes! Please [submit an issue or open a pull request][pr-issue-question] if this
|
||||
[semver]: http://semver.org/
|
||||
|
||||
[starter-example]: http://jsbin.com/axedog/edit?html,output
|
||||
|
||||
[debug-guide]: ./guides/debug.md
|
||||
|
112
docs/guides/debugging.md
Normal file
112
docs/guides/debugging.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Debugging
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Logging](#logging)
|
||||
* [API Overview](#api-overview)
|
||||
* [Log Safely](#log-safely)
|
||||
* [Log Objects Usefully](#log-objects-usefully)
|
||||
* [Log Levels](#log-levels)
|
||||
* [Available Log Levels](#available-log-levels)
|
||||
* [History](#history)
|
||||
|
||||
## Logging
|
||||
|
||||
Video.js includes a lightweight wrapper - `videojs.log` - around a subset of [the `console` API][console]. The available methods are `videojs.log`, `videojs.log.warn`, and `videojs.log.error`.
|
||||
|
||||
### API Overview
|
||||
|
||||
Most of these methods should be fairly self-explanatory, but for complete details, see [the API docs][api].
|
||||
|
||||
| Method | Alias Of | Matching Level(s) |
|
||||
| ------------------------------- | --------------- | ----------------- |
|
||||
| `videojs.log()` | `console.log` | all |
|
||||
| `videojs.log.warn()` | `console.warn` | all, warn |
|
||||
| `videojs.log.error()` | `console.error` | all, warn, error |
|
||||
| `videojs.log.level()` | n/a | n/a |
|
||||
| `videojs.log.history()` | n/a | n/a |
|
||||
| `videojs.log.history.clear()` | n/a | n/a |
|
||||
| `videojs.log.history.disable()` | n/a | n/a |
|
||||
| `videojs.log.history.enable()` | n/a | n/a |
|
||||
|
||||
For descriptions of these features, please refer to the sections below.
|
||||
|
||||
### Log Safely
|
||||
|
||||
Unlike the `console`, it's safe to leave `videojs.log` calls in your code. They won't throw errors when the `console` doesn't exist.
|
||||
|
||||
### Log Objects Usefully
|
||||
|
||||
Similar to the `console`, any number of mixed-type values can be passed to `videojs.log` methods:
|
||||
|
||||
```js
|
||||
videojs.log('this is a string', {butThis: 'is an object'});
|
||||
```
|
||||
|
||||
However, certain browser consoles (namely, IE10 and lower) do not support non-string values. Video.js improves on this situation by passing objects through `JSON.stringify` before logging them in IE10 and below. In other words, instead of the above producing this:
|
||||
|
||||
```txt
|
||||
VIDEOJS: this is a string [object Object]
|
||||
```
|
||||
|
||||
it will produce this:
|
||||
|
||||
```txt
|
||||
VIDEOJS: this is a string {"butThis": "is an object"}
|
||||
```
|
||||
|
||||
### Log Levels
|
||||
|
||||
Unlike the `console`, `videojs.log` includes the concept of logging levels. These levels toggle logging methods on or off.
|
||||
|
||||
Levels are exposed through the `videojs.log.level` method. This method acts as both a getter and setter for the current logging level. With no arguments, it returns the current logging level:
|
||||
|
||||
```js
|
||||
videojs.log.level(); // "all"
|
||||
```
|
||||
|
||||
By passing a string, the logging level can be changed to one of the available logging levels:
|
||||
|
||||
```js
|
||||
videojs.log.level('error'); // show only error messages and suppress others
|
||||
videojs.log('foo'); // does nothing
|
||||
videojs.log.warn('foo'); // does nothing
|
||||
videojs.log.error('foo'); // logs "foo" as an error
|
||||
```
|
||||
|
||||
### Available Log Levels
|
||||
|
||||
* **all** (default): enables all logging methods
|
||||
* **error**: only show `log.error` messages
|
||||
* **off**: disable all logging methods
|
||||
* **warn**: only show `log.warn` _and_ `log.error` messages
|
||||
|
||||
### History
|
||||
|
||||
> **Note:** In Video.js 5, `videojs.log.history` was an array. As of Video.js 6, it is a function which returns an array. This change was made to provide a richer, safer logging history API.
|
||||
|
||||
By default, the `videojs.log` module tracks a history of _everything_ passed to it regardless of logging level:
|
||||
|
||||
```js
|
||||
videojs.log.history(); // an array of everything that's been logged up to now
|
||||
```
|
||||
|
||||
This will work even when logging is set to **off**.
|
||||
|
||||
This can be useful, but it can also be a source of memory leaks. For example, logged objects will be retained in history even if references are removed everywhere else!
|
||||
|
||||
To avoid this problem, history can be disabled or enabled via method calls (using the `disable` and `enable` methods respectively). Disabling history is as easy as:
|
||||
|
||||
```js
|
||||
videojs.log.history.disable();
|
||||
```
|
||||
|
||||
Finally, the history (if enabled) can be cleared at any time via:
|
||||
|
||||
```js
|
||||
videojs.log.history.clear();
|
||||
```
|
||||
|
||||
[api]: http://docs.videojs.com/docs/api/index.html
|
||||
|
||||
[console]: https://developer.mozilla.org/en-US/docs/Web/API/Console
|
@@ -8,9 +8,16 @@ import {isObject} from './obj';
|
||||
|
||||
let log;
|
||||
|
||||
// This is the private tracking variable for logging level.
|
||||
let level = 'all';
|
||||
|
||||
// This is the private tracking variable for the logging history.
|
||||
let history = [];
|
||||
|
||||
/**
|
||||
* Log messages to the console and history based on the type of message
|
||||
*
|
||||
* @private
|
||||
* @param {string} type
|
||||
* The name of the console method to use.
|
||||
*
|
||||
@@ -22,29 +29,34 @@ let log;
|
||||
* but this is exposed as a parameter to facilitate testing.
|
||||
*/
|
||||
export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 11) => {
|
||||
const lvl = log.levels[level];
|
||||
const lvlRegExp = new RegExp(`^(${lvl})$`);
|
||||
|
||||
if (type !== 'log') {
|
||||
|
||||
// add the type to the front of the message when it's not "log"
|
||||
// Add the type to the front of the message when it's not "log".
|
||||
args.unshift(type.toUpperCase() + ':');
|
||||
}
|
||||
|
||||
// add to history
|
||||
log.history.push(args);
|
||||
// Add a clone of the args at this point to history.
|
||||
if (history) {
|
||||
history.push([].concat(args));
|
||||
}
|
||||
|
||||
// add console prefix after adding to history
|
||||
// Add console prefix after adding to history.
|
||||
args.unshift('VIDEOJS:');
|
||||
|
||||
// If there's no console then don't try to output messages, but they will
|
||||
// still be stored in `log.history`.
|
||||
// still be stored in history.
|
||||
//
|
||||
// Was setting these once outside of this function, but containing them
|
||||
// in the function makes it easier to test cases where console doesn't exist
|
||||
// when the module is executed.
|
||||
const fn = window.console && window.console[type];
|
||||
|
||||
// Bail out if there's no console.
|
||||
if (!fn) {
|
||||
// Bail out if there's no console or if this type is not allowed by the
|
||||
// current logging level.
|
||||
if (!fn || !lvl || !lvlRegExp.test(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,24 +88,104 @@ export const logByType = (type, args, stringify = !!IE_VERSION && IE_VERSION < 1
|
||||
};
|
||||
|
||||
/**
|
||||
* Log plain debug messages
|
||||
* Logs plain debug messages. Similar to `console.log`.
|
||||
*
|
||||
* @param {Mixed[]} args
|
||||
* One or more messages or objects that should be logged.
|
||||
* @class
|
||||
* @param {Mixed[]} args
|
||||
* One or more messages or objects that should be logged.
|
||||
*/
|
||||
log = function(...args) {
|
||||
logByType('log', args);
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep a history of log messages
|
||||
* Enumeration of available logging levels, where the keys are the level names
|
||||
* and the values are `|`-separated strings containing logging methods allowed
|
||||
* in that logging level. These strings are used to create a regular expression
|
||||
* matching the function name being called.
|
||||
*
|
||||
* @type {Array}
|
||||
* Levels provided by video.js are:
|
||||
*
|
||||
* - `off`: Matches no calls. Any value that can be cast to `false` will have
|
||||
* this effect. The most restrictive.
|
||||
* - `all` (default): Matches only Video.js-provided functions (`log`,
|
||||
* `log.warn`, and `log.error`).
|
||||
* - `warn`: Matches `log.warn` and `log.error` calls.
|
||||
* - `error`: Matches only `log.error` calls.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
log.history = [];
|
||||
log.levels = {
|
||||
all: 'log|warn|error',
|
||||
error: 'error',
|
||||
off: '',
|
||||
warn: 'warn|error',
|
||||
DEFAULT: level
|
||||
};
|
||||
|
||||
/**
|
||||
* Log error messages
|
||||
* Get or set the current logging level. If a string matching a key from
|
||||
* {@link log.levels} is provided, acts as a setter. Regardless of argument,
|
||||
* returns the current logging level.
|
||||
*
|
||||
* @param {string} [lvl]
|
||||
* Pass to set a new logging level.
|
||||
*
|
||||
* @return {string}
|
||||
* The current logging level.
|
||||
*/
|
||||
log.level = (lvl) => {
|
||||
if (typeof lvl === 'string') {
|
||||
if (!log.levels.hasOwnProperty(lvl)) {
|
||||
throw new Error(`"${lvl}" in not a valid log level`);
|
||||
}
|
||||
level = lvl;
|
||||
}
|
||||
return level;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array containing everything that has been logged to the history.
|
||||
*
|
||||
* This array is a shallow clone of the internal history record. However, its
|
||||
* contents are _not_ cloned; so, mutating objects inside this array will
|
||||
* mutate them in history.
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
log.history = () => history ? [].concat(history) : [];
|
||||
|
||||
/**
|
||||
* Clears the internal history tracking, but does not prevent further history
|
||||
* tracking.
|
||||
*/
|
||||
log.history.clear = () => {
|
||||
if (history) {
|
||||
history.length = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disable history tracking if it is currently enabled.
|
||||
*/
|
||||
log.history.disable = () => {
|
||||
if (history !== null) {
|
||||
history.length = 0;
|
||||
history = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable history tracking if it is currently disabled.
|
||||
*/
|
||||
log.history.enable = () => {
|
||||
if (history === null) {
|
||||
history = [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs error messages. Similar to `console.error`.
|
||||
*
|
||||
* @param {Mixed[]} args
|
||||
* One or more messages or objects that should be logged as an error
|
||||
@@ -101,7 +193,7 @@ log.history = [];
|
||||
log.error = (...args) => logByType('error', args);
|
||||
|
||||
/**
|
||||
* Log warning messages
|
||||
* Logs warning messages. Similar to `console.warn`.
|
||||
*
|
||||
* @param {Mixed[]} args
|
||||
* One or more messages or objects that should be logged as a warning.
|
||||
|
@@ -5,7 +5,7 @@ import {logByType} from '../../../src/js/utils/log.js';
|
||||
import window from 'global/window';
|
||||
import sinon from 'sinon';
|
||||
|
||||
QUnit.module('log', {
|
||||
QUnit.module('utils/log', {
|
||||
|
||||
beforeEach() {
|
||||
|
||||
@@ -29,8 +29,11 @@ QUnit.module('log', {
|
||||
// Restore the native/original console.
|
||||
window.console = this.originalConsole;
|
||||
|
||||
// Restore the default logging level.
|
||||
log.level(log.levels.DEFAULT);
|
||||
|
||||
// Empty the logger's history.
|
||||
log.history.length = 0;
|
||||
log.history.clear();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -41,7 +44,7 @@ QUnit.test('logging functions should work', function(assert) {
|
||||
|
||||
// Need to reset history here because there are extra messages logged
|
||||
// when running via Karma.
|
||||
log.history.length = 0;
|
||||
log.history.clear();
|
||||
|
||||
log('log1', 'log2');
|
||||
log.warn('warn1', 'warn2');
|
||||
@@ -65,11 +68,20 @@ QUnit.test('logging functions should work', function(assert) {
|
||||
getConsoleArgs('VIDEOJS:', 'ERROR:', 'error1', 'error2')
|
||||
);
|
||||
|
||||
assert.equal(log.history.length, 3, 'there should be three messages in the log history');
|
||||
const history = log.history();
|
||||
|
||||
assert.equal(history.length, 3, 'there should be three messages in the log history');
|
||||
assert.deepEqual(history[0], ['log1', 'log2'], 'history recorded the correct arguments');
|
||||
assert.deepEqual(history[1], ['WARN:', 'warn1', 'warn2'], 'history recorded the correct arguments');
|
||||
assert.deepEqual(history[2], ['ERROR:', 'error1', 'error2'], 'history recorded the correct arguments');
|
||||
});
|
||||
|
||||
QUnit.test('in IE pre-11 (or when requested) objects and arrays are stringified', function(assert) {
|
||||
|
||||
// Need to reset history here because there are extra messages logged
|
||||
// when running via Karma.
|
||||
log.history.clear();
|
||||
|
||||
// Run a custom log call, explicitly requesting object/array stringification.
|
||||
logByType('log', [
|
||||
'test',
|
||||
@@ -84,3 +96,67 @@ QUnit.test('in IE pre-11 (or when requested) objects and arrays are stringified'
|
||||
assert.deepEqual(window.console.log.firstCall.args,
|
||||
['VIDEOJS: test {"foo":"bar"} [1,2,3] 0 false null']);
|
||||
});
|
||||
|
||||
QUnit.test('setting the log level changes what is actually logged', function(assert) {
|
||||
|
||||
// Need to reset history here because there are extra messages logged
|
||||
// when running via Karma.
|
||||
log.history.clear();
|
||||
|
||||
log.level('error');
|
||||
|
||||
log('log1', 'log2');
|
||||
log.warn('warn1', 'warn2');
|
||||
log.error('error1', 'error2');
|
||||
|
||||
assert.notOk(window.console.log.called, 'console.log was not called');
|
||||
assert.notOk(window.console.warn.called, 'console.warn was not called');
|
||||
assert.ok(window.console.error.called, 'console.error was called');
|
||||
|
||||
const history = log.history();
|
||||
|
||||
assert.deepEqual(history[0], ['log1', 'log2'], 'history is maintained even when logging is not performed');
|
||||
assert.deepEqual(history[1], ['WARN:', 'warn1', 'warn2'], 'history is maintained even when logging is not performed');
|
||||
assert.deepEqual(history[2], ['ERROR:', 'error1', 'error2'], 'history is maintained even when logging is not performed');
|
||||
|
||||
log.level('off');
|
||||
|
||||
log('log1', 'log2');
|
||||
log.warn('warn1', 'warn2');
|
||||
log.error('error1', 'error2');
|
||||
|
||||
assert.notOk(window.console.log.called, 'console.log was not called');
|
||||
assert.notOk(window.console.warn.called, 'console.warn was not called');
|
||||
assert.strictEqual(window.console.error.callCount, 1, 'console.error was not called again');
|
||||
|
||||
assert.throws(
|
||||
() => log.level('foobar'),
|
||||
new Error('"foobar" in not a valid log level'),
|
||||
'log.level() only accepts valid log levels when used as a setter'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('history can be enabled/disabled', function(assert) {
|
||||
|
||||
// Need to reset history here because there are extra messages logged
|
||||
// when running via Karma.
|
||||
log.history.clear();
|
||||
|
||||
log.history.disable();
|
||||
log('log1');
|
||||
log.warn('warn1');
|
||||
log.error('error1');
|
||||
|
||||
let history = log.history();
|
||||
|
||||
assert.strictEqual(history.length, 0, 'no history was tracked');
|
||||
|
||||
log.history.enable();
|
||||
log('log1');
|
||||
log.warn('warn1');
|
||||
log.error('error1');
|
||||
|
||||
history = log.history();
|
||||
|
||||
assert.strictEqual(history.length, 3, 'history was tracked');
|
||||
});
|
||||
|
Reference in New Issue
Block a user