Skip to content

Commit 915b320

Browse files
refactor: Refactor memcached instrumentation to subscribe to events emitted (#3849)
1 parent 057fca2 commit 915b320

File tree

8 files changed

+1068
-923
lines changed

8 files changed

+1068
-923
lines changed

lib/instrumentation/memcached.js

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

lib/instrumentations.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ module.exports = function instrumentations() {
1919
fastify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },
2020
kafkajs: { type: InstrumentationDescriptor.TYPE_MESSAGE },
2121
koa: { module: './instrumentation/koa' },
22-
memcached: { type: InstrumentationDescriptor.TYPE_DATASTORE },
2322
mongodb: { type: InstrumentationDescriptor.TYPE_DATASTORE },
2423
next: { module: './instrumentation/nextjs' },
2524
restify: { type: InstrumentationDescriptor.TYPE_WEB_FRAMEWORK },

lib/subscriber-configs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const subscribers = {
2222
...require('./subscribers/langchain/config'),
2323
...require('./subscribers/langgraph/config'),
2424
...require('./subscribers/mcp-sdk/config'),
25+
...require('./subscribers/memcached/config'),
2526
...require('./subscribers/mysql/config'),
2627
...require('./subscribers/mysql2/config'),
2728
...require('./subscribers/nestjs/config'),

lib/subscribers/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ module.exports = {
8484
}
8585
```
8686

87+
#### Function Query
88+
89+
| Query type | Use when |
90+
| ----------------------------------- | ---------------------------------------------------------------------------------- |
91+
| `expressionName` | Function expression assigned to a variable/property (`x.y = function name() {}`) |
92+
| `functionName` | Standalone function declaration (`function foo() {}`) |
93+
| `className` + `methodName` | Class method (`class Foo { bar() {} }`) |
94+
| `methodName` alone | Any method with that name across all classes |
95+
| `moduleName` + `expressionName` | Disambiguate by the object it's assigned to |
96+
8797
### Creating the Subscribers
8898
8999
Now that you have the config specified for the function that you are instrumenting, you'll then need to create a subscriber for it. All subscribers should at least inherit from the base [`Subscriber`](./base.js) with the exception of subscribers that do not rely on `orchestrion` to create their tracing channels (they inherit from the `node:diagnostics_channel` `Subscriber` in [`dc-base.js`](./dc-base.js)).
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2026 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
'use strict'
7+
const DbOperationSubscriber = require('../db-operation')
8+
const stringify = require('json-stringify-safe')
9+
10+
module.exports = class CommandSubscriber extends DbOperationSubscriber {
11+
constructor({ agent, logger }) {
12+
super({ agent, logger, channelName: 'nr_command', packageName: 'memcached', system: 'Memcache' })
13+
this.events = ['end']
14+
}
15+
16+
handler(data, ctx) {
17+
const { arguments: args, self: client } = data
18+
// The `command` method takes two arguments: a query generator and a server
19+
// address. The query generator returns a simple object describing the
20+
// memcached call. The server parameter is only provided for multi-calls.
21+
// When not provided, it can be derived from the key being interacted with.
22+
const [queryCompiler, server] = args
23+
const metacall = queryCompiler()
24+
this.parameters = this.#getInstanceParameters({ keys: this.#wrapKeys(metacall), metacall, server, client })
25+
this.operation = metacall.type || 'Unknown'
26+
const newCtx = super.handler(data, ctx)
27+
metacall.callback = this.agent.tracer.bindFunction(metacall.callback, newCtx, true)
28+
args[0] = function rewrapped() {
29+
// we wrap the queryCompiler function to return the
30+
// updated `metacall` object with our callback
31+
return metacall
32+
}
33+
return newCtx
34+
}
35+
36+
#getInstanceParameters({ keys, metacall, server, client }) {
37+
const parameters = {}
38+
try {
39+
parameters.key = stringify(keys[0])
40+
} catch (err) {
41+
this.logger.debug(err, 'Unable to stringify memcache key')
42+
parameters.key = '<unknown>'
43+
}
44+
45+
// Capture connection info for datastore instance metric.
46+
let location = null
47+
if (typeof server === 'string') {
48+
location = server
49+
} else if (client.HashRing && client.HashRing.get && metacall.key) {
50+
location = client.HashRing.get(metacall.key)
51+
}
52+
if (location) {
53+
location = location.split(':')
54+
parameters.host = location[0]
55+
parameters.port_path_or_id = location[1]
56+
}
57+
return parameters
58+
}
59+
60+
#wrapKeys(metacall) {
61+
if (metacall.key) {
62+
return [metacall.key]
63+
} else if (metacall.multi) {
64+
return metacall.command.split(' ').slice(1)
65+
}
66+
67+
return []
68+
}
69+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2026 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
const command = {
7+
path: './memcached/command.js',
8+
instrumentations: [{
9+
channelName: 'nr_command',
10+
module: { name: 'memcached', versionRange: '>=2', filePath: 'lib/memcached.js' },
11+
functionQuery: {
12+
// memcached.command = function memcachedCommand(queryCompiler, server) { ... }
13+
expressionName: 'command',
14+
kind: 'Sync'
15+
}
16+
}]
17+
}
18+
19+
module.exports = {
20+
memcached: [
21+
command
22+
]
23+
}

test/unit/instrumentation/memcached.test.js

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

0 commit comments

Comments
 (0)