diff --git a/plugins/node/opentelemetry-instrumentation-ioredis/README.md b/plugins/node/opentelemetry-instrumentation-ioredis/README.md index 6f92ad1e5e..14b9f226e4 100644 --- a/plugins/node/opentelemetry-instrumentation-ioredis/README.md +++ b/plugins/node/opentelemetry-instrumentation-ioredis/README.md @@ -57,6 +57,7 @@ IORedis instrumentation has few options available to choose from. You can set th | ------- | ---- | ----------- | | `dbStatementSerializer` | `DbStatementSerializer` | IORedis instrumentation will serialize db.statement using the specified function. | | `responseHook` | `RedisResponseCustomAttributeFunction` | Function for adding custom attributes on db response | +| `requireParentSpan` | `boolean` | Require parent to create ioredis span, default when unset is true | #### Custom db.statement Serializer The instrumentation serializes the whole command into a Span attribute called `db.statement`. The standard serialization format is `{cmdName} {cmdArgs.join(',')}`. diff --git a/plugins/node/opentelemetry-instrumentation-ioredis/src/ioredis.ts b/plugins/node/opentelemetry-instrumentation-ioredis/src/ioredis.ts index 2c2d88351a..62aaff2460 100644 --- a/plugins/node/opentelemetry-instrumentation-ioredis/src/ioredis.ts +++ b/plugins/node/opentelemetry-instrumentation-ioredis/src/ioredis.ts @@ -25,14 +25,22 @@ import { IORedisInstrumentationConfig } from './types'; import { traceConnection, traceSendCommand } from './utils'; import { VERSION } from './version'; +const DEFAULT_CONFIG: IORedisInstrumentationConfig = { + requireParentSpan: true, +}; + export class IORedisInstrumentation extends InstrumentationBase< typeof ioredisTypes > { static readonly DB_SYSTEM = 'redis'; readonly supportedVersions = ['>1 <5']; - constructor(protected _config: IORedisInstrumentationConfig = {}) { - super('@opentelemetry/instrumentation-ioredis', VERSION, _config); + constructor(_config: IORedisInstrumentationConfig = {}) { + super( + '@opentelemetry/instrumentation-ioredis', + VERSION, + Object.assign({}, DEFAULT_CONFIG, _config) + ); } init(): InstrumentationNodeModuleDefinition[] { diff --git a/plugins/node/opentelemetry-instrumentation-ioredis/src/types.ts b/plugins/node/opentelemetry-instrumentation-ioredis/src/types.ts index 4f60add7e7..4119c97415 100644 --- a/plugins/node/opentelemetry-instrumentation-ioredis/src/types.ts +++ b/plugins/node/opentelemetry-instrumentation-ioredis/src/types.ts @@ -68,4 +68,7 @@ export interface IORedisInstrumentationConfig extends InstrumentationConfig { /** Function for adding custom attributes on db response */ responseHook?: RedisResponseCustomAttributeFunction; + + /** Require parent to create ioredis span, default when unset is true */ + requireParentSpan?: boolean; } diff --git a/plugins/node/opentelemetry-instrumentation-ioredis/src/utils.ts b/plugins/node/opentelemetry-instrumentation-ioredis/src/utils.ts index 326f1dfc9b..c1db621a33 100644 --- a/plugins/node/opentelemetry-instrumentation-ioredis/src/utils.ts +++ b/plugins/node/opentelemetry-instrumentation-ioredis/src/utils.ts @@ -93,8 +93,9 @@ export const traceSendCommand = ( if (arguments.length < 1 || typeof cmd !== 'object') { return original.apply(this, arguments); } - // Do not trace if there is not parent span - if (getSpan(context.active()) === undefined) { + + const hasNoParentSpan = getSpan(context.active()) === undefined; + if (config?.requireParentSpan === true && hasNoParentSpan) { return original.apply(this, arguments); } diff --git a/plugins/node/opentelemetry-instrumentation-ioredis/test/ioredis.test.ts b/plugins/node/opentelemetry-instrumentation-ioredis/test/ioredis.test.ts index 9a4537b057..841de12337 100644 --- a/plugins/node/opentelemetry-instrumentation-ioredis/test/ioredis.test.ts +++ b/plugins/node/opentelemetry-instrumentation-ioredis/test/ioredis.test.ts @@ -627,6 +627,52 @@ describe('ioredis', () => { }); }); + describe('Instrumentation with requireParentSpan', () => { + it('should instrument with requireParentSpan equal false', async () => { + instrumentation.disable(); + const config: IORedisInstrumentationConfig = { + requireParentSpan: false, + }; + instrumentation = new IORedisInstrumentation(config); + instrumentation.setTracerProvider(provider); + require('ioredis'); + + await client.set(testKeyName, 'data'); + const result = await client.del(testKeyName); + assert.strictEqual(result, 1); + + const endedSpans = memoryExporter.getFinishedSpans(); + assert.strictEqual(endedSpans.length, 2); + + testUtils.assertSpan( + endedSpans[0], + SpanKind.CLIENT, + { + ...DEFAULT_ATTRIBUTES, + [DatabaseAttribute.DB_STATEMENT]: `set ${testKeyName} data`, + }, + [], + unsetStatus + ); + }); + + it('should not instrument with requireParentSpan equal true', async () => { + instrumentation.disable(); + const config: IORedisInstrumentationConfig = { + requireParentSpan: true, + }; + instrumentation = new IORedisInstrumentation(config); + instrumentation.setTracerProvider(provider); + require('ioredis'); + + await client.set(testKeyName, 'data'); + const result = await client.del(testKeyName); + assert.strictEqual(result, 1); + + assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); + }); + }); + describe('Instrumenting with a custom db.statement serializer', () => { const dbStatementSerializer: DbStatementSerializer = (cmdName, cmdArgs) => `FOOBAR_${cmdName}: ${cmdArgs[0]}`;