Skip to content

Commit e197f74

Browse files
committed
feat(koa): Adds support to ignore a span by its layer name
1 parent 407f615 commit e197f74

File tree

6 files changed

+190
-14
lines changed

6 files changed

+190
-14
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,15 @@ Note that generator-based middleware are deprecated and won't be instrumented.
5454
| Options | Type | Example | Description |
5555
| ------------------ | ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
5656
| `ignoreLayersType` | `KoaLayerType[]` | `['middleware']` | Ignore layers of specified type. |
57+
| `ignoreLayers` | `IgnoreMatcher[]` | `['logger', /router/]` | Ignore layers with specified names. |
5758
| `requestHook` | `KoaRequestCustomAttributeFunction` | `(span, info) => {}` | Function for adding custom attributes to Koa middleware layers. Receives params: `Span, KoaRequestInfo`. |
5859

60+
`ignoreLayers` accepts an array of elements of types:
61+
62+
- `string` for full match of the path,
63+
- `RegExp` for partial match of the path,
64+
- `function` in the form of `(path) => boolean` for custom logic.
65+
5966
`ignoreLayersType` accepts an array of `KoaLayerType` which can take the following string values:
6067

6168
- `router`,

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import type * as koa from 'koa';
2626
import { KoaLayerType, KoaInstrumentationConfig } from './types';
2727
/** @knipignore */
2828
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
29-
import { getMiddlewareMetadata, isLayerIgnored } from './utils';
29+
import {
30+
getMiddlewareMetadata,
31+
isLayerNameIgnored,
32+
isLayerTypeIgnored,
33+
} from './utils';
3034
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
3135
import {
3236
kLayerPatched,
@@ -136,7 +140,7 @@ export class KoaInstrumentation extends InstrumentationBase<KoaInstrumentationCo
136140
// Skip patching layer if its ignored in the config
137141
if (
138142
middlewareLayer[kLayerPatched] === true ||
139-
isLayerIgnored(layerType, this.getConfig())
143+
isLayerTypeIgnored(layerType, this.getConfig())
140144
)
141145
return middlewareLayer;
142146

@@ -162,6 +166,11 @@ export class KoaInstrumentation extends InstrumentationBase<KoaInstrumentationCo
162166
isRouter,
163167
layerPath
164168
);
169+
170+
if (isLayerNameIgnored(metadata.name, this.getConfig())) {
171+
return middlewareLayer(context, next);
172+
}
173+
165174
const span = this.tracer.startSpan(metadata.name, {
166175
attributes: metadata.attributes,
167176
});

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,13 @@ export interface KoaInstrumentationConfig<
6969
> extends InstrumentationConfig {
7070
/** Ignore specific layers based on their type */
7171
ignoreLayersType?: KoaLayerType[];
72+
/** Ignore specific layers based on their name */
73+
ignoreLayers?: IgnoreMatcher[];
7274
/** Function for adding custom attributes to each middleware layer span */
7375
requestHook?: KoaRequestCustomAttributeFunction<
7476
KoaContextType,
7577
KoaMiddlewareType
7678
>;
7779
}
80+
81+
export type IgnoreMatcher = string | RegExp | ((name: string) => boolean);

plugins/node/opentelemetry-instrumentation-koa/src/utils.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { KoaLayerType, KoaInstrumentationConfig } from './types';
16+
import { KoaLayerType, KoaInstrumentationConfig, IgnoreMatcher } from './types';
1717
import { KoaContext, KoaMiddleware } from './internal-types';
1818
import { AttributeNames } from './enums/AttributeNames';
1919
import { Attributes } from '@opentelemetry/api';
@@ -49,12 +49,12 @@ export const getMiddlewareMetadata = (
4949
};
5050

5151
/**
52-
* Check whether the given request is ignored by configuration
52+
* Check whether the given request layer type is ignored by configuration
5353
* @param [list] List of ignore patterns
5454
* @param [onException] callback for doing something when an exception has
5555
* occurred
5656
*/
57-
export const isLayerIgnored = (
57+
export const isLayerTypeIgnored = (
5858
type: KoaLayerType,
5959
config?: KoaInstrumentationConfig
6060
): boolean => {
@@ -63,3 +63,51 @@ export const isLayerIgnored = (
6363
config?.ignoreLayersType?.includes(type)
6464
);
6565
};
66+
67+
/**
68+
* Check whether the given request layer name is ignored by configuration
69+
* @param [list] List of ignore patterns
70+
* @param [onException] callback for doing something when an exception has
71+
* occurred
72+
*/
73+
export const isLayerNameIgnored = (
74+
name: string,
75+
config?: KoaInstrumentationConfig
76+
): boolean => {
77+
if (Array.isArray(config?.ignoreLayers) === false || !config?.ignoreLayers)
78+
return false;
79+
try {
80+
for (const pattern of config.ignoreLayers) {
81+
if (satisfiesPattern(name, pattern)) {
82+
return true;
83+
}
84+
}
85+
} catch (e) {
86+
/* catch block*/
87+
}
88+
89+
return false;
90+
};
91+
92+
/**
93+
* Check whether the given obj match pattern
94+
* @param constant e.g URL of request
95+
* @param obj obj to inspect
96+
* @param pattern Match pattern
97+
*/
98+
export const satisfiesPattern = (
99+
constant: string,
100+
pattern: IgnoreMatcher
101+
): boolean => {
102+
console.warn(`constant: ${constant}`);
103+
console.warn(`pattern: ${pattern}`);
104+
if (typeof pattern === 'string') {
105+
return pattern === constant;
106+
} else if (pattern instanceof RegExp) {
107+
return pattern.test(constant);
108+
} else if (typeof pattern === 'function') {
109+
return pattern(constant);
110+
} else {
111+
throw new TypeError('Pattern is in unsupported datatype');
112+
}
113+
};

plugins/node/opentelemetry-instrumentation-koa/test/koa.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,7 +751,7 @@ describe('Koa Instrumentation', function () {
751751
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
752752
NODE_NO_WARNINGS: '1',
753753
},
754-
checkResult: (err, stdout, stderr) => {
754+
checkResult: (err: any, stdout: any, stderr: any) => {
755755
assert.ifError(err);
756756
},
757757
checkCollector: (collector: testUtils.TestCollector) => {

plugins/node/opentelemetry-instrumentation-koa/test/utils.test.ts

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,34 @@
1616

1717
import * as utils from '../src/utils';
1818
import * as assert from 'assert';
19-
import { KoaInstrumentationConfig, KoaLayerType } from '../src/types';
19+
import {
20+
IgnoreMatcher,
21+
KoaInstrumentationConfig,
22+
KoaLayerType,
23+
} from '../src/types';
2024

2125
describe('Utils', () => {
22-
describe('isLayerIgnored()', () => {
26+
describe('isLayerTypeIgnored()', () => {
2327
it('should not fail with invalid config', () => {
24-
assert.strictEqual(utils.isLayerIgnored(KoaLayerType.MIDDLEWARE), false);
2528
assert.strictEqual(
26-
utils.isLayerIgnored(
29+
utils.isLayerTypeIgnored(KoaLayerType.MIDDLEWARE),
30+
false
31+
);
32+
assert.strictEqual(
33+
utils.isLayerTypeIgnored(
2734
KoaLayerType.MIDDLEWARE,
2835
{} as KoaInstrumentationConfig
2936
),
3037
false
3138
);
3239
assert.strictEqual(
33-
utils.isLayerIgnored(KoaLayerType.MIDDLEWARE, {
40+
utils.isLayerTypeIgnored(KoaLayerType.MIDDLEWARE, {
3441
ignoreLayersType: {},
3542
} as KoaInstrumentationConfig),
3643
false
3744
);
3845
assert.strictEqual(
39-
utils.isLayerIgnored(KoaLayerType.ROUTER, {
46+
utils.isLayerTypeIgnored(KoaLayerType.ROUTER, {
4047
ignoreLayersType: {},
4148
} as KoaInstrumentationConfig),
4249
false
@@ -45,17 +52,118 @@ describe('Utils', () => {
4552

4653
it('should ignore based on type', () => {
4754
assert.strictEqual(
48-
utils.isLayerIgnored(KoaLayerType.MIDDLEWARE, {
55+
utils.isLayerTypeIgnored(KoaLayerType.MIDDLEWARE, {
4956
ignoreLayersType: [KoaLayerType.MIDDLEWARE],
5057
}),
5158
true
5259
);
5360
assert.strictEqual(
54-
utils.isLayerIgnored(KoaLayerType.ROUTER, {
61+
utils.isLayerTypeIgnored(KoaLayerType.ROUTER, {
5562
ignoreLayersType: [KoaLayerType.MIDDLEWARE],
5663
}),
5764
false
5865
);
5966
});
6067
});
68+
describe('isLayerNameIgnored()', () => {
69+
it('should not fail with invalid config', () => {
70+
assert.strictEqual(utils.isLayerNameIgnored('name', {}), false);
71+
assert.strictEqual(
72+
utils.isLayerNameIgnored('name', {} as KoaInstrumentationConfig),
73+
false
74+
);
75+
assert.strictEqual(
76+
utils.isLayerNameIgnored('name', {
77+
ignoreLayers: {},
78+
} as KoaInstrumentationConfig),
79+
false
80+
);
81+
assert.strictEqual(utils.isLayerNameIgnored('name'), false);
82+
});
83+
84+
it('should ignore based on name', () => {
85+
assert.strictEqual(
86+
utils.isLayerNameIgnored('logger', {
87+
ignoreLayers: ['logger'],
88+
}),
89+
true
90+
);
91+
assert.strictEqual(
92+
utils.isLayerNameIgnored('logger', {
93+
ignoreLayers: ['logger'],
94+
}),
95+
true
96+
);
97+
assert.strictEqual(
98+
utils.isLayerNameIgnored('', {
99+
ignoreLayers: ['logger'],
100+
}),
101+
false
102+
);
103+
assert.strictEqual(
104+
utils.isLayerNameIgnored('logger - test', {
105+
ignoreLayers: [/logger/],
106+
}),
107+
true
108+
);
109+
assert.strictEqual(
110+
utils.isLayerNameIgnored('router - test', {
111+
ignoreLayers: [/logger/],
112+
}),
113+
false
114+
);
115+
assert.strictEqual(
116+
utils.isLayerNameIgnored('test', {
117+
ignoreLayers: [(name: string) => name === 'test'],
118+
}),
119+
true
120+
);
121+
assert.strictEqual(
122+
utils.isLayerNameIgnored('test', {
123+
ignoreLayers: [(name: string) => name === 'router'],
124+
}),
125+
false
126+
);
127+
});
128+
});
129+
});
130+
131+
describe('Utility', () => {
132+
describe('satisfiesPattern()', () => {
133+
it('string pattern', () => {
134+
const answer1 = utils.satisfiesPattern('localhost', 'localhost');
135+
assert.strictEqual(answer1, true);
136+
const answer2 = utils.satisfiesPattern('hostname', 'localhost');
137+
assert.strictEqual(answer2, false);
138+
});
139+
140+
it('regex pattern', () => {
141+
const answer1 = utils.satisfiesPattern('LocalHost', /localhost/i);
142+
assert.strictEqual(answer1, true);
143+
const answer2 = utils.satisfiesPattern('Montreal.ca', /montreal.ca/);
144+
assert.strictEqual(answer2, false);
145+
});
146+
147+
it('should throw if type is unknown', () => {
148+
try {
149+
utils.satisfiesPattern('google.com', true as unknown as IgnoreMatcher);
150+
assert.fail();
151+
} catch (error) {
152+
assert.strictEqual(error instanceof TypeError, true);
153+
}
154+
});
155+
156+
it('function pattern', () => {
157+
const answer1 = utils.satisfiesPattern(
158+
'montreal.ca',
159+
(url: string) => url === 'montreal.ca'
160+
);
161+
assert.strictEqual(answer1, true);
162+
const answer2 = utils.satisfiesPattern(
163+
'montreal.ca',
164+
(url: string) => url !== 'montreal.ca'
165+
);
166+
assert.strictEqual(answer2, false);
167+
});
168+
});
61169
});

0 commit comments

Comments
 (0)