Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: createLogger for easier logging in individual modules #5418

Merged
merged 16 commits into from
Sep 28, 2018
68 changes: 56 additions & 12 deletions docs/guides/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
* [API Overview](#api-overview)
* [Log Safely](#log-safely)
* [Log Objects Usefully](#log-objects-usefully)
* [Creating new Loggers](#creating-new-loggers)
* [Log Levels](#log-levels)
* [Available Log Levels](#available-log-levels)
* [Debug Logging](#debug-logging)
* [History](#history)
* [History filtering](#history-filtering)

## Logging

Expand All @@ -18,17 +21,19 @@ Video.js includes `videojs.log`, a lightweight wrapper around a subset of [the `

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, debug, info |
| `videojs.log.debug()` | `console.debug` | all, debug |
| `videojs.log.warn()` | `console.warn` | all, debug, info, warn |
| `videojs.log.error()` | `console.error` | all, debug, info, 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 |
| Method | Alias Of | Matching Level(s) |
| ------------------------------- | --------------- | ----------------------------- |
| `videojs.log()` | `console.log` | all, debug, info |
| `videojs.log.debug()` | `console.debug` | all, debug |
| `videojs.log.warn()` | `console.warn` | all, debug, info, warn |
| `videojs.log.error()` | `console.error` | all, debug, info, warn, error |
| `videojs.log.createLogger()` | n/a | n/a |
| `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 |
| `videojs.log.history.filter()` | n/a | n/a |

For descriptions of these features, please refer to the sections below.

Expand All @@ -44,6 +49,23 @@ Similar to the `console`, any number of mixed-type values can be passed to `vide
videojs.log('this is a string', {butThis: 'is an object'});
```

### Creating new Loggers

Sometimes, you want to make a new module or plugin and log messages with a label. Kind of how all these logs are prepended with `VIDEOJS:`. You can do that via the `createLogger` method. It takes a name and gives you back a log object like `videojs.log`. Here's an example:

```js
const mylogger = videojs.log.createLogger('mylogger');

mylogger('hello world!');
// > VIDEOJS: mylogger: hello world!

// We can even chain it further
const anotherlogger = mylogger.createLogger('anotherlogger');

anotherlogger('well, hello there');
// > VIDEOJS: mylogger: anotherlogger: well, hello there
```

### Log Levels

Unlike the `console`, `videojs.log` includes the concept of logging levels. These levels toggle logging methods on or off.
Expand Down Expand Up @@ -78,7 +100,7 @@ Although the log levels attempt to match their `window.console` counterparts, `w

### 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.
> **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. You can also filter the history based on the name of the logger.

By default, the `videojs.log` module tracks a history of _everything_ passed to it regardless of logging level:

Expand All @@ -102,6 +124,28 @@ Finally, the history (if enabled) can be cleared at any time via:
videojs.log.history.clear();
```

#### History filtering

If you want to find all the history that was created by a particular logger, you can do so via `history.filter()`.
Given a specific logger with name `foo`, you can pass `foo` to `history.filter()` and get all items logger by foo.
Let me show you an example:

```js
const mylogger = videojs.log.createLogger('mylogger');
const anotherlogger = mylogger.createLogger('anotherlogger');

videojs.log('hello');
mylogger('how are you');
anotherlogger('today');

videojs.log.history.filter('VIDEOJS');
// > [['VIDEOJS:', 'hello'], ['VIDEOJS: mylogger:', 'how are you'], ['VIDEOJS: mylogger: anotherlogger:', 'today']]
videojs.log.history.filter('mylogger');
// > [['VIDEOJS: mylogger:', 'how are you'], ['VIDEOJS: mylogger: anotherlogger:', 'today']]
videojs.log.history.filter('anotherlogger');
// > [['VIDEOJS: mylogger: anotherlogger:', 'today']]
```

[api]: https://docs.videojs.com/

[console]: https://developer.mozilla.org/en-US/docs/Web/API/Console
5 changes: 4 additions & 1 deletion src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as Dom from './utils/dom.js';
import * as Fn from './utils/fn.js';
import * as Guid from './utils/guid.js';
import * as browser from './utils/browser.js';
import log from './utils/log.js';
import log, { createLogger } from './utils/log.js';
import toTitleCase, { titleCaseEquals } from './utils/to-title-case.js';
import { createTimeRange } from './utils/time-ranges.js';
import { bufferedPercent } from './utils/buffer.js';
Expand Down Expand Up @@ -313,6 +313,9 @@ class Player extends Component {
// Run base component initializing with new options
super(null, options, ready);

// create logger
this.log = createLogger(this.id_);

// Tracks when a tech changes the poster
this.isPosterFromTech_ = false;

Expand Down
247 changes: 247 additions & 0 deletions src/js/utils/create-logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/**
* @file create-logger.js
* @module create-logger
*/
import window from 'global/window';

// 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.
*
* @param {Array} args
* The arguments to be passed to the matching console method.
*/
const LogByTypeFactory = (name, log) => (type, level, args) => {
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".
args.unshift(type.toUpperCase() + ':');
}

// Add console prefix after adding to history.
args.unshift(name + ':');

// Add a clone of the args at this point to history.
if (history) {
history.push([].concat(args));
}

// If there's no console then don't try to output messages, but they will
// still be stored in history.
if (!window.console) {
return;
}

// 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.
let fn = window.console[type];

if (!fn && type === 'debug') {
// Certain browsers don't have support for console.debug. For those, we
// should default to the closest comparable log.
fn = window.console.info || window.console.log;
}

// 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)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall which versions it is, but there are some IEs that don't allow using apply/call on console methods. Probably not the case in IE11, but might be worth checking.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code wasn't changed from what was around previously.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, then, it's probably safe. I could be showing my age again. 😄

return;
}

fn[Array.isArray(args) ? 'apply' : 'call'](window.console, args);
};

export default function createLogger(name) {
// This is the private tracking variable for logging level.
let level = 'info';

// the curried logByType bound to the specific log and history
let logByType;

/**
* Logs plain debug messages. Similar to `console.log`.
*
* Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
* of our JSDoc template, we cannot properly document this as both a function
* and a namespace, so its function signature is documented here.
*
* #### Arguments
* ##### *args
* Mixed[]
*
* Any combination of values that could be passed to `console.log()`.
*
* #### Return Value
*
* `undefined`
*
* @namespace
* @param {Mixed[]} args
* One or more messages or objects that should be logged.
*/
const log = function(...args) {
logByType('log', level, args);
};

// This is the logByType helper that the logging methods below use
logByType = LogByTypeFactory(name, log);

/**
* Create a new sublogger which chains the old name to the new name.
*
* For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following:
* ```js
* mylogger('foo');
* // > VIDEOJS: player: foo
* ```
*
* @param {string} name
* The name to add call the new logger
* @return {Object}
*/
log.createLogger = (subname) => createLogger(name + ': ' + subname);

/**
* 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.
*
* 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`: Matches only Video.js-provided functions (`debug`, `log`,
* `log.warn`, and `log.error`).
* - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
* - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
* - `warn`: Matches `log.warn` and `log.error` calls.
* - `error`: Matches only `log.error` calls.
*
* @type {Object}
*/
log.levels = {
all: 'debug|log|warn|error',
off: '',
debug: 'debug|log|warn|error',
info: 'log|warn|error',
warn: 'warn|error',
error: 'error',
DEFAULT: level
};

/**
* Get or set the current logging level.
*
* If a string matching a key from {@link module:log.levels} is provided, acts
* as a setter.
*
* @param {string} [lvl]
* Pass a valid level 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) : [];

/**
* Allows you to filter the history by the given logger name
*
* @param {string} fname
* The name to filter by
*
* @return {Array}
* The filtered list to return
*/
log.history.filter = (fname) => {
return (history || []).filter((historyItem) => {
// if the first item in each historyItem includes `fname`, then it's a match
return new RegExp(`.*${fname}.*`).test(historyItem[0]);
});
};

/**
* 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
*/
log.error = (...args) => logByType('error', level, args);

/**
* Logs warning messages. Similar to `console.warn`.
*
* @param {Mixed[]} args
* One or more messages or objects that should be logged as a warning.
*/
log.warn = (...args) => logByType('warn', level, args);

/**
* Logs debug messages. Similar to `console.debug`, but may also act as a comparable
* log if `console.debug` is not available
*
* @param {Mixed[]} args
* One or more messages or objects that should be logged as debug.
*/
log.debug = (...args) => logByType('debug', level, args);

return log;
}
Loading