Skip to content

Commit

Permalink
feat(config): add captureHeaders config (#788)
Browse files Browse the repository at this point in the history
Closes #739
  • Loading branch information
Qard authored and watson committed Jan 22, 2019
1 parent 3295e62 commit 86a4237
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 29 deletions.
12 changes: 12 additions & 0 deletions docs/configuration.asciidoc
Expand Up @@ -218,6 +218,18 @@ the body needs to be available as a property on the incoming HTTP https://nodejs
The agent will look for the body on the following properties:
`req.json || req.body || req.payload`

[[capture-headers]]
==== `captureHeaders`

* *Type:* Boolean
* *Default:* true
* *Env:* `ELASTIC_APM_CAPTURE_HEADERS`

The HTTP headers of incoming HTTP requests,
and its resulting response headers,
are recorded and sent to the APM Server by default.
This can be disabled by setting this option to `false`.

[[error-on-aborted-requests]]
==== `errorOnAbortedRequests`

Expand Down
6 changes: 2 additions & 4 deletions lib/agent.js
Expand Up @@ -283,13 +283,11 @@ Agent.prototype.captureError = function (err, opts, cb) {
}

if (req) {
var config = agent._conf.captureBody
var captureBody = config === 'errors' || config === 'all'
error.context.request = parsers.getContextFromRequest(req, captureBody)
error.context.request = parsers.getContextFromRequest(req, agent._conf, 'errors')
}

if (res) {
error.context.response = parsers.getContextFromResponse(res, true)
error.context.response = parsers.getContextFromResponse(res, agent._conf, true)
}

if (captureLocation) {
Expand Down
9 changes: 6 additions & 3 deletions lib/config.js
Expand Up @@ -62,7 +62,8 @@ var DEFAULTS = {
kubernetesNodeName: undefined,
kubernetesNamespace: undefined,
kubernetesPodName: undefined,
kubernetesPodUID: undefined
kubernetesPodUID: undefined,
captureHeaders: true
}

var ENV_TABLE = {
Expand Down Expand Up @@ -102,7 +103,8 @@ var ENV_TABLE = {
kubernetesNodeName: 'ELASTIC_APM_KUBERNETES_NODE_NAME',
kubernetesNamespace: 'ELASTIC_APM_KUBERNETES_NAMESPACE',
kubernetesPodName: 'ELASTIC_APM_KUBERNETES_POD_NAME',
kubernetesPodUID: 'ELASTIC_APM_KUBERNETES_POD_UID'
kubernetesPodUID: 'ELASTIC_APM_KUBERNETES_POD_UID',
captureHeaders: 'ELASTIC_APM_CAPTURE_HEADERS'
}

var BOOL_OPTS = [
Expand All @@ -113,7 +115,8 @@ var BOOL_OPTS = [
'captureSpanStackTraces',
'errorOnAbortedRequests',
'instrument',
'asyncHooks'
'asyncHooks',
'captureHeaders'
]

var NUM_OPTS = [
Expand Down
7 changes: 3 additions & 4 deletions lib/instrumentation/transaction.js
Expand Up @@ -129,18 +129,17 @@ Transaction.prototype.toJSON = function () {
payload.span_count.dropped = this._droppedSpans
}

var conf = this._agent._conf
if (this.req) {
var config = this._agent._conf.captureBody
var captureBody = config === 'transactions' || config === 'all'
payload.context.request = parsers.getContextFromRequest(this.req, captureBody)
payload.context.request = parsers.getContextFromRequest(this.req, conf, 'transactions')

// TODO: Tempoary fix for https://github.com/elastic/apm-agent-nodejs/issues/813
if (payload.context.request && payload.context.request.url && payload.context.request.url.port) {
payload.context.request.url.port = String(payload.context.request.url.port)
}
}
if (this.res) {
payload.context.response = parsers.getContextFromResponse(this.res)
payload.context.response = parsers.getContextFromResponse(this.res, conf)
}
}

Expand Down
18 changes: 14 additions & 4 deletions lib/parsers.js
Expand Up @@ -88,7 +88,9 @@ exports.parseError = function (err, agent, cb) {
})
}

exports.getContextFromRequest = function (req, captureBody) {
exports.getContextFromRequest = function (req, conf, type) {
var captureBody = conf.captureBody === type || conf.captureBody === 'all'

var context = {
http_version: req.httpVersion,
method: req.method,
Expand All @@ -97,7 +99,11 @@ exports.getContextFromRequest = function (req, captureBody) {
remote_address: req.socket.remoteAddress,
encrypted: !!req.socket.encrypted
},
headers: Object.assign({}, req.headers)
headers: undefined
}

if (conf.captureHeaders) {
context.headers = Object.assign({}, req.headers)
}

var contentLength = parseInt(req.headers['content-length'], 10)
Expand All @@ -123,10 +129,14 @@ exports.getContextFromRequest = function (req, captureBody) {
return context
}

exports.getContextFromResponse = function (res, isError) {
exports.getContextFromResponse = function (res, conf, isError) {
var context = {
status_code: res.statusCode,
headers: res.headers || httpHeaders(res, true)
headers: undefined
}

if (conf.captureHeaders) {
context.headers = res.headers || httpHeaders(res, true)
}

if (isError) {
Expand Down
3 changes: 2 additions & 1 deletion test/instrumentation/_agent.js
Expand Up @@ -26,7 +26,8 @@ module.exports = function mockAgent (expected, cb) {
ignoreUserAgentStr: [],
ignoreUserAgentRegExp: [],
transactionSampleRate: 1.0,
disableInstrumentations: []
disableInstrumentations: [],
captureHeaders: true
},
_errorFilters: new Filters(),
_transactionFilters: new Filters(),
Expand Down
32 changes: 19 additions & 13 deletions test/parsers.js
Expand Up @@ -51,7 +51,7 @@ test('#getContextFromResponse()', function (t) {

res.sendDate = false

var context = parsers.getContextFromResponse(res, true)
var context = parsers.getContextFromResponse(res, { captureHeaders: true }, true)
t.deepEqual(context, {
status_code: 200,
headers: {},
Expand All @@ -72,7 +72,7 @@ test('#getContextFromResponse()', function (t) {
res.sendDate = false
res.write('foo')

var context = parsers.getContextFromResponse(res, true)
var context = parsers.getContextFromResponse(res, { captureHeaders: true }, true)
t.deepEqual(context, {
status_code: 200,
headers: { connection: 'close', 'transfer-encoding': 'chunked' },
Expand All @@ -87,7 +87,7 @@ test('#getContextFromResponse()', function (t) {
t.test('for error (request finished)', function (t) {
onRequest(function (req, res) {
req.on('end', function () {
var context = parsers.getContextFromResponse(res, true)
var context = parsers.getContextFromResponse(res, { captureHeaders: true }, true)
t.deepEqual(context, {
status_code: 200,
headers: { connection: 'close', 'content-length': '0' },
Expand All @@ -106,7 +106,7 @@ test('#getContextFromResponse()', function (t) {
t.test('for transaction', function (t) {
onRequest(function (req, res) {
req.on('end', function () {
var context = parsers.getContextFromResponse(res, false)
var context = parsers.getContextFromResponse(res, { captureHeaders: true }, false)
t.deepEqual(context, {
status_code: 200,
headers: { connection: 'close', 'content-length': '0' }
Expand All @@ -121,7 +121,8 @@ test('#getContextFromResponse()', function (t) {

test('#getContextFromRequest()', function (t) {
t.test('should parse a request object', function (t) {
var parsed = parsers.getContextFromRequest(getMockReq())
var conf = { captureHeaders: true, captureBody: 'off' }
var parsed = parsers.getContextFromRequest(getMockReq(), conf)
t.deepEqual(parsed, {
http_version: '1.1',
method: 'GET',
Expand All @@ -148,7 +149,7 @@ test('#getContextFromRequest()', function (t) {
t.test('full URI', function (t) {
var req = getMockReq()
req.url = 'https://www.example.com:8080/some/path?key=value'
var parsed = parsers.getContextFromRequest(req)
var parsed = parsers.getContextFromRequest(req, {})
t.deepEqual(parsed.url, {
pathname: '/some/path',
search: '?key=value',
Expand All @@ -164,7 +165,7 @@ test('#getContextFromRequest()', function (t) {
t.test('port in host header', function (t) {
var req = getMockReq()
req.headers.host = 'example.com:8080'
var parsed = parsers.getContextFromRequest(req)
var parsed = parsers.getContextFromRequest(req, {})
t.deepEqual(parsed.url, {
hostname: 'example.com',
port: 8080,
Expand All @@ -180,7 +181,7 @@ test('#getContextFromRequest()', function (t) {
t.test('empty query string', function (t) {
var req = getMockReq()
req.url = '/some/path?'
var parsed = parsers.getContextFromRequest(req)
var parsed = parsers.getContextFromRequest(req, {})
t.deepEqual(parsed.url, {
hostname: 'example.com',
pathname: '/some/path',
Expand All @@ -193,55 +194,60 @@ test('#getContextFromRequest()', function (t) {
})

t.test('should slice too large body\'s', function (t) {
var conf = { captureBody: 'all' }
var req = getMockReq()
req.body = ''
for (var n = 0; n < parsers._MAX_HTTP_BODY_CHARS + 10; n++) {
req.body += 'x'
}
req.headers['content-length'] = String(req.body.length)
var parsed = parsers.getContextFromRequest(req, true)
var parsed = parsers.getContextFromRequest(req, conf)
t.equal(parsed.body.length, parsers._MAX_HTTP_BODY_CHARS)
t.end()
})

t.test('should not log body if opts.body is false', function (t) {
var conf = { captureBody: 'off' }
var req = getMockReq()
req.body = 'secret stuff'
req.headers['content-length'] = String(req.body.length)
var parsed = parsers.getContextFromRequest(req, false)
var parsed = parsers.getContextFromRequest(req, conf)
t.equal(parsed.body, '[REDACTED]')
t.end()
})

t.test('body is object', function (t) {
var conf = { captureBody: 'all' }
var req = getMockReq()
req.body = { foo: 42 }
req.headers['content-length'] = JSON.stringify(req.body).length
var parsed = parsers.getContextFromRequest(req, true)
var parsed = parsers.getContextFromRequest(req, conf)
t.deepEqual(parsed.body, { foo: 42 })
t.end()
})

t.test('body is object, but too large', function (t) {
var conf = { captureBody: 'all' }
var req = getMockReq()
req.body = { foo: '' }
for (var n = 0; n < parsers._MAX_HTTP_BODY_CHARS + 10; n++) {
req.body.foo += 'x'
}
req.headers['content-length'] = JSON.stringify(req.body).length
var parsed = parsers.getContextFromRequest(req, true)
var parsed = parsers.getContextFromRequest(req, conf)
t.equal(typeof parsed.body, 'string')
t.equal(parsed.body.length, parsers._MAX_HTTP_BODY_CHARS)
t.equal(parsed.body.slice(0, 10), '{"foo":"xx')
t.end()
})

t.test('body is object, but not safe to stringify', function (t) {
var conf = { captureBody: 'all' }
var req = getMockReq()
req.body = { foo: 42 }
req.body.bar = req.body
req.headers['transfer-encoding'] = 'chunked'
var parsed = parsers.getContextFromRequest(req, true)
var parsed = parsers.getContextFromRequest(req, conf)
t.deepEqual(parsed.body, req.body)
t.end()
})
Expand Down

0 comments on commit 86a4237

Please sign in to comment.