Skip to content

Commit 170b95a

Browse files
committed
feat: improved filtering function API (elastic#579)
Changes the `agent.addFilter()` function to be called once per event instead of with an array of events. Besides the existing filtering function, three new custom filtering functions have been added: - `agent.addErrorFilter()` - `agent.addTransactionFilter()` - `agent.addSpanFilter()` BREAKING CHANGE: The `agent.addFilter()` API have changed.
1 parent b87d28d commit 170b95a

File tree

16 files changed

+477
-135
lines changed

16 files changed

+477
-135
lines changed

docs/agent-api.asciidoc

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ Currently the following HTTP headers are anonymized by default:
616616
See the https://github.com/watson/is-secret[is-secret] module for details about which patterns are considered sensitive.
617617

618618
If you wish to filter or sanitize other data,
619-
use the <<apm-add-filter,`apm.addFilter()`>> function.
619+
use one of the <<apm-add-filter,filtering>> functions.
620620

621621
[[disable-instrumentations]]
622622
===== `disableInstrumentations`
@@ -679,7 +679,8 @@ Each filter function will be called in the order they were added,
679679
and will receive a `payload` object as the only argument,
680680
containing the data about to be sent to the APM Server.
681681

682-
For details on the format of the payload,
682+
The format of the payload depends on the event type being sent.
683+
For details about the different formats,
683684
see the {apm-server-ref}/intake-api.html[APM Server intake API documentation].
684685

685686
The filter function is synchronous and should return the manipulated payload object.
@@ -691,17 +692,10 @@ Example usage:
691692
[source,js]
692693
----
693694
apm.addFilter(function (payload) {
694-
// the payload can either contain an array of transactions or errors
695-
var items = payload.transactions || payload.errors || []
696-
697-
// loop over each item in the array to redact any secrets we don't
698-
// want sent to the APM Server
699-
items.forEach(function (item) {
700-
if (item.context.request && item.context.request.headers) {
701-
// redact sensitive data
702-
payload.context.request.headers['x-secret'] = '[REDACTED]'
703-
}
704-
})
695+
if (payload.context.request && payload.context.request.headers) {
696+
// redact sensitive data
697+
payload.context.request.headers['x-secret'] = '[REDACTED]'
698+
}
705699
706700
// remember to return the modified payload
707701
return payload
@@ -714,6 +708,39 @@ See <<filter-http-headers,`filterHttpHeaders`>> for details.
714708
Though you can also use filter functions to add new contextual information to the `user` and `custom` properties,
715709
it's recommended that you use <<apm-set-user-context,`apm.setUserContext()`>> and <<apm-set-custom-context,`apm.setCustomContext()`>> for that purpose.
716710

711+
[[apm-add-error-filter]]
712+
==== `apm.addErrorFilter()`
713+
714+
[source,js]
715+
----
716+
apm.addErrorFilter(callback)
717+
----
718+
719+
Similar to <<apm-add-filter,`apm.addFilter()`>>,
720+
but the `callback` will only be called with error payloads.
721+
722+
[[apm-add-transaction-filter]]
723+
==== `apm.addTransactionFilter()`
724+
725+
[source,js]
726+
----
727+
apm.addTransactionFilter(callback)
728+
----
729+
730+
Similar to <<apm-add-filter,`apm.addFilter()`>>,
731+
but the `callback` will only be called with transaction payloads.
732+
733+
[[apm-add-span-filter]]
734+
==== `apm.addSpanFilter()`
735+
736+
[source,js]
737+
----
738+
apm.addSpanFilter(callback)
739+
----
740+
741+
Similar to <<apm-add-filter,`apm.addFilter()`>>,
742+
but the `callback` will only be called with span payloads.
743+
717744
[[apm-set-user-context]]
718745
==== `apm.setUserContext()`
719746

docs/custom-stack.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ use the <<capture-body,`captureBody`>> config option
252252
To disable this,
253253
use the <<filter-http-headers,`filterHttpHeaders`>> config option
254254
* To apply custom filters,
255-
use the <<apm-add-filter,`apm.addFilter()`>> function
255+
use one of the <<apm-add-filter,filtering>> functions
256256

257257
[float]
258258
[[custom-stack-add-your-own-data]]

docs/express.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ use the <<capture-body,`captureBody`>> config option
164164
To disable this,
165165
use the <<filter-http-headers,`filterHttpHeaders`>> config option
166166
* To apply custom filters,
167-
use the <<apm-add-filter,`apm.addFilter()`>> function
167+
use one of the <<apm-add-filter,filtering>> functions
168168

169169
[float]
170170
[[express-add-your-own-data]]

docs/hapi.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ use the <<capture-body,`captureBody`>> config option
175175
To disable this,
176176
use the <<filter-http-headers,`filterHttpHeaders`>> config option
177177
* To apply custom filters,
178-
use the <<apm-add-filter,`apm.addFilter()`>> function
178+
use one of the <<apm-add-filter,filtering>> functions
179179

180180
[float]
181181
[[hapi-add-your-own-data]]

docs/koa.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ use the <<capture-body,`captureBody`>> config option
177177
To disable this,
178178
use the <<filter-http-headers,`filterHttpHeaders`>> config option
179179
* To apply custom filters,
180-
use the <<apm-add-filter,`apm.addFilter()`>> function
180+
use one of the <<apm-add-filter,filtering>> functions
181181

182182
[float]
183183
[[koa-add-your-own-data]]

docs/lambda.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ use the <<capture-body,`logBody`>> config option
113113
To disable this,
114114
use the <<filter-http-headers,`filterHttpHeaders`>> config option
115115
* To apply custom filters,
116-
use the <<apm-add-filter,`apm.addFilter()`>> function
116+
use one of the <<apm-add-filter,filtering>> functions
117117

118118
[float]
119119
[[lambda-add-your-own-data]]

docs/restify.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ use the <<capture-body,`captureBody`>> config option
167167
To disable this,
168168
use the <<filter-http-headers,`filterHttpHeaders`>> config option
169169
* To apply custom filters,
170-
use the <<apm-add-filter,`apm.addFilter()`>> function
170+
use one of the <<apm-add-filter,filtering>> functions
171171

172172
[float]
173173
[[restify-add-your-own-data]]

lib/agent.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ function Agent () {
3030
this.middleware = { connect: connect.bind(this) }
3131

3232
this._instrumentation = new Instrumentation(this)
33-
this._filters = new Filters()
33+
this._errorFilters = new Filters()
34+
this._transactionFilters = new Filters()
35+
this._spanFilters = new Filters()
3436
this._apmServer = null
3537

3638
this._conf = null
@@ -95,7 +97,10 @@ Agent.prototype.start = function (opts) {
9597
global[symbols.agentInitialized] = true
9698

9799
this._config(opts)
98-
this._filters.config(this._conf)
100+
101+
if (this._conf.filterHttpHeaders) {
102+
this.addFilter(require('./filters/http-headers'))
103+
}
99104

100105
if (!this._conf.active) {
101106
this.logger.info('Elastic APM agent is inactive due to configuration')
@@ -200,12 +205,36 @@ Agent.prototype.addTags = function (tags) {
200205
}
201206

202207
Agent.prototype.addFilter = function (fn) {
208+
this.addErrorFilter(fn)
209+
this.addTransactionFilter(fn)
210+
this.addSpanFilter(fn)
211+
}
212+
213+
Agent.prototype.addErrorFilter = function (fn) {
214+
if (typeof fn !== 'function') {
215+
this.logger.error('Can\'t add filter of type %s', typeof fn)
216+
return
217+
}
218+
219+
this._errorFilters.add(fn)
220+
}
221+
222+
Agent.prototype.addTransactionFilter = function (fn) {
223+
if (typeof fn !== 'function') {
224+
this.logger.error('Can\'t add filter of type %s', typeof fn)
225+
return
226+
}
227+
228+
this._transactionFilters.add(fn)
229+
}
230+
231+
Agent.prototype.addSpanFilter = function (fn) {
203232
if (typeof fn !== 'function') {
204233
this.logger.error('Can\'t add filter of type %s', typeof fn)
205234
return
206235
}
207236

208-
this._filters.add(fn)
237+
this._spanFilters.add(fn)
209238
}
210239

211240
Agent.prototype.captureError = function (err, opts, cb) {
@@ -313,7 +342,7 @@ Agent.prototype.captureError = function (err, opts, cb) {
313342
}
314343

315344
function send (error) {
316-
error = agent._filters.process(error) // TODO: Update filter to expect this format
345+
error = agent._errorFilters.process(error)
317346

318347
if (!error) {
319348
agent.logger.debug('error ignored by filter %o', { id: id })

lib/filters.js

Lines changed: 0 additions & 75 deletions
This file was deleted.

lib/filters/http-headers.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict'
2+
3+
const REDACTED = require('./').REDACTED
4+
5+
const cookie = require('cookie')
6+
const redact = require('redact-secrets')(REDACTED)
7+
const SetCookie = require('set-cookie-serde')
8+
9+
module.exports = httpHeaders
10+
11+
function httpHeaders (obj) {
12+
const headers = obj.context && obj.context.request && obj.context.request.headers
13+
14+
if (!headers) return obj
15+
16+
if (headers.authorization) headers.authorization = REDACTED
17+
18+
if (typeof headers.cookie === 'string') {
19+
var cookies = cookie.parse(headers.cookie)
20+
redact.forEach(cookies)
21+
headers.cookie = Object.keys(cookies)
22+
.map(function (k) { return k + '=' + cookies[k] })
23+
.join('; ')
24+
}
25+
26+
if (typeof headers['set-cookie'] !== 'undefined') {
27+
try {
28+
var setCookies = new SetCookie(headers['set-cookie'])
29+
redact.forEach(setCookies)
30+
headers['set-cookie'] = stringify(setCookies)
31+
} catch (err) {
32+
// Ignore error
33+
headers['set-cookie'] = '[malformed set-cookie header]'
34+
}
35+
}
36+
37+
return obj
38+
}
39+
40+
function stringify (value) {
41+
return Array.isArray(value)
42+
? value.map(value => value.toString())
43+
: value.toString()
44+
}

0 commit comments

Comments
 (0)