├── .jshintignore ├── .jshintrc ├── index.d.ts ├── .gitignore ├── package.json ├── LICENSE ├── index.js ├── README.md └── tests └── index.js /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | 4 | "curly": true, 5 | "latedef": "nofunc", 6 | "quotmark": true, 7 | "undef": true, 8 | "unused": true, 9 | "trailing": true, 10 | "eqnull": true, 11 | "eqeqeq": true, 12 | "esnext": true, 13 | "predef": [] 14 | } 15 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as logger from "bunyan" 2 | 3 | export = BunyanLoggly 4 | 5 | declare class BunyanLoggly implements logger.Stream { 6 | constructor(options: BunyanLoggly.IOptions, bufferLength?: number, bufferTimeout?: number, logglyCallback?: Function); 7 | } 8 | 9 | declare namespace BunyanLoggly { 10 | interface IOptions { 11 | token: string 12 | subdomain: string 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Development 30 | .vscode/launch.json 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bunyan-loggly", 3 | "version": "1.3.5", 4 | "description": "A bunyan stream to transport logs to loggly", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "keywords": [ 8 | "bunyan", 9 | "log", 10 | "loggly" 11 | ], 12 | "author": "Maurice Butler ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "json-stringify-safe": "^5.0.1", 16 | "node-loggly-bulk": "^2.2.2" 17 | }, 18 | "devDependencies": { 19 | "@types/bunyan": "^1.8.4", 20 | "proxyquire": "^2.0.1", 21 | "tape": "^4.9.0" 22 | }, 23 | "directories": { 24 | "test": "test" 25 | }, 26 | "scripts": { 27 | "test": "node ./tests" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/MauriceButler/bunyan-loggly.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/MauriceButler/bunyan-loggly/issues" 35 | }, 36 | "homepage": "https://github.com/MauriceButler/bunyan-loggly#readme" 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Maurice Butler 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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var loggly = require('node-loggly-bulk'); 2 | var stringifySafe = require('json-stringify-safe'); 3 | var noop = function () {}; 4 | 5 | function Bunyan2Loggly(logglyConfig, bufferLength, bufferTimeout, callback) { 6 | if (!logglyConfig || !logglyConfig.token || !logglyConfig.subdomain) { 7 | throw new Error('bunyan-loggly requires a config object with token and subdomain'); 8 | } 9 | 10 | this.callback = callback || noop; 11 | 12 | this.bufferLength = bufferLength || 1; 13 | this.bufferTimeout = bufferTimeout || 30 * 1000; 14 | 15 | logglyConfig.json = true; 16 | logglyConfig.isBulk = true; 17 | 18 | logglyConfig.bufferOptions = { 19 | size: this.bufferLength, 20 | retriesInMilliSeconds: this.bufferTimeout, 21 | }; 22 | 23 | this.logglyClient = loggly.createClient(logglyConfig); 24 | } 25 | 26 | Bunyan2Loggly.prototype.write = function (originalData) { 27 | if (typeof originalData !== 'object') { 28 | throw new Error('bunyan-loggly requires a raw stream. Please define the type as raw when setting up the bunyan stream.'); 29 | } 30 | 31 | var data = originalData; 32 | var bunyan2Loggly = this; 33 | 34 | // loggly prefers timestamp over time 35 | if (data.time) { 36 | data = JSON.parse(stringifySafe(data, null, null, noop)); 37 | data.timestamp = data.time; 38 | delete data.time; 39 | } 40 | 41 | bunyan2Loggly.logglyClient.log(data, function (error, result) { 42 | bunyan2Loggly.callback(error, result, data); 43 | }); 44 | }; 45 | 46 | module.exports = Bunyan2Loggly; 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bunyan-loggly 2 | 3 | A bunyan stream to send logs through to loggly. 4 | 5 | ## Configuration 6 | 7 | bunyan-loggly uses node-loggly under the hood. As such, when configuring bunyan-loggly as a stream for bunyan, you need to pass in the standard and required node-loggly configuration object. 8 | 9 | For example: 10 | 11 | ```javascript 12 | { 13 | token: "your-really-long-input-token", 14 | subdomain: "your-subdomain" 15 | } 16 | ``` 17 | 18 | ## Usage 19 | 20 | This is a basic usage example. 21 | 22 | ```javascript 23 | var bunyan = require('bunyan'); 24 | var Bunyan2Loggly = require('bunyan-loggly'); 25 | var logglyConfig = { 26 | token: 'your-account-token', 27 | subdomain: 'your-sub-domain', 28 | }; 29 | 30 | var logglyStream = new Bunyan2Loggly(logglyConfig); 31 | 32 | // create the logger 33 | var logger = bunyan.createLogger({ 34 | name: 'logglylog', 35 | streams: [ 36 | { 37 | type: 'raw', 38 | stream: logglyStream, 39 | }, 40 | ], 41 | }); 42 | 43 | logger.info({}); 44 | ``` 45 | 46 | > Please note: you MUST define `type: 'raw'` as bunyan-loggly expects to receive objects so that certain values can be changed as required by loggly (i.e. time to timestamp). 47 | 48 | ## Buffering 49 | 50 | bunyan-loggly supports basic buffering and when setup, will only send your logs through to loggly on every x logs. To setup buffering, just pass an integer as the second parameter when creating a new instance of Bunyan2Loggly: 51 | 52 | ```javascript 53 | var bunyan = require('bunyan'); 54 | var Bunyan2Loggly = require('bunyan-loggly'); 55 | var logglyConfig = { 56 | token: 'your-account-token', 57 | subdomain: 'your-sub-domain', 58 | }; 59 | var bufferLength = 5; 60 | 61 | var logglyStream = new Bunyan2Loggly(logglyConfig, bufferLength); 62 | 63 | // create the logger 64 | var logger = bunyan.createLogger({ 65 | name: 'logglylog', 66 | streams: [ 67 | { 68 | type: 'raw', 69 | stream: logglyStream, 70 | }, 71 | ], 72 | }); 73 | 74 | logger.info({}); // won't send to loggly 75 | logger.info({}); // won't send to loggly 76 | logger.info({}); // won't send to loggly 77 | logger.info({}); // won't send to loggly 78 | logger.info({}); // will send to loggly 79 | logger.info({}); // won't send to loggly 80 | ``` 81 | 82 | ### Buffer Timeout 83 | 84 | When buffering, a timeout can be provided to force flushing the buffer after a period of time. To setup a flush timeout, pass a timeout value (in ms) as the third parameter when creating a new instance of Bunyan2Loggly: 85 | 86 | ```javascript 87 | var bunyan = require('bunyan'); 88 | var Bunyan2Loggly = require('bunyan-loggly'); 89 | var logglyConfig = { 90 | token: 'your-account-token', 91 | subdomain: 'your-sub-domain', 92 | }; 93 | var bufferLength = 5; 94 | var bufferTimeout = 500; 95 | 96 | var logglyStream = new Bunyan2Loggly(logglyConfig, bufferLength, bufferTimeout); 97 | 98 | // create the logger 99 | var logger = bunyan.createLogger({ 100 | name: 'logglylog', 101 | streams: [ 102 | { 103 | type: 'raw', 104 | stream: logglyStream, 105 | }, 106 | ], 107 | }); 108 | 109 | logger.info({}); // will be sent to loggly in 500ms if buffer threshold is not reached 110 | ``` 111 | 112 | ### Loggly request information 113 | 114 | Each time log content is sent to loggly, the result of this request will be passed to the optional callback paramer `logglyCallback` 115 | 116 | ```javascript 117 | var bunyan = require('bunyan'); 118 | var Bunyan2Loggly = require('bunyan-loggly'); 119 | var logglyConfig = { 120 | token: 'your-account-token', 121 | subdomain: 'your-sub-domain', 122 | }; 123 | 124 | function logglyCallback(error, result, content) { 125 | // handle loggly callback 126 | } 127 | 128 | var logglyStream = new Bunyan2Loggly(logglyConfig, null, null, logglyCallback); 129 | ``` 130 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var proxyquire = require('proxyquire'); 3 | var testConfig = { 4 | token: 'foo', 5 | subdomain: 'bar', 6 | }; 7 | 8 | function getBaseMocks() { 9 | return { 10 | 'node-loggly-bulk': { 11 | createClient: function () { 12 | return { 13 | log: function () {}, 14 | }; 15 | }, 16 | }, 17 | }; 18 | } 19 | 20 | test('Bunyan2Loggly Exists', function (t) { 21 | t.plan(1); 22 | 23 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 24 | 25 | t.equal(typeof Bunyan2Loggly, 'function', 'Bunyan2Loggly is a function'); 26 | }); 27 | 28 | test('Bunyan2Loggly throws on bad config', function (t) { 29 | t.plan(4); 30 | 31 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 32 | var exceptionMessage = /bunyan-loggly requires a config object with token and subdomain/; 33 | 34 | t.throws(function () { new Bunyan2Loggly(); }, exceptionMessage, 'throws on bad config'); 35 | t.throws(function () { new Bunyan2Loggly({}); }, exceptionMessage, 'throws on bad config'); 36 | t.throws(function () { new Bunyan2Loggly({ token: 'foo' }); }, exceptionMessage, 'throws on bad config'); 37 | t.throws(function () { new Bunyan2Loggly({ subdomain: 'foo' }); }, exceptionMessage, 'throws on bad config'); 38 | }); 39 | 40 | test('Bunyan2Loggly creates loggly client', function (t) { 41 | t.plan(3); 42 | 43 | var mocks = getBaseMocks(); 44 | 45 | mocks['node-loggly-bulk'].createClient = function (config) { 46 | t.equal(config.token, testConfig.token, 'correct token'); 47 | t.equal(config.subdomain, testConfig.subdomain, 'correct subdomain'); 48 | t.equal(config.json, true, 'correct json'); 49 | }; 50 | 51 | var Bunyan2Loggly = proxyquire('../', mocks); 52 | 53 | new Bunyan2Loggly(testConfig); 54 | }); 55 | 56 | test('Bunyan2Loggly sets default bufferLength', function (t) { 57 | t.plan(1); 58 | 59 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 60 | var bunyan2Loggly = new Bunyan2Loggly(testConfig); 61 | 62 | t.equal(bunyan2Loggly.bufferLength, 1, 'bufferLength defaulted correctly'); 63 | }); 64 | 65 | test('Bunyan2Loggly sets bufferLength if provided', function (t) { 66 | t.plan(1); 67 | 68 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 69 | var bunyan2Loggly = new Bunyan2Loggly(testConfig, 123); 70 | 71 | t.equal(bunyan2Loggly.bufferLength, 123, 'bufferLength set correctly'); 72 | }); 73 | 74 | test('Bunyan2Loggly sets default bufferTimeout', function (t) { 75 | t.plan(1); 76 | 77 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 78 | var bunyan2Loggly = new Bunyan2Loggly(testConfig); 79 | 80 | t.equal(bunyan2Loggly.bufferTimeout, 30000, 'bufferTimeout defaulted correctly'); 81 | }); 82 | 83 | test('Bunyan2Loggly sets bufferTimeout if provided', function (t) { 84 | t.plan(1); 85 | 86 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 87 | var bunyan2Loggly = new Bunyan2Loggly(testConfig, null, 123); 88 | 89 | t.equal(bunyan2Loggly.bufferTimeout, 123, 'bufferTimeout set correctly'); 90 | }); 91 | 92 | test('Bunyan2Loggly throws if write called with non raw stream', function (t) { 93 | t.plan(2); 94 | 95 | var Bunyan2Loggly = proxyquire('../', getBaseMocks()); 96 | var bunyan2Loggly = new Bunyan2Loggly(testConfig); 97 | var exceptionMessage = /bunyan-loggly requires a raw stream. Please define the type as raw when setting up the bunyan stream./; 98 | 99 | t.throws(function () { bunyan2Loggly.write(); }, exceptionMessage, 'throws on bad stream'); 100 | t.throws(function () { bunyan2Loggly.write('foo'); }, exceptionMessage, 'throws on bad stream'); 101 | }); 102 | 103 | test('Bunyan2Loggly changes time to timestamp', function (t) { 104 | t.plan(1); 105 | 106 | var mocks = getBaseMocks(); 107 | var Bunyan2Loggly = proxyquire('../', mocks); 108 | var testData = { foo: 'bar', time: 'nao' }; 109 | var responseData = { foo: 'bar', timestamp: 'nao' }; 110 | 111 | mocks['node-loggly-bulk'].createClient = function () { 112 | return { 113 | log: function (data) { 114 | t.deepEqual(data, responseData, 'data sent to loggly'); 115 | }, 116 | }; 117 | }; 118 | 119 | var bunyan2Loggly = new Bunyan2Loggly(testConfig); 120 | 121 | bunyan2Loggly.write(testData); 122 | }); 123 | 124 | test('Bunyan2Loggly sends data to loggly', function (t) { 125 | t.plan(1); 126 | 127 | var mocks = getBaseMocks(); 128 | var Bunyan2Loggly = proxyquire('../', mocks); 129 | var testData = { foo: 'bar' }; 130 | 131 | mocks['node-loggly-bulk'].createClient = function () { 132 | return { 133 | log: function (data) { 134 | t.deepEqual(data, testData, 'data sent to loggly'); 135 | }, 136 | }; 137 | }; 138 | 139 | var bunyan2Loggly = new Bunyan2Loggly(testConfig); 140 | 141 | bunyan2Loggly.write(testData); 142 | }); 143 | 144 | test('Bunyan2Loggly uses logglyCallback if provided', function (t) { 145 | t.plan(3); 146 | 147 | var mocks = getBaseMocks(); 148 | var Bunyan2Loggly = proxyquire('../', mocks); 149 | var testData = { foo: 'bar' }; 150 | var testError = 'testError'; 151 | var testResult = 'testResult'; 152 | 153 | function logglyCallback(error, result, content) { 154 | t.equal(error, testError, 'correct error'); 155 | t.equal(result, testResult, 'correct result'); 156 | t.deepEqual(content, testData, 'correct content'); 157 | } 158 | 159 | mocks['node-loggly-bulk'].createClient = function () { 160 | return { 161 | log: function (data, callback) { 162 | callback(testError, testResult); 163 | }, 164 | }; 165 | }; 166 | 167 | var bunyan2Loggly = new Bunyan2Loggly(testConfig, null, null, logglyCallback); 168 | 169 | bunyan2Loggly.write(testData); 170 | }); 171 | 172 | test('Bunyan2Loggly handles circular references', function (t) { 173 | t.plan(2); 174 | 175 | var mocks = getBaseMocks(); 176 | var Bunyan2Loggly = proxyquire('../', mocks); 177 | var testData = { time: 'nao' }; 178 | 179 | testData.x = testData; 180 | 181 | mocks['node-loggly-bulk'].createClient = function () { 182 | return { 183 | log: function (data) { 184 | t.notEqual(data, testData, 'original data was not mutated'); 185 | t.deepEqual(data, { timestamp: 'nao' }, 'changed to timestamp'); 186 | }, 187 | }; 188 | }; 189 | 190 | var bunyan2Loggly = new Bunyan2Loggly(testConfig); 191 | 192 | bunyan2Loggly.write(testData); 193 | }); 194 | --------------------------------------------------------------------------------