diff --git a/.gitignore b/.gitignore index 78f4955..b4905d0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ lib-cov *.pid *.gz *.tgz +*.code-workspace +package-lock.json +bun.lock +.vscode/ pids logs diff --git a/README.md b/README.md index f641f46..6d9839c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # loglevel [![NPM version][npm-image]][npm-url] [![NPM downloads](https://img.shields.io/npm/dw/loglevel.svg)](https://www.npmjs.com/package/loglevel) [![Build Status](https://github.com/pimterry/loglevel/actions/workflows/ci.yml/badge.svg)](https://github.com/pimterry/loglevel/actions/workflows/ci.yml) [npm-image]: https://img.shields.io/npm/v/loglevel.svg?style=flat @@ -18,24 +17,24 @@ Loglevel is a barebones reliable everyday logging library. It does not do fancy ### Simple -* Log things at a given level (trace/debug/info/warn/error) to the `console` object (as seen in all modern browsers & node.js). -* Filter logging by level (all the above or 'silent'), so you can disable all but error logging in production, and then run `log.setLevel("trace")` in your console to turn it all back on for a furious debugging session. -* Single file, no dependencies, weighs in at 1.4 KB minified and gzipped. +- Log things at a given level (trace/debug/info/warn/error) to the `console` object (as seen in all modern browsers & node.js). +- Filter logging by level (all the above or 'silent'), so you can disable all but error logging in production, and then run `log.setLevel("trace")` in your console to turn it all back on for a furious debugging session. +- Single file, no dependencies, weighs in at 1.4 KB minified and gzipped. ### Effective -* Log methods gracefully fall back to simpler console logging methods if more specific ones aren't available: so calls to `log.debug()` go to `console.debug()` if possible, or `console.log()` if not. -* Logging calls still succeed even if there's no `console` object at all, so your site doesn't break when people visit with old browsers that don't support the `console` object (here's looking at you, IE) and similar. -* This then comes together giving a consistent reliable API that works in every JavaScript environment with a console available, and never breaks anything anywhere else. +- Log methods gracefully fall back to simpler console logging methods if more specific ones aren't available: so calls to `log.debug()` go to `console.debug()` if possible, or `console.log()` if not. +- Logging calls still succeed even if there's no `console` object at all, so your site doesn't break when people visit with old browsers that don't support the `console` object (here's looking at you, IE) and similar. +- This then comes together giving a consistent reliable API that works in every JavaScript environment with a console available, and never breaks anything anywhere else. ### Convenient -* Log output keeps line numbers: most JS logging frameworks call `console.log` methods through wrapper functions, clobbering your stacktrace and making the extra info many browsers provide useless. We'll have none of that thanks. -* It works with all the standard JavaScript loading systems out of the box (CommonJS, AMD, or just as a global). -* Logging is filtered to "warn" level by default, to keep your live site clean in normal usage (or you can trivially re-enable everything with an initial `log.enableAll()` call). -* Magically handles situations where console logging is not initially available (IE8/9), and automatically enables logging as soon as it does become available (when developer console is opened). -* TypeScript type definitions included, so no need for extra `@types` packages. -* Extensible, to add other log redirection, filtering, or formatting functionality, while keeping all the above (except you will clobber your stacktrace, see [“Plugins”](#plugins) below). +- Log output keeps line numbers: most JS logging frameworks call `console.log` methods through wrapper functions, clobbering your stacktrace and making the extra info many browsers provide useless. We'll have none of that thanks. +- It works with all the standard JavaScript loading systems out of the box (CommonJS, AMD, or just as a global). +- Logging is filtered to "warn" level by default, to keep your live site clean in normal usage (or you can trivially re-enable everything with an initial `log.enableAll()` call). +- Magically handles situations where console logging is not initially available (IE8/9), and automatically enables logging as soon as it does become available (when developer console is opened). +- TypeScript type definitions included, so no need for extra `@types` packages. +- Extensible, to add other log redirection, filtering, or formatting functionality, while keeping all the above (except you will clobber your stacktrace, see [“Plugins”](#plugins) below). ## Downloading loglevel @@ -58,15 +57,15 @@ loglevel supports AMD (e.g. RequireJS), CommonJS (e.g. Node.js) and direct usage ### CommonsJS (e.g. Node) ```javascript -var log = require('loglevel'); +const log = require("loglevel"); log.warn("unreasonably simple"); ``` ### AMD (e.g. RequireJS) ```javascript -define(['loglevel'], function(log) { - log.warn("dangerously convenient"); +define(["loglevel"], function (log) { + log.warn("dangerously convenient"); }); ``` @@ -75,7 +74,7 @@ define(['loglevel'], function(log) { ```html ``` @@ -86,19 +85,28 @@ loglevel is written as a UMD module, with a single object exported. Unfortunatel For most tools, using the default import is the most convenient and flexible option: ```javascript -import log from 'loglevel'; +import log from "loglevel"; log.warn("module-tastic"); ``` For some tools though, it might better to wildcard import the whole object: ```javascript -import * as log from 'loglevel'; +import * as log from "loglevel"; log.warn("module-tastic"); ``` There's no major difference, unless you're using TypeScript & building a loglevel plugin (in that case, see ). In general though, just use whichever suits your environment, and everything should work out fine. +### Non minified code + +The non-minified code can be imported in module based systems using: + +```javascript +import log from "loglevel/nomin"; +log.warn("module-not-minified"); +``` + ### With noConflict() If you're using another JavaScript library that exposes a `log` global, you can run into conflicts with loglevel. Similarly to jQuery, you can solve this by putting loglevel into no-conflict mode immediately after it is loaded onto the page. This resets the `log` global to its value before loglevel was loaded (typically `undefined`), and returns the loglevel object, which you can then bind to another name yourself. @@ -108,9 +116,9 @@ For example: ```html ``` @@ -120,12 +128,12 @@ loglevel includes its own type definitions, assuming you're using a modern modul If you really want to use LogLevel as a global however, but from TypeScript, you'll need to declare it as such first. To do that: -* Create a `loglevel.d.ts` file -* Ensure that file is included in your build (e.g. add it to `include` in your tsconfig, pass it on the command line, or use `///`) -* In that file, add: +- Create a `loglevel.d.ts` file +- Ensure that file is included in your build (e.g. add it to `include` in your tsconfig, pass it on the command line, or use `///`) +- In that file, add: ```typescript - import * as log from 'loglevel'; + import * as log from "loglevel"; export as namespace log; export = log; ``` @@ -140,11 +148,11 @@ The loglevel API is extremely minimal. All methods are available on the root log 5 actual logging methods, ordered and available as: -* `log.trace(msg)` -* `log.debug(msg)` -* `log.info(msg)` -* `log.warn(msg)` -* `log.error(msg)` +- `log.trace(msg)` +- `log.debug(msg)` +- `log.info(msg)` +- `log.warn(msg)` +- `log.error(msg)` `log.log(msg)` is also available, as an alias for `log.debug(msg)`, to improve compatibility with `console`, and make migration easier. @@ -160,9 +168,9 @@ This disables all logging below the given level, so that after a `log.setLevel(" This can take either a log level name or `'silent'` (which disables everything) in one of a few forms: -* As a log level from the internal levels list, e.g. `log.levels.SILENT` ← _for type safety_ -* As a string, like `'error'` (case-insensitive) ← _for a reasonable practical balance_ -* As a numeric index from `0` (trace) to `5` (silent) ← _deliciously terse, and more easily programmable (...although, why?)_ +- As a log level from the internal levels list, e.g. `log.levels.SILENT` ← _for type safety_ +- As a string, like `'error'` (case-insensitive) ← _for a reasonable practical balance_ +- As a numeric index from `0` (trace) to `5` (silent) ← _deliciously terse, and more easily programmable (...although, why?)_ Where possible, the log level will be persisted. LocalStorage will be used if available, falling back to cookies if not. If neither is available in the current environment (e.g. in Node), or if you pass `false` as the optional 'persist' second argument, persistence will be skipped. @@ -190,7 +198,7 @@ It's very unlikely you'll need to use this for normal application logging; it's ```javascript if (log.getLevel() <= log.levels.DEBUG) { - var logData = runExpensiveDataGeneration(); + const logData = runExpensiveDataGeneration(); log.debug(logData); } ``` @@ -214,21 +222,21 @@ Example usage _(using CommonJS modules, but you could do the same with any modul ```javascript // In module-one.js: -var log = require("loglevel").getLogger("module-one"); +const log = require("loglevel").getLogger("module-one"); function doSomethingAmazing() { log.debug("Amazing message from module one."); } // In module-two.js: -var log = require("loglevel").getLogger("module-two"); +const log = require("loglevel").getLogger("module-two"); function doSomethingSpecial() { log.debug("Special message from module two."); } // In your main application module: -var log = require("loglevel"); -var moduleOne = require("module-one"); -var moduleTwo = require("module-two"); +const log = require("loglevel"); +const moduleOne = require("module-one"); +const moduleTwo = require("module-two"); log.getLogger("module-two").setLevel("TRACE"); moduleOne.doSomethingAmazing(); @@ -243,9 +251,49 @@ Like the root logger, other loggers can have their logging level saved. If a log Likewise, loggers inherit the root logger’s `methodFactory`. After creation, each logger can have its `methodFactory` independently set. See the _plugins_ section below for more about `methodFactory`. -#### `log.getLoggers()` +#### Hierarchical Loggers `log.getLogger(parentLoggerName).getLogger(childLoggerName)` -This will return the dictionary of all loggers created with `getLogger()`, keyed by their names. +This gets you a new logger object which inherits the parent logger's log level, being named `parentLogger.childLogger`. That allows using `setLevel` on the parent logger and having that apply to the child logger. Suppose each class has it's own logger in the form `app` with child `ui`, `controller`, `model`, `util` etc followed by the class name. Now, you might set the `app` to `debug`, but you don't care about the util logging, so you set `app.util` to `warn`. That is two different levels to set, and might apply to a large number of classes. You might also decide you don't care about `app.model.NoisyClass`, so you can set that one to warn, but still leave the other model ones active. Or conversely, set the `app.model` to `warn` and then a model class you do care `app.model.ImportantClass` to debug. + +Example usage _(using CommonJS modules, but you could do the same with any module system):_ + +```javascript +// In module-one.js: +const loglevel = require("loglevel"); +loglevel.setLevel("error"); +const logApp = loglevel.getLogger("app"); +const logUI = logApp.getLogger("ui"); +const logUIClass1 = logUI.getLogger("class1"); + +logApp.setLevel("debug"); +// Turn off utility logging +logApp.getLogger("util").setLevel("warn"); + +const utility = { + callMethod: () => { + logApp.getLogger("util").debug("Verbose utility logging"); + }, +}; + +function doSomethingAmazing() { + logUIClass1.debug("Amazing message from class 1 one."); + // Will NOT send out messages + utility.callMethod(); +} + +// logs "Special message from class 1." +// (but nothing from utils module) +``` + +Loggers returned by `getLogger()` support all the same properties and methods as the default root logger, excepting `noConflict()` and the `getLogger()` creating a child logger instead of a root logger. + +Like the root logger, other loggers can have their logging level saved. If a logger’s level has not been saved, it will inherit the root logger’s level when it is first created. If the root logger’s level changes later, the new level will not affect other loggers that have already been created. Loggers with Symbol names (rather than string names) will always be considered unique instances, and will never have their logging level saved or restored. + +Likewise, loggers inherit the root logger’s `methodFactory`. After creation, each logger can have its `methodFactory` independently set. See the _plugins_ section below for more about `methodFactory`. + +#### `log.getChildLoggers()` + +This will return the dictionary of all direct child loggers of log created with `getLogger()`, keyed by their names. To see all child loggers everywhere, you will need to do a recursive search on the child loggers. #### `log.rebuild()` @@ -256,23 +304,23 @@ This is mostly useful for plugin development. When you call `log.setLevel()` or It is also useful if you change the level of the root logger and want it to affect child loggers that you’ve already created (and have not called `someChildLogger.setLevel()` or `someChildLogger.setDefaultLevel()` on). For example: ```js -var childLogger1 = log.getLogger("child1"); -childLogger1.getLevel(); // WARN (inherited from the root logger) +const childLogger1 = log.getLogger("child1"); +childLogger1.getLevel(); // WARN (inherited from the root logger) -var childLogger2 = log.getLogger("child2"); +const childLogger2 = log.getLogger("child2"); childLogger2.setDefaultLevel("TRACE"); -childLogger2.getLevel(); // TRACE +childLogger2.getLevel(); // TRACE log.setLevel("ERROR"); // At this point, the child loggers have not changed: -childLogger1.getLevel(); // WARN -childLogger2.getLevel(); // TRACE +childLogger1.getLevel(); // WARN +childLogger2.getLevel(); // TRACE // To update them: log.rebuild(); -childLogger1.getLevel(); // ERROR (still inheriting from root logger) -childLogger2.getLevel(); // TRACE (no longer inheriting because `.setDefaultLevel() was called`) +childLogger1.getLevel(); // ERROR (still inheriting from root logger) +childLogger2.getLevel(); // TRACE (no longer inheriting because `.setDefaultLevel() was called`) ``` ## Plugins @@ -298,18 +346,18 @@ There's clearly enough enthusiasm for this even at that cost that loglevel now i For example, a plugin to prefix all log messages with "Newsflash: " would look like: ```javascript -var originalFactory = log.methodFactory; +const originalFactory = log.methodFactory; log.methodFactory = function (methodName, logLevel, loggerName) { - var rawMethod = originalFactory(methodName, logLevel, loggerName); + const rawMethod = originalFactory(methodName, logLevel, loggerName); - return function (message) { - rawMethod("Newsflash: " + message); - }; + return function (message) { + rawMethod("Newsflash: " + message); + }; }; log.rebuild(); // Be sure to call the rebuild method in order to apply plugin. ``` -*(The above supports only a single string `log.warn("...")` argument for clarity, but it's easy to extend to a [fuller variadic version](http://jsbin.com/xehoye/edit?html,console).)* +_(The above supports only a single string `log.warn("...")` argument for clarity, but it's easy to extend to a [fuller variadic version](http://jsbin.com/xehoye/edit?html,console).)_ If you develop and release a plugin, please get in contact! I'd be happy to reference it here for future users. Some consistency is helpful; naming your plugin 'loglevel-PLUGINNAME' (e.g. loglevel-newsflash) is preferred, as is giving it the 'loglevel-plugin' keyword in your `package.json`. @@ -325,12 +373,12 @@ _Also, please don't manually edit files in the `dist/` subdirectory as they are To do a release of loglevel: -* Update the version number in `package.json` and `bower.json`. -* Run `npm run dist` to build a distributable version in `dist/`. -* Update the release history in this file (below). -* Commit the built code, tagging it with the version number and a brief message about the release. -* Push to Github. -* Run `npm publish .` to publish to NPM. +- Update the version number in `package.json` and `bower.json`. +- Run `npm run dist` to build a distributable version in `dist/`. +- Update the release history in this file (below). +- Commit the built code, tagging it with the version number and a brief message about the release. +- Push to Github. +- Run `npm publish .` to publish to NPM. ## Release History @@ -399,6 +447,7 @@ v1.9.1 - Fix a bug introduced in 1.9.0 that broke `setLevel()` in some ESM-focus v1.9.2 - Remove unnecessarily extra test & CI files from deployed package v2.0.0 **(In development)** + - Removed support for Internet Explorer v10 and older. - Calling `debug(msg)` shows up as an actual “debug” level message in browser consoles (in v1, it showed up as “log” or “info” depending on your browser). - The `log()` method is now equivalent to `info()` instead of `debug()`. diff --git a/bower.json b/bower.json index ad6d826..5c88538 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "loglevel", - "version": "1.9.2", + "version": "2.0.0-alpha1", "main": "dist/loglevel.min.js", "dependencies": {}, "ignore": [ diff --git a/dist/loglevel.js b/dist/loglevel.js index 893e4cb..9d9ee51 100644 --- a/dist/loglevel.js +++ b/dist/loglevel.js @@ -1,4 +1,4 @@ -/*! loglevel - v1.9.2 - https://github.com/pimterry/loglevel - (c) 2024 Tim Perry - licensed MIT */ +/*! loglevel - v2.0.0-alpha1 - https://github.com/pimterry/loglevel - (c) 2025 Tim Perry - licensed MIT */ (function (root, definition) { "use strict"; if (typeof define === 'function' && define.amd) { @@ -12,8 +12,8 @@ "use strict"; // Slightly dubious tricks to cut down minimized file size - var noop = function() {}; - var undefinedType = "undefined"; + const noop = function() {}; + const undefinedType = "undefined"; var logMethods = [ "trace", @@ -23,7 +23,6 @@ "error" ]; - var _loggersByName = {}; var defaultLogger = null; // Build the best logging method possible for this env @@ -62,190 +61,246 @@ } } - function Logger(name, factory) { - // Private instance variables. - var self = this; - /** - * The level inherited from a parent logger (or a global default). We - * cache this here rather than delegating to the parent so that it stays - * in sync with the actual logging methods that we have installed (the - * parent could change levels but we might not have rebuilt the loggers - * in this child yet). - * @type {number} - */ - var inheritedLevel; - /** - * The default level for this logger, if any. If set, this overrides - * `inheritedLevel`. - * @type {number|null} - */ - var defaultLevel; - /** - * A user-specific level for this logger. If set, this overrides - * `defaultLevel`. - * @type {number|null} - */ - var userLevel; - - var storageKey = "loglevel"; - if (typeof name === "string") { - storageKey += ":" + name; - } else if (typeof name === "symbol") { - storageKey = undefined; - } - - function persistLevelIfPossible(levelNum) { - var levelName = (logMethods[levelNum] || 'silent').toUpperCase(); - - if (typeof window === undefinedType || !storageKey) return; - - // Use localStorage if available - try { - window.localStorage[storageKey] = levelName; - return; - } catch (ignore) {} - - // Use session cookie as fallback - try { - window.document.cookie = - encodeURIComponent(storageKey) + "=" + levelName + ";"; - } catch (ignore) {} - } - - function getPersistedLevel() { - var storedLevel; - - if (typeof window === undefinedType || !storageKey) return; - - try { - storedLevel = window.localStorage[storageKey]; - } catch (ignore) {} - - // Fallback to cookies if local storage gives us nothing - if (typeof storedLevel === undefinedType) { - try { - var cookie = window.document.cookie; - var cookieName = encodeURIComponent(storageKey); - var location = cookie.indexOf(cookieName + "="); - if (location !== -1) { - storedLevel = /^([^;]+)/.exec( - cookie.slice(location + cookieName.length + 1) - )[1]; - } - } catch (ignore) {} - } - - // If the stored level is not valid, treat it as if nothing was stored. - if (self.levels[storedLevel] === undefined) { - storedLevel = undefined; - } - - return storedLevel; - } - - function clearPersistedLevel() { - if (typeof window === undefinedType || !storageKey) return; - - // Use localStorage if available - try { - window.localStorage.removeItem(storageKey); - } catch (ignore) {} - - // Use session cookie as fallback - try { - window.document.cookie = - encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; - } catch (ignore) {} - } - - function normalizeLevel(input) { - var level = input; - if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) { - level = self.levels[level.toUpperCase()]; - } - if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) { - return level; - } else { - throw new TypeError("log.setLevel() called with invalid level: " + input); - } - } - - /* - * - * Public logger API - see https://github.com/pimterry/loglevel for details - * - */ - - self.name = name; - - self.levels = { "TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, - "ERROR": 4, "SILENT": 5}; - - self.methodFactory = factory || defaultMethodFactory; - - self.getLevel = function () { - if (userLevel != null) { - return userLevel; - } else if (defaultLevel != null) { - return defaultLevel; - } else { + function Logger(name, factory, parent) { + // Private instance variables. + const self = this; + /** + * The level inherited from a parent logger (or a global default). We + * cache this here rather than delegating to the parent so that it stays + * in sync with the actual logging methods that we have installed (the + * parent could change levels but we might not have rebuilt the loggers + * in this child yet). + * @type {number} + */ + let inheritedLevel = null; + /** + * The default level for this logger, if any. If set, this overrides + * `inheritedLevel`. + * @type {number|null} + */ + let defaultLevel = null; + /** + * A user-specific level for this logger. If set, this overrides + * `defaultLevel`. + * @type {number|null} + */ + let userLevel = null; + + /** + * The categories is the inherited hierarchy of this logger, used + * to get parent categories/definitions. + */ + this.categories = parent ? [...parent.categories, name] : []; + + /** + * The storage key is used for storing persisted levels in the + * browser storage. + */ + const hasSymbol = this.categories.find(it => typeof it === 'symbol'); + const storageKey = + hasSymbol ? null : (parent ? + 'loglevel:' + this.categories + .map((category) => category.toString()) + .join(".") : + "loglevel"); + + /** + * The child loggers that are contained within this logger. + */ + const loggers = {}; + + function persistLevelIfPossible(levelNum) { + var levelName = (logMethods[levelNum] || "silent").toUpperCase(); + + if (typeof window === undefinedType || !storageKey) return; + + // Use localStorage if available + try { + window.localStorage[storageKey] = levelName; + return; + } catch (ignore) {} + + // Use session cookie as fallback + try { + window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";"; + } catch (ignore) {} + } + + function getPersistedLevel() { + var storedLevel; + + if (typeof window === undefinedType || !storageKey) return; + + try { + storedLevel = window.localStorage[storageKey]; + } catch (ignore) {} + + // Fallback to cookies if local storage gives us nothing + if (typeof storedLevel === undefinedType) { + try { + var cookie = window.document.cookie; + var cookieName = encodeURIComponent(storageKey); + var location = cookie.indexOf(cookieName + "="); + if (location !== -1) { + storedLevel = /^([^;]+)/.exec( + cookie.slice(location + cookieName.length + 1) + )[1]; + } + } catch (ignore) {} + } + + // If the stored level is not valid, treat it as if nothing was stored. + if (self.levels[storedLevel] === undefined) { + storedLevel = undefined; + } + + return storedLevel; + } + + function clearPersistedLevel() { + if (typeof window === undefinedType || !storageKey) return; + + // Use localStorage if available + try { + window.localStorage.removeItem(storageKey); + } catch (ignore) {} + + // Use session cookie as fallback + try { + window.document.cookie = + encodeURIComponent(storageKey) + + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; + } catch (ignore) {} + } + + function normalizeLevel(input) { + var level = input; + if ( + typeof level === "string" && + self.levels[level.toUpperCase()] !== undefined + ) { + level = self.levels[level.toUpperCase()]; + } + if ( + typeof level === "number" && + level >= 0 && + level <= self.levels.SILENT + ) { + return level; + } else { + throw new TypeError( + "log.setLevel() called with invalid level: " + input + ); + } + } + + /* + * + * Public logger API - see https://github.com/pimterry/loglevel for details + * + */ + + self.name = name; + + self.levels = { + TRACE: 0, + DEBUG: 1, + INFO: 2, + WARN: 3, + ERROR: 4, + SILENT: 5, + }; + + self.methodFactory = factory || defaultMethodFactory; + + self.getLevel = function () { + if (userLevel !== null) { + return userLevel; + } + if (defaultLevel !== null) { + return defaultLevel; + } + if (inheritedLevel === null) { + inheritedLevel = parent ? parent.getLevel() : this.levels.WARN; + } return inheritedLevel; - } - }; - - self.setLevel = function (level, persist) { - userLevel = normalizeLevel(level); - if (persist !== false) { // defaults to true - persistLevelIfPossible(userLevel); - } - - // NOTE: in v2, this should call rebuild(), which updates children. - return replaceLoggingMethods.call(self); - }; - - self.setDefaultLevel = function (level) { - defaultLevel = normalizeLevel(level); - if (!getPersistedLevel()) { - self.setLevel(level, false); - } - }; - - self.resetLevel = function () { - userLevel = null; - clearPersistedLevel(); - replaceLoggingMethods.call(self); - }; - - self.enableAll = function(persist) { - self.setLevel(self.levels.TRACE, persist); - }; - - self.disableAll = function(persist) { - self.setLevel(self.levels.SILENT, persist); - }; - - self.rebuild = function () { - if (defaultLogger !== self) { - inheritedLevel = normalizeLevel(defaultLogger.getLevel()); - } - replaceLoggingMethods.call(self); - - if (defaultLogger === self) { - for (var childName in _loggersByName) { - _loggersByName[childName].rebuild(); - } - } - }; - - // Initialize all the internal levels. - inheritedLevel = normalizeLevel( - defaultLogger ? defaultLogger.getLevel() : "WARN" - ); - var initialLevel = getPersistedLevel(); - if (initialLevel != null) { - userLevel = normalizeLevel(initialLevel); - } - replaceLoggingMethods.call(self); + }; + + self.getLogger = function (...childCategories) { + if (!childCategories.length) { + return self; + } + let newCategories = this.categories ? [...this.categories] : []; + return childCategories.reduce((logger, category) => { + let childLogger = logger.getChildLoggers()[category]; + newCategories.push(category); + if (!childLogger) { + if( typeof category !== 'symbol' && typeof category !== 'string' ) { + throw new Error('Category names must be a symbol or string, but is a '+typeof category); + } + childLogger = new Logger(category, self.methodFactory, logger); + loggers[category] = childLogger; + } + return childLogger; + }, self); + }; + + self.getChildLoggers = function () { + return loggers; + }; + + self.setLevel = function (level, persist) { + userLevel = normalizeLevel(level); + if (persist !== false) { + // defaults to true + persistLevelIfPossible(userLevel); + } + return self.rebuild(); + }; + + self.setDefaultLevel = function (level) { + defaultLevel = normalizeLevel(level); + if (getPersistedLevel()===undefined) { + self.setLevel(level, false); + } else { + self.rebuild(); + } + }; + + self.resetLevel = function () { + userLevel = null; + clearPersistedLevel(); + return self.rebuild(); + }; + + self.enableAll = function (persist) { + self.setLevel(self.levels.TRACE, persist); + }; + + self.disableAll = function (persist) { + self.setLevel(self.levels.SILENT, persist); + }; + + self.rebuild = function () { + inheritedLevel = null; + const result = replaceLoggingMethods.call(self); + if( self.methodFactory && self.methodFactory===(factory || defaultMethodFactory)) { + self.methodFactory = parent && parent.methodFactory || factory || defaultMethodFactory; + } + + for (const child of Object.values(loggers)) { + child.rebuild(); + } + return result; + }; + + // Initialize all the internal levels. + inheritedLevel = null; + const initialLevel = getPersistedLevel(); + if (initialLevel !== undefined) { + userLevel = normalizeLevel(initialLevel); + } + replaceLoggingMethods.call(self); } /* @@ -256,21 +311,6 @@ defaultLogger = new Logger(); - defaultLogger.getLogger = function getLogger(name) { - if ((typeof name !== "symbol" && typeof name !== "string") || name === "") { - throw new TypeError("You must supply a name when creating a logger."); - } - - var logger = _loggersByName[name]; - if (!logger) { - logger = _loggersByName[name] = new Logger( - name, - defaultLogger.methodFactory - ); - } - return logger; - }; - // Grab the current global log variable in case of overwrite var _log = (typeof window !== undefinedType) ? window.log : undefined; defaultLogger.noConflict = function() { @@ -282,10 +322,6 @@ return defaultLogger; }; - defaultLogger.getLoggers = function getLoggers() { - return _loggersByName; - }; - // ES6 default export, for compatibility defaultLogger['default'] = defaultLogger; diff --git a/dist/loglevel.min.js b/dist/loglevel.min.js index 5d1b396..b772c5c 100644 --- a/dist/loglevel.min.js +++ b/dist/loglevel.min.js @@ -1,3 +1,2 @@ -/*! loglevel - v1.9.2 - https://github.com/pimterry/loglevel - (c) 2024 Tim Perry - licensed MIT */ - -!function(e,o){"use strict";"function"==typeof define&&define.amd?define(o):"object"==typeof module&&module.exports?module.exports=o():e.log=o()}(this,function(){"use strict";var l=function(){},a="undefined",s=["trace","debug","info","warn","error"],d={},v=null;function w(e,o,n){if(typeof console!==a){var t=console[e]||console.log;if("function"==typeof t)return t.bind(console)}return l}function g(){for(var e=this.getLevel(),o=0;o{"function"==typeof define&&define.amd?define(o):"object"==typeof module&&module.exports?module.exports=o():e.log=o()})(this,function(){let n=function(){},d="undefined";var g=["trace","debug","info","warn","error"],e=null;function v(e,o,t){if(typeof console!==d){e=console[e]||console.log;if("function"==typeof e)return e.bind(console)}return n}function h(){for(var e=this.getLevel(),o=0;o"symbol"==typeof e)?null:n?"loglevel:"+this.categories.map(e=>e.toString()).join("."):"loglevel",s={};function f(){var e;if(typeof window!==d&&u){try{e=window.localStorage[u]}catch(e){}if(typeof e===d)try{var o=window.document.cookie,t=encodeURIComponent(u),n=o.indexOf(t+"=");-1!==n&&(e=/^([^;]+)/.exec(o.slice(n+t.length+1))[1])}catch(e){}return e=void 0===i.levels[e]?void 0:e}}function a(e){var o=e;if("number"==typeof(o="string"==typeof o&&void 0!==i.levels[o.toUpperCase()]?i.levels[o.toUpperCase()]:o)&&0<=o&&o<=i.levels.SILENT)return o;throw new TypeError("log.setLevel() called with invalid level: "+e)}i.name=e,i.levels={TRACE:0,DEBUG:1,INFO:2,WARN:3,ERROR:4,SILENT:5},i.methodFactory=t||v,i.getLevel=function(){return null!==c?c:null!==o?o:r=null===r?n?n.getLevel():this.levels.WARN:r},i.getLogger=function(...e){if(!e.length)return i;let n=this.categories?[...this.categories]:[];return e.reduce((e,o)=>{let t=e.getChildLoggers()[o];if(n.push(o),!t){if("symbol"!=typeof o&&"string"!=typeof o)throw new Error("Category names must be a symbol or string, but is a "+typeof o);t=new l(o,i.methodFactory,e),s[o]=t}return t},i)},i.getChildLoggers=function(){return s},i.setLevel=function(e,o){return c=a(e),!1!==o&&(e=>{if(e=(g[e]||"silent").toUpperCase(),typeof window!==d&&u){try{return window.localStorage[u]=e}catch(e){}try{window.document.cookie=encodeURIComponent(u)+"="+e+";"}catch(e){}}})(c),i.rebuild()},i.setDefaultLevel=function(e){o=a(e),void 0===f()?i.setLevel(e,!1):i.rebuild()},i.resetLevel=function(){if(c=null,typeof window!==d&&u){try{window.localStorage.removeItem(u)}catch(e){}try{window.document.cookie=encodeURIComponent(u)+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC"}catch(e){}}return i.rebuild()},i.enableAll=function(e){i.setLevel(i.levels.TRACE,e)},i.disableAll=function(e){i.setLevel(i.levels.SILENT,e)},i.rebuild=function(){r=null;var e,o=h.call(i);i.methodFactory&&i.methodFactory===(t||v)&&(i.methodFactory=n&&n.methodFactory||t||v);for(e of Object.values(s))e.rebuild();return o},r=null;e=f();void 0!==e&&(c=a(e)),h.call(i)},o=typeof window!==d?window.log:void 0;return e.noConflict=function(){return typeof window!==d&&window.log===e&&(window.log=o),e},e.default=e}); \ No newline at end of file diff --git a/lib/.jshintrc b/lib/.jshintrc index 0eacda6..c83ebd1 100644 --- a/lib/.jshintrc +++ b/lib/.jshintrc @@ -1,6 +1,7 @@ { "curly": false, "eqeqeq": true, + "esversion": "6", "immed": true, "latedef": true, "newcap": true, @@ -9,7 +10,6 @@ "undef": true, "boss": true, "eqnull": true, - "es3": true, "notypeof": true, "globals": { "console": false, @@ -17,5 +17,5 @@ "define": false, "module": false, "window": false - } + } } diff --git a/lib/loglevel.js b/lib/loglevel.js index 2c9361d..352c759 100644 --- a/lib/loglevel.js +++ b/lib/loglevel.js @@ -1,8 +1,8 @@ /* -* loglevel - https://github.com/pimterry/loglevel -* -* Copyright (c) 2013 Tim Perry -* Licensed under the MIT license. + * loglevel - https://github.com/pimterry/loglevel + * + * Copyright (c) 2013 Tim Perry + * Licensed under the MIT license. */ (function (root, definition) { "use strict"; @@ -17,8 +17,8 @@ "use strict"; // Slightly dubious tricks to cut down minimized file size - var noop = function() {}; - var undefinedType = "undefined"; + const noop = function() {}; + const undefinedType = "undefined"; var logMethods = [ "trace", @@ -28,7 +28,6 @@ "error" ]; - var _loggersByName = {}; var defaultLogger = null; // Build the best logging method possible for this env @@ -67,190 +66,246 @@ } } - function Logger(name, factory) { - // Private instance variables. - var self = this; - /** - * The level inherited from a parent logger (or a global default). We - * cache this here rather than delegating to the parent so that it stays - * in sync with the actual logging methods that we have installed (the - * parent could change levels but we might not have rebuilt the loggers - * in this child yet). - * @type {number} - */ - var inheritedLevel; - /** - * The default level for this logger, if any. If set, this overrides - * `inheritedLevel`. - * @type {number|null} - */ - var defaultLevel; - /** - * A user-specific level for this logger. If set, this overrides - * `defaultLevel`. - * @type {number|null} - */ - var userLevel; - - var storageKey = "loglevel"; - if (typeof name === "string") { - storageKey += ":" + name; - } else if (typeof name === "symbol") { - storageKey = undefined; - } - - function persistLevelIfPossible(levelNum) { - var levelName = (logMethods[levelNum] || 'silent').toUpperCase(); - - if (typeof window === undefinedType || !storageKey) return; - - // Use localStorage if available - try { - window.localStorage[storageKey] = levelName; - return; - } catch (ignore) {} - - // Use session cookie as fallback - try { - window.document.cookie = - encodeURIComponent(storageKey) + "=" + levelName + ";"; - } catch (ignore) {} - } - - function getPersistedLevel() { - var storedLevel; - - if (typeof window === undefinedType || !storageKey) return; - - try { - storedLevel = window.localStorage[storageKey]; - } catch (ignore) {} - - // Fallback to cookies if local storage gives us nothing - if (typeof storedLevel === undefinedType) { - try { - var cookie = window.document.cookie; - var cookieName = encodeURIComponent(storageKey); - var location = cookie.indexOf(cookieName + "="); - if (location !== -1) { - storedLevel = /^([^;]+)/.exec( - cookie.slice(location + cookieName.length + 1) - )[1]; - } - } catch (ignore) {} - } - - // If the stored level is not valid, treat it as if nothing was stored. - if (self.levels[storedLevel] === undefined) { - storedLevel = undefined; - } - - return storedLevel; - } - - function clearPersistedLevel() { - if (typeof window === undefinedType || !storageKey) return; - - // Use localStorage if available - try { - window.localStorage.removeItem(storageKey); - } catch (ignore) {} - - // Use session cookie as fallback - try { - window.document.cookie = - encodeURIComponent(storageKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; - } catch (ignore) {} - } - - function normalizeLevel(input) { - var level = input; - if (typeof level === "string" && self.levels[level.toUpperCase()] !== undefined) { - level = self.levels[level.toUpperCase()]; - } - if (typeof level === "number" && level >= 0 && level <= self.levels.SILENT) { - return level; - } else { - throw new TypeError("log.setLevel() called with invalid level: " + input); - } - } - - /* - * - * Public logger API - see https://github.com/pimterry/loglevel for details - * - */ - - self.name = name; - - self.levels = { "TRACE": 0, "DEBUG": 1, "INFO": 2, "WARN": 3, - "ERROR": 4, "SILENT": 5}; - - self.methodFactory = factory || defaultMethodFactory; - - self.getLevel = function () { - if (userLevel != null) { - return userLevel; - } else if (defaultLevel != null) { - return defaultLevel; - } else { + function Logger(name, factory, parent) { + // Private instance variables. + const self = this; + /** + * The level inherited from a parent logger (or a global default). We + * cache this here rather than delegating to the parent so that it stays + * in sync with the actual logging methods that we have installed (the + * parent could change levels but we might not have rebuilt the loggers + * in this child yet). + * @type {number} + */ + let inheritedLevel = null; + /** + * The default level for this logger, if any. If set, this overrides + * `inheritedLevel`. + * @type {number|null} + */ + let defaultLevel = null; + /** + * A user-specific level for this logger. If set, this overrides + * `defaultLevel`. + * @type {number|null} + */ + let userLevel = null; + + /** + * The categories is the inherited hierarchy of this logger, used + * to get parent categories/definitions. + */ + this.categories = parent ? [...parent.categories, name] : []; + + /** + * The storage key is used for storing persisted levels in the + * browser storage. + */ + const hasSymbol = this.categories.find(it => typeof it === 'symbol'); + const storageKey = + hasSymbol ? null : (parent ? + 'loglevel:' + this.categories + .map((category) => category.toString()) + .join(".") : + "loglevel"); + + /** + * The child loggers that are contained within this logger. + */ + const loggers = {}; + + function persistLevelIfPossible(levelNum) { + var levelName = (logMethods[levelNum] || "silent").toUpperCase(); + + if (typeof window === undefinedType || !storageKey) return; + + // Use localStorage if available + try { + window.localStorage[storageKey] = levelName; + return; + } catch (ignore) {} + + // Use session cookie as fallback + try { + window.document.cookie = encodeURIComponent(storageKey) + "=" + levelName + ";"; + } catch (ignore) {} + } + + function getPersistedLevel() { + var storedLevel; + + if (typeof window === undefinedType || !storageKey) return; + + try { + storedLevel = window.localStorage[storageKey]; + } catch (ignore) {} + + // Fallback to cookies if local storage gives us nothing + if (typeof storedLevel === undefinedType) { + try { + var cookie = window.document.cookie; + var cookieName = encodeURIComponent(storageKey); + var location = cookie.indexOf(cookieName + "="); + if (location !== -1) { + storedLevel = /^([^;]+)/.exec( + cookie.slice(location + cookieName.length + 1) + )[1]; + } + } catch (ignore) {} + } + + // If the stored level is not valid, treat it as if nothing was stored. + if (self.levels[storedLevel] === undefined) { + storedLevel = undefined; + } + + return storedLevel; + } + + function clearPersistedLevel() { + if (typeof window === undefinedType || !storageKey) return; + + // Use localStorage if available + try { + window.localStorage.removeItem(storageKey); + } catch (ignore) {} + + // Use session cookie as fallback + try { + window.document.cookie = + encodeURIComponent(storageKey) + + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; + } catch (ignore) {} + } + + function normalizeLevel(input) { + var level = input; + if ( + typeof level === "string" && + self.levels[level.toUpperCase()] !== undefined + ) { + level = self.levels[level.toUpperCase()]; + } + if ( + typeof level === "number" && + level >= 0 && + level <= self.levels.SILENT + ) { + return level; + } else { + throw new TypeError( + "log.setLevel() called with invalid level: " + input + ); + } + } + + /* + * + * Public logger API - see https://github.com/pimterry/loglevel for details + * + */ + + self.name = name; + + self.levels = { + TRACE: 0, + DEBUG: 1, + INFO: 2, + WARN: 3, + ERROR: 4, + SILENT: 5, + }; + + self.methodFactory = factory || defaultMethodFactory; + + self.getLevel = function () { + if (userLevel !== null) { + return userLevel; + } + if (defaultLevel !== null) { + return defaultLevel; + } + if (inheritedLevel === null) { + inheritedLevel = parent ? parent.getLevel() : this.levels.WARN; + } return inheritedLevel; - } - }; - - self.setLevel = function (level, persist) { - userLevel = normalizeLevel(level); - if (persist !== false) { // defaults to true - persistLevelIfPossible(userLevel); - } - - // NOTE: in v2, this should call rebuild(), which updates children. - return replaceLoggingMethods.call(self); - }; - - self.setDefaultLevel = function (level) { - defaultLevel = normalizeLevel(level); - if (!getPersistedLevel()) { - self.setLevel(level, false); - } - }; - - self.resetLevel = function () { - userLevel = null; - clearPersistedLevel(); - replaceLoggingMethods.call(self); - }; - - self.enableAll = function(persist) { - self.setLevel(self.levels.TRACE, persist); - }; - - self.disableAll = function(persist) { - self.setLevel(self.levels.SILENT, persist); - }; - - self.rebuild = function () { - if (defaultLogger !== self) { - inheritedLevel = normalizeLevel(defaultLogger.getLevel()); - } - replaceLoggingMethods.call(self); - - if (defaultLogger === self) { - for (var childName in _loggersByName) { - _loggersByName[childName].rebuild(); - } - } - }; - - // Initialize all the internal levels. - inheritedLevel = normalizeLevel( - defaultLogger ? defaultLogger.getLevel() : "WARN" - ); - var initialLevel = getPersistedLevel(); - if (initialLevel != null) { - userLevel = normalizeLevel(initialLevel); - } - replaceLoggingMethods.call(self); + }; + + self.getLogger = function (...childCategories) { + if (!childCategories.length) { + return self; + } + let newCategories = this.categories ? [...this.categories] : []; + return childCategories.reduce((logger, category) => { + let childLogger = logger.getChildLoggers()[category]; + newCategories.push(category); + if (!childLogger) { + if( typeof category !== 'symbol' && typeof category !== 'string' ) { + throw new Error('Category names must be a symbol or string, but is a '+typeof category); + } + childLogger = new Logger(category, self.methodFactory, logger); + loggers[category] = childLogger; + } + return childLogger; + }, self); + }; + + self.getChildLoggers = function () { + return loggers; + }; + + self.setLevel = function (level, persist) { + userLevel = normalizeLevel(level); + if (persist !== false) { + // defaults to true + persistLevelIfPossible(userLevel); + } + return self.rebuild(); + }; + + self.setDefaultLevel = function (level) { + defaultLevel = normalizeLevel(level); + if (getPersistedLevel()===undefined) { + self.setLevel(level, false); + } else { + self.rebuild(); + } + }; + + self.resetLevel = function () { + userLevel = null; + clearPersistedLevel(); + return self.rebuild(); + }; + + self.enableAll = function (persist) { + self.setLevel(self.levels.TRACE, persist); + }; + + self.disableAll = function (persist) { + self.setLevel(self.levels.SILENT, persist); + }; + + self.rebuild = function () { + inheritedLevel = null; + const result = replaceLoggingMethods.call(self); + if( self.methodFactory && self.methodFactory===(factory || defaultMethodFactory)) { + self.methodFactory = parent && parent.methodFactory || factory || defaultMethodFactory; + } + + for (const child of Object.values(loggers)) { + child.rebuild(); + } + return result; + }; + + // Initialize all the internal levels. + inheritedLevel = null; + const initialLevel = getPersistedLevel(); + if (initialLevel !== undefined) { + userLevel = normalizeLevel(initialLevel); + } + replaceLoggingMethods.call(self); } /* @@ -261,21 +316,6 @@ defaultLogger = new Logger(); - defaultLogger.getLogger = function getLogger(name) { - if ((typeof name !== "symbol" && typeof name !== "string") || name === "") { - throw new TypeError("You must supply a name when creating a logger."); - } - - var logger = _loggersByName[name]; - if (!logger) { - logger = _loggersByName[name] = new Logger( - name, - defaultLogger.methodFactory - ); - } - return logger; - }; - // Grab the current global log variable in case of overwrite var _log = (typeof window !== undefinedType) ? window.log : undefined; defaultLogger.noConflict = function() { @@ -287,10 +327,6 @@ return defaultLogger; }; - defaultLogger.getLoggers = function getLoggers() { - return _loggersByName; - }; - // ES6 default export, for compatibility defaultLogger['default'] = defaultLogger; diff --git a/package.json b/package.json index 565008c..340e2c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "loglevel", "description": "Minimal lightweight logging for JavaScript, adding reliable log level methods to any available console.log methods", - "version": "1.9.2", + "version": "2.0.0-alpha1", "homepage": "https://github.com/pimterry/loglevel", "author": { "name": "Tim Perry", @@ -28,8 +28,8 @@ "scripts": { "lint": "grunt jshint", "test": "grunt test && npm run test-types", - "test-browser": "grunt test-browser", - "test-node": "grunt test-node", + "test-browser": "grunt test-browser --", + "test-node": "grunt test-node --", "test-types": "tsc --noEmit ./test/type-test.ts && ts-node ./test/type-test.ts", "dist": "grunt dist", "dist-build": "grunt dist-build", @@ -46,7 +46,7 @@ "grunt-contrib-connect": "^3.0.0", "grunt-contrib-jasmine": "^4.0.0", "grunt-contrib-jshint": "^3.2.0", - "grunt-contrib-uglify": "^3.4.0", + "grunt-contrib-uglify": "^5.2.1", "grunt-contrib-watch": "^1.1.0", "grunt-open": "~0.2.3", "grunt-preprocess": "^5.1.0", diff --git a/test/.jshintrc b/test/.jshintrc index cfbbfc7..10d3baa 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -2,6 +2,7 @@ "curly": true, "globalstrict": true, "eqeqeq": true, + "es3": true, "immed": true, "latedef": true, "newcap": true, @@ -10,7 +11,6 @@ "undef": true, "boss": true, "eqnull": true, - "es3": true, "globals": { "window": true, "console": true, @@ -30,5 +30,5 @@ "waitsFor": false, "runs": false, "Symbol": false - } + } } diff --git a/test/default-level-test.js b/test/default-level-test.js index 44d2755..8cb3bce 100644 --- a/test/default-level-test.js +++ b/test/default-level-test.js @@ -44,6 +44,7 @@ define(['test/test-helpers'], function(testHelpers) { }); it("saved level is not modified", function (log) { + expect(log).toBeAtLevel("trace"); log.setDefaultLevel("debug"); expect(log).toBeAtLevel("trace"); }); diff --git a/test/multiple-logger-test.js b/test/multiple-logger-test.js index 80905be..da85b1a 100644 --- a/test/multiple-logger-test.js +++ b/test/multiple-logger-test.js @@ -1,7 +1,6 @@ "use strict"; define(['test/test-helpers'], function(testHelpers) { - var describeIf = testHelpers.describeIf; var it = testHelpers.itWithFreshLog; var originalConsole = window.console; @@ -24,9 +23,8 @@ define(['test/test-helpers'], function(testHelpers) { expect(newLogger.methodFactory).toBeDefined(); }); - it("returns loggers without `getLogger()` and `noConflict()`", function(log) { + it("returns loggers without `noConflict()`", function(log) { var newLogger = log.getLogger("newLogger"); - expect(newLogger.getLogger).toBeUndefined(); expect(newLogger.noConflict).toBeUndefined(); }); @@ -37,26 +35,12 @@ define(['test/test-helpers'], function(testHelpers) { expect(logger1).toEqual(logger2); }); - it("should throw if called with no name", function(log) { - expect(function() { - log.getLogger(); - }).toThrow(); - }); - - it("should throw if called with empty string for name", function(log) { - expect(function() { - log.getLogger(""); - }).toThrow(); - }); - it("should throw if called with a non-string name", function(log) { expect(function() { log.getLogger(true); }).toThrow(); expect(function() { log.getLogger({}); }).toThrow(); expect(function() { log.getLogger([]); }).toThrow(); expect(function() { log.getLogger(10); }).toThrow(); expect(function() { log.getLogger(function(){}); }).toThrow(); - expect(function() { log.getLogger(null); }).toThrow(); - expect(function() { log.getLogger(undefined); }).toThrow(); }); // NOTE: this test is the same as the similarly-named test in @@ -104,20 +88,22 @@ define(['test/test-helpers'], function(testHelpers) { expect(newLogger).toBeAtLevel("error"); }); - it("other loggers do not change when the default logger's level is changed", function(log) { + it("other loggers do not change when the default logger's level is changed and they have a level", function(log) { log.setLevel("TRACE"); var newLogger = log.getLogger("newLogger"); + newLogger.setLevel("TRACE"); log.setLevel("ERROR"); expect(newLogger).toBeAtLevel("TRACE"); expect(log.getLogger("newLogger")).toBeAtLevel("TRACE"); }); it("loggers are created with the same methodFactory as the default logger", function(log) { - log.methodFactory = function(methodName, level) { + log.methodFactory = function(_methodName, _level) { return function() {}; }; var newLogger = log.getLogger("newLogger"); + expect(newLogger.methodFactory).toEqual(log.methodFactory); }); @@ -149,6 +135,20 @@ define(['test/test-helpers'], function(testHelpers) { var newLogger = log.getLogger("newLogger"); expect(newLogger).toBeAtLevel("trace"); }); + + it("child loggers inherit parent", function(log) { + log.setLevel(0); + var newLogger = log.getLogger("newLogger"); + expect(newLogger.getLevel()).toBe(log.levels.TRACE); + var newLoggerChild = newLogger.getLogger("child"); + expect(newLoggerChild).not.toBeUndefined(); + expect(newLoggerChild.getLevel()).toBe(log.levels.TRACE); + log.setLevel("debug"); + expect(newLogger.getLevel()).toBe(log.levels.DEBUG); + newLogger.setLevel("warn"); + expect(newLogger.getLevel()).toBe(log.levels.WARN); + expect(newLoggerChild.getLevel()).toBe(log.levels.WARN); + }); }); describe("logger.resetLevel()", function() { @@ -176,7 +176,6 @@ define(['test/test-helpers'], function(testHelpers) { // resetLevel() should not have broken inheritance. log.setLevel("DEBUG"); - log.rebuild(); expect(newLogger).toBeAtLevel("DEBUG"); }); @@ -193,7 +192,6 @@ define(['test/test-helpers'], function(testHelpers) { // resetLevel() should not have broken inheritance. log.setLevel("DEBUG"); - log.rebuild(); expect(newLogger).toBeAtLevel("DEBUG"); }); @@ -228,19 +226,16 @@ define(['test/test-helpers'], function(testHelpers) { window.console = originalConsole; }); - it("rebuilds existing child loggers", function(log) { + it("rebuilds existing child loggers", function (log) { log.setLevel("TRACE"); var newLogger = log.getLogger("newLogger"); expect(newLogger).toBeAtLevel("TRACE"); log.setLevel("ERROR"); - expect(newLogger).toBeAtLevel("TRACE"); - - log.rebuild(); expect(newLogger).toBeAtLevel("ERROR"); }); - it("should not change a child's persisted level", function(log) { + it("should not change a child's persisted level", function (log) { testHelpers.setStoredLevel("ERROR", "newLogger"); log.setLevel("TRACE"); @@ -251,7 +246,7 @@ define(['test/test-helpers'], function(testHelpers) { expect(newLogger).toBeAtLevel("ERROR"); }); - it("should not change a child's level set with `setLevel()`", function(log) { + it("should not change a child's level set with `setLevel()`", function (log) { log.setLevel("TRACE"); var newLogger = log.getLogger("newLogger"); expect(newLogger).toBeAtLevel("TRACE"); @@ -261,7 +256,7 @@ define(['test/test-helpers'], function(testHelpers) { expect(newLogger).toBeAtLevel("DEBUG"); }); - it("should not change a child's level set with `setDefaultLevel()`", function(log) { + it("should not change a child's level set with `setDefaultLevel()`", function (log) { log.setLevel("TRACE"); var newLogger = log.getLogger("newLogger"); expect(newLogger).toBeAtLevel("TRACE"); diff --git a/test/node-integration.js b/test/node-integration.js index ee1bc04..e0f3f77 100644 --- a/test/node-integration.js +++ b/test/node-integration.js @@ -44,4 +44,29 @@ describe("loglevel included via node", function () { expect(logger1).not.toEqual(logger2); expect(logger2.getLevel()).toEqual(defaultLevel); }); + // Supports getting child nodes + it("supports child logger inheritance", function() { + var log = require('../lib/loglevel'); + var logger1 = log.getLogger('one'); + log.setLevel("debug"); + expect(logger1.getLevel()).toBe(log.levels.DEBUG); + logger1.setLevel("info"); + expect(logger1.getLevel()).toBe(log.levels.INFO); + + var logger12 = logger1.getLogger('two'); + expect(logger12.getLevel()).toBe(log.levels.INFO); + + logger1.resetLevel(); + log.setLevel("warn"); + expect(log.getLevel()).toBe(log.levels.WARN); + expect(logger12.getLevel()).toBe(log.levels.WARN); + }); + + it("same logger is returned for combined and separate retrieves", function() { + var log = require('../lib/loglevel'); + var parent = log.getLogger("parent"); + var child = parent.getLogger("child"); + var parentChild = log.getLogger("parent", "child"); + expect(child).toBe(parentChild); + }); }); diff --git a/test/test-helpers.js b/test/test-helpers.js index f517d01..22e1d51 100644 --- a/test/test-helpers.js +++ b/test/test-helpers.js @@ -28,6 +28,9 @@ define(function () { self.toBeAtLevel = function toBeAtLevel() { return { compare: function (log, level) { + if( !log.levels ) { + throw new Error("Can't read levels from "+log); + } var expectedWorkingCalls = log.levels.SILENT - log.levels[level.toUpperCase()]; var realLogMethod = window.console.log; var priorCalls = realLogMethod.calls.count(); @@ -158,6 +161,9 @@ define(function () { if (self.isLocalStorageAvailable()) { self.setLocalStorageStoredLevel(level, name); } + if( !self.isCookieStorageAvailable() && !self.isLocalStorageAvailable()) { + throw new Error('No storage available'); + } }; self.clearStoredLevels = function clearStoredLevels() {