From f56881efdae67229d3903e59d6446e23052fbe33 Mon Sep 17 00:00:00 2001 From: Stijn de Witt Date: Thu, 8 Apr 2021 19:11:31 +0200 Subject: [PATCH] WIP Structured logging via a format Consider anylogger-structured? --- README.md | 36 ++++----- core/grab.js | 2 +- core/index.js | 39 ++++----- core/merge.js | 2 +- core/parse.js | 118 ---------------------------- mods/align/index.js | 4 - mods/channels/index.js | 9 +-- mods/colors/index.js | 5 +- mods/config/index.js | 6 +- mods/config/read.js | 3 +- mods/config/watched.js | 3 +- mods/config/watches.js | 4 +- mods/formats/apply-formatting.js | 2 +- mods/formats/cr.js | 5 ++ mods/formats/default.js | 2 +- mods/formats/index.js | 35 ++++++--- mods/formats/json.js | 5 ++ mods/formats/{message.js => msg.js} | 4 +- mods/formats/structured.js | 18 +++++ mods/outputs/index.js | 6 +- mods/props/index.js | 8 +- mods/props/test.js | 1 + mods/settings/index.js | 6 +- style.css | 35 ++++++++- tutorial/index.html | 21 +++-- tutorial/noscript.html | 10 +++ webpack.config.js | 2 +- 27 files changed, 171 insertions(+), 220 deletions(-) delete mode 100644 core/parse.js create mode 100644 mods/formats/cr.js create mode 100644 mods/formats/json.js rename mods/formats/{message.js => msg.js} (58%) create mode 100644 mods/formats/structured.js create mode 100644 tutorial/noscript.html diff --git a/README.md b/README.md index 08e597a..032ad1a 100644 --- a/README.md +++ b/README.md @@ -146,12 +146,12 @@ log('Logging is easy!') 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.18/ulog.min.js) (~2.7kB minified and gzipped) -* [ulog.lazy.min.js](https://unpkg.com/ulog@2.0.0-beta.18/ulog.lazy.min.js) (~4.3kB minified and gzipped) +* [ulog.min.js](https://unpkg.com/ulog@2.0.0-beta.18/ulog.min.js) (~8.0kB minified and gzipped) +* [ulog.lazy.min.js](https://unpkg.com/ulog@2.0.0-beta.18/ulog.lazy.min.js) (~9.1kB minified and gzipped) > `ulog.min.js` lazy loads `ulog.lazy.min.js` on demand, so make sure to include both files in your download -* [full.min.js](https://unpkg.com/ulog@2.0.0-beta.18/full.min.js) (~5.7kB minified and gzipped) +* [full.min.js](https://unpkg.com/ulog@2.0.0-beta.18/full.min.js) (~14.2kB minified and gzipped) > Full bundle, no lazy loading @@ -398,8 +398,8 @@ ulog.use({ outputs: { custom: function(ctx){ return function(rec) { - rec.message.shift('Custom!!') - console[rec.level].apply(console, rec.message) + rec.msg.shift('Custom!!') + console[rec.level].apply(console, rec.msg) } }} } @@ -427,11 +427,11 @@ configuration mechanism. The default format string on Node is: ```sh -lvl name:20 message perf +lvl name:20 msg perf ``` -This sacrifices the callstack for a colored and formatted `message` and having -the `perf` measurements after the message i.s.o before it. The Node JS doesn't +This sacrifices the callstack for a colored and formatted `msg` and having +the `perf` measurements after the message i.s.o before it. Node JS doesn't output any file name / line number information anyway. On browsers, we want to spare the call stack, so there the default is: @@ -440,7 +440,7 @@ On browsers, we want to spare the call stack, so there the default is: lvl name perf ``` -We don't include the `message`, but it will be appended as the last argument +We don't include the `msg`, but it will be appended as the last argument automatically. The result is nicely formatted messages with the file name / line number entries in the browser debug console intact. @@ -452,7 +452,7 @@ Formats available out of the box include: * [Format `cr`](#format-cr) * [Format `date`](#format-date) * [Format `lvl`](#format-lvl) -* [Format `message`](#format-message) +* [Format `msg`](#format-msg) * [Format `name`](#format-name) * [Format `perf`](#format-perf) * [Format `time`](#format-time) @@ -500,7 +500,7 @@ Prints the level of the message as a single character: * `'>'` for debug messages * `'}'` for trace messages -#### Format `message` +#### Format `msg` Prints the message, formatted and colored. Using this format breaks the callstack as it is dynamic. @@ -538,7 +538,7 @@ import formats from 'ulog/mods/formats' ulog.use({ use: [ formats ], formats: { - custom: (ctx) => (rec) => (['custom'].concat(rec.message)), + custom: (ctx) => (rec) => (['custom'].concat(rec.msg)), random: (ctx, rec) => () => Math.random() } }) @@ -562,7 +562,7 @@ ulog.use({ dynamicFormat: function(ctx) { // one-time init here return function(rec) { - // rec.message contains full message + // rec.msg contains full message return /* ... */ } } @@ -582,8 +582,8 @@ ulog.use({ staticFormat: function(ctx, rec) { // one-time init here return function(){ - // rec.message is undefined - // rec.name, rec.level etc is populated + // rec.msg is undefined + // rec.name, rec.level etc are populated return /* ... */ } } @@ -781,12 +781,12 @@ log_drain=my:*,-my:lib=noop ### Config option `log_format` -Specify the format to use. Defaults to `lvl name:22 message perf` on Node JS and `lvl name perf` on browsers. +Specify the format to use. Defaults to `lvl name:22 msg perf` on Node JS and `lvl name perf` on browsers. ```sh -log_format=lvl name perf message;my:*=lvl name perf +log_format=lvl name perf msg;my:*=lvl name perf ``` -This sets `lvl name perf message` as the default format, while assigning a different format string to all loggers starting with `my:`. +This sets `lvl name perf msg` as the default format, while assigning a different format string to all loggers starting with `my:`. For more details, refer to the [section on formatting](#formatting) diff --git a/core/grab.js b/core/grab.js index d82b231..368e11a 100644 --- a/core/grab.js +++ b/core/grab.js @@ -1,4 +1,4 @@ -var merge = require('./merge') +var merge = require('./merge') module.exports = function(ulog, name, result) { ulog.mods.reduce(function(r,mod){ diff --git a/core/index.js b/core/index.js index f23e54b..4b01289 100644 --- a/core/index.js +++ b/core/index.js @@ -1,5 +1,5 @@ -var ulog = require('anylogger') -var grab = require('./grab') +var ulog = require('anylogger') +var merge = require('./merge') var ext = ulog.ext @@ -18,10 +18,10 @@ var ext = ulog.ext ulog.ext = function(logger) { if (logger) { ext(logger) - grab(ulog, 'ext', []).map(function(ext){ + ulog.grab('ext', []).map(function(ext){ ext.call(ulog, logger) }) - grab(ulog, 'after', []).map(function(ext){ + ulog.grab('after', []).map(function(ext){ ext.call(ulog, logger) }) return logger @@ -34,6 +34,18 @@ ulog.ext = function(logger) { ulog.mods = [] +ulog.grab = function(name, result) { + this.mods.reduce(function(r,mod){ + if (Array.isArray(r) && (name in mod)) { + r.push(mod[name]) + } else { + merge(r, mod[name]) + } + return r + }, result) + return result +} + /** * ### `ulog.use(mod: Object|Array): Number` * @@ -90,23 +102,4 @@ ulog.use = function(mod) { return result } -// ulog.grab = function(name){ -// return ulog.mods.reduce(function(r,mod){ -// for (var o in mod[name]) { -// r[o] = mod[name][o] -// } -// return r -// }, {}) -// } - -// var recorders = [] -// for (var i=0,mod; mod=ulog.mods[i]; i++) { -// if (mod.record) recorders.push(mod.record) -// } - - -// ulog.enabled = ulog.get.bind(ulog, 'debug') -// ulog.enable = ulog.set.bind(ulog, 'debug') -// ulog.disable = ulog.set.bind(ulog, 'debug', undefined) - module.exports = ulog diff --git a/core/merge.js b/core/merge.js index fb69cb8..6d94bcb 100644 --- a/core/merge.js +++ b/core/merge.js @@ -1,4 +1,4 @@ -var merge = module.exports = function(result, obj) { +var merge = module.exports = function(result, obj) { for (var o in obj) { if ((typeof obj[o] == 'object') && (Object.getPrototypeOf(obj[o]) === Object.prototype)) { if (! (o in result)) result[o] = {} diff --git a/core/parse.js b/core/parse.js deleted file mode 100644 index b91f5ed..0000000 --- a/core/parse.js +++ /dev/null @@ -1,118 +0,0 @@ -"lvl name:22 date:yy/mm/dd perf" - -"console file:./log.output url:https://deze-auto-kopen.nl component:( with nested components )" - - -function parse(str) { - var tag, result = [] - if (str || (str === '')) { - while (tag = nextTag(str)) { - var before = str.substring(0, tag.index) - if (before) result.push(before) - result.push({ - name: tag.name, - text: tag.text, - ast: parse(tag.text) - }) - str = str.substring(tag.end + 1) - } - if (str) result.push(str) - } - return result -} - -function nextTag(str) { - var match = str.match(/\{[_a-zA-Z][_a-zA-Z0-9]*([^_a-zA-Z0-9].*)?\}/) - var result - if (match) { - var name = match[1] ? match[0].substring(1, match[0].indexOf(match[1])) : match[0].substring(1, match[0].indexOf('}')) - result = { name: name, index: match.index, end: -1, text: '' } - // loop through the string, parsing it as we go through it - var esc = false - var open=1 // we already found one open brace - for (var i=match.index+name.length+1; i} ast An abstract syntax tree created with `parse` - * @param {Object} tags An object of tags keyed by tag name - * @param {Function} parent Optionally, a compiled parent function for the ast - * - * @returns An array, possibly empty but never null or undefined. - */ -function compile(ast, tags, parent) { - if (process.env.NODE_ENV != 'production') { - log.debug('compile', ast, tags, parent) - if ((ast === undefined) || (ast === null)) throw new Error('parameter `ast` is required') - if (! Array.isArray(ast)) throw new Error('parameter `ast` must be an array') - if ((tags === undefined) || (tags === null)) throw new Error('parameter `tags` is required') - if (typeof tags != 'object') throw new Error('parameter `tags` must be an object') - } - - // recursively compile the ast - var nodes = ast.map(function(n){ - return typeof n == 'string' - ? n : - compile(n.ast, tags, - tags[n.name] ? tags[n.name](n) : - tags['*'] ? tags['*'](n) : - undefined - ) - }) - - // create the result function - var result = function(rec) { - // clone rec into res - var res = {} - for (k in rec) res[k] = rec[k] - // get the result children - res.children = nodes.reduce(function(r, n){ - if (typeof n == 'function') n = n(rec) - r.push.apply(r, Array.isArray(n) ? n : [n]) - return r - }, []) - // invoke parent if we have it - return parent ? parent(res) : res.children - } - if (process.env.NODE_ENV != 'production') { - log('compile', ast, tags, parent, '=>', '[Function]') - } - return result; -} - - diff --git a/mods/align/index.js b/mods/align/index.js index 1b61a0d..5140434 100644 --- a/mods/align/index.js +++ b/mods/align/index.js @@ -1,7 +1,3 @@ -// var grab = require('../../core/grab') -// var palette = require('./utils').palette -// var levels = require('./utils').levels - var boolean = require('../props/boolean') module.exports = { diff --git a/mods/channels/index.js b/mods/channels/index.js index 894e0c6..7980fac 100644 --- a/mods/channels/index.js +++ b/mods/channels/index.js @@ -1,4 +1,3 @@ -var grab = require('../../core/grab') var console = require('./console') var noop = require('./noop') var method = require('./method') @@ -35,9 +34,9 @@ module.exports = { // enhance the given loggers with channels ext: function(logger) { var ulog = this - var channels = grab(ulog, 'channels', {}) - var channelOutputs = grab(ulog, 'channelOutput', []) - var recorders = grab(ulog, 'record', []) + var channels = ulog.grab('channels', {}) + var channelOutputs = ulog.grab('channelOutput', []) + var recorders = ulog.grab('record', []) logger.channels = {} for (var channel in channels) { var ch = logger.channels[channel] = { @@ -58,7 +57,7 @@ module.exports = { ch.fns[level] = (function(ch,rec){ return (typeof ch.out == 'function' ? function(){ - rec.message = [].slice.call(arguments) + rec.msg = [].slice.call(arguments) ch.out(rec) } : method(ch.out, rec) diff --git a/mods/colors/index.js b/mods/colors/index.js index b08517e..a3fa87b 100644 --- a/mods/colors/index.js +++ b/mods/colors/index.js @@ -1,7 +1,6 @@ -var grab = require('../../core/grab') +var boolean = require('../props/boolean') var palette = require('./utils').palette var levels = require('./utils').levels -var boolean = require('../props/boolean') module.exports = { use: [ @@ -23,7 +22,7 @@ module.exports = { record: function(logger, rec) { if (logger.colored) { if (!logger.colors) { - logger.colors = grab(this, 'colors', {}) + logger.colors = this.grab('colors', {}) logger.colors.index = hash(logger.name) % logger.colors.palette.length } if (!logger.color) { diff --git a/mods/config/index.js b/mods/config/index.js index 9c8dc09..4a8b937 100644 --- a/mods/config/index.js +++ b/mods/config/index.js @@ -1,10 +1,8 @@ -var grab = require('../../core/grab') -// var args = require('./args') -// var env = require('./env') var read = require('./read') var update = require('./update') var notify = require('./notify') var watch = require('./watch') + var config = module.exports = { use: [ require('../settings'), @@ -25,7 +23,7 @@ var config = module.exports = { config.update(this) } if (!result) { - var settings = grab(this, 'settings', {}) + var settings = this.grab('settings', {}) name = settings[name] && settings[name].config || name result = this.config[name] } diff --git a/mods/config/read.js b/mods/config/read.js index 771c332..f0a529c 100644 --- a/mods/config/read.js +++ b/mods/config/read.js @@ -1,13 +1,12 @@ var fs = require('fs') var path = require('path') -var grab = require('../../core/grab') var parse = require('./parse') var watched = require('./watched') var configure = require('./configure') module.exports = function(ulog, callback) { - var settings = grab(ulog, 'settings', {}) + var settings = ulog.grab('settings', {}) var log_config = ulog.get('log_config') || 'log.config' var filename = path.resolve(log_config) if (callback) { diff --git a/mods/config/watched.js b/mods/config/watched.js index c6066f1..03ec65b 100644 --- a/mods/config/watched.js +++ b/mods/config/watched.js @@ -1,8 +1,7 @@ -var grab = require('../../core/grab') var watches = require('./watches') module.exports = function(ulog){ - var settings = grab(ulog, 'settings', {}) + var settings = ulog.grab('settings', {}) var watchers = watches(ulog) var watched = {} watchers.forEach(function(watcher){ diff --git a/mods/config/watches.js b/mods/config/watches.js index 6e46de0..f925a66 100644 --- a/mods/config/watches.js +++ b/mods/config/watches.js @@ -1,7 +1,5 @@ -var grab = require('../../core/grab') - module.exports = function(ulog){ - return grab(ulog, 'watch', []).map(function(watch){ + return ulog.grab('watch', []).map(function(watch){ var result = {} for (var key in watch) { key.split(',').forEach(function(name){ diff --git a/mods/formats/apply-formatting.js b/mods/formats/apply-formatting.js index d631071..78ecfc7 100644 --- a/mods/formats/apply-formatting.js +++ b/mods/formats/apply-formatting.js @@ -15,7 +15,7 @@ module.exports = function(rec, fmt, msg, r){ var len = Array.isArray(msg) ? msg.length : 1 for (var i=0; i= 1) && (typeof rec.msg[0] == 'object') && !Array.isArray(rec.msg[0]) && + ((rec.msg.length > 1) || (rec.msg[0].msg))) { + // call signature matches stuctured logging pattern + // first, remove the record object from the message + var object = rec.msg.shift() + if (object.msg) { + // append the message on the object to the (possibly empty) rec.msg array + rec.msg.push.apply(rec.msg, Array.isArray(object.msg) ? object.msg : [ object.msg ]) + } + // merge the object onto the log record + merge(rec, object) + } + } +} diff --git a/mods/outputs/index.js b/mods/outputs/index.js index 7dff985..cbcc48f 100644 --- a/mods/outputs/index.js +++ b/mods/outputs/index.js @@ -1,6 +1,6 @@ var parse = require('kurly/parse') var pipe = require('kurly/pipe') -var grab = require('../../core/grab') + var console = require('../channels/console') var method = require('../channels/method') const noop = require('../channels/noop') @@ -43,7 +43,7 @@ module.exports = { // override the channel output constructor to take logger props into account channelOutput: function(logger, ch){ if (! (ch.cfg = logger[ch.name])) return - ch.outputs = grab(this, 'outputs', {}) + ch.outputs = this.grab('outputs', {}) var ast = parse(ch.cfg, { optional: true }) .filter(function(node){return typeof node == 'object'}) var outs = pipe(ast, ch.outputs) @@ -54,7 +54,7 @@ module.exports = { function(rec) { for (var i=0,out; out=outs[i]; i++) { if (typeof out == 'function') out(rec) - else method(out, rec).apply(out, rec.message) + else method(out, rec).apply(out, rec.msg) } } ) diff --git a/mods/props/index.js b/mods/props/index.js index 30645b7..7fc205a 100644 --- a/mods/props/index.js +++ b/mods/props/index.js @@ -1,6 +1,4 @@ -var grab = require('../../core/grab') - -/** +/** * Mod: props * * Enables properties on loggers that are backed by options on ulog. @@ -21,7 +19,7 @@ var props = module.exports = { // // called when a logger needs to be enhanced ext: function(logger) { - var settings = grab(this, 'settings', {}) + var settings = this.grab('settings', {}) for (var name in settings) { if (settings[name].prop) { props.new.call(this, logger, name, settings[name].prop) @@ -32,7 +30,7 @@ var props = module.exports = { // contribute props to log records record: function(logger, rec) { - var settings = grab(this, 'settings', {}) + var settings = this.grab('settings', {}) for (var name in settings) { if (settings[name].prop) { rec['log_' + name] = this.get(name, logger.name) diff --git a/mods/props/test.js b/mods/props/test.js index 1db971c..946d496 100644 --- a/mods/props/test.js +++ b/mods/props/test.js @@ -21,6 +21,7 @@ test('mod: props', function (t) { t.test('props.ext(logger: Function)', function (t) { var ulogStub = sinon.stub() ulogStub.mods = [] + ulogStub.grab = ulog.grab ulogStub.mods.push({ settings: { level: { prop: {} } } }) sinon.spy(props, 'new') props.ext.call(ulogStub, function(){}) diff --git a/mods/settings/index.js b/mods/settings/index.js index ba51c0b..cf4c52b 100644 --- a/mods/settings/index.js +++ b/mods/settings/index.js @@ -1,5 +1,3 @@ -var grab = require('../../core/grab') - module.exports = { extend: { settings: {}, @@ -24,7 +22,7 @@ module.exports = { var name = args[0] if (! name) return ulog.settings args.unshift(ulog.settings[name]) - var getters = grab(ulog, 'get', []) + var getters = ulog.grab('get', []) getters.map(function(get){ args[0] = get.apply(ulog, args) }) @@ -72,7 +70,7 @@ module.exports = { var ulog = this var changed = ulog.settings[name] !== value ulog.settings[name] = value - grab(ulog, 'set', []).map(function(set){ + ulog.grab('set', []).map(function(set){ set.call(ulog, name, value) }) if (changed) ulog.ext() diff --git a/style.css b/style.css index 1fe6048..cccc05a 100644 --- a/style.css +++ b/style.css @@ -4,6 +4,8 @@ img { max-width: 100%; } /* theming */ :root { + + /** PROSE */ --root-font-family: "Roboto"; --root-font-size: 16px; @@ -17,8 +19,6 @@ img { max-width: 100%; } --h2-font-size: min(max(1.5rem, calc(1.3rem + 2.05vw)), 2.8rem); --h1-font-size: min(max(1.75rem, calc(1.58rem + 2.5vw)), 3.4rem); - --main-max-width: min(max( 30rem, calc(20rem + 50vw)), 50rem); - --body-font-weight: normal; --p-font-weight: normal; --button-font-weight: normal; @@ -102,8 +102,16 @@ img { max-width: 100%; } --h2-line-height: 1.3; --h1-line-height: 1.25; --button-line-height: 24px; + + /** LAYOUT */ + --main-max-width: min(max( 30rem, calc(20rem + 50vw)), 50rem); + --header-max-width: var(--main-max-width); + --footer-max-width: var(--main-max-width); } + +/** PROSE */ + html { font-family: var(--root-font-family); font-size: var(--root-font-size); @@ -111,8 +119,6 @@ html { body, .body {font-size: var(--body-font-size); font-weight: var(--body-font-weight); margin: var(--body-margin); padding: var(--body-padding); color: var(--body-color); line-height: var(--body-line-height)} -main, -.main {max-width: var(--main-max-width); margin: 0 auto;} p, .p {font-size: var(--p-font-size); font-weight: var(--p-font-weight); margin: var(--p-margin); padding: var(--p-padding); color: var(--p-color); line-height: var(--p-line-height)} h6, .h6 {font-size: var(--h6-font-size); font-weight: var(--h6-font-weight); margin: var(--h6-margin); padding: var(--h6-padding); color: var(--h6-color); line-height: var(--h6-line-height)} h5, .h5 {font-size: var(--h5-font-size); font-weight: var(--h5-font-weight); margin: var(--h5-margin); padding: var(--h5-padding); color: var(--h5-color); line-height: var(--h5-line-height)} @@ -153,3 +159,24 @@ h1 > .img, margin: 0px var(--button-margin-r) calc(0px - 0.33 * var(--h1-font-size)) 0px; } + +/** LAYOUT */ + +main, +.main {max-width: var(--main-max-width); margin: 0 auto;} + +header, +.header {max-width: var(--header-max-width); margin: 0 auto;} + +footer, +.footer {max-width: var(--footer-max-width); margin: 0 auto;} + + +/** STYLE */ +.alert { + background: #f5ccc5; +} + +.alert h2 { + color: #550000; +} \ No newline at end of file diff --git a/tutorial/index.html b/tutorial/index.html index fde0955..8569994 100644 --- a/tutorial/index.html +++ b/tutorial/index.html @@ -4,7 +4,8 @@ ulog tutorial - + + @@ -56,6 +57,14 @@
+ +

Tutorial

Follow the instructions to learn ulog as you read.

Have fun!

@@ -320,16 +329,16 @@

Preserves callstack

static tags the callstack will be preserved. For comparison, let's log some messages with a format that includes - message, a dynamic tag, to see the difference.

-

First, set the format to lvl name message:

-

Then, test the callstack again and compare:

-

Because message is a dynamic tag, ulog was +

Because msg is a dynamic tag, ulog was unable to preserve the callstack and the result is that now, the filename and line numbers all point to ulog.lazy.min.js, which is a lot less useful. But the message is now formatted and diff --git a/tutorial/noscript.html b/tutorial/noscript.html new file mode 100644 index 0000000..d01815e --- /dev/null +++ b/tutorial/noscript.html @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a2b3d93..0be6c1d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,7 +19,7 @@ module.exports = { }, devtool: false, optimization: { - minimize: true, + minimize: false, minimizer: [ new TerserPlugin({ extractComments: false,