diff --git a/.gitignore b/.gitignore
index 66fd66c..fb12865 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
-node_modules
-npm-debug.log*
+/dist
+/node_modules
+/ulog.min.js
+/.nyc_output
+/test.min.js
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9308537..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-language: node_js
-node_js:
- - "node"
- - "6"
- - "5"
- - "4"
- - "0.10"
-# https://github.com/greenkeeperio/greenkeeper-lockfile
-before_install:
-- npm install -g npm@5
-- npm install -g greenkeeper-lockfile@1
-before_script: greenkeeper-lockfile-update
-after_script: greenkeeper-lockfile-upload
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..efe9b30
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Stijn de Witt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/LICENSE.md b/LICENSE.md
deleted file mode 100644
index 1dec0b0..0000000
--- a/LICENSE.md
+++ /dev/null
@@ -1,161 +0,0 @@
-## creative commons
-
-# Attribution 4.0 International
-
-Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
-
-### Using Creative Commons Public Licenses
-
-Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
-
-* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).
-
-* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).
-
-## Creative Commons Attribution 4.0 International Public License
-
-By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
-
-### Section 1 – Definitions.
-
-a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
-
-b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
-
-c. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
-
-d. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
-
-e. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
-
-f. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
-
-g. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
-
-h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.
-
-i. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
-
-j. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
-
-k. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.
-
-### Section 2 – Scope.
-
-a. ___License grant.___
-
- 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
-
- A. reproduce and Share the Licensed Material, in whole or in part; and
-
- B. produce, reproduce, and Share Adapted Material.
-
- 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
-
- 3. __Term.__ The term of this Public License is specified in Section 6(a).
-
- 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
-
- 5. __Downstream recipients.__
-
- A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
-
- B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
-
- 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
-
-b. ___Other rights.___
-
- 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
-
- 2. Patent and trademark rights are not licensed under this Public License.
-
- 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.
-
-### Section 3 – License Conditions.
-
-Your exercise of the Licensed Rights is expressly made subject to the following conditions.
-
-a. ___Attribution.___
-
- 1. If You Share the Licensed Material (including in modified form), You must:
-
- A. retain the following if it is supplied by the Licensor with the Licensed Material:
-
- i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
-
- ii. a copyright notice;
-
- iii. a notice that refers to this Public License;
-
- iv. a notice that refers to the disclaimer of warranties;
-
- v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
-
- B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
-
- C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
-
- 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
-
- 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
-
- 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License.
-
-### Section 4 – Sui Generis Database Rights.
-
-Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
-
-a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;
-
-b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and
-
-c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
-
-For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
-
-### Section 5 – Disclaimer of Warranties and Limitation of Liability.
-
-a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__
-
-b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__
-
-c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
-
-### Section 6 – Term and Termination.
-
-a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
-
-b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
-
- 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
-
- 2. upon express reinstatement by the Licensor.
-
- For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
-
-c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
-
-d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
-
-### Section 7 – Other Terms and Conditions.
-
-a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
-
-b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
-
-### Section 8 – Interpretation.
-
-*a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
-
-b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
-
-c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
-
-d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
-
-```
-Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
-
-Creative Commons may be contacted at creativecommons.org
-```
\ No newline at end of file
diff --git a/README.md b/README.md
index 80c36d5..98976d4 100644
--- a/README.md
+++ b/README.md
@@ -1,207 +1,291 @@
-# ulog v2.0.0-beta.7
-### Microscopically small universal logging library
+# ulog v2.0.0-beta.8
+### The Universal Logger
[![npm](https://img.shields.io/npm/v/ulog.svg)](https://npmjs.com/package/ulog)
-[![license](https://img.shields.io/npm/l/ulog.svg)](https://creativecommons.org/licenses/by/4.0/)
+[![license](https://img.shields.io/npm/l/ulog.svg)](https://opensource.org/licenses/MIT)
[![travis](https://img.shields.io/travis/Download/ulog.svg)](https://travis-ci.org/Download/ulog)
-[![greenkeeper](https://img.shields.io/david/Download/ulog.svg)](https://greenkeeper.io/)
![mind BLOWN](https://img.shields.io/badge/mind-BLOWN-ff69b4.svg)
.
-![logo](https://cdn.rawgit.com/download/ulog/1.0.0/ulog.png)
+![logo](https://unpkg.com/ulog@2.0.0-beta.8/ulog.png)
-`ulog` started as [Picolog](https://npmjs.com/package/picolog), the smallest
-possible logging library I could build that could act as a drop-in replacement
-for the native console (which varied wildly across environments back then).
-Compared to `picolog`, `ulog` adds some features from
-[debug](https://npmjs.com/package/debug) that I missed. Even with these extra
-features, `ulog` is still **very** small, weighing in just over 1 kB minified
-and gzipped.
+## The logger for applications
-## ulog v2
+`ulog` is *the* logger for Javascript applications. It's universal, meaning it runs everywhere. You can use `ulog` in your Express server application running on Node JS just as well as in your React single page application running in the browser. It just works.
-In version 2, `ulog` is more compatible with `debug` than ever before.
-The biggest change is that the logger objects returned from `ulog()` are no
-longer objects. They are functions, just like in `debug`. But unlike the
-functions returned by `debug`, those from `ulog` are *also* fully functional
-logger objects will all methods we have on the native console and more.
+![screenshot](https://unpkg.com/ulog@2.0.0-beta.8/screenshot.jpg)
-For most code using `ulog` v1 or `debug`, upgrading to `ulog` v2 should be
-relatively painless. The only backward-incompatibility this version introduces
-compared to `ulog` v1 is the fact that the statement `typeof log == 'object'`
-is no longer true for `ulog` v2 loggers. For code
-[using `ulog` as a replacement for `debug`](#using-ulog-as-a-replacement-for-debug),
-the upgrade path should be easier than for `ulog` v1, because of the improved
-compatibility in v2.
-### beta
-Please try out the latest v2 beta. To do so, you need to add a beta flag to the
-npm install command:
+## Install
```sh
-npm install --save ulog@beta
+npm i -S anylogger ulog
```
-I do recommend trying out the beta, but if you just need the tried and tested
-v1, switch branch to the latest 1.x tag for the docs for that release and just
-don't add the beta flag to your npm install command.
+### Add to entry point
+In the entry point of your application import `ulog`:
+*index.js*
+```js
+import "ulog"
+```
-## Install
-I recommend installing `ulog` with NPM:
+## Use
-```sh
-npm install --save ulog
+In your code, import `anylogger` and use it to create loggers and do logging:
+
+```js
+import anylogger from 'anylogger'
+const log = anylogger('my-app')
+log('Logging is easy!')
```
-You should also be able to use e.g. Yarn etc.
+This way, your code is decoupled from `ulog` and if you ever want to switch to another logging library, you will be able to do so without having to change any of that code.
+> **anylogger**
+> [`anylogger`](https://npmjs.com/package/anylogger) is a logging facade that allows code to use logging without getting coupled to a specific logging system. You can use that code with any logging system out there.
-## Download
-If you want the file for the browser to include in your project yourself,
-you can download it from here.
+## The logger for libraries
-* [ulog.umd.js](https://cdn.rawgit.com/download/ulog/2.0.0-beta.7/ulog.umd.js) (~3kB, source)
-* [ulog.min.js](https://cdn.rawgit.com/download/ulog/2.0.0-beta.7/ulog.min.js) (~2kB, minified)
+When we write a library to be used by other libraries or applications, we typically don't want to decide which logger these libraries or applications should use. Instead, we want to use whatever logging framework the client code is using.
+### Install ulog as a dev dependency
-## Include in your app
+We can have the cake and eat it to. We can decouple our library from `ulog`
+for the client code, while still using it in development. To do so, we install
+`anylogger` as a regular dependency and `ulog` as a dev dependency:
-I recommend using `require` or `import` in your projects and bundling a browser
-version with Webpack.
+```sh
+npm install --save anylogger && npm install --save-dev ulog
+```
-### require
-*my-module.js*
+In our tests:
+
+*test.js*
```js
-var ulog = require('ulog')
-var log = ulog('my-module')
-// or, shorthand
-var log = require('ulog')('my-module')
+ import `ulog`
```
-### import
-*my-module.js*
-```sh
-import ulog from 'ulog'
-const log = ulog('my-module')
+In our library code:
+
+*my-lib.js*
+```js
+ import anylogger
+ const log = anylogger('my-lib')
+ log('Logging is easy')
```
-### Script tag
+
+## Script tag
+
If you want, you can import `ulog` with a script tag:
```html
-
+
+
```
*myscript.js*
```js
- var log = ulog('my-module')
+ var log = anylogger('my-module')
+ log('Logging is easy!')
```
+Or, if you want the full version:
-## Logging methods
-
-`ulog` defines 6 logging methods, which correspond with available log levels:
-```js
-log.error('This logs an ERROR message')
-log.warn('This logs a WARN message')
-log.info('This logs an INFO message')
-log.log('This logs a LOG message')
-log.debug('This logs a DEBUG message')
-log.trace('This logs a TRACE message')
+```html
+
```
-Note how these methods are all from the
-[standard console API](https://console.spec.whatwg.org/).
-Whenever possible, calls to these methods go directly to the corresponding
-native method.
-> On some systems, `debug()` is not natively available.
-In addition, ulog v2 defines some additional methods that map to native
-functions and are there to increase compatibility with other popular logging
-libraries:
+## Download
+
+If you want the file for the browser to include in your project yourself, you can download it from here.
+
+* [ulog.min.js](https://unpkg.com/ulog@2.0.0-beta.8) (~2.8kB minified and gzipped)
+* [full.min.js](https://unpkg.com/ulog@2.0.0-beta.8/full.min.js) (~3.5kB minified and gzipped)
+
+
+## Why `ulog`
+
+The two most popular logging libraries on NPM at the moment are [`debug`](https://npmjs.com/package/debug) and [`loglevel`](https://npmjs.com/package/loglevel), with 85.6M and 8.8M weekly downloads, respectively. They are both great loggers, but neither of them completely satisfied my requirements for a logging library.
+
+`debug` has a simple API and is configurable on both Node JS via environment variables and in the browser via localStorage, though not dynamic, requiring a restart before changes take effect. It's simplicity makes it an excellent choice for debug logging (as it's name implies), but it lacks support for log levels, so if you want to log error messages for example, you end up needing another library for that. It offers nicely formatted (and even colored!) log messages, but because of that mangles the call stack, which is a huge downside in the browser imho. It offers some rudimentary support for configuring the destination of log messages, allowing you to send logging to a file instead of the console. Weighing in at 3.1kB minified and gzipped, it's a bit large for the feature set that it offers. And it's not very extensible either, basically being a monolith.
+
+`loglevel` does offer log levels. These names start to make sense now right? It's API is also simple, but it has a global logger which is a downside imho as it allows for 'anonymous' logging, making it less suitable for debug logging. Also, though it's API is modeled after that of the console (a good thing imho), it does not have the `console.log` method, so it's not quitte a drop-in replacement for the console. It's configurable via localStorage but not via environment variables and just like `debug` requires a restart before configuration changes take effect. By default, it leaves your call stack alone, making the filename/line number entries in the browser console that much more useful. It does not offer alternative log destinations or formatters out of the box. It can be extended via plugins and there are some good plugins out there, but it's base feature set is coded as a monolith, so you cannot easily remove features. You probably won't have to though as it weighs only 1.4kB.
+
+Both these loggers lack the ability to configure the logger from the querystring, which I found to be a very desirable feature for web development as it allows you to create a URL that has the log config embedded, which you can then send to other developers or users etc. E.g: `https://example.com/page?log=debug`.
+
+Finally, both these loggers couple your code to the logging library. What I mean by that is that when you install a library using one of these loggers, that logger gets pulled in to your codebase. The downside of this is that in a more complex application using many libraries, you often end up with **both** loggers included in your app. In fact you often end up with 3 or even 4 logging libraries... which is a bit wasteful!
+
+What I want is a logging library that combines the best aspects of both these loggers and adds the features that I miss in both, without coupling the client code to it. Specifically here are the features that I want in my ultimate logging library:
+
+* Simple API (nothing can beat `debug`'s single log function, but `ulog` gets close)
+* Supports all `console` log methods
+* No 'anonymous' logging
+* Configurable at runtime (without requiring a restart)
+* Accepts configuration from the querystring
+* Leaves the call stack alone
+* Formatted log messages
+* Configurable log output and formatting options built in
+* Extensible
+* Decoupled from client code
+* Compact
+
+`ulog` is my attempt at building this library. It's base API is compatible with that of `debug` and `loglevel` and with the console, making it a drop-in replacement for all of these in many cases. It does not support 'anonymous' logging, so no hunting down where a log message came from and no log messages that cannot be suppressed. And it has a configuration mechanism that is compatible with that of `debug`, but that is more powerful and is monitored for changes at runtime. It accepts configuration from the querystring allowing you to craft URLs with log config embedded in it. And even though it uses a simple formatter by default, I found a way to do this without mangling the call stack, so the filename/line number entries in the browser console remain unharmed. You can specify where the log output should go and where it should drain. It's completely modular, so you can not only easily add features through 'mods', but you can actually even drop features you don't need by not loading the mods those features are in. It has native `anylogger` support, decoupling the client code from the logger. And even with `anylogger` included, it still weighs just 2.8kB. Substantially smaller than `debug`, but offering way more features.
+
+
+## API
+
+`ulog` is very natural to use:
```js
-log.verbose('This logs a message at level LOG using method log()')
-log.silly('This logs a message at level TRACE using method log()')
+var log = require('anylogger')('my-module') // same as with `debug`
+
+log('A log message') // same as with `debug`
+log('info', 'An info message') // not possible with `debug`
+log('warn', 'A warning message') // not possible with `debug`
+log.info('Starting...') // same as with `loglevel` or console
+log.log('Yeah!') // same as with console
+log.error('Something went wrong', new Error('Oh no!'))
+if (log.enabledFor('warn')) {
+ log.warn(expensiveArguments())
+}
```
+> Note that in the code above, we import `anylogger` and not `ulog`. This way the client code is decoupled from the logger.
-Calls to all these methods will be direct calls to the native logging
-functions if available and enabled. This means that there will be no code in
-the callstack that is not yours. This helps with debugging because browsers
-tend to show the file name and line number of the point the log call was made
-from. When logging using these methods, line numbers shown in the console will
-be from your code, not from some wrapper function.
+`ulog` inherits it's API from `anylogger`. If you are able to restrict yourself to the [Anylogger API](https://www.npmjs.com/package/anylogger#anylogger-api), your code will be framework independent and will work with any supported logging library.
-> In recent years, browsers have been adding features to ignore some parts of the call stack
+Note that any logging code written for either `debug`, `loglevel` or the console should be able to do it's logging just like it did before, but now using a `ulog` logger instead. This backward compatibility should make migrating from any of these to `ulog` very easy. And because this is just the `anylogger` API, you should even be able to migrate back to `debug` or `loglevel` without any changes at all, by just including the right adapter in your entry point. Of course once you get used to `ulog`, you will never want to go back! :p
-## log()
+## Configure
-In v2, the logger objects returned by `ulog` are functions. We can call them to
-log messages, like so:
+`ulog` features a simple, yet powerful and flexible configuration mechanism. On Node JS, we can configure `ulog` via program arguments, environment variables or a configuration file. On browsers, we use querystring arguments or localStorage. On both platforms, the configuration is monitored and changes to it are picked up by `ulog` at runtime without the need to restart the application.
-```js
-log('This logs a DEBUG message')
+`ulog`'s configuration mechanism is an extension to that of [`debug`](https://npmjs.com/package/debug) and is compatible with it. `debug` is one of the most popular logging packages in the NPM ecosystem, with tens of thousands of packages depending on it, so having `ulog`'s configuration mechanism be compatible with it makes for a very smooth migration path. If your app or library is currently using `debug`, you should be able to replace it with `ulog` with no or only minor changes.
+
+We configure `ulog` by adjusting configuration options. Those options include (but are not limited to):
+* [`log`](#config_option_log): The main setting to control logger's levels with
+* [`debug`](#config_option_debug): For compatibility with `debug`
+* [`log_config`](#config_option_log_config): To specify the configuration file (Node JS only)
+* [`log_output`](#config_option_log_output): To configure where logging should go (defaults to `console`)
+* [`log_drain`](#config_option_log_drain): To configure where logs should drain (defaults to `drain`)
+* [`log_format`](#config_option_log_format): To configure the format for log messages
+
+### Via program arguments
+On Node JS we can pass log configuration options as program arguments:
+```sh
+node ./myapp log=debug
+```
+This should be helpful when making CLI applications. These strongly rely on console messages, but are also often used in scripted setups where we would actually want to suppress that logging. Don't go and build in all kinds of custom methods to configure the logging but just use `ulog` and rely on it's powerful configuration mechanism instead.
+
+### Via environment variables
+On Node JS we can pass log configuration options via environment variables:
+```sh
+log=debug node ./myapp
```
-By default, this will log a message at level `DEBUG`. But we can change that
-by specifying the desired log level as the first argument:
+### Via a config file
+On Node JS we can place our log configuration in a file that will be read at startup and monitored for changes at runtime:
+*./log.config*
+```
+log=debug
+```
+### Via querystring parameters
+In browsers, we can pass log configuration options as querystring parameters in the URL:
+```
+https://example.com/page?log=debug
+```
+
+### Via localStorage
+In browsers, we can place our log configuration in localStorage and it will be read at startup and monitored for changes at runtime:
```js
-log('info', 'This logs an INFO message')
+localStorage.setItem('log', 'debug')
```
+### Log configuration syntax
+`debug` has a simple but powerful configuration mechanism. You set an environment variable or localStorage option named `DEBUG` and you assign it a value that expresses which loggers to enable. E.g.:
-## Formatting
+```sh
+DEBUG=test,my:*,-my:lib
+```
+The format is basically a comma-separated list of logger names, using the asterisk as a wildcard character and optionally negating the expression by preceding it with a minus sign. So the expression above includes `test` and `my:*` loggers, except for `my:lib` which is excluded.
-In the section about [logging methods](#logging-methods) we discussed how calls to
-these methods will be direct native calls whenever possible. This is great for our
-line numbers. But this also means `ulog` can't do any formatting on these log calls.
-And formatting log messages can be a great feature.
+`ulog` extends this configuration mechanism. With `debug`, you can only turn loggers on and off, but `ulog` allows for a much more varied range of options. This is achieved by extending the configuration syntax so it also accepts the value applicable for the loggers matching the expression. Also we allow a semicolon-separated list of such expression=value pairs. For example, to set the logger `test` to debug and `my:*` loggers except for `my:lib` to info, we could write:
-So for v2, calls to the new logger function will be intercepted, so we can do some
-formatting if we want. No formatting is applied by default, but you can easily add
-your own formatter like this:
+```sh
+log=test=debug;my:*,-my:lib=info
+```
-```js
-import ulog from 'ulog'
+If an option only contains a value, `ulog` implicitly adds `*` as the expression. So we can write:
+```sh
+log=info
+```
+and it's equivalent to
+```sh
+log=*=info
+```
+We can even combine this. So we could write:
+```sh
+log=info;my:*=debug
+```
+and it will set the level for all loggers to info, except for the ones starting with `my:`, which are set to debug.
+
+A special case is the config option [debug](#config_option_debug), which is designed to be compatible with `debug` so code using `ulog` will react to that setting in the same way.
-function myFormat(logger, method, args){
- // add the logger name to the call
- args.unshift(logger.name + ': ')
-}
-ulog.formats.push(myFormat) // from here on, our format is used
+### Config option `log`
+Configure the levels loggers should be filtered at.
+```sh
+log=test=debug;my:*,-my:lib=info
```
-Any code calling the logger function will have the formatting applied:
+### Config option `debug`
+Enables debug mode for the selected loggers.
+```sh
+debug=test,my:*,-my:lib
+```
+This option is compatible with that of `debug`.
-```js
-import ulog from 'ulog'
-const log = ulog('my-module')
-log.level = log.DEBUG
-log('Hello, World') // > 'my-module: Hello World'
+### Config option `log_config`
+Specify the path to the log configuration file, absolute or relative to the current working directory. Node JS only. Default to `./log.config`. This option does not support expressions.
+```sh
+log_config=./my.log.config
```
-Calls to native methods are not formatted:
+### Config option `log_output`
+Specify the name of the output logs should be written to. Defaults to `'console'`.
+```sh
+log_output=my:*,-my:lib=console
+```
-```js
-log.info('Hello, World') // > 'Hello World'
+### Config option `log_drain`
+Specify the name of the output logs should be drained to. Defaults to `drain`.
+When log messages are filtered out, they are sent to the drain instead of to the normal output. The default `drain` output is just a noop, but you could override this to send them to a separate file for example.
+```sh
+log_drain=my:*,-my:lib=console
+```
+
+### Config option `log_format`
+Specify the named format to use, or a custom format string. Defaults to `simple`.
+```sh
+log_format=simple;some-lib=none;my:*,-my:lib={date}{time}{lvl}{name}{message}{perf}
```
+This sets `simple` as the default format, while assigning `none` to `some-lib` and a custom format string to all loggers starting with `my:` except for `my:lib`.
-Adding the option to add formatters came at a cost of ~50 bytes on the total
-file but I feel it is worth it.
+For more details, refer to the [section on formatting](#formatting)
## Logging levels
-ulog defines 6 logging levels, which correspond with the natively available
-logging functions:
+`anylogger` defines 6 logging levels, which correspond with the natively available
+logging functions on the console. `ulog` creates constants for these levels on all loggers:
```js
log.ERROR // 1
@@ -212,14 +296,26 @@ log.DEBUG // 5
log.TRACE // 6
```
-In addition, there is a 7th level that completely disables all logging:
+In addition, `ulog` adds constants for pseudo-levels that enable or completely disable all logging:
```js
+log.ALL // 9007199254740991 (Number.MAX_SAFE_INTEGER)
log.NONE // 0
```
-### log.level
-To get or set the log level, we use the `log.level` property:
+### `log.enabledFor`
+`anylogger` defines `log.enabledFor` and `ulog` implements it by checking the logger's current log level and whether it's in debug mode. Normally, you should not have to use this method, unless you are doing some expensive calculations only to write log output. In such a case you can write:
+
+```js
+import anylogger from 'anylogger'
+const log = anylogger('my-app')
+if (log.enabledFor('info')) {
+ log.info(calculateResults())
+}
+```
+
+### `log.level`
+`ulog` adds a property `level` to each logger that is a numeric representation of the current log level.
```js
if (log.level >= log.INFO) {
@@ -232,247 +328,269 @@ log.level = log.NONE
log.error('Logging is completely disabled.')
```
-> In general, code should not set the log level directly, but instead rely on
-> the host environment for the log level. See the sections below about the
-> [default log level](#default-log-level), the related
-> [startup parameter](#changing-the-log-level-via-a-startup-parameter) and
-> [debug mode](#debug-mode).
+> In general, code should not set the log level directly, but instead should rely on the host environment for the log level. See the section on [configuring ulog](#configure).
-
-## Default log level
-
-I've found that it makes sense to have different default log levels in
-the browser and in Node. In Node, logging is often the only UI we have
-available and we (the devs) are the only ones that will see that logging.
-In the browser, we have an alternative UI (the webpage itself), so
+### Default log level
+I've found that it makes sense to have different default log levels in the
+browser and in Node. In Node, logging is often the only UI we have available
+and we (the devs/admins) are the only ones that will see that logging.
+In the browser, we have an alternative UI (the webpage itself), so
logging will be less useful for normal users.
-### In Node
-In Node, the log level defaults to `log.INFO`. This allows you to use
-INFO, WARN and ERROR when informing the user of stuff that happened.
-With Picolog I found I had to resort to logging informational messages
-at WARN because I wanted them to be visible with the default settings
-and this did not feel right.
+#### In Node
+In Node, the log level defaults to `info`. This allows you to use
+`info`, `warn` and `error` when informing the user of stuff that happened.
-### In the browser
-In the browser the log level defaults to `log.WARN`. This means INFO
-messages will be excluded, but for most users these messages won't be
-relevant anyway and we can easily change the log level in the browser
-using a query parameter in the URL or localStorage (see next section).
+#### In the browser
+In the browser the log level defaults to `warn`. This means `info`
+messages will be excluded, but for most users these messages won't be
+relevant anyway.
+> Attention! Chrome these days has it's own level filter and by default, debug messages are filtered away.
-## Changing the log level
-Changing the log level can be done in two ways:
- 1. Programmatically, through the API
- 2. Via a startup parameter
+## Debug mode
-### Changing the log level via the API
-We can set the global log level directly on the `ulog` function:
+Debug mode is a feature that `ulog` copied from `debug` and it responds to the
+same [config option](#config_option_debug). Setting a logger to debug mode
+effectively means forcing it's log level to be at least debug:
-```js
-import ulog from 'ulog'
-ulog.level = ulog.DEBUG
+```sh
+DEBUG=my:app
```
-But this is not recommended for most code! It is much better to set the
-log level directly on a single module and leave the global settings alone.
-
-We can set the level of a specific module in much the same way:
```js
-import ulog from 'ulog'
-const log = ulog('my-module')
-log.level = log.DEBUG
+import anylogger from 'anylogger'
+const log = anylogger('my-app')
+log('Hi!') // is output because logger is in debug mode
```
-For most code, it is best to treat `log.level` as if it was read-only,
-and rely upon the environment to set the log level, using the startup
-parameters described below.
+## Outputs
-### Changing the log level via a startup parameter
-We can set the initial global log level with a startup parameter. In
-Node we use an environment variable, whereas in the browser we use a
-querystring parameter in the url or a key in localStorage.
+Outputs is a feature that separates `ulog` from `debug` and `loglevel`. This corresponds with what other libraries sometimes refer to as 'appenders'. In `ulog`, where messages are going is completely configurable at runtime. You can even configure where discarded messages are going!
-#### Environment variable
-Set the environment variable `LOG` to the desired log level.
+By default, all log methods on a logger are associated with one of two outputs, `output` and `drain`. To configure these, two properties are added on each logger:
+
+* `log.output`, defaults to `'console'`
+* `log.drain`, defaults to `'drain'`
+
+These correspond with config options [`log_output`](#config_option_log_output) and [`log_drain`](#config_option_log_drain) respectively.
+
+By using a separate output for the drain, we can override the default behavior of using noops for all log levels that are outside of the active levels. We could for example send *all* logging to a database and only later filter it, when we display it for example.
+
+When the logger is created, each log method is sent either to the `output`, or to the `drain`, based on the current log level for that logger and whether that logger is in debug mode.
+
+To configure the output for a logger, we assign the name of the output to use
+to the relevant logger:
```sh
-$ LOG=info && node ./myapp.js
-```
-or, in Windows:
-```sh
-$ set LOG=INFO && node ./myapp.js
+log_output=console
```
-#### Querystring parameter
-Add the parameter `log` to the querystring of the page:
+By default, the following outputs are included:
+
+### Output `console`
+This actually is the native console object. Using the native console directly is what allows us to leave the call stack intact in the browser developer tools.
-`http://www.example.com/?log=debug`
+### Output `drain`
+This is just an object with a noop `log` function
-Both the uppercase and lowercase names of the log levels work,
-as well as their numerical values.
+### Custom outputs
+The default outputs are not very special, but the entire machinery is in place for you to easily add any custom outputs you could ever need. You can define additional outputs by making `ulog` use a mod with an `outputs` key:
-#### localStorage key
-Add the key `log` to the localStorage of the page:
+*index.js*
```js
-localStorage.setItem('log', 'info')
+import ulog from 'ulog'
+ulog.use({
+ outputs: {
+ custom: { log: function(){
+ var args = [].slice.call(arguments)
+ args.shift('Custom!!')
+ console.log.apply(console, args)
+ }}
+ }
+})
```
-then refresh the page.
-Both the uppercase and lowercase names of the log levels work,
-as well as their numerical values.
+This way you can add outputs that send log messages to memory, a file, localStorage, a database etc etc.
-## Debug mode
+## Formatting
-In addition to setting the global log level and setting the log levels of
-individual loggers, you can also enable debug mode for a group of loggers.
-When in debug mode, the logger's individual log level will only be used if
-it is set to TRACE. Otherwise it will be ignored and the module will behave
-as if its level was set to DEBUG.
+Formatting is another feature that separates `ulog` from `debug` and `loglevel`. `debug` has formatting, but it is hardcoded and messes up the call stack and there is not much you can do about it. `loglevel` does not mess up the call stack, but it also has no formatting at all out of the box. If we are giving all loggers names, it would at least be good to see those names in the log output right? How else do we know which loggers to enable and disable?
-### Enabling debug mode via the API
-The `ulog` function has 3 methods that allow us to control debug mode:
-* `ulog.enable(str)` - Enables debug mode for the loggers listed in `str`
-* `ulog.enabled(name)` - Tests whether the logger is currently in debug mode
-* `ulog.disable()` - Disables debug mode for all loggers
+Turns out you can have the cake and eat it to. We can have formatted messages that do not mess up the call stack. The way we do it is by `bind`ing the extra information into the log methods at logger creation/extension time. Granted, you cannot do this with dynamic info that changes on every call, but the logger name is static for each log method so we can add this without disrupting the call stack.
-The `*` character may be used as a wildcard. Suppose for example your module has
-loggers named "connect:bodyParser", "connect:compress" and "connect:session".
-Instead of listing all three with `connect:bodyParser,connect:compress,connect:session`,
-you may simply use `connect:*`.
+Below is a list of formats that come bundled with `ulog` and it is mentioned for each of them whether it leaves the call stack intact.
-You can also exclude specific loggers by prefixing them with a "-" character.
-For example, `*,-connect:*` would include all debuggers except those
-starting with "connect:".
+### Format `none`
+This is a noop format that does not do anything. Leaves the call stack intact.
-```js
-// given modules app, lib, connect:bodyParser, connect:compress and connect:session
-ulog.enable('app,connect:*')
-ulog.enabled('app') // true
-ulog.enabled('lib') // false
-ulog.enabled('connect:compress') // true
-ulog.enable('app,connect:*,-connect:compress') // negation symbol means 'except'
-ulog.enabled('app') // true
-ulog.enabled('lib') // false
-ulog.enabled('connect:compress') // false
-ulog.disable()
-ulog.enabled('app') // false
-```
-
-### Enabling debug mode via a startup parameter
-We can enable debug mode for some loggers using a startup parameter. On Node
-we use environment variables and on the browser we use querystring parameters
-or localStorage keys.
-
-#### Environment variable
-Set the environment variable `DEBUG` to the string with logger names:
+### Format `simple`
+Adds the logger name (and level on Node JS) into the log methods. Leaves the call stack intact.
+
+Output looks like this on Node JS:
-```sh
-$ DEBUG=my-module && node ./myapp.js
```
-or, in Windows:
-```sh
-$ set DEBUG=my-module && node ./myapp.js
+i my:logger This is an INFO message
```
-#### Querystring parameter
-Add the parameter `debug` to the querystring of the page:
-
-`http://www.example.com/?`**`debug=my-module`**
+and like this in browsers:
-#### localStorage key
-Add the key `debug` to the localStorage of the page:
-```js
-localStorage.setItem('debug', 'my-module')
```
-then refresh the page.
+my:logger This is an INFO message
+```
+> Most browsers already do an excellent job in showing the level a message was logged at, so here we don't add the level indicator.
-## Using ulog as a replacement for debug
+### Format `json`
+Logs messages as JSON strings. Messes up the call stack.
-If you are using `ulog` as a replacement for `debug`, you may want to try the
-new endpoint `ulog/debug` introduced in v2:
+Sometimes we prefer that all data is logged in some structured format. `json` is an example of how to do that.
-```js
-import ulog from 'ulog/debug'
-const log = ulog('my-module')
-const other = ulog('other-module')
-log('Message') // > '20:15 my-module Message'
-other('Another message') // > '20:15 other-module Another message'
-```
+Output looks like this:
-It is not doing that much at the moment but I plan to make this a
-compatibility endpoint for debug.
+```
+{"time":"2020-11-14T14:42:29.839Z","name":"my:logger","message":["This is a WARN message"],"level":2,"levelName":"warn","log_output":"console","log_drain":"drain","log_level":5,"log_format":"json"}
+```
-You can also use this as a Webpack alias, to make all modules that use `debug`
-switch to `ulog/debug` instead. Compatibility is not guaranteed though so your
-mileage may vary:
+### Make your own custom format
+You can easily add your own custom format to the list above. To do so, just `ulog.use` a mod with a `formats` property including your format, like so:
+*index.js*
```js
-{
- resolve: {
- alias: {
- 'debug': 'ulog/debug'
- }
+import ulog from 'ulog'
+ulog.use({
+ formats: {
+ cool: function(logger) {
+ // will be called on logger creation/extension
+ // replace the default log methods with formatters
+ // use bind so we leave the call stack intact.
+ // only works for static info like our 'Cool!' message
+ for (var level in this.levels) logger[level] = logger[level].bind(logger, 'Cool!')
+ // don't forget to format the drain as well!
+ logger.drain = logger.drain.bind(logger, 'Cool!')
}
-}
+ }
+})
```
+### Custom format strings using kurly
+To keep the base bundle size down, `ulog` only bundles the formats above in the default build. But you can very easily make `ulog` understand custom format strings. For example you could set:
+
+```sh
+log_format={date}{time}{lvl}{name}{message}{perf}
+```
+To get log output looking like this:
-## Using ulog as a polyfill
+```
+2020/10/14 15:46 i my:logger This is an INFO message
+```
-ulog supports all functions in the
-[NodeJS Console API](https://nodejs.org/api/console.html), so you should be
-able to use it as a polyfill in environments where there is no `console`
-available (e.g. Nashorn):
+To support this, we need to include the mod `kurly`, which uses the template engine [`kurly`](https://npmjs.com/package/kurly) behind the surface to parse the custom format string. In our entry point, change this:
+*index.js*
```js
-// assuming you already made sure there is a `global` object
-global.console = log;
-console.info('Nashorn can do logging to!');
+import ulog
```
-Since v2, the logger objects are functions and not objects, which may affect
-code testing for the existence of the console like:
+to
+*index.js*
```js
-if (typeof console == 'object') {
- // use the console
-}
+ import ulog from 'ulog'
+ import kurly from 'ulog/mods/kurly'
+ ulog.use(kurly)
```
-Such tests will fail as of v2. I am hoping to solve this with a dedicated
-`ulog/polyfill` endpoint, but have not yet got around to implementing it.
-In the mean time you can use some `extend` function to create an object
-from a logger function:
+or, use the `ulog/full` endpoint instead:
+
+*index.js*
+```js
+import `ulog/full`
+```
+
+#### Kurly formatters
+In the example above we were using tags like `{name}`, `{lvl}` etc.
+The following is a list of such tags that are built in to the `kurly` mod:
+
+##### Formatter `date`
+Returns the date part of the time the message was logged as `yyyy/MM/dd`.
+
+##### Formatter `time`
+Returns the time part of the time the message was logged as `hh:mm`.
+
+##### Formatter `lvl`
+Returns the level of the message as a single character:
+* `'x'` for error messages
+* `'!'` for warning messages
+* `'i'` for info messages
+* `' '` (space) for log, debug and trace messages
+
+##### Formatter `name`
+Returns the name of the logger, right-padded with spaces for alignment.
+
+##### Formatter `perf`
+Returns the difference in ms between two invocations to the same logger, only if this difference is larger than 1ms. Produces output that looks like `+62ms`.
+
+##### Fallback formatter
+Any unrecognized tags are being interpreted by the fallback formatter. This just returns the field on the log record whose name matches. In the example above we were using `{message}`. This is an unrecognized tag so the wildcard formatter is used, which just returns the `message` field from the log record. To get an idea for what fields are available on the log record, use the [json](#format_json) format.
+
+#### Custom kurly formatters
+
+With custom kurly formatters we customize on a higher level of abstraction. Instead of replacing the entire format with a custom one, we write small functions that format just a part of the message, leaving the composition of the message as a whole to be configured with a custom format string. We can add custom `kurly` formatters in much the same way as we add custom formats:
+
+*index.js*
```js
-// assuming you have some `extend` function:
-global.console = extend({}, log)
-console.info('Nashorn can do logging to!');
+import ulog from 'ulog'
+import kurly from 'ulog/mods/kurly'
+ulog.use(kurly)
+ulog.use({
+ formatters: {
+ custom: function(ctx) {
+ return function(rec) {
+ return 'Custom!'
+ }
+ }
+ }
+})
```
+To read more about kurly and custom kurly tags, refer to the kurly documentation on [creating kurly tags](https://www.npmjs.com/package/kurly#creating-tags)
-## `assert` does not throw
-ulog patches the [different behavior](https://github.com/jasnell/node/blob/master/doc/api/console.md#consoleassertvalue-message-args)
-of `console.assert` in Node compared to browsers. In ulog, `assert` behaves
-just like in the browsers and never throws.
+
+## Decoupling code from `debug`
+
+As I said before, `debug` is excellent for debug logging and there are tens of thousands of packages using it. The downside of this is that you will more or less get `debug` shoved through your throat if you include any of these libraries because they are *tighly coupled* to it. But we can uncouple them with the magic of bundlers like Webpack.
+
+To replace `debug` with `anylogger` everywhere, you can use this Webpack alias to make all modules that use `debug` switch to `anylogger` instead:
+
+```js
+{
+ resolve: {
+ alias: {
+ 'debug': 'anylogger'
+ }
+ }
+}
+```
+
+This works because the anylogger API is backwards compatible with that of `debug`. And because `ulog` has native `anylogger` support, once you import `ulog` in your entry point, all those libraries that were using `debug` start to use `ulog` automagically!
## Performance considerations
-The logging methods on the `log` object that correspond to a log level which
-is higher than the currently set level, are replaced by no-op methods. As such,
-you generally don't have to worry about the performance overhead of leaving
+By default, the logging methods on the `log` object that correspond to a log level
+which is higher than the currently set level, are replaced by no-op methods. As such,
+you generally don't have to worry about the performance overhead of leaving
the log statements in the production code. There is one exception to this rule
though. If preparing the message itself is a costly operation, you may want to
-surround the log code with an `if (log.level >= myLevel)` statement:
+surround the log code with an `if (log.enabledFor(level))` statement:
```js
-if (log.level >= log.INFO) {
+if (log.enabledFor('info')) {
var message = doLotsOfWorkToGenerateLogMessage();
log.info(message);
}
@@ -488,17 +606,19 @@ to let me know of any problems you find, or questions you may have.
## Credits
Credits go to:
-* Felix Geisendörfer from [debuggable.com](http://debuggable.com/) for kindly
+* Felix Geisendörfer from [debuggable.com](http://debuggable.com/) for kindly
giving up the `ulog` namespace on NPM. Thanks Felix!
-* TJ Holowaychuk for creating [debug](https://github.com/visionmedia/debug),
+* TJ Holowaychuk for creating [debug](https://npmjs.com/package/debug),
which was a great inspiration for ulog.
+* Tim Perry for creating [loglevel](https://npmjs.com/package/loglevel),
+ which was another great inspiration for ulog.
## Copyright
-Copyright 2018 by [Stijn de Witt](http://StijnDeWitt.com). Some rights reserved.
+Copyright 2020 by [Stijn de Witt](https://stijndewitt.com).
## License
-Licensed under the [Creative Commons Attribution 4.0 International (CC-BY-4.0)](https://creativecommons.org/licenses/by/4.0/) Open Source license.
+Licensed under the [MIT](https://opensource.org/licenses/MIT) Open Source license.
diff --git a/_config.yml b/_config.yml
deleted file mode 100644
index 259a24e..0000000
--- a/_config.yml
+++ /dev/null
@@ -1 +0,0 @@
-theme: jekyll-theme-tactile
\ No newline at end of file
diff --git a/browser.js b/browser.js
deleted file mode 100644
index 141954d..0000000
--- a/browser.js
+++ /dev/null
@@ -1,22 +0,0 @@
-var log = require('./ulog')
-
-var qs = location.search.substring(1),
- args = qs && qs.split('&'),
- lvl, dbg, i, m
-
-try {
- lvl = localStorage.getItem('log')
- dbg = localStorage.getItem('debug')
-} catch(e) {}
-
-for (i=0; m=args && args[i] && args[i].split('='); i++) {
- m[0] == 'log' ? lvl = m[1] : 0
- m[0] == 'debug' ? dbg = m[1] : 0
-}
-
-log.con = function(){return window.console}
-dbg && log.enable(dbg)
-log()
-log.level = lvl || log.WARN
-
-module.exports = log
diff --git a/build.js b/build.js
new file mode 100644
index 0000000..fed734f
--- /dev/null
+++ b/build.js
@@ -0,0 +1,27 @@
+var fs = require('fs')
+var path = require('path')
+
+var gzipSize = require('gzip-size')
+// be cool and use ulog to print the logging in the build of ulog :)
+var log = require('./')('ulog:build')
+
+var [ processName, script, command, ...args ] = process.argv
+var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'))
+var v = pkg.version
+
+;(function(){
+ var data = fs.readFileSync(path.resolve(__dirname, pkg.unpkg), 'utf8')
+ var gzip = (gzipSize.sync(data) / 1024).toFixed(1)
+ log.info(`Built ${pkg.unpkg} (~${gzip} kB minified and gzipped)`)
+ data = fs.readFileSync(path.resolve(__dirname, 'full.min.js'), 'utf8')
+ var fullzip = (gzipSize.sync(data) / 1024).toFixed(1)
+ log.info(`Built full.min.js (~${fullzip} kB minified and gzipped)`)
+ var readme = fs.readFileSync('README.md', 'utf-8')
+ readme = readme.replace(/ulog@\d(\d)?\.\d(\d)?\.\d(\d)?(-[a-z]+\.\d(\d)?)?/g, `ulog@${v}`)
+ readme = readme.replace(/\\v\d(\d)?\.\d(\d)?\.\d(\d)?(-[a-z]+\.\d(\d)?)?/g, `v${v}`)
+ readme = readme.replace(/ulog@\d(\d)?\.\d(\d)?\.\d(\d)?(-[a-z]+\.\d(\d)?)?\) \(~\d\.\dkB/g, `ulog@${v}) (~${gzip}kB`)
+ readme = readme.replace(/just 2.7kB/g, `just ${gzip}kB`)
+ readme = readme.replace(/ulog@\d(\d)?\.\d(\d)?\.\d(\d)?(-[a-z]+\.\d(\d)?)?\/full\.min\.js\) \(~\d\.\dkB/g, `ulog@${v}/full.min.js) (~${fullzip}kB`)
+ fs.writeFileSync('README.md', readme, 'utf8')
+ log.info(`Updated README.md`)
+})()
\ No newline at end of file
diff --git a/build/build-umd.js b/build/build-umd.js
deleted file mode 100644
index ea714a5..0000000
--- a/build/build-umd.js
+++ /dev/null
@@ -1,28 +0,0 @@
-var fs = require('fs')
-var path = require('path')
-
-var root = path.resolve(process.cwd())
-
-var commonScript = fs.readFileSync(path.resolve(root, 'ulog.js'), 'utf8')
-commonScript = commonScript.replace('module.exports = ulog', '')
-
-var browserScript = fs.readFileSync(path.resolve(root, 'browser.js'), 'utf8')
-browserScript = browserScript.replace("var log = require('./ulog')", '')
-browserScript = browserScript.replace('module.exports = log', 'return log')
-
-var combinedScript = commonScript + browserScript
-
-var umdScript =
- "(function(u,m,d){\n" +
- "\ttypeof define == 'function' && define.amd ? define(m,[],d) : (u[m] = d())\n" +
- "})(this, \'ulog\', function(){'use strict'\n" +
- combinedScript + '\n' +
- "}) // umd\n"
-
-fs.writeFile(path.resolve(root, 'ulog.umd.js'), umdScript, function(err) {
- if (err) console.log(err)
- else {
- var stats = fs.statSync("ulog.umd.js")
- console.info("Saved ulog.umd.js: " + stats.size + ' bytes.');
- }
-});
\ No newline at end of file
diff --git a/core/grab.js b/core/grab.js
new file mode 100644
index 0000000..36df19f
--- /dev/null
+++ b/core/grab.js
@@ -0,0 +1,12 @@
+module.exports = function(ulog, name, result) {
+ ulog.mods.reduce(function(r,item){
+ if (Array.isArray(r) && (name in item)) {
+ r.push(item[name])
+ } else {
+ for (var o in item[name])
+ r[o] = item[name][o]
+ }
+ return r
+ }, result)
+ return result
+}
diff --git a/core/index.js b/core/index.js
new file mode 100644
index 0000000..ecb5517
--- /dev/null
+++ b/core/index.js
@@ -0,0 +1,111 @@
+var ulog = require('anylogger')
+var grab = require('./grab')
+
+// ulog.levels.none = 0
+// ulog.levels.all = 9007199254740991 // Number.MAX_SAFE_INTEGER
+// var ext = ulog.ext // save for later
+
+/**
+ * `ulog.ext(logger) => logger`
+ *
+ * Called when a logger needs to be extended, either because it was newly
+ * created, or because it's configuration or settings changed in some way.
+ *
+ * This method must ensure that a log method is available on the logger
+ * for each level in `ulog.levels`.
+ *
+ * This override calls `ext` on all mods when a logger is extended and
+ * enables calling ext on all loggers by passing no arguments.
+ */
+ulog.ext = function(logger) {
+ if (logger) {
+// ext(logger) // create default methods
+ grab(ulog, 'ext', []).map(function(ext){
+ ext.call(ulog, logger) // call hook on registered mods
+ })
+ return logger
+ } else {
+ for (logger in ulog()) {
+ ulog.ext(ulog(logger))
+ }
+ }
+}
+
+ulog.mods = []
+
+/**
+ * ### `ulog.use(mod: Object|Array