-
Notifications
You must be signed in to change notification settings - Fork 227
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: 'elasticsearchCaptureBodyUrls' config option #2873
Changes from 9 commits
9206c16
f7e21b4
0452ebc
8001549
19b7c7b
29bd7e9
87dbe9b
df0b9e7
e5f8e68
f8631c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -249,6 +249,7 @@ declare namespace apm { | |
contextPropagationOnly?: boolean; | ||
disableInstrumentations?: string | string[]; | ||
disableSend?: boolean; | ||
elasticsearchCaptureBodyUrls?: Array<string>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No objection, but why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly just copying syntax from elsewhere in this file. https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#arrays says both are fine and doesn't claim a preference, so I'm fine with either way. Both are used in index.d.ts. |
||
environment?: string; | ||
errorMessageMaxLength?: string; // DEPRECATED: use `longFieldMaxLength`. | ||
errorOnAbortedRequests?: boolean; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,64 +10,50 @@ | |
// - elasticsearch - the legacy Elasticsearch JS client | ||
// - @elastic/elasticsearch - the new Elasticsearch JS client | ||
|
||
const querystring = require('querystring') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 dings the "two |
||
|
||
// URL paths matching the following pattern will have their query params and | ||
// request body captured in the span (as `context.db.statement`). We match | ||
// a complete URL path component to attempt to avoid accidental matches of | ||
// user data, like `GET /my_index_search/...`. | ||
const pathIsAQuery = /\/(_search|_msearch|_count|_async_search|_sql|_eql)(\/|$)/ | ||
|
||
// (This is exported for testing.) | ||
exports.pathIsAQuery = pathIsAQuery | ||
|
||
// Set the span's `context.db` from the Elasticsearch request querystring and | ||
// body, if the request path looks like it is a query API. | ||
// | ||
// Some ES endpoints, e.g. '_search', support both query params and a body. | ||
// We encode both into 'span.context.db.statement', separated by '\n\n' | ||
// if both are present. E.g. for a possible msearch: | ||
// | ||
// search_type=query_then_fetch&typed_keys=false | ||
// | ||
// {} | ||
// {"query":{"query_string":{"query":"pants"}}} | ||
// | ||
// `path`, `query`, and `body` can all be null or undefined. | ||
exports.setElasticsearchDbContext = function (span, path, query, body) { | ||
if (path && pathIsAQuery.test(path)) { | ||
const parts = [] | ||
if (query) { | ||
if (typeof (query) === 'string') { | ||
parts.push(query) | ||
} else if (typeof (query) === 'object') { | ||
const encodedQuery = querystring.encode(query) | ||
if (encodedQuery) { | ||
parts.push(encodedQuery) | ||
} | ||
} | ||
} | ||
if (body) { | ||
if (typeof (body) === 'string') { | ||
parts.push(body) | ||
} else if (Buffer.isBuffer(body) || typeof body.pipe === 'function') { | ||
// Never serialize a Buffer or a Readable. These guards mirror | ||
// `shouldSerialize()` in the ES client, e.g.: | ||
// https://github.com/elastic/elastic-transport-js/blob/069172506d1fcd544b23747d8c2d497bab053038/src/Transport.ts#L614-L618 | ||
} else if (Array.isArray(body)) { | ||
try { | ||
parts.push(body.map(JSON.stringify).join('\n') + '\n') // ndjson | ||
} catch (_ignoredErr) {} | ||
} else if (typeof (body) === 'object') { | ||
try { | ||
parts.push(JSON.stringify(body)) | ||
} catch (_ignoredErr) {} | ||
} | ||
// Only capture the ES request body if the request path matches the | ||
// `elasticsearchCaptureBodyUrls` config. | ||
function shouldCaptureBody (path, elasticsearchCaptureBodyUrlsRegExp) { | ||
if (!path) { | ||
return false | ||
} | ||
for (var i = 0; i < elasticsearchCaptureBodyUrlsRegExp.length; i++) { | ||
const re = elasticsearchCaptureBodyUrlsRegExp[i] | ||
if (re.test(path)) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
span.setDbContext({ | ||
type: 'elasticsearch', | ||
statement: parts.join('\n\n') | ||
}) | ||
/** | ||
* Get an appropriate `span.context.db.statement` for this ES client request, if any. | ||
* https://github.com/elastic/apm/blob/main/specs/agents/tracing-instrumentation-db.md#elasticsearch_capture_body_urls-configuration | ||
* | ||
* @param {string | null} path | ||
* @param {string | null} body | ||
* @param {RegExp[]} elasticsearchCaptureBodyUrlsRegExp | ||
* @return {string | undefined} | ||
*/ | ||
function getElasticsearchDbStatement (path, body, elasticsearchCaptureBodyUrlsRegExp) { | ||
if (body && shouldCaptureBody(path, elasticsearchCaptureBodyUrlsRegExp)) { | ||
if (typeof (body) === 'string') { | ||
return body | ||
} else if (Buffer.isBuffer(body) || typeof body.pipe === 'function') { | ||
// Never serialize a Buffer or a Readable. These guards mirror | ||
// `shouldSerialize()` in the ES client, e.g.: | ||
// https://github.com/elastic/elastic-transport-js/blob/069172506d1fcd544b23747d8c2d497bab053038/src/Transport.ts#L614-L618 | ||
} else if (Array.isArray(body)) { | ||
try { | ||
return body.map(JSON.stringify).join('\n') + '\n' // ndjson | ||
} catch (_ignoredErr) {} | ||
} else if (typeof (body) === 'object') { | ||
try { | ||
return JSON.stringify(body) | ||
} catch (_ignoredErr) {} | ||
} | ||
} | ||
} | ||
|
||
module.exports = { | ||
getElasticsearchDbStatement | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Might be better to say version 7 is not installed by default instead of Note that version 8 is installed by default, since the later will inevitably be not true one day but the former will almost certainly be true into the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me, thanks.
Note, if interested: For a while I'd had both v7 and v8 installed using an alias for v7 --
"@elastic/elasticsearch7": "npm:@elastic/[email protected]"
. However the default npm in node v8 doesn't support that alias syntax, so I dropped it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commit f8631c0