├── .github └── workflows │ └── ci-module.yml ├── .gitignore ├── API.md ├── LICENSE.md ├── README.md ├── lib └── index.js ├── package.json └── test └── index.js /.github/workflows/ci-module.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu, windows, macos] 15 | node: ['*', '14', '12'] 16 | 17 | runs-on: ${{ matrix.os }}-latest 18 | name: ${{ matrix.os }} node@${{ matrix.node }} 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node }} 24 | - name: install 25 | run: npm install 26 | - name: test 27 | run: npm test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/package-lock.json 3 | 4 | coverage.* 5 | 6 | **/.DS_Store 7 | **/._* 8 | 9 | **/*.pem 10 | 11 | **/.vs 12 | **/.vscode 13 | **/.idea 14 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | ## Usage 3 | 4 | ## `new GoodConsole([config])` 5 | Creates a new GoodConsole object with the following arguments: 6 | 7 | - `[config]` - optional configuration object with the following keys 8 | - `format` - [MomentJS](http://momentjs.com/docs/#/displaying/format/) format string. Defaults to 'YYMMDD/HHmmss.SSS'. 9 | - `utc` - boolean controlling Moment using [utc mode](http://momentjs.com/docs/#/parsing/utc/) or not. Defaults to `true`. 10 | - `color` - a boolean specifying whether to output in color. Defaults to `true`. 11 | 12 | ## Output Formats 13 | 14 | Below are example outputs for the designated event type: 15 | 16 | - "ops" - 160318/013330.957, [ops] memory: 29Mb, uptime (seconds): 6, load: [1.650390625,1.6162109375,1.65234375] 17 | - "error" - 160318/013330.957, [error,`event.tags`] message: Just a simple error, stack: `event.error.stack` 18 | - "request" - 160318/013330.957, [request,`event.tags`] data: you made a request 19 | - "log" - 160318/013330.957, [log,`event.tags`] data: you made a default 20 | - "response" - 160318/013330.957, [response, `event.tags`] http://localhost:61253: post /data {"name":"adam"} 200 (150ms) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2020, Sideway Inc, and project contributors 2 | Copyright (c) 2012-2014, Walmart. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # @hapi/good-console 4 | 5 | #### Console reporting for good console monitor. 6 | 7 | **good-console** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together. 8 | 9 | ### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support 10 | 11 | ## Useful resources 12 | 13 | - [Documentation and API](https://hapi.dev/family/good-console/) 14 | - [Version status](https://hapi.dev/resources/status/#good-console) (builds, dependencies, node versions, licenses, eol) 15 | - [Changelog](https://hapi.dev/family/good-console/changelog/) 16 | - [Project policies](https://hapi.dev/policies/) 17 | - [Free and commercial support options](https://hapi.dev/support/) 18 | 19 | ## Note: this module is being deprecated on December 31st, 2020 due to lack to available support resources. Please consider using [another logging plugin](https://hapi.dev/plugins/#logging). 20 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Stream = require('stream'); 4 | 5 | const Hoek = require('@hapi/hoek'); 6 | const Moment = require('moment'); 7 | const SafeStringify = require('json-stringify-safe'); 8 | 9 | 10 | const internals = { 11 | defaults: { 12 | format: 'YYMMDD/HHmmss.SSS', 13 | utc: true, 14 | color: true 15 | } 16 | }; 17 | 18 | 19 | module.exports = internals.GoodConsole = class extends Stream.Transform { 20 | 21 | constructor(config) { 22 | 23 | super({ objectMode: true }); 24 | 25 | config = config || {}; 26 | this._settings = Hoek.applyToDefaults(internals.defaults, config); 27 | } 28 | 29 | _transform(data, enc, next) { 30 | 31 | const eventName = data.event; 32 | let tags = []; 33 | 34 | if (Array.isArray(data.tags)) { 35 | tags = data.tags.concat([]); 36 | } 37 | else if (data.tags) { 38 | tags = [data.tags]; 39 | } 40 | 41 | tags.unshift(eventName); 42 | 43 | if (eventName === 'error' || data.error instanceof Error) { 44 | return next(null, internals.formatError(data, tags, this._settings)); 45 | } 46 | 47 | if (eventName === 'ops') { 48 | return next(null, internals.formatOps(data, tags, this._settings)); 49 | } 50 | 51 | if (eventName === 'response') { 52 | return next(null, internals.formatResponse(data, tags, this._settings)); 53 | } 54 | 55 | if (!data.data) { 56 | data.data = '(none)'; 57 | } 58 | 59 | return next(null, internals.formatDefault(data, tags, this._settings)); 60 | } 61 | }; 62 | 63 | 64 | internals.formatOutput = function (event, settings) { 65 | 66 | let timestamp = Moment(parseInt(event.timestamp, 10)); 67 | 68 | if (settings.utc) { 69 | timestamp = timestamp.utc(); 70 | } 71 | 72 | timestamp = timestamp.format(settings.format); 73 | 74 | event.tags = event.tags.toString(); 75 | const tags = ` [${event.tags}] `; 76 | 77 | // Add event id information if available, typically for 'request' events 78 | 79 | const id = event.id ? ` (${event.id})` : ''; 80 | 81 | return `${timestamp},${id}${tags}${event.data}\n`; 82 | }; 83 | 84 | 85 | internals.formatMethod = function (method, settings) { 86 | 87 | const methodColors = { 88 | get: 32, 89 | delete: 31, 90 | put: 36, 91 | post: 33 92 | }; 93 | 94 | let formattedMethod = method.toLowerCase(); 95 | if (settings.color) { 96 | const color = methodColors[method.toLowerCase()] || 34; 97 | formattedMethod = `\x1b[1;${color}m${formattedMethod}\x1b[0m`; 98 | } 99 | 100 | return formattedMethod; 101 | }; 102 | 103 | 104 | internals.formatStatusCode = function (statusCode, settings) { 105 | 106 | let color; 107 | if (statusCode && settings.color) { 108 | color = 32; 109 | if (statusCode >= 500) { 110 | color = 31; 111 | } 112 | else if (statusCode >= 400) { 113 | color = 33; 114 | } 115 | else if (statusCode >= 300) { 116 | color = 36; 117 | } 118 | 119 | return `\x1b[${color}m${statusCode}\x1b[0m`; 120 | } 121 | 122 | return statusCode; 123 | }; 124 | 125 | 126 | internals.formatResponse = function (event, tags, settings) { 127 | 128 | const query = event.query ? SafeStringify(event.query) : ''; 129 | const method = internals.formatMethod(event.method, settings); 130 | const statusCode = internals.formatStatusCode(event.statusCode, settings) || ''; 131 | 132 | // event, timestamp, id, instance, labels, method, path, query, responseTime, 133 | // statusCode, pid, httpVersion, source, remoteAddress, userAgent, referer, log 134 | // method, pid, error 135 | 136 | const output = `${event.instance}: ${method} ${event.path} ${query} ${statusCode} (${event.responseTime}ms)`; 137 | 138 | const response = { 139 | id: event.id, 140 | timestamp: event.timestamp, 141 | tags, 142 | data: output 143 | }; 144 | 145 | return internals.formatOutput(response, settings); 146 | }; 147 | 148 | 149 | internals.formatOps = function (event, tags, settings) { 150 | 151 | const memory = Math.round(event.proc.mem.rss / (1024 * 1024)); 152 | const output = `memory: ${memory}Mb, uptime (seconds): ${event.proc.uptime}, load: [${event.os.load}]`; 153 | 154 | const ops = { 155 | timestamp: event.timestamp, 156 | tags, 157 | data: output 158 | }; 159 | 160 | return internals.formatOutput(ops, settings); 161 | }; 162 | 163 | 164 | internals.formatError = function (event, tags, settings) { 165 | 166 | const output = `message: ${event.error.message}, stack: ${event.error.stack}`; 167 | 168 | const error = { 169 | id: event.id, 170 | timestamp: event.timestamp, 171 | tags, 172 | data: output 173 | }; 174 | 175 | return internals.formatOutput(error, settings); 176 | }; 177 | 178 | 179 | internals.formatDefault = function (event, tags, settings) { 180 | 181 | const data = typeof event.data === 'object' ? SafeStringify(event.data) : event.data; 182 | const output = `data: ${data}`; 183 | 184 | const defaults = { 185 | timestamp: event.timestamp, 186 | id: event.id, 187 | tags, 188 | data: output 189 | }; 190 | 191 | return internals.formatOutput(defaults, settings); 192 | }; 193 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hapi/good-console", 3 | "version": "9.0.1", 4 | "repository": "git://github.com/hapijs/good-console", 5 | "description": "Console broadcasting for Good process monitor", 6 | "main": "lib/index.js", 7 | "files": [ 8 | "lib" 9 | ], 10 | "dependencies": { 11 | "@hapi/hoek": "9.x.x", 12 | "json-stringify-safe": "5.x.x", 13 | "moment": "2.x.x" 14 | }, 15 | "devDependencies": { 16 | "@hapi/code": "8.x.x", 17 | "@hapi/lab": "24.x.x", 18 | "@hapi/teamwork": "5.x.x" 19 | }, 20 | "scripts": { 21 | "test": "lab -t 100 -L -a @hapi/code" 22 | }, 23 | "license": "BSD-3-Clause" 24 | } 25 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Stream = require('stream'); 4 | 5 | const Code = require('@hapi/code'); 6 | const GoodConsole = require('..'); 7 | const Lab = require('@hapi/lab'); 8 | const Moment = require('moment'); 9 | const Teamwork = require('@hapi/teamwork'); 10 | 11 | 12 | const internals = { 13 | defaults: { 14 | format: 'YYMMDD/HHmmss.SSS' 15 | } 16 | }; 17 | 18 | 19 | const { it, describe } = exports.lab = Lab.script(); 20 | const { expect } = Code; 21 | 22 | 23 | describe('GoodConsole', () => { 24 | 25 | describe('report', () => { 26 | 27 | describe('response events', () => { 28 | 29 | it('returns a formatted string for "response" events', { plan: 2 }, async () => { 30 | 31 | const reporter = new GoodConsole(); 32 | const out = new internals.Writer(); 33 | const reader = new internals.Reader(); 34 | 35 | reader.pipe(reporter).pipe(out); 36 | reader.push(internals.response); 37 | reader.push(null); 38 | 39 | const team = new Teamwork.Team(); 40 | reader.once('end', () => { 41 | 42 | expect(out.data).to.have.length(1); 43 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data {"name":"adam"} \u001b[32m200\u001b[0m (150ms)\n'); 44 | team.attend(); 45 | }); 46 | 47 | await team.work; 48 | }); 49 | 50 | it('returns a formatted string for "response" events without a query', { plan: 2 }, async () => { 51 | 52 | const reporter = new GoodConsole(); 53 | const out = new internals.Writer(); 54 | const reader = new internals.Reader(); 55 | 56 | reader.pipe(reporter).pipe(out); 57 | 58 | const response = Object.assign({}, internals.response); 59 | delete response.query; 60 | 61 | reader.push(response); 62 | reader.push(null); 63 | 64 | const team = new Teamwork.Team(); 65 | reader.once('end', () => { 66 | 67 | expect(out.data).to.have.length(1); 68 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data \u001b[32m200\u001b[0m (150ms)\n'); 69 | team.attend(); 70 | }); 71 | 72 | await team.work; 73 | }); 74 | 75 | it('returns a formatted string for "response" events without a statusCode', { plan: 2 }, async () => { 76 | 77 | const reporter = new GoodConsole(); 78 | const out = new internals.Writer(); 79 | const reader = new internals.Reader(); 80 | 81 | reader.pipe(reporter).pipe(out); 82 | 83 | const response = Object.assign({}, internals.response); 84 | delete response.statusCode; 85 | 86 | reader.push(response); 87 | reader.push(null); 88 | 89 | const team = new Teamwork.Team(); 90 | reader.once('end', () => { 91 | 92 | expect(out.data).to.have.length(1); 93 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data {"name":"adam"} (150ms)\n'); 94 | team.attend(); 95 | }); 96 | 97 | await team.work; 98 | }); 99 | 100 | it('returns a formatted string for "response" events uncolored', { plan: 2 }, async () => { 101 | 102 | const reporter = new GoodConsole({ color: false }); 103 | const out = new internals.Writer(); 104 | const reader = new internals.Reader(); 105 | 106 | reader.pipe(reporter).pipe(out); 107 | 108 | reader.push(internals.response); 109 | reader.push(null); 110 | 111 | const team = new Teamwork.Team(); 112 | reader.once('end', () => { 113 | 114 | expect(out.data).to.have.length(1); 115 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: post /data {"name":"adam"} 200 (150ms)\n'); 116 | team.attend(); 117 | }); 118 | 119 | await team.work; 120 | }); 121 | 122 | it('returns a formatted string for "response" events with local time', { plan: 2 }, async () => { 123 | 124 | const reporter = new GoodConsole({ utc: false }); 125 | const out = new internals.Writer(); 126 | const reader = new internals.Reader(); 127 | 128 | reader.pipe(reporter).pipe(out); 129 | 130 | const response = Object.assign({}, internals.response); 131 | response.timestamp = Date.now(); 132 | 133 | const date = Moment(response.timestamp).format(internals.defaults.format); 134 | 135 | reader.push(response); 136 | reader.push(null); 137 | 138 | const team = new Teamwork.Team(); 139 | reader.once('end', () => { 140 | 141 | expect(out.data).to.have.length(1); 142 | expect(out.data[0]).to.be.equal(`${date}, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data {"name":"adam"} \u001b[32m200\u001b[0m (150ms)\n`); 143 | team.attend(); 144 | }); 145 | 146 | await team.work; 147 | }); 148 | 149 | it('returns a formatted string for "response" events with "head" as method', { plan: 2 }, async () => { 150 | 151 | const reporter = new GoodConsole(); 152 | const out = new internals.Writer(); 153 | const reader = new internals.Reader(); 154 | 155 | reader.pipe(reporter).pipe(out); 156 | 157 | const response = Object.assign({}, internals.response); 158 | response.method = 'head'; 159 | 160 | reader.push(response); 161 | reader.push(null); 162 | 163 | const team = new Teamwork.Team(); 164 | reader.once('end', () => { 165 | 166 | expect(out.data).to.have.length(1); 167 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;34mhead\u001b[0m /data {"name":"adam"} \u001b[32m200\u001b[0m (150ms)\n'); 168 | team.attend(); 169 | }); 170 | 171 | await team.work; 172 | }); 173 | 174 | it('returns a formatted string for "response" events with "statusCode" 500', { plan: 2 }, async () => { 175 | 176 | const reporter = new GoodConsole(); 177 | const out = new internals.Writer(); 178 | const reader = new internals.Reader(); 179 | 180 | reader.pipe(reporter).pipe(out); 181 | 182 | const response = Object.assign({}, internals.response); 183 | response.statusCode = 599; 184 | 185 | reader.push(response); 186 | reader.push(null); 187 | 188 | const team = new Teamwork.Team(); 189 | reader.once('end', () => { 190 | 191 | expect(out.data).to.have.length(1); 192 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data {"name":"adam"} \u001b[31m599\u001b[0m (150ms)\n'); 193 | team.attend(); 194 | }); 195 | 196 | await team.work; 197 | }); 198 | 199 | it('returns a formatted string for "response" events with "statusCode" 400', { plan: 2 }, async () => { 200 | 201 | const reporter = new GoodConsole(); 202 | const out = new internals.Writer(); 203 | const reader = new internals.Reader(); 204 | 205 | reader.pipe(reporter).pipe(out); 206 | 207 | const response = Object.assign({}, internals.response); 208 | response.statusCode = 418; 209 | 210 | reader.push(response); 211 | reader.push(null); 212 | 213 | const team = new Teamwork.Team(); 214 | reader.once('end', () => { 215 | 216 | expect(out.data).to.have.length(1); 217 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data {"name":"adam"} \u001b[33m418\u001b[0m (150ms)\n'); 218 | team.attend(); 219 | }); 220 | 221 | await team.work; 222 | }); 223 | 224 | it('returns a formatted string for "response" events with "statusCode" 300', { plan: 2 }, async () => { 225 | 226 | const reporter = new GoodConsole(); 227 | const out = new internals.Writer(); 228 | const reader = new internals.Reader(); 229 | 230 | reader.pipe(reporter).pipe(out); 231 | 232 | const response = Object.assign({}, internals.response); 233 | response.statusCode = 304; 234 | 235 | reader.push(response); 236 | reader.push(null); 237 | 238 | const team = new Teamwork.Team(); 239 | reader.once('end', () => { 240 | 241 | expect(out.data).to.have.length(1); 242 | expect(out.data[0]).to.be.equal('160318/013330.957, (1458264811279:localhost:16014:ilx17kv4:10001) [response] http://localhost:61253: \u001b[1;33mpost\u001b[0m /data {"name":"adam"} \u001b[36m304\u001b[0m (150ms)\n'); 243 | team.attend(); 244 | }); 245 | 246 | await team.work; 247 | }); 248 | }); 249 | 250 | describe('ops events', () => { 251 | 252 | it('returns a formatted string for "ops" events', { plan: 2 }, async () => { 253 | 254 | const reporter = new GoodConsole(); 255 | const out = new internals.Writer(); 256 | const reader = new internals.Reader(); 257 | 258 | reader.pipe(reporter).pipe(out); 259 | 260 | for (let i = 0; i < 20; ++i) { 261 | reader.push(internals.ops); 262 | } 263 | 264 | reader.push(null); 265 | 266 | const team = new Teamwork.Team(); 267 | reader.once('end', () => { 268 | 269 | expect(out.data).to.have.length(20); 270 | expect(out.data[0]).to.be.equal('160318/013330.957, [ops] memory: 29Mb, uptime (seconds): 6, load: [1.650390625,1.6162109375,1.65234375]\n'); 271 | team.attend(); 272 | }); 273 | 274 | await team.work; 275 | }); 276 | }); 277 | 278 | describe('error events', () => { 279 | 280 | it('returns a formatted string for "error" events', { plan: 2 }, async () => { 281 | 282 | const reporter = new GoodConsole(); 283 | const out = new internals.Writer(); 284 | const reader = new internals.Reader(); 285 | 286 | reader.pipe(reporter).pipe(out); 287 | 288 | reader.push(internals.error); 289 | reader.push(null); 290 | 291 | const team = new Teamwork.Team(); 292 | reader.once('end', () => { 293 | 294 | expect(out.data).to.have.length(1); 295 | expect(out.data[0]).to.be.equal('160318/013330.957, (1419005623332:new-host.local:48767:i3vrb3z7:10000) [error,user,info] message: Just a simple error, stack: Error: Just a simple Error\n'); 296 | team.attend(); 297 | }); 298 | 299 | await team.work; 300 | 301 | }); 302 | }); 303 | 304 | describe('request events', () => { 305 | 306 | it('returns a formatted string for "request" events', { plan: 2 }, async () => { 307 | 308 | const reporter = new GoodConsole(); 309 | const out = new internals.Writer(); 310 | const reader = new internals.Reader(); 311 | 312 | reader.pipe(reporter).pipe(out); 313 | 314 | reader.push(internals.request); 315 | reader.push(null); 316 | 317 | const team = new Teamwork.Team(); 318 | reader.once('end', () => { 319 | 320 | expect(out.data).to.have.length(1); 321 | expect(out.data[0]).to.be.equal('160318/013330.957, (1419005623332:new-host.local:48767:i3vrb3z7:10000) [request,user,info] data: you made a request\n'); 322 | team.attend(); 323 | }); 324 | 325 | await team.work; 326 | 327 | }); 328 | }); 329 | 330 | describe('log and default events', () => { 331 | 332 | it('returns a formatted string for "log" and "default" events', { plan: 2 }, async () => { 333 | 334 | const reporter = new GoodConsole(); 335 | const out = new internals.Writer(); 336 | const reader = new internals.Reader(); 337 | 338 | reader.pipe(reporter).pipe(out); 339 | 340 | reader.push(internals.default); 341 | reader.push(null); 342 | 343 | const team = new Teamwork.Team(); 344 | reader.once('end', () => { 345 | 346 | expect(out.data).to.have.length(1); 347 | expect(out.data[0]).to.be.equal('160318/013330.957, [request,user,info] data: you made a default\n'); 348 | team.attend(); 349 | }); 350 | 351 | await team.work; 352 | 353 | }); 354 | 355 | it('returns a formatted string for "default" events without data', { plan: 2 }, async () => { 356 | 357 | const reporter = new GoodConsole(); 358 | const out = new internals.Writer(); 359 | const reader = new internals.Reader(); 360 | 361 | reader.pipe(reporter).pipe(out); 362 | 363 | const noData = Object.assign({}, internals.default); 364 | delete noData.data; 365 | 366 | reader.push(noData); 367 | reader.push(null); 368 | 369 | const team = new Teamwork.Team(); 370 | reader.once('end', () => { 371 | 372 | expect(out.data).to.have.length(1); 373 | expect(out.data[0]).to.be.equal('160318/013330.957, [request,user,info] data: (none)\n'); 374 | team.attend(); 375 | }); 376 | 377 | await team.work; 378 | 379 | }); 380 | 381 | it('returns a formatted string for "default" events with data as object', { plan: 2 }, async () => { 382 | 383 | const reporter = new GoodConsole(); 384 | const out = new internals.Writer(); 385 | const reader = new internals.Reader(); 386 | 387 | reader.pipe(reporter).pipe(out); 388 | 389 | const defaultEvent = Object.assign({}, internals.default); 390 | defaultEvent.data = { hello: 'world' }; 391 | 392 | reader.push(defaultEvent); 393 | reader.push(null); 394 | 395 | const team = new Teamwork.Team(); 396 | reader.once('end', () => { 397 | 398 | expect(out.data).to.have.length(1); 399 | expect(out.data[0]).to.be.equal('160318/013330.957, [request,user,info] data: {"hello":"world"}\n'); 400 | team.attend(); 401 | }); 402 | 403 | await team.work; 404 | 405 | }); 406 | 407 | it('returns a formatted string for "default" events with data as object', { plan: 2 }, async () => { 408 | 409 | const reporter = new GoodConsole(); 410 | const out = new internals.Writer(); 411 | const reader = new internals.Reader(); 412 | 413 | reader.pipe(reporter).pipe(out); 414 | 415 | const defaultEvent = Object.assign({}, internals.default); 416 | defaultEvent.tags = 'test'; 417 | 418 | reader.push(defaultEvent); 419 | reader.push(null); 420 | 421 | const team = new Teamwork.Team(); 422 | reader.once('end', () => { 423 | 424 | expect(out.data).to.have.length(1); 425 | expect(out.data[0]).to.be.equal('160318/013330.957, [request,test] data: you made a default\n'); 426 | team.attend(); 427 | }); 428 | 429 | await team.work; 430 | 431 | }); 432 | 433 | it('returns a formatted string for "default" events with data as Error', { plan: 2 }, async () => { 434 | 435 | const reporter = new GoodConsole(); 436 | const out = new internals.Writer(); 437 | const reader = new internals.Reader(); 438 | 439 | reader.pipe(reporter).pipe(out); 440 | 441 | const defaultEvent = Object.assign({}, internals.default); 442 | defaultEvent.error = new Error('you logged an error'); 443 | 444 | reader.push(defaultEvent); 445 | reader.push(null); 446 | 447 | const team = new Teamwork.Team(); 448 | reader.once('end', () => { 449 | 450 | expect(out.data).to.have.length(1); 451 | expect(out.data[0].split('\n')[0]).to.be.equal('160318/013330.957, [request,user,info] message: you logged an error, stack: Error: you logged an error'); 452 | team.attend(); 453 | }); 454 | 455 | await team.work; 456 | 457 | }); 458 | }); 459 | }); 460 | }); 461 | 462 | 463 | internals.Writer = class extends Stream.Writable { 464 | 465 | constructor() { 466 | 467 | super({ objectMode: true }); 468 | this.data = []; 469 | } 470 | 471 | _write(chunk, enc, callback) { 472 | 473 | this.data.push(chunk); 474 | callback(null); 475 | } 476 | }; 477 | 478 | 479 | internals.Reader = class extends Stream.Readable { 480 | 481 | constructor() { 482 | 483 | super({ objectMode: true }); 484 | } 485 | 486 | _read() { } 487 | }; 488 | 489 | 490 | internals.ops = { 491 | event: 'ops', 492 | timestamp: 1458264810957, 493 | host: 'localhost', 494 | pid: 64291, 495 | os: { 496 | load: [1.650390625, 1.6162109375, 1.65234375], 497 | mem: { total: 17179869184, free: 8190681088 }, 498 | uptime: 704891 499 | }, 500 | proc: { 501 | uptime: 6, 502 | mem: { 503 | rss: 30019584, 504 | heapTotal: 18635008, 505 | heapUsed: 9989304 506 | }, 507 | delay: 0.03084501624107361 508 | }, 509 | load: { 510 | requests: {}, 511 | concurrents: {}, 512 | responseTimes: {}, 513 | listener: {}, 514 | sockets: { http: {}, https: {} } 515 | } 516 | }; 517 | 518 | 519 | internals.response = { 520 | event: 'response', 521 | timestamp: 1458264810957, 522 | id: '1458264811279:localhost:16014:ilx17kv4:10001', 523 | instance: 'http://localhost:61253', 524 | labels: [], 525 | method: 'post', 526 | path: '/data', 527 | query: { 528 | name: 'adam' 529 | }, 530 | responseTime: 150, 531 | statusCode: 200, 532 | pid: 16014, 533 | httpVersion: '1.1', 534 | source: { 535 | remoteAddress: '127.0.0.1', 536 | userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36', 537 | referer: 'http://localhost:61253/' 538 | } 539 | }; 540 | 541 | 542 | internals.request = { 543 | event: 'request', 544 | timestamp: 1458264810957, 545 | tags: ['user', 'info'], 546 | data: 'you made a request', 547 | pid: 64291, 548 | id: '1419005623332:new-host.local:48767:i3vrb3z7:10000', 549 | method: 'get', 550 | path: '/' 551 | }; 552 | 553 | 554 | internals.error = { 555 | event: 'error', 556 | timestamp: 1458264810957, 557 | id: '1419005623332:new-host.local:48767:i3vrb3z7:10000', 558 | tags: ['user', 'info'], 559 | url: 'http://localhost/test', 560 | method: 'get', 561 | pid: 64291, 562 | error: { 563 | message: 'Just a simple error', 564 | stack: 'Error: Just a simple Error' 565 | } 566 | }; 567 | 568 | 569 | internals.default = { 570 | event: 'request', 571 | timestamp: 1458264810957, 572 | tags: ['user', 'info'], 573 | data: 'you made a default', 574 | pid: 64291 575 | }; 576 | --------------------------------------------------------------------------------