Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config): add captureHeaders config #788

Merged
merged 4 commits into from Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -273,13 +273,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 @@ -57,7 +57,8 @@ var DEFAULTS = {
transactionMaxSpans: 500,
transactionSampleRate: 1.0,
serverTimeout: '30s',
disableInstrumentations: []
disableInstrumentations: [],
captureHeaders: true
}

var ENV_TABLE = {
Expand Down Expand Up @@ -92,7 +93,8 @@ var ENV_TABLE = {
transactionSampleRate: 'ELASTIC_APM_TRANSACTION_SAMPLE_RATE',
serverTimeout: 'ELASTIC_APM_SERVER_TIMEOUT',
disableInstrumentations: 'ELASTIC_APM_DISABLE_INSTRUMENTATIONS',
payloadLogFile: 'ELASTIC_APM_PAYLOAD_LOG_FILE'
payloadLogFile: 'ELASTIC_APM_PAYLOAD_LOG_FILE',
captureHeaders: 'ELASTIC_APM_CAPTURE_HEADERS'
}

var BOOL_OPTS = [
Expand All @@ -103,7 +105,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,13 +129,12 @@ 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')
}
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)
Qard marked this conversation as resolved.
Show resolved Hide resolved
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)
Qard marked this conversation as resolved.
Show resolved Hide resolved
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