Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

[DEVX-3565] add neo4j plugin for v4.41 #15

Open
wants to merge 9 commits into
base: v4.x
Choose a base branch
from
Open
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
23 changes: 23 additions & 0 deletions .github/workflows/plugins.yml
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,29 @@ jobs:
uses: ./.github/actions/testagent/logs
- uses: codecov/codecov-action@v3

neo4j:
runs-on: ubuntu-latest
services:
neo4j:
image: neo4j:5.11.0
env:
NEO4J_AUTH: 'neo4j/test-password'
ports:
- 7474:7474
- 7687:7687
env:
PLUGINS: neo4j
SERVICES: neo4j
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/node/setup
- run: yarn install
- uses: ./.github/actions/node/oldest
- run: yarn test:plugins:ci
- uses: ./.github/actions/node/latest
- run: yarn test:plugins:ci
- uses: codecov/codecov-action@v2

net:
runs-on: ubuntu-latest
env:
Expand Down
1 change: 0 additions & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
* @DataDog/dd-trace-js
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ services:
image: ghcr.io/ridedott/pubsub-emulator
ports:
- "127.0.0.1:8081:8081"
neo4j:
image: neo4j:5.11.0
environment:
- NEO4J_AUTH=neo4j/test-password
ports:
- "7474:7474"
- "7687:7687"
localstack:
image: localstack/localstack:3.0.2
ports:
Expand Down
4 changes: 4 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ tracer.use('pg', {
<h5 id="mysql2"></h5>
<h5 id="mysql2-tags"></h5>
<h5 id="mysql2-config"></h5>
<h5 id="neo4j"></h5>
<h5 id="neo4j-tags"></h5>
<h5 id="neo4j-config"></h5>
<h5 id="net"></h5>
<h5 id="next"></h5>
<h5 id="opensearch"></h5>
Expand Down Expand Up @@ -132,6 +135,7 @@ tracer.use('pg', {
* [mongodb-core](./interfaces/export_.plugins.mongodb_core.html)
* [mysql](./interfaces/export_.plugins.mysql.html)
* [mysql2](./interfaces/export_.plugins.mysql2.html)
* [neo4j](./interfaces/plugins.neo4j.html)
* [net](./interfaces/export_.plugins.net.html)
* [next](./interfaces/export_.plugins.next.html)
* [opensearch](./interfaces/export_.plugins.opensearch.html)
Expand Down
1 change: 1 addition & 0 deletions docs/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ tracer.use('mysql');
tracer.use('mysql', { service: () => `my-custom-mysql` });
tracer.use('mysql2');
tracer.use('mysql2', { service: () => `my-custom-mysql2` });
tracer.use('neo4j');
tracer.use('net');
tracer.use('next');
tracer.use('next', nextOptions);
Expand Down
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ interface Plugins {
"mongoose": tracer.plugins.mongoose;
"mysql": tracer.plugins.mysql;
"mysql2": tracer.plugins.mysql2;
"neo4j": tracer.plugins.neo4j;
"net": tracer.plugins.net;
"next": tracer.plugins.next;
"openai": tracer.plugins.openai;
Expand Down Expand Up @@ -1625,6 +1626,12 @@ declare namespace tracer {
*/
interface mysql2 extends mysql {}

/**
* This plugin automatically instruments the
* [neo4j](https://github.com/neo4j/neo4j-javascript-driver) module.
*/
interface neo4j extends Instrumentation {}

/**
* This plugin automatically instruments the
* [net](https://nodejs.org/api/net.html) module.
Expand Down
2 changes: 2 additions & 0 deletions packages/datadog-instrumentations/src/helpers/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ module.exports = {
mquery: () => require('../mquery'),
mysql: () => require('../mysql'),
mysql2: () => require('../mysql2'),
'neo4j-driver': () => require('../neo4j'),
'neo4j-driver-core': () => require('../neo4j'),
net: () => require('../net'),
next: () => require('../next'),
'node:child_process': () => require('../child_process'),
Expand Down
138 changes: 138 additions & 0 deletions packages/datadog-instrumentations/src/neo4j.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
'use strict'

const {
channel,
addHook,
AsyncResource
} = require('./helpers/instrument')
const shimmer = require('../../datadog-shimmer')

const startCh = channel('apm:neo4j:query:start')
const finishCh = channel('apm:neo4j:query:finish')
const errorCh = channel('apm:neo4j:query:error')

addHook(
{
name: 'neo4j-driver-core',
file: 'lib/session.js',
versions: ['>=4.3.0']
},
(exports) => {
shimmer.wrap(exports.default.prototype, 'run', wrapRun)
return exports
}
)

addHook(
{
name: 'neo4j-driver-core',
file: 'lib/transaction.js',
versions: ['>=4.3.0']
},
(exports) => {
shimmer.wrap(exports.default.prototype, 'run', wrapRun)
return exports
}
)

addHook(
{
name: 'neo4j-driver',
file: 'lib/session.js',
versions: ['>=4.0.0 <4.3.0']
},
(exports) => {
shimmer.wrap(exports.default.prototype, 'run', wrapRun)
return exports
}
)

addHook(
{
name: 'neo4j-driver',
file: 'lib/transaction.js',
versions: ['>=4.0.0 <4.3.0']
},
(exports) => {
shimmer.wrap(exports.default.prototype, 'run', wrapRun)
return exports
}
)

function wrapRun (run) {
return function (statement) {
if (!startCh.hasSubscribers) {
return run.apply(this, arguments)
}

if (!statement) return run.apply(this, arguments)

const asyncResource = new AsyncResource('bound-anonymous-fn')
const attributes = getAttributesFromNeo4jSession(this)

return asyncResource.runInAsyncScope(() => {
startCh.publish({ attributes, statement })

try {
const promise = run.apply(this, arguments)
if (promise && typeof promise.then === 'function') {
const onResolve = asyncResource.bind(() => finish())
const onReject = asyncResource.bind(e => finish(e))

promise.then(onResolve, onReject)
} else {
finish()
}
return promise
} catch (err) {
err.stack // trigger getting the stack at the original throwing point
errorCh.publish(err)

throw err
}
})
}
}

function finish (error) {
if (error) {
errorCh.publish(error)
}
finishCh.publish()
}

function getAttributesFromNeo4jSession (session) {
const connectionHolder =
(session._mode === 'WRITE' ? session._writeConnectionHolder : session._readConnectionHolder) ||
session._connectionHolder ||
{}
const connectionProvider = connectionHolder._connectionProvider || {}

// seedRouter is used when connecting to a url that starts with "neo4j", usually aura
const address = connectionProvider._address || connectionProvider._seedRouter

const attributes = {
// "neo4j" is the default database name. When used, "session._database" is an empty string
dbName: session._database ? session._database : 'neo4j'
}
if (address) {
attributes.host = address._host
attributes.port = address._port
}

// neo4j-driver <5.12.0
const auth = connectionProvider._authToken || {}
if (auth.principal) {
attributes.dbUser = auth.principal
}

// neo4j-driver >=5.12.0
const authProvider = connectionProvider._authenticationProvider || {}
const authTokenManager = authProvider._authTokenManager || {}
const authToken = authTokenManager._authToken || {}
if (authToken.principal) {
attributes.dbUser = authToken.principal
}

return attributes
}
50 changes: 50 additions & 0 deletions packages/datadog-plugin-neo4j/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict'

const StoragePlugin = require('../../dd-trace/src/plugins/storage')
const { storage } = require('../../datadog-core')
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')

class Neo4jPlugin extends StoragePlugin {
static get id () { return 'neo4j' }
static get system () { return 'neo4j' }
static get name () {
return 'neo4j'
}

constructor (...args) {
super(...args)

this.addSub('apm:neo4j:query:start', ({ attributes, statement }) => {
const store = storage.getStore()
const childOf = store ? store.span : store
const span = this.tracer.startSpan('neo4j.query', {
childOf,
tags: {
'db.name': attributes.dbName,
'db.type': 'neo4j',
'db.user': attributes.dbUser,
'out.host': attributes.host,
'out.port': attributes.port,
'resource.name': statement,
'service.name': this.config.service || `${this.tracer._service}-neo4j`,
'span.kind': 'client',
'span.type': 'cypher'
}
})
analyticsSampler.sample(span, this.config.measured)
this.enter(span, store)
})

this.addSub('apm:neo4j:query:error', err => {
const span = storage.getStore().span
span.setTag('error', err)
})

this.addSub('apm:neo4j:query:finish', () => {
const span = storage.getStore().span
span.finish()
})
}
}

module.exports = Neo4jPlugin
Loading
Loading