Comparing version 0.5.0 to 0.6.0
70
any.js
@@ -0,1 +1,10 @@ | ||
/** | ||
* A N Y L O G G E R | ||
* Get a logger. Any logger. | ||
* | ||
* © 2019 by Stijn de Witt, some rights reserved | ||
* Licensed under the MIT Open Source license | ||
* https://opensource.org/licenses/MIT | ||
* Removal of this message in production builds is permitted. | ||
*/ | ||
(function(m,a){ | ||
@@ -8,3 +17,3 @@ // stores log modules keyed by name | ||
* | ||
* The main anylogger function creates a new or returns an existing logger | ||
* The main `anylogger` function creates a new or returns an existing logger | ||
* with the given `name`. It maintains a registry of all created loggers, | ||
@@ -15,3 +24,3 @@ * which it returns when called without a name, or with an empty name. | ||
*/ | ||
a = module.exports = function(n,o){ | ||
a = function(n,o){ | ||
// return the existing logger, or create a new one. if no name was given, return all loggers | ||
@@ -26,15 +35,16 @@ return n ? m[n] || (m[n] = a.create(n,o)) : m | ||
* | ||
* The lowest level of logging (none at all) has value `0`, whereas the highest | ||
* level of logging is named `trace` and has value `60`. Default log levels | ||
* are spaced out by 10 units each so to make room for additional log levels | ||
* in between. | ||
* The lowest level of logging (none at all) has value `0`. Higher levels have | ||
* higher values. To be compliant with the anylogger API, loggers should support | ||
* at least the default levels, but they may define additional levels. | ||
*/ | ||
a.levels = {error:10, warn:20, info:30, log:40, debug:50, trace:60} | ||
a.levels = {error:1, warn:2, info:3, log:4, debug:5, trace:6} | ||
/** | ||
* The anylogger console, defaults to the native console, or undefined. | ||
* The anylogger console. | ||
* | ||
* This object is used to perform the actual logging. If it is undefined, the | ||
* Defaults to the native console or false if no native console is available. | ||
* | ||
* This object is used to perform the actual logging. If it is false, the | ||
* log methods on the logger will all be noop methods. | ||
* | ||
* | ||
* The anylogger console may be overridden by a different object to intercept | ||
@@ -45,24 +55,32 @@ * individual logging calls. For example this property could be overridden | ||
* When a method is not available on the anylogger console, but | ||
* `anylogger.con.log` is defined, `anylogger.con.log` will be used. So you | ||
* `anylogger.out.log` is defined, `anylogger.out.log` will be used. So you | ||
* can define a level `silly` and it will create a method `silly()` which is | ||
* an alias for `anylogger.con.log`. | ||
* an alias for `anylogger.out.log`. | ||
*/ | ||
a.con = (typeof console != 'undefined') && console | ||
a.out = typeof console != 'undefined' && console | ||
/** | ||
* Returns the logger with the given name, or creates it by calling `anylogger.new` | ||
* and extending the created log function by calling `anylogger.ext` on the result. | ||
* Called when a logger needs to be created. | ||
* | ||
* You can replace this method with a custom factory, or leave this one in place | ||
* and instead override `anylogger.ext` and/or `anylogger.new` separately. | ||
* `anylogger.create(name, options)` | ||
* | ||
* Creates a new logger by calling `anylogger.new`, then extends it by calling | ||
* `anylogger.ext` on the result. | ||
* | ||
* You can replace this method with a custom factory, or leave this one in | ||
* place and instead override `anylogger.ext` and/or `anylogger.new` separately. | ||
* | ||
* @param name String, The name of the logger to create | ||
* @param options Object, An optional options object | ||
* | ||
* @returns A new logger with the given `name` and `options`. | ||
*/ | ||
a.create = function(n,o) { | ||
return a.ext(a.new(n),n,o) | ||
return a.ext(a.new(n,o)) | ||
} | ||
/** | ||
* Called when a logger needs to be created. | ||
* | ||
* `anylogger.new(name)` | ||
* `anylogger.new(name, options)` | ||
* | ||
@@ -73,6 +91,8 @@ * Creates and returns a new named function that calls `anylogger.log` to | ||
* | ||
* @param n String The name of the logger to create | ||
* @param name String The name of the logger to create | ||
* @param options Object An optional options object | ||
* | ||
* @return function log([level='log'], args...) | ||
*/ | ||
a.new = function(n,r) { | ||
a.new = function(n,o,r) { | ||
// use eval to create a named function, this method has best cross-browser | ||
@@ -108,3 +128,3 @@ // support and allows us to create functions with names containing symbols | ||
* | ||
* `anylogger.ext(logger, name, options) => logger` | ||
* `anylogger.ext(logger) => logger` | ||
* | ||
@@ -119,5 +139,7 @@ * This method must ensure that a log method is available on the logger for | ||
for (v in a.levels) | ||
l[v] = a.con[v] || a.con.log || function(){} | ||
l[v] = a.out[v] || a.out.log || function(){} | ||
return l; | ||
} | ||
module.exports = a | ||
})() |
@@ -1,1 +0,1 @@ | ||
!function(m,a){m=Object.create(null),a=window.anylogger=function(n,e){return n?m[n]||(m[n]=a.create(n,e)):m},a.levels={error:10,warn:20,info:30,log:40,debug:50,trace:60},a.con="undefined"!=typeof console&&console,a.create=function(n,e){return a.ext(a.new(n),n,e)},a.new=function(n,r){return eval("r = {'"+n+"': function(){a.log(n, [].slice.call(arguments))}}[n]"),r.name?r:Object.defineProperty(r,"name",{get:function(){return n}})},a.log=function(n,e){m[n][1<e.length&&a.levels[e[0]]?e.shift():"log"].apply(m[n],e)},a.ext=function(n){for(v in a.levels)n[v]=a.con[v]||a.con.log||function(){};return n}}(); | ||
!function(m,a){m=Object.create(null),a=function(n,e){return n?m[n]||(m[n]=a.create(n,e)):m},a.levels={error:1,warn:2,info:3,log:4,debug:5,trace:6},a.out="undefined"!=typeof console&&console,a.create=function(n,e){return a.ext(a.new(n,e))},a.new=function(n,o,r){return eval("r = {'"+n+"': function(){a.log(n, [].slice.call(arguments))}}[n]"),r.name?r:Object.defineProperty(r,"name",{get:function(){return n}})},a.log=function(n,e){m[n][1<e.length&&a.levels[e[0]]?e.shift():"log"].apply(m[n],e)},a.ext=function(n){for(v in a.levels)n[v]=a.out[v]||a.out.log||function(){};return n},window.anylogger=a}(); |
{ | ||
"name": "anylogger", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Get a logger. Any logger.", | ||
@@ -5,0 +5,0 @@ "main": "any.js", |
223
README.md
@@ -1,2 +0,2 @@ | ||
# anylogger <sub><sup>v0.5.0</sup></sub> | ||
# anylogger <sub><sup>v0.6.0</sup></sub> | ||
### Get a logger. Any logger. | ||
@@ -12,14 +12,21 @@ | ||
Get whatever logging framework is present in the host project, or a wrapper | ||
around the console, or a dummy. Anything really, that will let your library | ||
do logging without you having to decide what logging framework the app using | ||
your library should use. | ||
## A logger for libraries | ||
Get whatever logging framework the host project is using, or a wrapper around | ||
the console, or a dummy log object that does nothing. Anything really, that | ||
will let your library do logging without you having to decide what logging | ||
framework the app using your library should use. | ||
Anylogger will let the user of your library pick the logger for his app, and | ||
will let your library pick up on whatever choice he made and run with it. | ||
By choosing anylogger, you are explicitly not choosing any specific logging | ||
framework, but instead are limiting yourself to the | ||
[Anylogger API](#anylogger-api), a small API that only captures the bare | ||
essentials for logging, but because of that, is compatible with nearly every | ||
logging library out there. | ||
## What is this? | ||
**A logging facade.** | ||
**A logging facade** | ||
@@ -50,7 +57,7 @@ We, the Javascript community, really need a logging facade. Initially, the | ||
`anylogger` and for just 0.5 kB shared between all libraries doing this, we can | ||
plug in any framework of our choice and all of them will automatically start to | ||
use that framework. Wouldn't it be much better and easier? | ||
plug in any framework of our choice and all libraries will automatically | ||
start to use that framework. Wouldn't it be much better and easier? | ||
At the application level, the app developer chooses their logging framework | ||
of choice and installs the anylogger-to-their-framework adapter. They make | ||
At the application level, the app developers choose whatever logging framework | ||
they prefer and install the anylogger-to-their-framework adapter. They make | ||
sure to require the adapter in the application entry point and from that point | ||
@@ -63,4 +70,4 @@ on, any library using anylogger will automatically start using the selected | ||
* [any.js](https://unpkg.com/anylogger@0.5.0/any.js) (fully commented source ~5kB) | ||
* [any.min.js](https://unpkg.com/anylogger@0.5.0/any.min.js) (minified and gzipped ~0.5 kB) | ||
* [any.js](https://unpkg.com/anylogger@0.6.0/any.js) (fully commented source ~5kB) | ||
* [any.min.js](https://unpkg.com/anylogger@0.6.0/any.min.js) (minified and gzipped ~0.5 kB) | ||
@@ -72,3 +79,3 @@ | ||
```html | ||
<script src="https://unpkg.com/anylogger@0.5.0/any.min.js"></script> | ||
<script src="https://unpkg.com/anylogger@0.6.0/any.min.js"></script> | ||
<script>(function(){ // IIFE | ||
@@ -93,4 +100,43 @@ var log = anylogger('index.html') | ||
Done. | ||
This will add `anylogger` as a dependency to your `package.json`: | ||
```json | ||
{ | ||
"dependencies": { | ||
"anylogger": ">= 0.6.0 < 2" | ||
} | ||
} | ||
``` | ||
I recommend to expand the version range here. By default NPM will set a | ||
range looking like `"^0.6.0"`, which is equivalent to `"0.6.x"` or | ||
`">= 0.6.0 < 0.7.0"`. This version range is probably too narrow. When | ||
multiple libraries depend on the same library, if their version ranges overlap, | ||
NPM will be able to make them all use the same version. But if their version | ||
ranges do not overlap, NPM will bundle multiple versions of the same library. | ||
So ideally we make sure version ranges always overlap. To that end, this | ||
project will be very conservative towards any code changes and will take 2 | ||
major versions for any compatibility breaking changes; in the first major | ||
version, deprecation notices will be added but stuff will still work. | ||
In the next major version, the change will be implemented. So if your code is | ||
working without any deprecation notices, it should continue to work with all | ||
upcoming versions including the next major, up to (but not including) the | ||
second next major. | ||
So I recommend accepting everything up to the second next major release. | ||
That means that currently while at version `0.6.0`, we should set the version | ||
range to everything equal to or above `0.6.0` and below `2.0.0`: | ||
```json | ||
{ | ||
"dependencies": { | ||
"anylogger": ">= 0.6.0 < 2.0.0" | ||
} | ||
} | ||
``` | ||
This should minimize our chances of getting multiple conflicting versions | ||
added to our app. | ||
### Install in an application project | ||
@@ -107,2 +153,8 @@ If you are building an application project and have selected a logging | ||
> Changing the version range here is less important as NPM install will | ||
> install the latest version by default, and this project will not be | ||
> used within other projects anyway. As long as the version ranges of the | ||
> libraries the project is using cover the currently installed version, | ||
> everything should work out fine. | ||
or, for [ulog](https://npmjs.com/package/ulog): | ||
@@ -123,3 +175,4 @@ | ||
### Include in a library | ||
In your libraries, only use anylogger, to stay framework-independent: | ||
In your libraries, only use anylogger and restrict yourself to the | ||
[Anylogger API](#anylogger-api) to stay framework-independent: | ||
@@ -139,2 +192,3 @@ ### require | ||
### Include in an application project | ||
@@ -156,3 +210,4 @@ | ||
In your modules, use only anylogger to stay framework-independant: | ||
In your other modules, use only anylogger and restrict yourself to the | ||
[Anylogger API](#anylogger-api) to stay framework-independent: | ||
@@ -172,2 +227,3 @@ ### require | ||
## Using anylogger | ||
@@ -187,2 +243,13 @@ | ||
If you are able to restrict yourself to the [Anylogger API](#anylogger-api), | ||
your code will be framework independent and will work with any logger. | ||
```js | ||
log.info('Logging is easy!') | ||
// Avoid using methods like log.silly() and log.time() etc which are only | ||
// supported in some frameworks and not in others and your code will work | ||
// with any supported logging framework without any changes | ||
``` | ||
## Anylogger API | ||
@@ -195,8 +262,7 @@ | ||
```js | ||
function anylogger(name, options) | ||
function anylogger(name, options) => logger | ||
``` | ||
The main function to call to get a logger. | ||
Returns a logger based on two arguments, both of | ||
which are optional. | ||
Accepts two arguments. | ||
@@ -209,3 +275,5 @@ #### name | ||
used by the highly popular `debug` module. But you are free to pick any name | ||
you want. You only get a logger if you supply a name. | ||
you want. You only get a logger if you supply a name. If the name is | ||
not given `anylogger()` will return an object containing all loggers, | ||
keyed by name. | ||
@@ -231,7 +299,8 @@ #### options | ||
The logger returned by `anylogger` is a function that can do logging on it's own: | ||
The logger returned by `anylogger` is a function that can do logging on it's | ||
own: | ||
```js | ||
log('message') // logs a message at debug level | ||
log('info', 'message') // logs a message at info level | ||
log('message') // logs a message at `log` level | ||
log('info', 'message') // logs a message at `info` level | ||
``` | ||
@@ -261,29 +330,27 @@ | ||
And that's about it. However this covers the basic logging needs for most | ||
logging libraries, while at the same time leaving the details surrounding | ||
configuration to be decided upon by specific implementations. | ||
And that's about it. However this covers the basic logging needs. | ||
In fact, in most cases you should be able to replace the library you are | ||
currently using with anylogger without many changes to your code, if any. | ||
> Note that all logging methods here are part of the upcoming | ||
> [Console standard](https://console.spec.whatwg.org/), but not all platforms | ||
> and frameworks support all of them. In particular the `debug` method is not | ||
> available everywhere. Anylogger will make sure that the `debug` function is | ||
> polyfilled if needed. | ||
Note that all logging methods here are part of the default [console API](https://developer.mozilla.org/en-US/docs/Web/API/Console) as specified on MDN, | ||
but not all platforms and frameworks support all of them. In particular the `debug` | ||
method is not available everywhere and the `trace` method functions different on | ||
different platforms. On Node JS it will print a stacktrace with each message. | ||
Anylogger will make sure that the `debug` function is polyfilled if needed, but it | ||
does not attempt to change the actual implementation of any method. | ||
## Write an anylogger adapter | ||
To write an anylogger adapter, you need to make a project that includes both anylogger | ||
and the logging framework the adapter is for as peer dependencies. It then needs to | ||
modify one or more of the [anylogger extension points](#anylogger-extension-points) | ||
so the created loggers will be compliant with both the anylogger [Logging API](#logging-api) | ||
as well as with the logging framework's own API. | ||
To write an anylogger adapter, you need to make a project that includes both | ||
anylogger and the logging framework the adapter is for as peer dependencies. | ||
Again, make sure to choose a wide version range; 2 major versions is | ||
recommended. | ||
It is recommended you call your library `anylogger-adapter`, where `adapter` | ||
should be replaced with the name of the logging framework the adapter is for. | ||
For example, the adapter for `ulog` is called `anylogger-ulog`. | ||
You then need to modify one or more of the | ||
[anylogger extension points](#anylogger-extension-points) | ||
so the created loggers will be compliant with both the anylogger | ||
[Logging API](#logging-api) as well as with the logging framework's own API. | ||
It is recommended you call your library `anylogger-[adapter]`, where | ||
`[adapter]` should be replaced with the name of the logging framework the | ||
adapter is for. For example, the adapter for `ulog` is called `anylogger-ulog`. | ||
In addition, it is recommended you add the keyword `"anylogger"` to the | ||
@@ -301,9 +368,9 @@ *package.json* file of your adapter project, so it will show up in the list of | ||
An object describing log levels, keyed by name. | ||
You can replace or amend this object to include levels corresponding with | ||
You can replace or change this object to include levels corresponding with | ||
those available in the framework you are writing an adapter for. Please | ||
make sure to always include the existing levels as well so all code can | ||
make sure to always include the default levels as well so all code can | ||
rely on the 6 console methods `error`, `warn`, `info`, `log`, `debug` and | ||
`trace` to always be there. | ||
#### anylogger.con | ||
#### anylogger.out | ||
An object (defaults to the native `console`, or `undefined`) containing the | ||
@@ -316,19 +383,43 @@ log methods to perform the actual log calls. You can replace this object | ||
#### anylogger.create | ||
A method that is called whenever a new logger is created. Calls `anylogger.new` | ||
and `anylogger.ext`. You can override this method with your own factory, but | ||
it is probably easier to override just `anylogger.ext`. | ||
A method that is called whenever a new logger is created. Calls | ||
`anylogger.new` and `anylogger.ext`. You can override this method | ||
with your own factory, but it is probably easier to override just | ||
`anylogger.new`, chaining the old method. | ||
#### anylogger.new | ||
A method that is called to create the logger function. | ||
If you want to customize the way log invocation is handled, consider overriding | ||
`anylogger.log` instead. | ||
A method that is called to create the logger function. You can chain | ||
this method and include any one-time customizations here: | ||
```js | ||
import anylogger from 'anylogger' | ||
// save the original function | ||
const make = anylogger.new | ||
// override anylogger.new | ||
anylogger.new = function(name, options) { | ||
// call the original function to chain it | ||
var logger = make(name, options) | ||
// add your customizations | ||
logger.myCoolFeature = function(){logger.info('My cool feature!')} | ||
// return the customized logger | ||
return logger | ||
} | ||
``` | ||
If you need to re-apply customizations any time relevant config changes (such as | ||
active log level changing), override `anylogger.ext`. | ||
#### anylogger.ext | ||
A method that is called to extend the logger function. It loops over the | ||
`anylogger.levels` and creates log methods for each level. | ||
You can override or chain this method to add additional methods or properties | ||
to the logger. In case of frameworks supporting dynamic log levels, it is | ||
expected that `anylogger.ext` is called again whenever config changes that | ||
might influence the log level. This allows adapters to re-extend the logger | ||
so that the new configuration takes effect. | ||
A method that is called to extend the logger function. The default | ||
implementation loops over the `anylogger.levels` and creates log methods for | ||
each level. You can override or chain this method to change the way the log | ||
methods are (re-)created. By default, all log methods will delegate to the | ||
native console. But in a library that supports log levels, all methods | ||
corresponding to log levels lower than the currently active levels might be | ||
replaced with no-op methods instead. Or maybe the destination of the log | ||
messages might change dynamically based on configuration. Apply such changes | ||
in `anylogger.ext` as it will be called again whenever relevant config | ||
changes. This allows adapters to re-extend the logger so that the new | ||
configuration takes effect. | ||
@@ -339,6 +430,6 @@ #### anylogger.log | ||
through this method. Calls to the log methods, e.g. to `log.info`, are routed | ||
directly to the corresponding method on `any.con`. Hence `any.con` is a better | ||
directly to the corresponding method on `any.out`. Hence `any.out` is a better | ||
extension point when you need to intercept *all* log invocation. | ||
Please have a look at the [source](https://unpkg.com/anylogger@0.5.0/any.js) | ||
Please have a look at the [source](https://unpkg.com/anylogger@0.6.0/any.js) | ||
it should make it more clear how to write an adapter. Also consider studying | ||
@@ -349,2 +440,8 @@ the [available adapters](https://www.npmjs.com/search?q=keywords:anylogger) | ||
## Give something back | ||
If you wrote an `anylogger` adapter, make sure to share it back with the | ||
community. Publish it to NPM for all to use! | ||
## Issues | ||
@@ -358,3 +455,3 @@ | ||
Copyright 2019 by [Stijn de Witt](http://StijnDeWitt.com). | ||
Copyright 2019 by [Stijn de Witt](http://StijnDeWitt.com). Some rights reserved. | ||
@@ -364,2 +461,2 @@ | ||
Licensed under the MIT Open Source license. | ||
Licensed under the [MIT Open Source license](https://opensource.org/licenses/MIT). |
23685
128
442