Skip to content

Commit 2f45ff7

Browse files
authored
feat(redis): added endpoint filtering (#1448)
ref INSTA-16323
1 parent 50b8e6a commit 2f45ff7

File tree

19 files changed

+804
-12
lines changed

19 files changed

+804
-12
lines changed

packages/collector/src/announceCycle/unannounced.js

+33
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const maxRetryDelay = 60 * 1000; // one minute
4343
* @typedef {Object} TracingConfig
4444
* @property {Array.<string>} [extra-http-headers]
4545
* @property {KafkaTracingConfig} [kafka]
46+
* @property {Object.<string, (string | string[])>} [ignore-endpoints]
4647
* @property {boolean} [span-batching-enabled]
4748
*/
4849

@@ -126,6 +127,7 @@ function applyAgentConfiguration(agentResponse) {
126127
applyExtraHttpHeaderConfiguration(agentResponse);
127128
applyKafkaTracingConfiguration(agentResponse);
128129
applySpanBatchingConfiguration(agentResponse);
130+
applyIgnoreEndpointsConfiguration(agentResponse);
129131
}
130132

131133
/**
@@ -220,3 +222,34 @@ function applySpanBatchingConfiguration(agentResponse) {
220222
agentOpts.config.tracing.spanBatchingEnabled = true;
221223
}
222224
}
225+
226+
/**
227+
* - The agent configuration currently uses a pipe ('|') as a separator for endpoints.
228+
* - This function supports both ('|') and comma (',') to ensure future compatibility.
229+
* - Additionally, it supports the `string[]` format for backward compatibility,
230+
* as this was the previously used standard. The final design decision is not yet completed.
231+
* https://github.ibm.com/instana/requests-for-discussion/pull/84
232+
*
233+
* @param {AgentAnnounceResponse} agentResponse
234+
*/
235+
function applyIgnoreEndpointsConfiguration(agentResponse) {
236+
if (agentResponse?.tracing?.['ignore-endpoints']) {
237+
const endpointTracingConfigFromAgent = agentResponse.tracing['ignore-endpoints'];
238+
239+
const endpointTracingConfig = Object.fromEntries(
240+
Object.entries(endpointTracingConfigFromAgent).map(([service, endpoints]) => {
241+
let normalizedEndpoints = null;
242+
if (typeof endpoints === 'string') {
243+
normalizedEndpoints = endpoints.split(/[|,]/).map(endpoint => endpoint?.trim()?.toLowerCase());
244+
} else if (Array.isArray(endpoints)) {
245+
normalizedEndpoints = endpoints.map(endpoint => endpoint?.toLowerCase());
246+
}
247+
248+
return [service.toLowerCase(), normalizedEndpoints];
249+
})
250+
);
251+
252+
ensureNestedObjectExists(agentOpts.config, ['tracing', 'ignoreEndpoints']);
253+
agentOpts.config.tracing.ignoreEndpoints = endpointTracingConfig;
254+
}
255+
}

packages/collector/test/announceCycle/unannounced_test.js

+91
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,97 @@ describe('unannounced state', () => {
213213
}
214214
});
215215
});
216+
it('should apply the configuration to ignore a single endpoint for a package', done => {
217+
prepareAnnounceResponse({
218+
tracing: {
219+
'ignore-endpoints': {
220+
redis: 'get'
221+
}
222+
}
223+
});
224+
unannouncedState.enter({
225+
transitionTo: () => {
226+
expect(agentOptsStub.config).to.deep.equal({
227+
tracing: {
228+
ignoreEndpoints: {
229+
redis: ['get']
230+
}
231+
}
232+
});
233+
done();
234+
}
235+
});
236+
});
237+
238+
it('should apply the configuration to ignore multiple endpoints for a package', done => {
239+
prepareAnnounceResponse({
240+
tracing: {
241+
'ignore-endpoints': {
242+
redis: 'SET|GET'
243+
}
244+
}
245+
});
246+
unannouncedState.enter({
247+
transitionTo: () => {
248+
expect(agentOptsStub.config).to.deep.equal({
249+
tracing: {
250+
ignoreEndpoints: {
251+
redis: ['set', 'get']
252+
}
253+
}
254+
});
255+
done();
256+
}
257+
});
258+
});
259+
260+
it('should apply tracing configuration to ignore specified endpoints across different packages', done => {
261+
prepareAnnounceResponse({
262+
tracing: {
263+
'ignore-endpoints': {
264+
REDIS: 'get|set',
265+
dynamodb: 'query'
266+
}
267+
}
268+
});
269+
unannouncedState.enter({
270+
transitionTo: () => {
271+
expect(agentOptsStub.config).to.deep.equal({
272+
tracing: {
273+
ignoreEndpoints: {
274+
redis: ['get', 'set'],
275+
dynamodb: ['query']
276+
}
277+
}
278+
});
279+
done();
280+
}
281+
});
282+
});
283+
284+
it('should apply tracing configuration to ignore endpoints when specified using array format', done => {
285+
prepareAnnounceResponse({
286+
tracing: {
287+
'ignore-endpoints': {
288+
REDIS: ['get', 'type'],
289+
dynamodb: 'query'
290+
}
291+
}
292+
});
293+
unannouncedState.enter({
294+
transitionTo: () => {
295+
expect(agentOptsStub.config).to.deep.equal({
296+
tracing: {
297+
ignoreEndpoints: {
298+
redis: ['get', 'type'],
299+
dynamodb: ['query']
300+
}
301+
}
302+
});
303+
done();
304+
}
305+
});
306+
});
216307

217308
function prepareAnnounceResponse(announceResponse) {
218309
agentConnectionStub.announceNodeCollector.callsArgWithAsync(0, null, JSON.stringify(announceResponse));

packages/collector/test/apps/agentStub.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const enableSpanBatching = process.env.ENABLE_SPANBATCHING === 'true';
3838
const kafkaTraceCorrelation = process.env.KAFKA_TRACE_CORRELATION
3939
? process.env.KAFKA_TRACE_CORRELATION === 'true'
4040
: null;
41+
const ignoreEndpoints = process.env.IGNORE_ENDPOINTS && JSON.parse(process.env.IGNORE_ENDPOINTS);
4142

4243
let discoveries = {};
4344
let rejectAnnounceAttempts = 0;
@@ -86,7 +87,7 @@ app.put('/com.instana.plugin.nodejs.discovery', (req, res) => {
8687
}
8788
};
8889

89-
if (kafkaTraceCorrelation != null || extraHeaders.length > 0 || enableSpanBatching) {
90+
if (kafkaTraceCorrelation != null || extraHeaders.length > 0 || enableSpanBatching || ignoreEndpoints) {
9091
response.tracing = {};
9192

9293
if (extraHeaders.length > 0) {
@@ -103,8 +104,10 @@ app.put('/com.instana.plugin.nodejs.discovery', (req, res) => {
103104
if (enableSpanBatching) {
104105
response.tracing['span-batching-enabled'] = true;
105106
}
107+
if (ignoreEndpoints) {
108+
response.tracing['ignore-endpoints'] = ignoreEndpoints;
109+
}
106110
}
107-
108111
res.send(response);
109112
});
110113

packages/collector/test/apps/agentStubControls.js

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class AgentStubControls {
4343
env.KAFKA_TRACE_CORRELATION = opts.kafkaConfig.traceCorrelation.toString();
4444
}
4545
}
46+
// This is not the INSTANA_IGNORE_ENDPOINTS env. We use this "IGNORE_ENDPOINTS" env for the fake agent to
47+
// serve the ignore endpoints config to our tracer.
48+
if (opts.ignoreEndpoints) {
49+
env.IGNORE_ENDPOINTS = JSON.stringify(opts.ignoreEndpoints);
50+
}
4651

4752
this.agentStub = spawn('node', [path.join(__dirname, 'agentStub.js')], {
4853
stdio: config.getAppStdio(),

packages/collector/test/tracing/database/ioredis/test.js

+138
Original file line numberDiff line numberDiff line change
@@ -1281,4 +1281,142 @@ function checkConnection(span, setupType) {
12811281
});
12821282
}
12831283
});
1284+
mochaSuiteFn('ignore-endpoints:', function () {
1285+
describe('when ignore-endpoints is enabled via agent configuration', () => {
1286+
const { AgentStubControls } = require('../../../apps/agentStubControls');
1287+
const customAgentControls = new AgentStubControls();
1288+
let controls;
1289+
1290+
before(async () => {
1291+
await customAgentControls.startAgent({
1292+
ignoreEndpoints: { redis: 'get|set' }
1293+
});
1294+
1295+
controls = new ProcessControls({
1296+
agentControls: customAgentControls,
1297+
dirname: __dirname
1298+
});
1299+
await controls.startAndWaitForAgentConnection();
1300+
});
1301+
1302+
beforeEach(async () => {
1303+
await customAgentControls.clearReceivedTraceData();
1304+
});
1305+
1306+
after(async () => {
1307+
await customAgentControls.stopAgent();
1308+
await controls.stop();
1309+
});
1310+
1311+
it('should ignore redis spans for ignored endpoints (get, set)', async () => {
1312+
await controls
1313+
.sendRequest({
1314+
method: 'POST',
1315+
path: '/values',
1316+
qs: {
1317+
key: 'discount',
1318+
value: 50
1319+
}
1320+
})
1321+
.then(async () => {
1322+
return retry(async () => {
1323+
const spans = await customAgentControls.getSpans();
1324+
// 1 x http entry span
1325+
expect(spans.length).to.equal(1);
1326+
spans.forEach(span => {
1327+
expect(span.n).not.to.equal('redis');
1328+
});
1329+
expectAtLeastOneMatching(spans, [
1330+
span => expect(span.n).to.equal('node.http.server'),
1331+
span => expect(span.data.http.method).to.equal('POST')
1332+
]);
1333+
});
1334+
});
1335+
});
1336+
});
1337+
describe('when ignore-endpoints is enabled via tracing configuration', async () => {
1338+
globalAgent.setUpCleanUpHooks();
1339+
const agentControls = globalAgent.instance;
1340+
let controls;
1341+
1342+
before(async () => {
1343+
controls = new ProcessControls({
1344+
useGlobalAgent: true,
1345+
dirname: __dirname,
1346+
env: {
1347+
INSTANA_IGNORE_ENDPOINTS: '{"redis": ["get"]}'
1348+
}
1349+
});
1350+
await controls.start();
1351+
});
1352+
1353+
beforeEach(async () => {
1354+
await agentControls.clearReceivedTraceData();
1355+
});
1356+
1357+
after(async () => {
1358+
await controls.stop();
1359+
});
1360+
1361+
afterEach(async () => {
1362+
await controls.clearIpcMessages();
1363+
});
1364+
it('should ignore spans for ignored endpoint (get)', async function () {
1365+
await controls
1366+
.sendRequest({
1367+
method: 'GET',
1368+
path: '/values',
1369+
qs: {
1370+
key: 'discount',
1371+
value: 50
1372+
}
1373+
})
1374+
1375+
.then(async () => {
1376+
return retry(async () => {
1377+
const spans = await agentControls.getSpans();
1378+
// 1 x http entry span
1379+
expect(spans.length).to.equal(1);
1380+
spans.forEach(span => {
1381+
expect(span.n).not.to.equal('redis');
1382+
});
1383+
1384+
expectAtLeastOneMatching(spans, [
1385+
span => expect(span.n).to.equal('node.http.server'),
1386+
span => expect(span.data.http.method).to.equal('GET')
1387+
]);
1388+
});
1389+
});
1390+
});
1391+
it('should not ignore spans for endpoints that are not in the ignore list', async () => {
1392+
await controls
1393+
.sendRequest({
1394+
method: 'POST',
1395+
path: '/values',
1396+
qs: {
1397+
key: 'discount',
1398+
value: 50
1399+
}
1400+
})
1401+
.then(async () => {
1402+
return retry(async () => {
1403+
const spans = await agentControls.getSpans();
1404+
expect(spans.length).to.equal(2);
1405+
1406+
const entrySpan = expectAtLeastOneMatching(spans, [
1407+
span => expect(span.n).to.equal('node.http.server'),
1408+
span => expect(span.data.http.method).to.equal('POST')
1409+
]);
1410+
1411+
expectExactlyOneMatching(spans, [
1412+
span => expect(span.t).to.equal(entrySpan.t),
1413+
span => expect(span.p).to.equal(entrySpan.s),
1414+
span => expect(span.n).to.equal('redis'),
1415+
span => expect(span.data.redis.command).to.equal('set')
1416+
]);
1417+
});
1418+
});
1419+
});
1420+
});
1421+
});
12841422
});

0 commit comments

Comments
 (0)