Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(koa): Adds support to ignore a span by its layer name #2028

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions plugins/node/opentelemetry-instrumentation-koa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,15 @@ Note that generator-based middleware are deprecated and won't be instrumented.
| Options | Type | Example | Description |
| ------------------ | ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
| `ignoreLayersType` | `KoaLayerType[]` | `['middleware']` | Ignore layers of specified type. |
| `ignoreLayers` | `IgnoreMatcher[]` | `['logger', /router/]` | Ignore layers with specified names. |
| `requestHook` | `KoaRequestCustomAttributeFunction` | `(span, info) => {}` | Function for adding custom attributes to Koa middleware layers. Receives params: `Span, KoaRequestInfo`. |

`ignoreLayers` accepts an array of elements of types:

- `string` for full match of the path,
- `RegExp` for partial match of the path,
- `function` in the form of `(path) => boolean` for custom logic.

`ignoreLayersType` accepts an array of `KoaLayerType` which can take the following string values:
MrFabio marked this conversation as resolved.
Show resolved Hide resolved

- `router`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
import { KoaLayerType, KoaInstrumentationConfig } from './types';
/** @knipignore */
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
import { getMiddlewareMetadata, isLayerIgnored } from './utils';
import {
getMiddlewareMetadata,
isLayerNameIgnored,
isLayerTypeIgnored,
} from './utils';
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
import {
kLayerPatched,
Expand Down Expand Up @@ -136,7 +140,7 @@
// Skip patching layer if its ignored in the config
if (
middlewareLayer[kLayerPatched] === true ||
isLayerIgnored(layerType, this.getConfig())
isLayerTypeIgnored(layerType, this.getConfig())
)
return middlewareLayer;

Expand All @@ -162,6 +166,11 @@
isRouter,
layerPath
);

if (isLayerNameIgnored(metadata.name, this.getConfig())) {
return middlewareLayer(context, next);

Check warning on line 171 in plugins/node/opentelemetry-instrumentation-koa/src/instrumentation.ts

View check run for this annotation

Codecov / codecov/patch

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

Added line #L171 was not covered by tests
}

const span = this.tracer.startSpan(metadata.name, {
attributes: metadata.attributes,
});
Expand Down
4 changes: 4 additions & 0 deletions plugins/node/opentelemetry-instrumentation-koa/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,13 @@ export interface KoaInstrumentationConfig<
> extends InstrumentationConfig {
/** Ignore specific layers based on their type */
ignoreLayersType?: KoaLayerType[];
/** Ignore specific layers based on their name */
ignoreLayers?: IgnoreMatcher[];
/** Function for adding custom attributes to each middleware layer span */
requestHook?: KoaRequestCustomAttributeFunction<
KoaContextType,
KoaMiddlewareType
>;
}

export type IgnoreMatcher = string | RegExp | ((name: string) => boolean);
54 changes: 51 additions & 3 deletions plugins/node/opentelemetry-instrumentation-koa/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { KoaLayerType, KoaInstrumentationConfig } from './types';
import { KoaLayerType, KoaInstrumentationConfig, IgnoreMatcher } from './types';
import { KoaContext, KoaMiddleware } from './internal-types';
import { AttributeNames } from './enums/AttributeNames';
import { Attributes } from '@opentelemetry/api';
Expand Down Expand Up @@ -49,12 +49,12 @@ export const getMiddlewareMetadata = (
};

/**
* Check whether the given request is ignored by configuration
* Check whether the given request layer type is ignored by configuration
* @param [list] List of ignore patterns
* @param [onException] callback for doing something when an exception has
* occurred
*/
export const isLayerIgnored = (
export const isLayerTypeIgnored = (
type: KoaLayerType,
config?: KoaInstrumentationConfig
): boolean => {
Expand All @@ -63,3 +63,51 @@ export const isLayerIgnored = (
config?.ignoreLayersType?.includes(type)
);
};

/**
* Check whether the given request layer name is ignored by configuration
* @param [list] List of ignore patterns
* @param [onException] callback for doing something when an exception has
* occurred
*/
export const isLayerNameIgnored = (
name: string,
config?: KoaInstrumentationConfig
): boolean => {
if (Array.isArray(config?.ignoreLayers) === false || !config?.ignoreLayers)
return false;
try {
for (const pattern of config.ignoreLayers) {
if (satisfiesPattern(name, pattern)) {
return true;
}
}
} catch (e) {
/* catch block*/
}

return false;
};

/**
* Check whether the given obj match pattern
* @param constant e.g URL of request
* @param obj obj to inspect
* @param pattern Match pattern
*/
export const satisfiesPattern = (
constant: string,
pattern: IgnoreMatcher
): boolean => {
console.warn(`constant: ${constant}`);
console.warn(`pattern: ${pattern}`);
if (typeof pattern === 'string') {
return pattern === constant;
} else if (pattern instanceof RegExp) {
return pattern.test(constant);
} else if (typeof pattern === 'function') {
return pattern(constant);
} else {
throw new TypeError('Pattern is in unsupported datatype');
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ describe('Koa Instrumentation', () => {
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
NODE_NO_WARNINGS: '1',
},
checkResult: (err, stdout, stderr) => {
checkResult: (err: any, stdout: any, stderr: any) => {
assert.ifError(err);
},
checkCollector: (collector: testUtils.TestCollector) => {
Expand Down
124 changes: 116 additions & 8 deletions plugins/node/opentelemetry-instrumentation-koa/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,34 @@

import * as utils from '../src/utils';
import * as assert from 'assert';
import { KoaInstrumentationConfig, KoaLayerType } from '../src/types';
import {
IgnoreMatcher,
KoaInstrumentationConfig,
KoaLayerType,
} from '../src/types';

describe('Utils', () => {
describe('isLayerIgnored()', () => {
describe('isLayerTypeIgnored()', () => {
it('should not fail with invalid config', () => {
assert.strictEqual(utils.isLayerIgnored(KoaLayerType.MIDDLEWARE), false);
assert.strictEqual(
utils.isLayerIgnored(
utils.isLayerTypeIgnored(KoaLayerType.MIDDLEWARE),
false
);
assert.strictEqual(
utils.isLayerTypeIgnored(
KoaLayerType.MIDDLEWARE,
{} as KoaInstrumentationConfig
),
false
);
assert.strictEqual(
utils.isLayerIgnored(KoaLayerType.MIDDLEWARE, {
utils.isLayerTypeIgnored(KoaLayerType.MIDDLEWARE, {
ignoreLayersType: {},
} as KoaInstrumentationConfig),
false
);
assert.strictEqual(
utils.isLayerIgnored(KoaLayerType.ROUTER, {
utils.isLayerTypeIgnored(KoaLayerType.ROUTER, {
ignoreLayersType: {},
} as KoaInstrumentationConfig),
false
Expand All @@ -45,17 +52,118 @@ describe('Utils', () => {

it('should ignore based on type', () => {
assert.strictEqual(
utils.isLayerIgnored(KoaLayerType.MIDDLEWARE, {
utils.isLayerTypeIgnored(KoaLayerType.MIDDLEWARE, {
ignoreLayersType: [KoaLayerType.MIDDLEWARE],
}),
true
);
assert.strictEqual(
utils.isLayerIgnored(KoaLayerType.ROUTER, {
utils.isLayerTypeIgnored(KoaLayerType.ROUTER, {
ignoreLayersType: [KoaLayerType.MIDDLEWARE],
}),
false
);
});
});
describe('isLayerNameIgnored()', () => {
it('should not fail with invalid config', () => {
assert.strictEqual(utils.isLayerNameIgnored('name', {}), false);
assert.strictEqual(
utils.isLayerNameIgnored('name', {} as KoaInstrumentationConfig),
false
);
assert.strictEqual(
utils.isLayerNameIgnored('name', {
ignoreLayers: {},
} as KoaInstrumentationConfig),
false
);
assert.strictEqual(utils.isLayerNameIgnored('name'), false);
});

it('should ignore based on name', () => {
assert.strictEqual(
utils.isLayerNameIgnored('logger', {
ignoreLayers: ['logger'],
}),
true
);
assert.strictEqual(
utils.isLayerNameIgnored('logger', {
ignoreLayers: ['logger'],
}),
true
);
assert.strictEqual(
utils.isLayerNameIgnored('', {
ignoreLayers: ['logger'],
}),
false
);
assert.strictEqual(
utils.isLayerNameIgnored('logger - test', {
ignoreLayers: [/logger/],
}),
true
);
assert.strictEqual(
utils.isLayerNameIgnored('router - test', {
ignoreLayers: [/logger/],
}),
false
);
assert.strictEqual(
utils.isLayerNameIgnored('test', {
ignoreLayers: [(name: string) => name === 'test'],
}),
true
);
assert.strictEqual(
utils.isLayerNameIgnored('test', {
ignoreLayers: [(name: string) => name === 'router'],
}),
false
);
});
});
});

describe('Utility', () => {
describe('satisfiesPattern()', () => {
it('string pattern', () => {
const answer1 = utils.satisfiesPattern('localhost', 'localhost');
assert.strictEqual(answer1, true);
const answer2 = utils.satisfiesPattern('hostname', 'localhost');
assert.strictEqual(answer2, false);
});

it('regex pattern', () => {
const answer1 = utils.satisfiesPattern('LocalHost', /localhost/i);
assert.strictEqual(answer1, true);
const answer2 = utils.satisfiesPattern('Montreal.ca', /montreal.ca/);
assert.strictEqual(answer2, false);
});

it('should throw if type is unknown', () => {
try {
utils.satisfiesPattern('google.com', true as unknown as IgnoreMatcher);
assert.fail();
} catch (error) {
assert.strictEqual(error instanceof TypeError, true);
}
});

it('function pattern', () => {
const answer1 = utils.satisfiesPattern(
'montreal.ca',
(url: string) => url === 'montreal.ca'
);
assert.strictEqual(answer1, true);
const answer2 = utils.satisfiesPattern(
'montreal.ca',
(url: string) => url !== 'montreal.ca'
);
assert.strictEqual(answer2, false);
});
});
});