├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── lib └── instrument.js ├── package.json └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | tmp/ 4 | npm-debug.log* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - '6' 3 | - '7' 4 | - '8' 5 | sudo: false 6 | language: node_js 7 | script: "npm run test:cov" 8 | after_script: "npm i -g codecov.io && cat ./coverage/lcov.info | codecov" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yoshua Wuyts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # choo-log [![stability][0]][1] 2 | [![npm version][2]][3] [![build status][4]][5] 3 | [![downloads][8]][9] [![js-standard-style][10]][11] 4 | 5 | # This project has been deprecated. All functionality has been merged into [choo-devtools](https://github.com/choojs/choo-devtools/). Thanks for passing by! 6 | 7 | Development logger for [choo][12]. 8 | 9 | ![screen capture](./screenshot.png) 10 | 11 | ## Usage 12 | ```js 13 | var log = require('choo-log') 14 | var choo = require('choo') 15 | 16 | var app = choo() 17 | app.use(log()) 18 | app.mount('body') 19 | ``` 20 | 21 | ## API 22 | ### `logger = log(opts)` 23 | Create a new logger instance. Opts can contain: 24 | - __timing:__ defaults to `true`. Disable calls to `window.performance` timing 25 | API. Timing calls will not run in browsers that don't support it out of the 26 | box. 27 | - __clearResourceTimings:__ defaults to `true`. Disable clearing the 28 | [window.performance resourcetimingbuffer][buf] when full. Set to `false` if 29 | the buffer is cleared somewhere else. 30 | - __colors:__ defaults to the default theme of [nanologger][15]. 31 | 32 | ### `emitter.emit('log:', msg)` 33 | Send a log event. `choo-log` will pass `log:` events through to [nanologger](https://github.com/choojs/nanologger). For example: 34 | 35 | ```js 36 | emitter.emit('log:info', 'The rain in Spain stays mainly in the plain 🌧') 37 | ``` 38 | 39 | These are just normal events, so you can listen to them in addition to them being logged: 40 | 41 | ```js 42 | emitter.on('log:debug', function (msg) { 43 | // do something with debug message 44 | }) 45 | ``` 46 | 47 | ### `localStorage.setItem('logLevel', )` 48 | 49 | Set the [nanologger log level](https://github.com/choojs/nanologger#level--logloglevel), e.g.: 50 | 51 | ```js 52 | localStorage.setItem('logLevel','debug') 53 | ``` 54 | 55 | ## Installation 56 | ```sh 57 | $ npm install choo-log 58 | ``` 59 | 60 | ## License 61 | [MIT](https://tldrlegal.com/license/mit-license) 62 | 63 | [0]: https://img.shields.io/badge/stability-experimental-orange.svg?style=flat-square 64 | [1]: https://nodejs.org/api/documentation.html#documentation_stability_index 65 | [2]: https://img.shields.io/npm/v/choo-log.svg?style=flat-square 66 | [3]: https://npmjs.org/package/choo-log 67 | [4]: https://img.shields.io/travis/choojs/choo-log/master.svg?style=flat-square 68 | [5]: https://travis-ci.org/choojs/choo-log 69 | [6]: https://img.shields.io/codecov/c/github/choojs/choo-log/master.svg?style=flat-square 70 | [7]: https://codecov.io/github/choojs/choo-log 71 | [8]: http://img.shields.io/npm/dm/choo-log.svg?style=flat-square 72 | [9]: https://npmjs.org/package/choo-log 73 | [10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square 74 | [11]: https://github.com/feross/standard 75 | [12]: https://github.com/choojs/choo 76 | [13]: https://img.shields.io/badge/built%20for%20choo-v4-ffc3e4.svg?style=flat-square 77 | [14]: https://github.com/choojs/choo 78 | [15]: https://github.com/choojs/nanologger/blob/master/index.js#L17 79 | [buf]: https://developer.mozilla.org/en-US/docs/Web/API/Performance/onresourcetimingbufferfull 80 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export default logger; 2 | 3 | declare namespace Choo { 4 | export interface LogOpts { 5 | colors?: object 6 | } 7 | } 8 | 9 | declare function logger(opts?: Choo.LogOpts): (state: any, emitter: any) => void; 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var nanologger = require('nanologger') 2 | var assert = require('assert') 3 | 4 | var ChooInstrument = require('./lib/instrument') 5 | 6 | module.exports = logger 7 | 8 | function logger (opts) { 9 | opts = opts || {} 10 | var colors = opts.colors || {} 11 | var initialRender = true 12 | 13 | assert.equal(typeof opts, 'object', 'choo-log: opts should be type object') 14 | 15 | return function (state, emitter) { 16 | var hook = ChooInstrument(emitter) 17 | var log = nanologger('choo', { colors: colors }) 18 | 19 | hook.on('log:trace', log.trace.bind(log)) 20 | hook.on('log:debug', log.debug.bind(log)) 21 | hook.on('log:info', log.info.bind(log)) 22 | hook.on('log:warn', log.warn.bind(log)) 23 | hook.on('log:error', log.error.bind(log)) 24 | hook.on('log:fatal', log.fatal.bind(log)) 25 | 26 | hook.on('service-worker', function (data) { 27 | log.info('Service worker installed') 28 | }) 29 | 30 | hook.on('event', function (eventName, data, timing) { 31 | if (timing) { 32 | var duration = timing.duration.toFixed() 33 | var level = duration < 50 ? 'info' : 'warn' 34 | if (data !== undefined) log[level](eventName, data, duration + 'ms') 35 | else log[level](eventName, duration + 'ms') 36 | } else { 37 | if (data !== undefined) log.info(eventName, data) 38 | else log.info(eventName) 39 | } 40 | }) 41 | 42 | hook.on('use', function (count, duration) { 43 | log.debug('use', { count: count }, duration + 'ms') 44 | }) 45 | 46 | hook.on('unhandled', function (eventName, data) { 47 | log.error('No listeners for ' + eventName) 48 | }) 49 | 50 | hook.on('DOMContentLoaded', function (timing) { 51 | if (!timing) return log.info('DOMContentLoaded') 52 | var level = timing.interactive < 1000 ? 'info' : 'warn' 53 | log[level]('DOMContentLoaded', timing.interactive + 'ms to interactive') 54 | }) 55 | 56 | hook.on('render', function (timings) { 57 | if (!timings) return log.info('render') 58 | var duration = timings.render.duration.toFixed() 59 | var msg = 'render' 60 | 61 | if (initialRender) { 62 | initialRender = false 63 | msg = 'Initial ' + msg 64 | } 65 | 66 | // each frame has 10ms available for userland stuff 67 | var fps = Math.min((600 / duration).toFixed(), 60) 68 | 69 | if (fps === 60) { 70 | log.info(msg, fps + 'fps', duration + 'ms') 71 | } else { 72 | log.warn(msg, fps + 'fps', duration + 'ms', { 73 | render: timings.render.duration.toFixed() + 'ms', 74 | morph: timings.morph.duration.toFixed() + 'ms' 75 | }) 76 | } 77 | }) 78 | 79 | hook.on('resource-timing-buffer-full', function () { 80 | log.error("The browser's Resource Resource timing buffer is full. Cannot store any more timing information") 81 | }) 82 | 83 | hook.start() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/instrument.js: -------------------------------------------------------------------------------- 1 | var onPerformance = require('on-performance') 2 | var onIdle = require('on-idle') 3 | var assert = require('assert') 4 | 5 | module.exports = ChooInstrument 6 | 7 | function ChooInstrument (emitter) { 8 | if (!(this instanceof ChooInstrument)) return new ChooInstrument(emitter) 9 | 10 | assert.equal(typeof emitter, 'object') 11 | 12 | this.hasWindow = typeof window !== 'undefined' 13 | this.hasIdleCallback = this.hasWindow && window.requestIdleCallback 14 | this.hasPerformance = this.hasWindow && 15 | window.performance && 16 | window.performance.getEntriesByName 17 | 18 | this.emitter = emitter 19 | this.listeners = {} 20 | this.buffer = { 21 | use: [], 22 | render: [], 23 | events: {} 24 | } 25 | } 26 | 27 | ChooInstrument.prototype.on = function (name, handler) { 28 | this.listeners[name] = handler 29 | } 30 | 31 | ChooInstrument.prototype.start = function () { 32 | var self = this 33 | if (this.hasPerformance) { 34 | window.performance.onresourcetimingbufferfull = function () { 35 | var listener = self.listeners['resource-timing-buffer-full'] 36 | if (listener) listener() 37 | } 38 | } 39 | 40 | // TODO also handle log events 41 | onPerformance(function (timing) { 42 | if (!timing) return 43 | if (timing.entryType !== 'measure') return 44 | 45 | var eventName = timing.name 46 | if (/choo\.morph/.test(eventName)) { 47 | self.buffer.render.push(timing) 48 | } else if (/choo\.route/.test(eventName)) { 49 | self.buffer.render.push(timing) 50 | } else if (/choo\.render/.test(eventName)) { 51 | self.buffer.render.push(timing) 52 | } else if (/choo\.use/.test(eventName)) { 53 | self.buffer.use.push(timing) 54 | } else if (/choo\.emit/.test(eventName) && 55 | !/log:/.test(eventName) && 56 | !/sw:/.test(eventName)) { 57 | var eventListener = self.listeners['event'] 58 | if (eventListener) { 59 | var timingName = eventName.match(/choo\.emit\('(.*)'\)/)[1] 60 | if (timingName === 'render' || timingName === 'DOMContentLoaded') return 61 | 62 | var traceId = eventName.match(/\[(\d+)\]/)[1] 63 | var data = self.buffer.events[traceId] 64 | 65 | self.buffer.events[traceId] = null 66 | eventListener(timingName, data, timing) 67 | } 68 | } 69 | 70 | if (self.buffer.render.length === 3) { 71 | var renderListener = self.listeners['render'] 72 | if (!renderListener) return 73 | var timings = {} 74 | while (self.buffer.render.length) { 75 | var _timing = self.buffer.render.pop() 76 | var name = _timing.name 77 | if (/choo\.render/.test(name)) timings.render = _timing 78 | else if (/choo\.morph/.test(name)) timings.morph = _timing 79 | else timings.route = _timing 80 | } 81 | renderListener(timings) 82 | } 83 | }) 84 | 85 | // Check if there's timings without any listeners 86 | // and trigger the DOMContentLoaded event. 87 | // If the timing API is not available, we handle all events here 88 | this.emitter.on('*', function (eventName, data, uuid) { 89 | var logLevel = /^log:(\w{4,5})/.exec(eventName) 90 | 91 | if (!self.hasPerformance && eventName === 'render') { 92 | // Render 93 | var renderListener = self.listeners['render'] 94 | if (renderListener) renderListener() 95 | } else if (eventName === 'DOMContentLoaded') { 96 | // DOMContentLoaded 97 | self._emitLoaded() 98 | } else if (eventName === 'sw:installed') { 99 | var swListener = self.listeners['service-worker'] 100 | if (swListener) swListener(data) 101 | } else if (logLevel) { 102 | // Log:* 103 | var logListener = self.listeners['log:' + logLevel[1]] 104 | if (logListener) logListener(eventName, data) 105 | } else if (!self.emitter.listeners(eventName).length) { 106 | // Unhandled 107 | var unhandledListener = self.listeners['unhandled'] 108 | if (unhandledListener) unhandledListener(eventName, data) 109 | } else if (eventName !== 'render') { 110 | // * 111 | if (self.hasPerformance) self.buffer.events[uuid] = data 112 | } 113 | }) 114 | } 115 | 116 | // compute and log time till interactive when DOMContentLoaded event fires 117 | ChooInstrument.prototype._emitLoaded = function () { 118 | var self = this 119 | onIdle(function clear () { 120 | var listener = self.listeners['DOMContentLoaded'] 121 | var usesListener = self.listeners['use'] 122 | 123 | var timing = self.hasWindow && window.performance && window.performance.timing 124 | 125 | if (listener && timing) { 126 | listener({ 127 | interactive: timing.domInteractive - timing.navigationStart, 128 | loaded: timing.domContentLoadedEventEnd - timing.navigationStart 129 | }) 130 | } 131 | 132 | if (self.hasPerformance) { 133 | var duration = sumDurations(self.buffer.use) 134 | if (usesListener) usesListener(self.buffer.use.length, duration) 135 | } else { 136 | usesListener() 137 | } 138 | }) 139 | } 140 | 141 | function sumDurations (timings) { 142 | return timings.reduce(function (sum, timing) { 143 | return sum + timing.duration 144 | }, 0).toFixed() 145 | } 146 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "choo-log", 3 | "version": "8.0.0", 4 | "description": "Development logger for choo", 5 | "main": "index.js", 6 | "scripts": { 7 | "deps": "dependency-check . && dependency-check . --extra --no-dev", 8 | "test": "standard && npm run deps", 9 | "test:cov": "standard && npm run deps" 10 | }, 11 | "repository": "yoshuawuyts/choo-log", 12 | "keywords": [ 13 | "choo", 14 | "log", 15 | "tcby", 16 | "yo-yo", 17 | "fast", 18 | "browser", 19 | "hah" 20 | ], 21 | "license": "MIT", 22 | "dependencies": { 23 | "nanologger": "^1.3.0", 24 | "on-idle": "^3.0.2", 25 | "on-performance": "^1.0.0" 26 | }, 27 | "devDependencies": { 28 | "choo": "^5.0.0", 29 | "dependency-check": "^2.5.2", 30 | "istanbul": "^0.4.4", 31 | "standard": "^9.0.2", 32 | "tape": "^4.6.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/choojs/choo-log/8c11be6bb7eb99ca493bc9ba22641eb416d5c996/screenshot.png --------------------------------------------------------------------------------