Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions .ci/.jenkins_tav.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
TAV:
- @elastic/elasticsearch
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the diff... I only added this line, but sorted all entries alphabetically

- apollo-server-express
- bluebird
- cassandra-driver
- elasticsearch
- express
- express-graphql
- express-queue
- fastify
- finalhandler
- generic-pool
- mysql
- mysql2
- redis
- koa-router
- got
- graphql
- handlebars
- hapi
- ioredis
- jade
- pug
- knex
- koa-router
- mimic-response
- mongodb-core
- finalhandler
- ioredis
- mysql
- mysql2
- pg
- cassandra-driver
- tedious
- pug
- redis
- restify
- fastify
- mimic-response
- got
- bluebird
- apollo-server-express
- knex
- tedious
- ws
- graphql
- express-graphql
- elasticsearch
- hapi
- express
- express-queue
5 changes: 5 additions & 0 deletions .tav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,14 @@ koa-router:
peerDependencies: koa@2
versions: '>=5.2.0 <8'
commands: node test/instrumentation/modules/koa-router/index.js

elasticsearch:
versions: '>=8.0.0'
commands: node test/instrumentation/modules/elasticsearch.js
'@elastic/elasticsearch':
versions: '*'
commands: node test/instrumentation/modules/@elastic/elasticsearch.js

handlebars:
versions: '*'
commands: node test/instrumentation/modules/handlebars.js
Expand Down
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
-
node_js: '12'
if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.*
env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue
env: TAV=ws,graphql,express-graphql,elasticsearch,@elastic/elasticsearch,hapi,@hapi/hapi,express,express-queue
script: tav --quiet
-
node_js: '12'
Expand All @@ -142,7 +142,7 @@ jobs:
-
node_js: '11'
if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.*
env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue
env: TAV=ws,graphql,express-graphql,elasticsearch,@elastic/elasticsearch,hapi,@hapi/hapi,express,express-queue
script: tav --quiet
-
node_js: '11'
Expand All @@ -169,7 +169,7 @@ jobs:
-
node_js: '10'
if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.*
env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue
env: TAV=ws,graphql,express-graphql,elasticsearch,@elastic/elasticsearch,hapi,@hapi/hapi,express,express-queue
script: tav --quiet
-
node_js: '10'
Expand All @@ -196,7 +196,7 @@ jobs:
-
node_js: '8'
if: type IN (cron, pull_request) AND NOT branch =~ ^greenkeeper/.*
env: TAV=ws,graphql,express-graphql,elasticsearch,hapi,@hapi/hapi,express,express-queue
env: TAV=ws,graphql,express-graphql,elasticsearch,@elastic/elasticsearch,hapi,@hapi/hapi,express,express-queue
script: tav --quiet
-
node_js: '8'
Expand Down
1 change: 1 addition & 0 deletions docs/supported-technologies.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The Node.js agent will automatically instrument the following modules to give yo
|Module |Version |Note
|https://www.npmjs.com/package/cassandra-driver[cassandra-driver] |>=3.0.0 |Will instrument all queries
|https://www.npmjs.com/package/elasticsearch-js[elasticsearch-js] |>=8.0.0 |Will instrument all queries
|https://www.npmjs.com/package/@elastic/elasticsearch[@elastic/elasticsearch] |* |Will instrument all queries
|https://www.npmjs.com/package/graphql[graphql] |>=0.7.0 <15.0.0 |Will instrument all queries
|https://www.npmjs.com/package/handlebars[handlebars] |* |Will instrument compile and render calls
|https://www.npmjs.com/package/jade[jade] |>=0.5.6 |Will instrument compile and render calls
Expand Down
24 changes: 24 additions & 0 deletions lib/instrumentation/elasticsearch-shared.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict'

const queryRegexp = /_((search|msearch)(\/template)?|count)$/

exports.setDbContext = function (span, params) {
if (queryRegexp.test(params.path)) {
// The old client exposes body and query, the new client exposes body and querystring
const statement = Array.isArray(params.body)
? params.body.map(JSON.stringify).join('\n')
: stringifyQuery(params).trim()

if (statement) {
span.setDbContext({
type: 'elasticsearch',
statement
})
}
}
}

function stringifyQuery (params) {
const query = params.body || params.querystring || params.query
return typeof query === 'string' ? query : JSON.stringify(query)
}
1 change: 1 addition & 0 deletions lib/instrumentation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var shimmer = require('./shimmer')
var Transaction = require('./transaction')

var MODULES = [
'@elastic/elasticsearch',
'apollo-server-core',
'bluebird',
'cassandra-driver',
Expand Down
79 changes: 79 additions & 0 deletions lib/instrumentation/modules/@elastic/elasticsearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict'

const { setDbContext } = require('../../elasticsearch-shared')

module.exports = function (elasticsearch, agent, { version, enabled }) {
if (!enabled) return elasticsearch

function generateSpan (activeSpans, meta, params) {
const span = agent.startSpan(null, 'db.elasticsearch.request')
if (span === null) return null

const { request } = meta
span.name = `Elasticsearch: ${params.method} ${params.path}`

activeSpans.set(request.id, span)

return span
}

class ApmClient extends elasticsearch.Client {
constructor (opts) {
super(opts)

const activeSpans = new Map()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a map here might be a bit risky. If something interrupts the flow of the client and it fails to emit the next event in the sequence after the span has been added to the map it would result in a memory leak. Might want to consider using mapcap here, just to be safe.

let hasPrepareRequestEvent = false

this.on('prepare-request', (err, { meta, params }) => {
hasPrepareRequestEvent = true
if (err) {
return agent.captureError(err)
}

generateSpan(activeSpans, meta, params)
})

this.on('request', (err, { meta }) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the request event the body and querystring values will be already serialized, would it be possible to add the setDbContext call here?
In this way, we don't need to serialize the body/querystring twice ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love the make the query more readable in the case of an array of queries. What I do with the old client is format it as NDJSON so that each query is on its own line. If we use the JSON that you formatted, it will be one long line containing all the queries... not really sure how to best do this 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you mean with "array of queries"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example:

  const body = [
    {},
    {
      query: {
        query_string: {
          query: 'pants'
        }
      }
    }
  ]

  client.msearch({ body }, function () {...})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case you will get a ndjson serialized body.

const { request } = meta

if (err) {
const span = activeSpans.get(request.id)
agent.captureError(err)
if (span !== undefined) {
span.end()
activeSpans.delete(request.id)
}
return
}

const span = hasPrepareRequestEvent === false
? generateSpan(activeSpans, meta, request.params)
: activeSpans.get(request.id)

if (span) setDbContext(span, request.params)

// TODO: can we signal somehow that here
// we are starting the actual http request?
})

this.on('response', (err, { meta }) => {
if (err) {
// TODO: rebuild error object to avoid serialization issues
agent.captureError(err, { custom: err.message })
// agent.captureError(err, { labels: { meta: JSON.stringify(err.meta) } })
// agent.captureError(err, { custom: JSON.parse(JSON.stringify(err.meta)) })
// agent.captureError(err, { custom: err.meta.meta.connection })
}

const { request } = meta
const span = activeSpans.get(request.id)
if (span !== undefined) {
span.end()
activeSpans.delete(request.id)
}
})
}
}

return Object.assign(elasticsearch, { Client: ApmClient })
}
18 changes: 2 additions & 16 deletions lib/instrumentation/modules/elasticsearch.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict'

var { setDbContext } = require('../elasticsearch-shared')
var shimmer = require('../shimmer')

var queryRegexp = /_((search|msearch)(\/template)?|count)$/

module.exports = function (elasticsearch, agent, { enabled }) {
if (!enabled) return elasticsearch

Expand All @@ -18,26 +17,13 @@ module.exports = function (elasticsearch, agent, { enabled }) {
var id = span && span.transaction.id
var method = params && params.method
var path = params && params.path
var query = params && params.query
var body = params && params.body

agent.logger.debug('intercepted call to elasticsearch.Transport.prototype.request %o', { id: id, method: method, path: path })

if (span && method && path) {
span.name = `Elasticsearch: ${method} ${path}`

if (queryRegexp.test(path)) {
let statement = Array.isArray(body)
? body.map(JSON.stringify).join('\n')
: JSON.stringify(body || query)

if (statement) {
span.setDbContext({
type: 'elasticsearch',
statement
})
}
}
setDbContext(span, params)

if (typeof cb === 'function') {
var args = Array.prototype.slice.call(arguments)
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"@commitlint/cli": "^8.0.0",
"@commitlint/config-conventional": "^8.0.0",
"@commitlint/travis-cli": "^8.0.0",
"@elastic/elasticsearch": "elastic/elasticsearch-js#update-events",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is merged into master, it will break ones the branch is deleted. But for now it's the only way to test our compatibility with the yet unreleased prepare-request event which will ship in 7.4.

Maybe we should wait to merge this PR until that PR branch has been merged?

"@types/node": "^12.0.8",
"apollo-server-express": "^2.6.3",
"aws-sdk": "^2.477.0",
Expand Down
1 change: 1 addition & 0 deletions test/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ test('disableInstrumentations', function (t) {
var flattenedModules = Instrumentation.modules.reduce((acc, val) => acc.concat(val), [])
var modules = new Set(flattenedModules)
if (semver.lt(process.version, '8.6.0')) {
modules.delete('@elastic/elasticsearch')
modules.delete('restify')
}
if (semver.lt(process.version, '8.3.0')) {
Expand Down
Loading