elastic-apm-http-client
Advanced tools
Comparing version 8.0.0 to 8.1.0
186
index.js
@@ -7,2 +7,3 @@ 'use strict' | ||
const zlib = require('zlib') | ||
const querystring = require('querystring') | ||
const Writable = require('readable-stream').Writable | ||
@@ -77,2 +78,3 @@ const getContainerInfo = require('container-info') | ||
this._transport = null | ||
this._configTimer = null | ||
@@ -107,2 +109,4 @@ switch (this._conf.serverUrl.protocol.slice(0, -1)) { // 'http:' => 'http' | ||
clients.push(this) | ||
if (this._conf.centralConfig) this._pollConfig() | ||
} | ||
@@ -132,2 +136,3 @@ | ||
this._conf.keepAlive = this._conf.keepAlive !== false | ||
this._conf.centralConfig = this._conf.centralConfig || false | ||
@@ -150,5 +155,67 @@ // process | ||
// http request options | ||
this._conf.request = getRequestOptions(this._conf, this._agent) | ||
this._conf.requestIntake = getIntakeRequestOptions(this._conf, this._agent) | ||
this._conf.requestConfig = getConfigRequestOptions(this._conf, this._agent) | ||
} | ||
Client.prototype._pollConfig = function () { | ||
const opts = this._conf.requestConfig | ||
if (this._conf.lastConfigEtag) { | ||
opts.headers['If-None-Match'] = this._conf.lastConfigEtag | ||
} | ||
const req = this._transport.get(opts, res => { | ||
res.on('error', err => { | ||
// Not sure this event can ever be emitted, but just in case | ||
res.destroy(err) | ||
}) | ||
this._scheduleNextConfigPoll(getMaxAge(res)) | ||
if ( | ||
res.statusCode === 304 || // No new config since last time | ||
res.statusCode === 403 || // Central config not enabled in APM Server | ||
res.statusCode === 404 // No config for the given service.name / service.environment | ||
) { | ||
res.resume() | ||
return | ||
} | ||
streamToBuffer(res, (err, buf) => { | ||
if (err) return res.destroy(err) | ||
if (res.statusCode === 200) { | ||
// 200: New config available | ||
const etag = res.headers['etag'] | ||
if (etag) this._conf.lastConfigEtag = etag | ||
try { | ||
this.emit('config', JSON.parse(buf)) | ||
} catch (e) { | ||
res.destroy(e) | ||
} | ||
} else { | ||
res.destroy(processConfigErrorResponse(res, buf)) | ||
} | ||
}) | ||
}) | ||
req.on('error', err => { | ||
this._scheduleNextConfigPoll() | ||
this.emit('request-error', err) | ||
}) | ||
} | ||
Client.prototype._scheduleNextConfigPoll = function (seconds) { | ||
if (this._configTimer !== null) return | ||
seconds = seconds || 300 | ||
this._configTimer = setTimeout(() => { | ||
this._configTimer = null | ||
this._pollConfig() | ||
}, seconds * 1000) | ||
this._configTimer.unref() | ||
} | ||
// re-ref the open socket handles | ||
@@ -308,2 +375,6 @@ Client.prototype._ref = function () { | ||
Client.prototype._final = function (cb) { | ||
if (this._configTimer) { | ||
clearTimeout(this._configTimer) | ||
this._configTimer = null | ||
} | ||
clients[this._index] = null // remove global reference to ease garbage collection | ||
@@ -316,2 +387,6 @@ this._ref() | ||
Client.prototype._destroy = function (err, cb) { | ||
if (this._configTimer) { | ||
clearTimeout(this._configTimer) | ||
this._configTimer = null | ||
} | ||
clients[this._index] = null // remove global reference to ease garbage collection | ||
@@ -334,3 +409,3 @@ this._chopper.destroy() | ||
const req = client._transport.request(client._conf.request, onResult(onerror)) | ||
const req = client._transport.request(client._conf.requestIntake, onResult(onerror)) | ||
@@ -425,30 +500,27 @@ // Abort the current request if the server responds prior to the request | ||
if (res.statusCode < 200 || res.statusCode > 299) { | ||
const err = new Error('Unexpected APM Server response') | ||
onerror(processIntakeErrorResponse(res, buf)) | ||
} | ||
}) | ||
} | ||
err.code = res.statusCode | ||
function getIntakeRequestOptions (opts, agent) { | ||
const headers = getHeaders(opts) | ||
headers['Content-Type'] = 'application/x-ndjson' | ||
headers['Content-Encoding'] = 'gzip' | ||
if (buf.length > 0) { | ||
const body = buf.toString('utf8') | ||
const contentType = res.headers['content-type'] | ||
if (contentType && contentType.indexOf('application/json') === 0) { | ||
try { | ||
const data = JSON.parse(body) | ||
err.accepted = data.accepted | ||
err.errors = data.errors | ||
if (!err.errors) err.response = body | ||
} catch (e) { | ||
err.response = body | ||
} | ||
} else { | ||
err.response = body | ||
} | ||
} | ||
return getBasicRequestOptions('POST', '/intake/v2/events', headers, opts, agent) | ||
} | ||
onerror(err) | ||
} | ||
function getConfigRequestOptions (opts, agent) { | ||
const path = '/config/v1/agents?' + querystring.stringify({ | ||
'service.name': opts.serviceName, | ||
'service.environment': opts.environment | ||
}) | ||
const headers = getHeaders(opts) | ||
return getBasicRequestOptions('GET', path, headers, opts, agent) | ||
} | ||
function getRequestOptions (opts, agent) { | ||
const defaultPath = '/intake/v2/events' | ||
function getBasicRequestOptions (method, defaultPath, headers, opts, agent) { | ||
return { | ||
@@ -459,5 +531,5 @@ agent: agent, | ||
port: opts.serverUrl.port, | ||
method: 'POST', | ||
method, | ||
path: opts.serverUrl.path === '/' ? defaultPath : opts.serverUrl.path + defaultPath, | ||
headers: getHeaders(opts) | ||
headers | ||
} | ||
@@ -469,4 +541,2 @@ } | ||
if (opts.secretToken) headers['Authorization'] = 'Bearer ' + opts.secretToken | ||
headers['Content-Type'] = 'application/x-ndjson' | ||
headers['Content-Encoding'] = 'gzip' | ||
headers['Accept'] = 'application/json' | ||
@@ -595,1 +665,61 @@ headers['User-Agent'] = opts.userAgent + ' ' + pkg.name + '/' + pkg.version | ||
} | ||
function getMaxAge (res) { | ||
const header = res.headers['cache-control'] | ||
const match = header && header.match(/max-age=(\d+)/) | ||
return parseInt(match && match[1], 10) | ||
} | ||
function processIntakeErrorResponse (res, buf) { | ||
const err = new Error('Unexpected APM Server response') | ||
err.code = res.statusCode | ||
if (buf.length > 0) { | ||
const body = buf.toString('utf8') | ||
const contentType = res.headers['content-type'] | ||
if (contentType && contentType.startsWith('application/json')) { | ||
try { | ||
const data = JSON.parse(body) | ||
err.accepted = data.accepted | ||
err.errors = data.errors | ||
if (!err.errors) err.response = body | ||
} catch (e) { | ||
err.response = body | ||
} | ||
} else { | ||
err.response = body | ||
} | ||
} | ||
return err | ||
} | ||
function processConfigErrorResponse (res, buf) { | ||
const err = new Error('Unexpected APM Server response when polling config') | ||
err.code = res.statusCode | ||
if (buf.length > 0) { | ||
const body = buf.toString('utf8') | ||
const contentType = res.headers['content-type'] | ||
if (contentType && contentType.startsWith('application/json')) { | ||
try { | ||
const response = JSON.parse(body) | ||
if (typeof response === 'string') { | ||
err.response = response | ||
} else if (typeof response === 'object' && response !== null && typeof response.error === 'string') { | ||
err.response = response.error | ||
} else { | ||
err.response = body | ||
} | ||
} catch (e) { | ||
err.response = body | ||
} | ||
} else { | ||
err.response = body | ||
} | ||
} | ||
return err | ||
} |
{ | ||
"name": "elastic-apm-http-client", | ||
"version": "8.0.0", | ||
"version": "8.1.0", | ||
"description": "A low-level HTTP client for communicating with the Elastic APM intake API", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# elastic-apm-http-client | ||
[![npm](https://img.shields.io/npm/v/elastic-apm-http-client.svg)](https://www.npmjs.com/package/elastic-apm-http-client) | ||
[![Build status](https://travis-ci.org/elastic/apm-nodejs-http-client.svg?branch=master)](https://travis-ci.org/elastic/apm-nodejs-http-client) | ||
[![Build status in Travis](https://travis-ci.org/elastic/apm-nodejs-http-client.svg?branch=master)](https://travis-ci.org/elastic/apm-nodejs-http-client) | ||
[![Build Status in Jenkins](https://apm-ci.elastic.co/buildStatus/icon?job=apm-agent-nodejs%2Fapm-nodejs-http-client-mbp%2Fmaster)](https://apm-ci.elastic.co/job/apm-agent-nodejs/job/apm-nodejs-http-client-mbp/job/master/) | ||
[![codecov](https://img.shields.io/codecov/c/github/elastic/apm-nodejs-http-client.svg)](https://codecov.io/gh/elastic/apm-nodejs-http-client) | ||
@@ -103,2 +104,10 @@ [![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) | ||
APM Agent Configuration via Kibana: | ||
- `centralConfig` - Whether or not the client should poll the APM | ||
Server regularly for new agent configuration. If set to `true`, the | ||
`config` event will be emitted when there's an update to an agent config | ||
option (default: `false`). _Requires APM Server v7.3 or later and that | ||
the APM Server is configured with `kibana.enabled: true`._ | ||
Streaming configuration: | ||
@@ -146,2 +155,12 @@ | ||
### Event: `config` | ||
Emitted every time a change to the agent config is pulled from the APM | ||
Server. The listener is passed the updated config options as a key/value | ||
object. | ||
Each key is the lowercase version of the environment variable, without | ||
the `ELASTIC_APM_` prefix, e.g. `transaction_sample_rate` instead of | ||
`ELASTIC_APM_TRANSACTION_SAMPLE_RATE`. | ||
### Event: `close` | ||
@@ -168,4 +187,5 @@ | ||
This means that the current request to the APM Server is terminated and | ||
that the data included in that request is lost. | ||
The request to the APM Server that caused the error is terminated and | ||
the data included in that request is lost. This is normally only | ||
important to consider for requests to the Intake API. | ||
@@ -175,4 +195,4 @@ If a non-2xx response was received from the APM Server, the status code | ||
If the APM Serer responded with a structured error message, the `error` | ||
object will have the following properties: | ||
For requests to the Intake API where the response is a structured error | ||
message, the `error` object will have the following properties: | ||
@@ -189,3 +209,3 @@ - `error.accepted` - An integer indicating how many events was accepted | ||
If the APM returned an errro body that could not be parsed by the | ||
If the response contained an error body that could not be parsed by the | ||
client, the raw body will be available on `error.response`. | ||
@@ -213,2 +233,3 @@ | ||
- `maxFreeSockets` | ||
- `centralConfig` | ||
@@ -215,0 +236,0 @@ ### `client.sendSpan(span[, callback])` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
51548
14
869
301