From 1bba9a2b5e437a1fb4ad3043cde8196ac3e41c63 Mon Sep 17 00:00:00 2001 From: Darin Spivey Date: Wed, 7 Oct 2020 16:31:14 -0400 Subject: [PATCH] feat!: Change to use @logdna/logger as the client BREAKING CHANGE: This changes the client to be the newer logdna node client. Other changes were to add tests and map the log levels between winston and logdna since they differ. Also, the `index_meta` property was changed to `indexMeta` as required by the logdna logger client. Semver: major Ref: LOG-7378 --- .gitignore | 7 +- .npmignore | 11 ++ .npmrc | 1 + README.md | 25 +++-- index.js | 42 +++++--- package.json | 34 +++++- test/index.js | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 382 insertions(+), 31 deletions(-) create mode 100644 .npmignore create mode 100644 .npmrc create mode 100644 test/index.js diff --git a/.gitignore b/.gitignore index 2b5bd10..e578996 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -/node_modules/ -package-lock.json -**/*.zip \ No newline at end of file +node_modules +coverage +.nyc_output +.tap-output diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..9b2351c --- /dev/null +++ b/.npmignore @@ -0,0 +1,11 @@ +*.log +npm-debug.log* +coverage +.nyc_output +*.tgz +.env +*.swp +*.vim +.npm +.tap-output +Jenkinsfile diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/README.md b/README.md index 28f9224..270423e 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,14 @@ ## Install -```javascript +```sh $ npm install --save logdna-winston ``` ## API -Please see the [logdna](https://github.com/logdna/nodejs/) npm module for the API. +Please see [@logdna/logger](https://www.npmjs.com/package/@logdna/logger#createloggerkey-options) for +instantiation options to passthrough to LogDNA's logger client. ## Winston Transport @@ -39,8 +40,8 @@ const options = { app: appName, env: envName, level: level, // Default to debug, maximum level of log, doc: https://github.com/winstonjs/winston#logging-levels - index_meta: true // Defaults to false, when true ensures meta object will be searchable -}; + indexMeta: true // Defaults to false, when true ensures meta object will be searchable +} // Only add this line in order to track exceptions options.handleExceptions = true; @@ -52,17 +53,25 @@ logger.add(new logdnaWinston(options)); logger.log({ level: 'info' , message: 'Log from LogDNA-winston' - , index_meta: true // Ignore this if you would like to use default setting - , data:'Some information' // Properties besides level, message and index_meta are considered as "meta" + , indexMeta: true // Optional. If not provided, it will use the default. + , data:'Some information' // Properties besides level, message and indexMeta are considered as "meta" , error: new Error("It's a trap.") // Transport will parse the error object under property 'error' -}); +}) // log without meta logger.info('Info: Log from LogDNA-winston'); + +// A payload without 'message' will log the stringified object as the message +logger.info({ + key: 'value' +, text: 'This is some text to get logged' +, bool: true +}) ``` ## License -MIT © [LogDNA](https://logdna.com/) +Copyright © [LogDNA](https://logdna.com), released under an MIT license. +See the [LICENSE](./LICENSE) file and https://opensource.org/licenses/MIT *Happy Logging!* diff --git a/index.js b/index.js index cc1005f..34499cf 100644 --- a/index.js +++ b/index.js @@ -2,45 +2,53 @@ const pkg = require('./package.json') const Transport = require('winston-transport') -const Logger = require('logdna').Logger - -const DEFAULT_LEVEL = 'debug' -const DEFAULT_NAME = 'LogDNA' - +const {createLogger} = require('@logdna/logger') + +// Convert between Winston levels and @logdna/logger levels +const levelTranslate = new Map([ + ['error', 'error'] +, ['warn', 'warn'] +, ['info', 'info'] +, ['http', 'debug'] +, ['verbose', 'debug'] +, ['silly', 'trace'] +]) /* * Support for Winston Transport */ module.exports = class LogDNATransport extends Transport { constructor(options) { super(options) - this.name = options.name || DEFAULT_NAME - this.level = options.level || DEFAULT_LEVEL - this.index_meta = options.index_meta || false - this.logger = new Logger(options.key, { + + // Create an instance of @logdna/logger + this.logger = createLogger(options.key, { ...options , UserAgent: `${pkg.name}/${pkg.version}` }) } log(info, callback) { - info = info || {} - if (info.error instanceof Error) { info.error = info.error.stack || info.error.toString() } if (!info.message) { - info.message = JSON.stringify(info, null, 2, function() { return undefined }) + // Send the incoming object payload as the message + const level = levelTranslate.get(info.level) + this.logger.log(info, level) + callback(null, true) + return } - const {level, message, index_meta, ...meta} = info + const {level, message, indexMeta, timestamp, ...meta} = info const opts = { - level: level - , index_meta: typeof info.index_meta === 'boolean' ? index_meta : this.index_meta - , context: meta || {} + level + , indexMeta + , timestamp + , meta } this.logger.log(message, opts) - if (callback) { callback(null, true) } + callback(null, true) } } diff --git a/package.json b/package.json index 43ca7a7..a6b3d4d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "LogDNA's Node.js logging module with support for Winston", "main": "index.js", "scripts": { - "lint": "eslint ." + "lint": "eslint .", + "pretest": "npm run lint", + "test": "tap" }, "repository": { "type": "git", @@ -30,12 +32,15 @@ }, "homepage": "https://github.com/logdna/logdna-winston#readme", "dependencies": { - "logdna": "^3.5.0" + "@logdna/logger": "^1.3.2", + "winston-transport": "^4.4.0" }, "devDependencies": { "eslint": "^7.10.0", "eslint-config-logdna": "^2.0.0", - "winston-transport": "^4.4.0" + "nock": "^13.0.4", + "tap": "^14.10.8", + "winston": "^3.3.3" }, "eslintConfig": { "extends": [ @@ -49,5 +54,28 @@ "parserOptions": { "ecmaVersion": 2019 } + }, + "tap": { + "100": true, + "esm": false, + "ts": false, + "jsx": false, + "check-coverage": true, + "coverage-report": [ + "text", + "text-summary", + "json", + "html" + ], + "reporter": "tap", + "nyc-arg": [ + "--exclude=test/", + "--exclude=tools", + "--all" + ], + "files": [ + "test/**/*.js" + ], + "output-file": ".tap-output" } } diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..8adbbdd --- /dev/null +++ b/test/index.js @@ -0,0 +1,293 @@ +'use strict' + +const logdnaWinston = require('../index.js') +const {test} = require('tap') +const nock = require('nock') +const winston = require('winston') + +nock.disableNetConnect() + +test('Call .log() with an object and indexMeta: true', (t) => { + const logger = winston.createLogger() + const timestamp = Date.now() + const message = 'Log from LogDNA-winston' + const error = new Error('BOOM, FAKE ERROR') + + const meta = { + data: 'Some meta information' + , bool: true + , num: 3587 + } + + const options = { + key: 'abc123' + , hostname: 'My-Host' + , ip: '192.168.2.100' + , mac: '9e:a0:f8:20:86:3d' + , url: 'http://localhost:35870' + , app: 'LogDNA' + } + + logger.add(new logdnaWinston(options)) + + t.plan(2) + t.on('end', async () => { + nock.cleanAll() + }) + + nock(options.url) + .post('/', (body) => { + const payload = body.ls[0] + t.deepEqual(payload, { + timestamp + , line: message + , level: 'INFO' + , app: options.app + , meta: { + ...meta + , error: error.stack + } + }, 'Options were successfully placed into the message') + return true + }) + .query((q) => { + t.match(q, { + now: /^\d+$/ + , hostname: 'My-Host' + , mac: '9e:a0:f8:20:86:3d' + , ip: '192.168.2.100' + , tags: '' + }, 'LogDNA logger query string is correct') + return true + }) + .reply(200, 'Ingester response') + + logger.log({ + message + , level: 'info' + , indexMeta: true + , data: meta.data + , bool: meta.bool + , num: meta.num + , error + , timestamp + }) +}) + +test('Call .info() with simple string', (t) => { + const logger = winston.createLogger() + const message = 'Log from LogDNA-winston' + + const options = { + key: 'abc123' + , hostname: 'My-Host' + , ip: '192.168.2.100' + , mac: '9e:a0:f8:20:86:3d' + , url: 'http://localhost:35870' + , app: 'LogDNA' + } + + logger.add(new logdnaWinston(options)) + + t.plan(2) + t.on('end', async () => { + nock.cleanAll() + }) + + nock(options.url) + .post('/', (body) => { + const payload = body.ls[0] + t.match(payload, { + timestamp: Number + , line: message + , level: 'INFO' + , app: options.app + , meta: '{}' // indexMeta is `false`, so it's stringified + }, 'Options were successfully placed into the message') + return true + }) + .query((q) => { + t.match(q, { + now: /^\d+$/ + , hostname: 'My-Host' + , mac: '9e:a0:f8:20:86:3d' + , ip: '192.168.2.100' + , tags: '' + }, 'LogDNA logger query string is correct') + return true + }) + .reply(200, 'Ingester response') + + logger.info(message) +}) + +test('Call .log() with a message object', (t) => { + const logger = winston.createLogger({ + level: 'silly' // Log less than or equal to this + }) + const message = { + msg: 'Log from LogDNA-winston' + , key: 'value' + , bool: true + , level: 'warn' + } + + const options = { + key: 'abc123' + , hostname: 'My-Host' + , ip: '192.168.2.100' + , mac: '9e:a0:f8:20:86:3d' + , url: 'http://localhost:35870' + , app: 'LogDNA' + } + + logger.add(new logdnaWinston(options)) + + t.plan(2) + t.on('end', async () => { + nock.cleanAll() + }) + + nock(options.url) + .post('/', (body) => { + const payload = body.ls[0] + t.match(payload, { + timestamp: Number + , line: JSON.stringify(message) + , level: 'WARN' + , app: options.app + , meta: '{}' // indexMeta is `false`, so it's stringified + }, 'Options were successfully placed into the message') + return true + }) + .query((q) => { + t.match(q, { + now: /^\d+$/ + , hostname: 'My-Host' + , mac: '9e:a0:f8:20:86:3d' + , ip: '192.168.2.100' + , tags: '' + }, 'LogDNA logger query string is correct') + return true + }) + .reply(200, 'Ingester response') + + logger.log(message) +}) + +test('Call .log() with a message payload and level to translate', (t) => { + const logger = winston.createLogger({ + level: 'silly' + }) + const message = { + msg: 'Log from LogDNA-winston' + , key: 'value' + , bool: true + , level: 'verbose' + } + const options = { + key: 'abc123' + , hostname: 'My-Host' + , ip: '192.168.2.100' + , mac: '9e:a0:f8:20:86:3d' + , url: 'http://localhost:35870' + , app: 'LogDNA' + } + + logger.add(new logdnaWinston(options)) + + t.plan(2) + t.on('end', async () => { + nock.cleanAll() + }) + + nock(options.url) + .post('/', (body) => { + const payload = body.ls[0] + t.match(payload, { + timestamp: Number + , line: JSON.stringify(message) + , level: 'DEBUG' + , app: options.app + , meta: '{}' // indexMeta is `false`, so it's stringified + }, 'Options were successfully placed into the message') + return true + }) + .query((q) => { + t.match(q, { + now: /^\d+$/ + , hostname: 'My-Host' + , mac: '9e:a0:f8:20:86:3d' + , ip: '192.168.2.100' + , tags: '' + }, 'LogDNA logger query string is correct') + return true + }) + .reply(200, 'Ingester response') + + logger.log(message) +}) + +test('Error will still be processed if there is no stack trace', (t) => { + const logger = winston.createLogger() + const timestamp = Date.now() + const message = 'Log from LogDNA-winston' + + t.teardown(() => { + Error.stackTraceLimit = 10 + }) + + Error.stackTraceLimit = false + const error = new Error('BOOM, FAKE ERROR') + + const options = { + key: 'abc123' + , hostname: 'My-Host' + , ip: '192.168.2.100' + , mac: '9e:a0:f8:20:86:3d' + , url: 'http://localhost:35870' + , app: 'LogDNA' + } + + logger.add(new logdnaWinston(options)) + + t.plan(2) + t.on('end', async () => { + nock.cleanAll() + }) + + nock(options.url) + .post('/', (body) => { + const payload = body.ls[0] + t.deepEqual(payload, { + timestamp + , line: error.message + , level: 'ERROR' + , app: options.app + , meta: { + error: error.toString() + } + }, 'Options were successfully placed into the message') + return true + }) + .query((q) => { + t.match(q, { + now: /^\d+$/ + , hostname: 'My-Host' + , mac: '9e:a0:f8:20:86:3d' + , ip: '192.168.2.100' + , tags: '' + }, 'LogDNA logger query string is correct') + return true + }) + .reply(200, 'Ingester response') + + logger.log({ + message + , level: 'error' + , message: error.message + , timestamp + , indexMeta: true + , error + }) +})