Skip to content

Commit

Permalink
feat(koa): Adds support to ignore a span by its layer name
Browse files Browse the repository at this point in the history
  • Loading branch information
MrFabio committed Nov 8, 2024
1 parent 6e1cd53 commit accecb4
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 46 deletions.
2 changes: 1 addition & 1 deletion plugins/node/opentelemetry-instrumentation-koa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Note that generator-based middleware are deprecated and won't be instrumented.
| Options | Type | Example | Description |
| ------------------ | ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
| `ignoreLayersType` | `KoaLayerType[]` | `['middleware']` | Ignore layers of specified type. |
| `ignoreLayersName` | `string[]` | `['logger']` | Ignore layers with specified names. |
| `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`. |

`ignoreLayersType` accepts an array of `KoaLayerType` which can take the following string values:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ import type * as koa from 'koa';
import { KoaLayerType, KoaInstrumentationConfig } from './types';
/** @knipignore */
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
import {
getMiddlewareMetadata,
isLayerIgnored,
isLayerNameIgnored,
} from './utils';
import { getMiddlewareMetadata, isLayerIgnored } from './utils';
import { getRPCMetadata, RPCType } from '@opentelemetry/core';
import {
kLayerPatched,
Expand Down Expand Up @@ -167,7 +163,7 @@ export class KoaInstrumentation extends InstrumentationBase<KoaInstrumentationCo
layerPath
);

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

Expand Down
4 changes: 3 additions & 1 deletion plugins/node/opentelemetry-instrumentation-koa/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ export interface KoaInstrumentationConfig<
/** Ignore specific layers based on their type */
ignoreLayersType?: KoaLayerType[];
/** Ignore specific layers based on their name */
ignoreLayersName?: string[];
ignoreLayers?: IgnoreMatcher[];
/** Function for adding custom attributes to each middleware layer span */
requestHook?: KoaRequestCustomAttributeFunction<
KoaContextType,
KoaMiddlewareType
>;
}

export type IgnoreMatcher = string | RegExp | ((url: string) => boolean);
56 changes: 38 additions & 18 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 All @@ -27,7 +27,6 @@ export const getMiddlewareMetadata = (
): {
attributes: Attributes;
name: string;
layerName: string;
} => {
if (isRouter) {
return {
Expand All @@ -37,7 +36,6 @@ export const getMiddlewareMetadata = (
[SEMATTRS_HTTP_ROUTE]: layerPath?.toString(),
},
name: context._matchedRouteName || `router - ${layerPath}`,
layerName: context._matchedRouteName || layerPath?.toString() || '',
};
} else {
return {
Expand All @@ -46,7 +44,6 @@ export const getMiddlewareMetadata = (
[AttributeNames.KOA_TYPE]: KoaLayerType.MIDDLEWARE,
},
name: `middleware - ${layer.name}`,
layerName: layer.name,
};
}
};
Expand All @@ -59,26 +56,49 @@ export const getMiddlewareMetadata = (
*/
export const isLayerIgnored = (
type: KoaLayerType,
config?: KoaInstrumentationConfig
config?: KoaInstrumentationConfig,
name?: string
): boolean => {
return !!(
if (
Array.isArray(config?.ignoreLayersType) &&
config?.ignoreLayersType?.includes(type)
);
) {
return true;
}
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 request name is ignored by configuration
* @param [list] List of ignore name patterns
* @param [onException] callback for doing something when an exception has
* occurred
* 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 isLayerNameIgnored = (
layerName: string,
config?: KoaInstrumentationConfig
export const satisfiesPattern = (
constant: string,
pattern: IgnoreMatcher
): boolean => {
return !!(
Array.isArray(config?.ignoreLayersName) &&
config?.ignoreLayersName?.includes(layerName)
);
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');
}
};
163 changes: 143 additions & 20 deletions plugins/node/opentelemetry-instrumentation-koa/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

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()', () => {
Expand Down Expand Up @@ -60,45 +64,164 @@ describe('Utils', () => {
});
describe('isLayerNameIgnored()', () => {
it('should not fail with invalid config', () => {
assert.strictEqual(utils.isLayerNameIgnored(''), false);
assert.strictEqual(
utils.isLayerNameIgnored('', {} as KoaInstrumentationConfig),
utils.isLayerIgnored(KoaLayerType.MIDDLEWARE, {}),
false
);
assert.strictEqual(
utils.isLayerNameIgnored('', {
ignoreLayersName: {},
} as KoaInstrumentationConfig),
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{} as KoaInstrumentationConfig
),
false
);
assert.strictEqual(
utils.isLayerNameIgnored('logger', {
ignoreLayersName: {},
} as KoaInstrumentationConfig),
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: {},
} as KoaInstrumentationConfig,
''
),
false
);
assert.strictEqual(utils.isLayerNameIgnored('logger'), false);
assert.strictEqual(
utils.isLayerNameIgnored('', {
ignoreLayersName: [],
} as KoaInstrumentationConfig),
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: {},
} as KoaInstrumentationConfig,
'logger'
),
false
);
assert.strictEqual(
utils.isLayerIgnored(KoaLayerType.MIDDLEWARE, {}, 'logger'),
false
);
assert.strictEqual(
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: [],
} as KoaInstrumentationConfig,
''
),
false
);
});

it('should ignore based on type', () => {
it('should ignore based on name', () => {
assert.strictEqual(
utils.isLayerNameIgnored('logger', {
ignoreLayersName: ['logger'],
}),
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: ['logger'],
},
'logger'
),
true
);
assert.strictEqual(
utils.isLayerNameIgnored('', {
ignoreLayersName: ['logger'],
}),
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: ['logger'],
},
'logger'
),
true
);
assert.strictEqual(
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: ['logger'],
},
''
),
false
);
assert.strictEqual(
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: [/logger/],
},
'logger - test'
),
true
);
assert.strictEqual(
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: [/logger/],
},
'router - test'
),
false
);
assert.strictEqual(
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: [(name: string) => name === 'test'],
},
'test'
),
true
);
assert.strictEqual(
utils.isLayerIgnored(
KoaLayerType.MIDDLEWARE,
{
ignoreLayers: [(name: string) => name === 'router'],
},
'test'
),
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);
});
});
});

0 comments on commit accecb4

Please sign in to comment.