Skip to content

Commit 6e8989d

Browse files
feat(instr-knex): implement requireParentSpan config flag (#2288)
Co-authored-by: Amir Blum <[email protected]>
1 parent 6dfe93c commit 6e8989d

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

plugins/node/opentelemetry-instrumentation-knex/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ registerInstrumentations({
4747
| Options | Type | Example | Description |
4848
| ------- | ---- | ------- | ----------- |
4949
| `maxQueryLength` | `number` | `100` | Truncate `db.statement` attribute to a maximum length. If the statement is truncated `'..'` is added to it's end. Default `1022`. `-1` leaves `db.statement` untouched. |
50+
| `requireParentSpan` | `boolean` | `false` | Don't create spans unless they are part of an existing trace. Default is `false`. |
5051

5152
## Semantic Conventions
5253

plugins/node/opentelemetry-instrumentation-knex/src/instrumentation.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { KnexInstrumentationConfig } from './types';
4040
const contextSymbol = Symbol('opentelemetry.instrumentation-knex.context');
4141
const DEFAULT_CONFIG: KnexInstrumentationConfig = {
4242
maxQueryLength: 1022,
43+
requireParentSpan: false,
4344
};
4445

4546
export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentationConfig> {
@@ -120,7 +121,7 @@ export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentation
120121

121122
private createQueryWrapper(moduleVersion?: string) {
122123
const instrumentation = this;
123-
return function wrapQuery(original: () => any) {
124+
return function wrapQuery(original: (...args: any[]) => any) {
124125
return function wrapped_logging_method(this: any, query: any) {
125126
const config = this.client.config;
126127

@@ -152,14 +153,22 @@ export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentation
152153
);
153154
}
154155

155-
const parent = this.builder[contextSymbol];
156+
const parentContext =
157+
this.builder[contextSymbol] || api.context.active();
158+
const parentSpan = api.trace.getSpan(parentContext);
159+
const hasActiveParent =
160+
parentSpan && api.trace.isSpanContextValid(parentSpan.spanContext());
161+
if (instrumentation._config.requireParentSpan && !hasActiveParent) {
162+
return original.bind(this)(...arguments);
163+
}
164+
156165
const span = instrumentation.tracer.startSpan(
157166
utils.getName(name, operation, table),
158167
{
159168
kind: api.SpanKind.CLIENT,
160169
attributes,
161170
},
162-
parent
171+
parentContext
163172
);
164173
const spanContext = api.trace.setSpan(api.context.active(), span);
165174

plugins/node/opentelemetry-instrumentation-knex/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ import { InstrumentationConfig } from '@opentelemetry/instrumentation';
1818
export interface KnexInstrumentationConfig extends InstrumentationConfig {
1919
/** max query length in db.statement attribute ".." is added to the end when query is truncated */
2020
maxQueryLength?: number;
21+
/** only create spans if part of an existing trace */
22+
requireParentSpan?: boolean;
2123
}

plugins/node/opentelemetry-instrumentation-knex/test/index.test.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { SpanKind, context, trace } from '@opentelemetry/api';
17+
import {
18+
INVALID_SPAN_CONTEXT,
19+
SpanKind,
20+
context,
21+
trace,
22+
} from '@opentelemetry/api';
1823
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
1924
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
2025
import {
@@ -452,6 +457,73 @@ describe('Knex instrumentation', () => {
452457
);
453458
});
454459
});
460+
461+
describe('Setting requireParentSpan=true', () => {
462+
beforeEach(() => {
463+
plugin.disable();
464+
plugin.setConfig({ requireParentSpan: true });
465+
plugin.enable();
466+
});
467+
468+
it('should not create new spans when there is no parent', async () => {
469+
await client.schema.createTable('testTable1', (table: any) => {
470+
table.string('title');
471+
});
472+
assert.deepEqual(await client('testTable1').select('*'), []);
473+
assert.deepEqual(await client.raw('select 1 as result'), [{ result: 1 }]);
474+
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
475+
});
476+
477+
it('should not create new spans when there is an INVALID_SPAN_CONTEXT parent', async () => {
478+
const parentSpan = trace.wrapSpanContext(INVALID_SPAN_CONTEXT);
479+
await context.with(
480+
trace.setSpan(context.active(), parentSpan),
481+
async () => {
482+
await client.schema.createTable('testTable1', (table: any) => {
483+
table.string('title');
484+
});
485+
assert.deepEqual(await client('testTable1').select('*'), []);
486+
assert.deepEqual(await client.raw('select 1 as result'), [
487+
{ result: 1 },
488+
]);
489+
}
490+
);
491+
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
492+
});
493+
494+
it('should create new spans when there is a parent', async () => {
495+
await tracer.startActiveSpan('parentSpan', async parentSpan => {
496+
await client.schema.createTable('testTable1', (table: any) => {
497+
table.string('title');
498+
});
499+
assert.deepEqual(await client('testTable1').select('*'), []);
500+
assert.deepEqual(await client.raw('select 1 as result'), [
501+
{ result: 1 },
502+
]);
503+
parentSpan.end();
504+
});
505+
assert.strictEqual(memoryExporter.getFinishedSpans().length, 4);
506+
const instrumentationSpans = memoryExporter.getFinishedSpans();
507+
const last = instrumentationSpans.pop() as any;
508+
assertSpans(instrumentationSpans, [
509+
{
510+
statement: 'create table `testTable1` (`title` varchar(255))',
511+
parentSpan: last,
512+
},
513+
{
514+
op: 'select',
515+
table: 'testTable1',
516+
statement: 'select * from `testTable1`',
517+
parentSpan: last,
518+
},
519+
{
520+
op: 'raw',
521+
statement: 'select 1 as result',
522+
parentSpan: last,
523+
},
524+
]);
525+
});
526+
});
455527
});
456528

457529
const assertSpans = (actualSpans: any[], expectedSpans: any[]) => {

0 commit comments

Comments
 (0)