diff --git a/README.md b/README.md index 954336a..f341f4b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ $ yarn add datadog-winston - **ddsource**: The technology from which the logs originated - **ddtags**: Metadata assoicated with the logs - **intakeRegion**: The datadog intake to use. set to `eu` to force logs to be sent to the EU specific intake +- **retries**: The number of retry attempts to make sending logs to DataDog if there are communication errors. Defaults to 0, i.e. no retries ## Usage ```javascript @@ -36,7 +37,8 @@ logger.add( hostname: 'my_machine', service: 'super_service', ddsource: 'nodejs', - ddtags: 'foo:bar,boo:baz' + ddtags: 'foo:bar,boo:baz', + retries: 5 }) ) ``` diff --git a/__tests__/index.js b/__tests__/index.js index 14f9eed..e4dd82b 100644 --- a/__tests__/index.js +++ b/__tests__/index.js @@ -83,4 +83,90 @@ describe('DatadogTransport#log(info, callback)', () => { expect(scope.isDone()).toBe(true) }) }) + it('Retries transient errors', async () => { + const body = JSON.stringify({ + dd: { + trace_id: 'abc', + span_id: 'def' + }, + foo: 'bar' + }) + + const query = { + service: 'service', + ddsource: 'ddsource', + ddtags: 'env:production,trace_id:abc,span_id:def,tag_a:value_a,tag_b:value_b', + hostname: 'hostname' + } + + const scope = nock('https://http-intake.logs.datadoghq.com') + .post('/v1/input/apikey', body).query(query).once().reply(501) + .post('/v1/input/apikey', body).query(query).once().reply(204) + + const opts = Object.assign({}, { + apiKey: 'apikey', + service: 'service', + ddsource: 'ddsource', + ddtags: 'env:production', + hostname: 'hostname', + retries: 1 + }, {}) + + const transport = new DatadogTransport(opts) + const callback = jest.fn() + await transport.log({ + dd: { + trace_id: 'abc', + span_id: 'def' + }, + foo: 'bar', + ddtags: 'tag_a:value_a,tag_b:value_b' + }, callback) + + expect(scope.isDone()).toBe(true) + expect(callback).toHaveBeenCalled() + }) + it('Does not retries persistent errors', async () => { + const body = JSON.stringify({ + dd: { + trace_id: 'abc', + span_id: 'def' + }, + foo: 'bar' + }) + + const query = { + service: 'service', + ddsource: 'ddsource', + ddtags: 'env:production,trace_id:abc,span_id:def,tag_a:value_a,tag_b:value_b', + hostname: 'hostname' + } + + const scope = nock('https://http-intake.logs.datadoghq.com') + .post('/v1/input/apikey', body).query(query).once().reply(401) + .post('/v1/input/apikey', body).query(query).once().reply(204) + + const opts = Object.assign({}, { + apiKey: 'apikey', + service: 'service', + ddsource: 'ddsource', + ddtags: 'env:production', + hostname: 'hostname', + retries: 1 + }, {}) + + const transport = new DatadogTransport(opts) + const callback = jest.fn() + await transport.log({ + dd: { + trace_id: 'abc', + span_id: 'def' + }, + foo: 'bar', + ddtags: 'tag_a:value_a,tag_b:value_b' + }, callback) + + expect(scope.isDone()).toBe(false) + expect(callback).toHaveBeenCalled() + }) }) diff --git a/lib/index.js b/lib/index.js index 167f16c..2088646 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,6 @@ const Transport = require('winston-transport') const querystring = require('querystring') +const retry = require('async-retry') const fetch = require('node-fetch') /** @@ -74,15 +75,24 @@ module.exports = class DatadogTransport extends Transport { const api = querystring ? `${this.api}?${queryString}` : this.api try { - // Perform the writing to the remote service - await fetch(api, { - method: 'POST', - headers: { - 'content-type': 'application/json' - }, - body: JSON.stringify(logs) - }) + await retry(async bail => { + const res = await fetch(api, { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify(logs) + }) + + if (res.status >= 500) { + throw new Error('Retryable error from DataDog API') + } + if (res.status >= 400) { + bail() + } + }, { retries: this.opts.retries || 0 }) } catch (err) { + // Just throw the error away as there's nothing more we can do } finally { callback() } diff --git a/package.json b/package.json index 6b3402e..4d17529 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datadog-winston", - "version": "1.5.0", + "version": "1.5.2", "description": "Ship winston logs to datadog without breaking a sweat", "main": "index.js", "repository": "https://github.com/itsfadnis/datadog-winston.git", @@ -8,6 +8,7 @@ "license": "MIT", "dependencies": { "@babel/runtime": "^7.4.5", + "async-retry": "^1.3.1", "node-fetch": "^2.6.0", "winston-transport": "^4.3.0" }, diff --git a/yarn.lock b/yarn.lock index 21c6adc..0960da5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1052,6 +1052,13 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== +async-retry@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" + integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== + dependencies: + retry "0.12.0" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -4279,6 +4286,11 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@~2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -5104,4 +5116,4 @@ yargs@^12.0.2: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" \ No newline at end of file + yargs-parser "^11.1.1"