├── .npmrc ├── .npmignore ├── .gitignore ├── assets └── debug.png ├── package.json ├── LICENSE ├── example.js ├── index.js └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example.js 2 | assets/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store* 2 | node_modules 3 | -------------------------------------------------------------------------------- /assets/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koajs/trace/HEAD/assets/debug.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-trace", 3 | "description": "generic tracing for koa", 4 | "version": "1.0.1", 5 | "author": { 6 | "name": "Jonathan Ong", 7 | "email": "me@jongleberry.com", 8 | "url": "http://jongleberry.com", 9 | "twitter": "https://twitter.com/jongleberry" 10 | }, 11 | "license": "MIT", 12 | "repository": "koajs/trace", 13 | "dependencies": { 14 | "debug": "^4.1.0" 15 | }, 16 | "devDependencies": { 17 | "koa": "*", 18 | "mocha": "^5.2.0", 19 | "mz": "1" 20 | }, 21 | "scripts": { 22 | "start": "DEBUG=koa-trace:* node example.js", 23 | "test": "mocha --reporter spec --bail" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Jonathan Ong me@jongleberry.com 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 2 | const Koa = require('koa') 3 | const crypto = require('mz/crypto') 4 | 5 | const app = new Koa() 6 | 7 | require('./')(app) 8 | 9 | app.use(async function (ctx, next) { 10 | const start = Date.now() 11 | ctx.id = await crypto.randomBytes(12) 12 | 13 | ctx.trace('start') 14 | 15 | await next() 16 | 17 | ctx.trace('finish') 18 | ctx.set('X-Response-Time', (Date.now() - start) + 'ms') 19 | }) 20 | 21 | app.use(async function (ctx, next) { 22 | ctx.trace('wait:before') 23 | 24 | function delay () { 25 | return new Promise((resolve) => { 26 | setTimeout(resolve, Math.random() * 100) 27 | }) 28 | } 29 | 30 | await delay() 31 | 32 | ctx.trace('wait:after') 33 | 34 | await next() 35 | }) 36 | 37 | app.use(async function (ctx, next) { 38 | ctx.trace('user.id', random()) 39 | ctx.trace('some.event', random(), random()) 40 | ctx.trace('another.event', random(), random()) 41 | ctx.body = 'hello world!' 42 | }) 43 | 44 | app.debug() 45 | 46 | const port = process.env.PORT || 3210 47 | app.listen(port) 48 | console.log('koa-trace example listening on port %s', port) 49 | 50 | function random() { 51 | return Math.random().toString(36).slice(2) 52 | } 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | var slice = [].slice 3 | 4 | module.exports = function (app) { 5 | // trace something 6 | app.context.trace = function trace(event) { 7 | var args = [] 8 | switch (arguments.length) { 9 | case 0: 10 | throw new Error('No event defined!') 11 | case 1: break 12 | case 2: 13 | args = [arguments[1]] 14 | break 15 | default: 16 | args = slice.call(arguments, 1) 17 | } 18 | 19 | dispatch(this, event, args) 20 | return this 21 | } 22 | 23 | // attach a listener 24 | app.instrument = instrument 25 | 26 | // log all events to the debugger 27 | app.debug = function () { 28 | var Debug = require('debug') 29 | 30 | app.instrument(function (context, event, date, args) { 31 | var id = context.id 32 | if (!id) throw new Error('you need to set `this.id` to debug') 33 | if (Buffer.isBuffer(id)) id = id.toString('base64') 34 | var debug = context.debug 35 | if (!debug) debug = context.debug = Debug('koa-trace:' + id) 36 | 37 | var output = '' 38 | switch (args.length) { 39 | case 0: break 40 | case 1: 41 | output = args[0] 42 | break 43 | default: 44 | output = args 45 | } 46 | debug(event, output) 47 | }) 48 | 49 | return this 50 | } 51 | 52 | // attach a listener 53 | var listeners = [] 54 | function instrument(fn) { 55 | listeners.push(fn) 56 | return this 57 | } 58 | 59 | // dispatch an event to all listeners 60 | function dispatch(context, event, args) { 61 | var date = new Date() 62 | for (var i = 0; i < listeners.length; i++) 63 | listeners[i](context, event, date, args) 64 | } 65 | 66 | return app 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Koa Trace 3 | 4 | A generic tracing module. 5 | Use `.trace()` calls in your middleware 6 | and send all the data to your favorite tracer or stats aggregator 7 | like jstrace, dtrace, ktap, statds, etc. 8 | [debug](http://github.com/visionmedia/debug) also supported! 9 | 10 | ```js 11 | app.use(async function (ctx, next) { 12 | // give each request some sort of ID 13 | ctx.id = crypto.randomBytes(12) 14 | 15 | // log events with optional arguments 16 | ctx.trace('start') 17 | await next() 18 | ctx.trace('finish') 19 | }) 20 | ``` 21 | 22 | Enable [debug](http://github.com/visionmedia/debug) usage: 23 | 24 | ```js 25 | app.debug() 26 | ``` 27 | 28 | Now run your app with `DEBUG=koa-trace:*` and watch the events unfold: 29 | 30 | ![debug statements](assets/debug.png) 31 | 32 | You can see the debug statements grouped by request ID. 33 | Then the event is shown, the time difference since the last statement, 34 | and the arguments passed to `.trace()`. 35 | 36 | ## Background 37 | 38 | I want to add something like this to Koa, 39 | but we're not sure what the best way possible is. 40 | Please provide feedback on this module, 41 | including suggestions or criticisms. 42 | I'm sure the [debug](http://github.com/visionmedia/debug) usage 43 | could use a lot of work. 44 | 45 | ## Convention 46 | 47 | There are no conventions as to how to name your `events` or what arguments 48 | to trace. 49 | The only thing you __should__ do is create some sort of `this.id`, 50 | either a `Buffer` or a `String`, 51 | before you do any `.trace()` calls. 52 | You will need this anyway in your logs. 53 | 54 | This module is backend agnostic. 55 | There is no possible way to support all the features of all the different backends. 56 | If you want to use backend specific stuff, 57 | you might as well use a module specific to it! 58 | 59 | If you want to create a convention, let me know! 60 | We can start a doc or wiki or something. 61 | 62 | ## API 63 | 64 | ### require('koa-trace')(app) 65 | 66 | Add the instrumentation methods to `app`. 67 | 68 | ```js 69 | const Koa = require('koa') 70 | const app = new Koa() 71 | require('koa-trace')(app) 72 | ``` 73 | 74 | ### this.trace(event, args...) 75 | 76 | Emit an `event` with optional arguments. 77 | 78 | ```js 79 | app.use(async function (ctx, next) { 80 | ctx.trace('something', 1, 2, 3) 81 | await next() 82 | }) 83 | ``` 84 | 85 | ### app.instrument(function (context, event, date, args) {}) 86 | 87 | Listen to all the trace calls. 88 | 89 | - `context` is the koa `this` context. 90 | - `event` is the traced event. 91 | - `date` is a `Date` instance of when the trace was called. 92 | This means that you don't have to trace any of your own `Date`s. 93 | - `args` is an array of all the arguments passed. 94 | 95 | Any backends will need to use this method to hook into the trace calls. 96 | 97 | ### app.debug() 98 | 99 | Enable all the debug logging. 100 | This is not enabled by default, 101 | so you might want to do something like: 102 | 103 | ```js 104 | if (process.env.NODE_ENV !== 'production') app.debug() 105 | ``` 106 | 107 | To view all the debug logs, 108 | run the node process with `DEBUG=koa-trace:*`. 109 | --------------------------------------------------------------------------------