Skip to content

Commit

Permalink
feat: add config to ignore express layers
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarchaud committed Jan 26, 2020
1 parent 4970ac0 commit f92d939
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 10 deletions.
14 changes: 14 additions & 0 deletions packages/opentelemetry-plugin-express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ const registry = new NodeTracerRegistry();

See [examples/express](https://github.com/open-telemetry/opentelemetry-js/tree/master/examples/express) for a short example.

### Express Plugin Options

Express plugin has few options available to choose from. You can set the following:

| Options | Type | Description |
| ------- | ---- | ----------- |
| `ignoreLayers` | `IgnoreMatcher[]` | Express plugin will not trace all layers that match. |
| `ignoreLayersType`| `ExpressLayerType[]` | Express plugin will ignore the layers that match based on their type. |

For reference, here are the three different layer type:
- `router` is the name of `express.Router()`
- `middleware`
- `request_handler` is the name for anything thats not a router or a middleware.

## Useful links
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more about OpenTelemetry JavaScript: <https://github.com/open-telemetry/opentelemetry-js>
Expand Down
34 changes: 25 additions & 9 deletions packages/opentelemetry-plugin-express/src/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ import {
Parameters,
PathParams,
_MIDDLEWARES_STORE_PROPERTY,
ExpressPluginConfig,
ExpressLayerType,
} from './types';
import { getLayerMetadata, storeLayerPath, patchEnd } from './utils';
import {
getLayerMetadata,
storeLayerPath,
patchEnd,
isLayerIgnored,
} from './utils';
import { VERSION } from './version';

/**
Expand All @@ -41,6 +48,7 @@ export const kLayerPatched: unique symbol = Symbol('express-layer-patched');
export class ExpressPlugin extends BasePlugin<typeof express> {
readonly _COMPONENT = 'express';
readonly supportedVersions = ['^4.0.0'];
protected _config!: ExpressPluginConfig;

constructor(readonly moduleName: string) {
super('@opentelemetry/plugin-express', VERSION);
Expand Down Expand Up @@ -74,6 +82,15 @@ export class ExpressPlugin extends BasePlugin<typeof express> {
return this._moduleExports;
}

/** Unpatches all Express patched functions. */
unpatch(): void {
const routerProto = (this._moduleExports
.Router as unknown) as express.Router;
shimmer.unwrap(routerProto, 'use');
shimmer.unwrap(routerProto, 'route');
shimmer.unwrap(this._moduleExports.application, 'use');
}

/**
* Get the patch for Router.route function
* @param original
Expand Down Expand Up @@ -141,13 +158,6 @@ export class ExpressPlugin extends BasePlugin<typeof express> {
} as any;
}

/** Unpatches all Express patched functions. */
unpatch(): void {
shimmer.unwrap(this._moduleExports.Router.prototype, 'use');
shimmer.unwrap(this._moduleExports.Router.prototype, 'route');
shimmer.unwrap(this._moduleExports.application, 'use');
}

/** Patch each express layer to create span and propagate scope */
private _applyPatch(layer: ExpressLayer, layerPath?: string) {
const plugin = this;
Expand All @@ -170,7 +180,13 @@ export class ExpressPlugin extends BasePlugin<typeof express> {
[AttributeNames.HTTP_ROUTE]: route.length > 0 ? route : undefined,
};
const metadata = getLayerMetadata(layer, layerPath);

const type = metadata.attributes[
AttributeNames.EXPRESS_TYPE
] as ExpressLayerType;
// verify against the config if the layer should be ignored
if (isLayerIgnored(metadata.name, type, plugin._config)) {
return original.apply(this, arguments);
}
const span = plugin._tracer.startSpan(metadata.name, {
parent: plugin._tracer.getCurrentSpan(),
attributes: Object.assign(attributes, metadata.attributes),
Expand Down
18 changes: 18 additions & 0 deletions packages/opentelemetry-plugin-express/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import { kLayerPatched } from './express';
import { Request } from 'express';
import { PluginConfig, Attributes } from '@opentelemetry/types';

export const _MIDDLEWARES_STORE_PROPERTY = '__ot_middlewares';

Expand Down Expand Up @@ -45,6 +46,11 @@ export type ExpressLayer = {
regexp: RegExp;
};

export type LayerMetadata = {
attributes: Attributes;
name: string;
};

// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md#databases-client-calls
export enum AttributeNames {
COMPONENT = 'component',
Expand All @@ -58,3 +64,15 @@ export enum ExpressLayerType {
MIDDLEWARE = 'middleware',
REQUEST_HANDLER = 'request_handler',
}

export type IgnoreMatcher = string | RegExp | ((name: string) => boolean);

/**
* Options available for the Express Plugin (see [documentation](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-plugin-express#express-plugin-options))
*/
export interface ExpressPluginConfig extends PluginConfig {
/** Ingore specific based on their name */
ignoreLayers?: IgnoreMatcher[];
/** Ignore specific layers based on their type */
ignoreLayersType?: ExpressLayerType[];
}
55 changes: 55 additions & 0 deletions packages/opentelemetry-plugin-express/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
PatchedRequest,
_MIDDLEWARES_STORE_PROPERTY,
ExpressLayerType,
IgnoreMatcher,
ExpressPluginConfig,
} from './types';

/**
Expand Down Expand Up @@ -62,6 +64,7 @@ export const getLayerMetadata = (
} else if (layer.name === 'bound dispatch') {
return {
attributes: {
[AttributeNames.EXPRESS_NAME]: layerPath ?? 'request handler',
[AttributeNames.EXPRESS_TYPE]: ExpressLayerType.REQUEST_HANDLER,
},
name: 'request handler',
Expand Down Expand Up @@ -99,3 +102,55 @@ export const patchEnd = (span: Span, resultHandler: Function): Function => {
return resultHandler.apply(this, args);
};
};

/**
* Check whether the given obj match pattern
* @param constant e.g URL of request
* @param obj obj to inspect
* @param pattern Match pattern
*/
const satisfiesPattern = <T>(
constant: string,
pattern: IgnoreMatcher
): boolean => {
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');
}
};

/**
* Check whether the given request is ignored by configuration
* It will not re-throw exceptions from `list` provided by the client
* @param constant e.g URL of request
* @param [list] List of ignore patterns
* @param [onException] callback for doing something when an exception has
* occurred
*/
export const isLayerIgnored = (
name: string,
type: ExpressLayerType,
config?: ExpressPluginConfig
): boolean => {
if (
Array.isArray(config?.ignoreLayersType) &&
config?.ignoreLayersType?.includes(type)
) {
return true;
}
if (Array.isArray(config?.ignoreLayers) === false) return false;
try {
for (const pattern of config!.ignoreLayers!) {
if (satisfiesPattern(name, pattern)) {
return true;
}
}
} catch (e) {}

return false;
};
82 changes: 81 additions & 1 deletion packages/opentelemetry-plugin-express/test/express.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import {
InMemorySpanExporter,
SimpleSpanProcessor,
} from '@opentelemetry/tracing';
import { AttributeNames } from '../src/types';
import {
AttributeNames,
ExpressPluginConfig,
ExpressLayerType,
} from '../src/types';

const httpRequest = {
get: (options: http.ClientRequestArgs | string) => {
Expand Down Expand Up @@ -58,6 +62,10 @@ describe('Express Plugin', () => {
plugin.enable(express, registry, logger);
});

afterEach(() => {
memoryExporter.reset();
});

describe('Instrumenting normal get operations', () => {
it('should create a child span for middlewares', done => {
const rootSpan = tracer.startSpan('rootSpan');
Expand Down Expand Up @@ -123,4 +131,76 @@ describe('Express Plugin', () => {
});
});
});

describe('Instrumenting with specific config', () => {
it('should ignore specific middlewares based on config', done => {
plugin.disable();
const config: ExpressPluginConfig = {
ignoreLayersType: [ExpressLayerType.MIDDLEWARE],
};
plugin.enable(express, registry, logger, config);
const rootSpan = tracer.startSpan('rootSpan');
const app = express();
app.use(express.json());
app.use(function customMiddleware(req, res, next) {
for (let i = 0; i < 1000; i++) {
continue;
}
return next();
});
const server = http.createServer(app);
server.listen(0, () => {
const port = (server.address() as AddressInfo).port;
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
tracer.withSpan(rootSpan, async () => {
await httpRequest.get(`http://localhost:${port}/toto/tata`);
rootSpan.end();
assert.deepEqual(
memoryExporter
.getFinishedSpans()
.filter(
span =>
span.attributes[AttributeNames.EXPRESS_TYPE] ===
ExpressLayerType.MIDDLEWARE
).length,
0
);
let exportedRootSpan = memoryExporter
.getFinishedSpans()
.find(span => span.name === 'rootSpan');
assert(exportedRootSpan !== undefined);
server.close();
return done();
});
});
});
});

describe('Disabling plugin', () => {
it('should not create new spans', done => {
plugin.disable();
const rootSpan = tracer.startSpan('rootSpan');
const app = express();
app.use(express.json());
app.use(function customMiddleware(req, res, next) {
for (let i = 0; i < 1000; i++) {
continue;
}
return next();
});
const server = http.createServer(app);
server.listen(0, () => {
const port = (server.address() as AddressInfo).port;
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0);
tracer.withSpan(rootSpan, async () => {
await httpRequest.get(`http://localhost:${port}/toto/tata`);
rootSpan.end();
assert.deepEqual(memoryExporter.getFinishedSpans().length, 1);
assert(memoryExporter.getFinishedSpans()[0] !== undefined);
server.close();
return done();
});
});
});
});
});
Loading

0 comments on commit f92d939

Please sign in to comment.