Skip to content

Commit

Permalink
test(plugin-mongodb): add tests for mongodb plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarchaud committed Aug 22, 2019
1 parent 0af967e commit 7bc2b08
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 102 deletions.
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
version: 2

test_env: &test_env
RUN_MONGODB_TESTS: 1

mongo_service: &mongo_service
image: mongo

node_unit_tests: &node_unit_tests
steps:
- checkout
Expand Down Expand Up @@ -68,18 +74,26 @@ jobs:
node8:
docker:
- image: node:8
environment: *test_env
- *mongo_service
<<: *node_unit_tests
node10:
docker:
- image: node:10
environment: *test_env
- *mongo_service
<<: *node_unit_tests
node11:
docker:
- image: node:11
environment: *test_env
- *mongo_service
<<: *node_unit_tests
node12:
docker:
- image: node:12
environment: *test_env
- *mongo_service
<<: *node_unit_tests
node12-browsers:
docker:
Expand Down
41 changes: 39 additions & 2 deletions packages/opentelemetry-node-tracer/src/NodeTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,45 @@
* limitations under the License.
*/

import { BasicTracer, BasicTracerConfig } from '@opentelemetry/basic-tracer';
import { BasicTracer } from '@opentelemetry/basic-tracer';
import { AsyncHooksScopeManager } from '@opentelemetry/scope-async-hooks';
import { ScopeManager } from '@opentelemetry/scope-base';
import {
Attributes,
BinaryFormat,
HttpTextFormat,
Logger,
Sampler,
} from '@opentelemetry/types';

// @todo: Find a way to re-use BasicTracerConfig here
export declare interface NodeTracerConfig {
/**
* Binary formatter which can serialize/deserialize Spans.
*/
binaryFormat?: BinaryFormat;
/**
* Attributed that will be applied on every span created by Tracer.
* Useful to add infrastructure and environment information to your spans.
*/
defaultAttributes?: Attributes;
/**
* HTTP text formatter which can inject/extract Spans.
*/
httpTextFormat?: HttpTextFormat;
/**
* User provided logger.
*/
logger?: Logger;
/**
* Sampler determines if a span should be recorded or should be a NoopSpan.
*/
sampler?: Sampler;
/**
* Scope manager keeps context across in-process operations.
*/
scopeManager?: ScopeManager;
}

/**
* This class represents a node tracer with `async_hooks` module.
Expand All @@ -24,7 +61,7 @@ export class NodeTracer extends BasicTracer {
/**
* Constructs a new Tracer instance.
*/
constructor(config: BasicTracerConfig) {
constructor(config: NodeTracerConfig) {
super(
Object.assign({}, { scopeManager: new AsyncHooksScopeManager() }, config)
);
Expand Down
3 changes: 3 additions & 0 deletions packages/opentelemetry-node-tracer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './NodeTracer';
export { Span } from '@opentelemetry/basic-tracer';
3 changes: 2 additions & 1 deletion packages/opentelemetry-plugin-mongodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js",
"scripts": {
"test": "nyc ts-mocha -p tsconfig.json test/**/*.ts",
"test": "nyc ts-mocha -p tsconfig.json test/*.ts",
"tdd": "yarn test -- --watch-extensions ts --watch",
"clean": "rimraf build/*",
"check": "gts check",
Expand Down Expand Up @@ -45,6 +45,7 @@
"codecov": "^3.5.0",
"gts": "^1.1.0",
"mocha": "^6.2.0",
"mongodb": "^3.3.0",
"nyc": "^14.1.1",
"ts-mocha": "^6.0.0",
"ts-node": "^8.3.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/opentelemetry-plugin-mongodb/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export * from './mongodb';
143 changes: 44 additions & 99 deletions packages/opentelemetry-plugin-mongodb/src/mongodb.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2018, OpenCensus Authors
* Copyright 2019, OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -37,10 +37,10 @@ interface MongoInternalCommand {
ismaster: boolean;
}

/** MongoDB instrumentation plugin for Opencensus */
/** MongoDB instrumentation plugin for OpenTelemetry */
export class MongoDBPlugin extends BasePlugin<MongoDB> {
private readonly SERVER_FNS = ['insert', 'update', 'remove', 'auth'];
private readonly CURSOR_FNS_FIRST = ['_find', '_getmore'];
private readonly _SERVER_METHODS = ['insert', 'update', 'remove', 'command'];
private readonly _CURSOR_METHODS = ['_find', '_getmore'];
protected _logger!: Logger;
protected readonly _tracer!: Tracer;

Expand All @@ -58,35 +58,29 @@ export class MongoDBPlugin extends BasePlugin<MongoDB> {
this._logger.debug('Patched MongoDB');

if (this._moduleExports.Server) {
this._logger.debug('patching mongodb-core.Server.prototype.command');
shimmer.wrap(
this._moduleExports.Server.prototype,
'command' as never,
// tslint:disable-next-line:no-any
this.getPatchCommand() as any
);
this._logger.debug(
'patching mongodb-core.Server.prototype functions:',
this.SERVER_FNS
);
shimmer.massWrap(
[this._moduleExports.Server.prototype],
this.SERVER_FNS as never[],
// tslint:disable-next-line:no-any
this.getPatchQuery() as any
);
for (const fn of this._SERVER_METHODS) {
this._logger.debug(`patching mongodb-core.Server.prototype.${fn}`);
shimmer.wrap(
this._moduleExports.Server.prototype,
// Forced to ignore due to incomplete typings
// tslint:disable-next-line:ban-ts-ignore
// @ts-ignore
fn,
this._getPatchCommand(fn)
);
}
}

if (this._moduleExports.Cursor) {
this._logger.debug(
'patching mongodb-core.Cursor.prototype functions:',
this.CURSOR_FNS_FIRST
this._CURSOR_METHODS
);
shimmer.massWrap(
[this._moduleExports.Cursor.prototype],
this.CURSOR_FNS_FIRST as never[],
this._CURSOR_METHODS as never[],
// tslint:disable-next-line:no-any
this.getPatchCursor() as any
this._getPatchCursor() as any
);
}

Expand All @@ -95,138 +89,90 @@ export class MongoDBPlugin extends BasePlugin<MongoDB> {

/** Unpatches all MongoDB patched functions. */
unpatch(): void {
shimmer.unwrap(this._moduleExports.Server.prototype, 'command' as never);
shimmer.massUnwrap([this._moduleExports.Server.prototype], this
.SERVER_FNS as never[]);
._SERVER_METHODS as never[]);
shimmer.massUnwrap([this._moduleExports.Cursor.prototype], this
.CURSOR_FNS_FIRST as never[]);
._CURSOR_METHODS as never[]);
}

/** Creates spans for Command operations */
private getPatchCommand() {
private _getPatchCommand(operationName: string) {
const plugin = this;
return (original: Func<mongodb.Server>) => {
return function(
return function patchedServerCommand(
this: mongodb.Server,
ns: string,
command: MongoInternalCommand,
options: {} | Function,
callback: Func<unknown>
): mongodb.Server {
const currentSpan = plugin._tracer.getCurrentSpan();
if (currentSpan === null) {
return original.apply(this, (arguments as unknown) as unknown[]);
}
const resultHandler =
typeof options === 'function' ? options : callback;
if (typeof resultHandler !== 'function') {
return original.apply(this, (arguments as unknown) as unknown[]);
}
if (typeof command !== 'object') {
if (
currentSpan === null ||
typeof resultHandler !== 'function' ||
typeof command !== 'object'
) {
return original.apply(this, (arguments as unknown) as unknown[]);
}
let type: string;
if (command.createIndexes) {
if (command.createIndexes === true) {
type = 'createIndexes';
} else if (command.findandmodify) {
} else if (command.findandmodify === true) {
type = 'findAndModify';
} else if (command.ismaster) {
} else if (command.ismaster === true) {
type = 'isMaster';
} else if (command.count) {
} else if (command.count === true) {
type = 'count';
} else {
type = 'command';
type = operationName;
}

const span = plugin._tracer.startSpan(`${ns}.${type}`, {
const span = plugin._tracer.startSpan(`mongodb.${type}`, {
parent: currentSpan,
kind: SpanKind.CLIENT,
});
span.setAttribute('db', ns);
if (typeof options === 'function') {
return original.call(
this,
ns,
command,
plugin.patchEnd(span, options as Func<unknown>)
plugin._patchEnd(span, options as Func<unknown>)
);
} else {
return original.call(
this,
ns,
command,
options,
plugin.patchEnd(span, callback)
);
}
};
};
}

/** Creates spans for Query operations */
private getPatchQuery() {
const plugin = this;
return (original: Func<mongodb.Server>) => {
return function(
this: mongodb.Server,
ns: string,
command: MongoInternalCommand,
options: {},
callback: Func<unknown>
): mongodb.Server {
const currentSpan = plugin._tracer.getCurrentSpan();
if (currentSpan === null) {
return original.apply(this, (arguments as unknown) as unknown[]);
}
const resultHandler =
typeof options === 'function' ? options : callback;
if (typeof resultHandler !== 'function') {
return original.apply(this, (arguments as unknown) as unknown[]);
}
const span = plugin._tracer.startSpan(`${ns}.query`, {
kind: SpanKind.CLIENT,
parent: currentSpan,
});
if (typeof options === 'function') {
return original.call(
this,
ns,
command,
plugin.patchEnd(span, options as Func<unknown>)
);
} else {
return original.call(
this,
ns,
command,
options,
plugin.patchEnd(span, callback)
plugin._patchEnd(span, callback)
);
}
};
};
}

/** Creates spans for Cursor operations */
private getPatchCursor() {
private _getPatchCursor() {
const plugin = this;
return (original: Func<mongodb.Cursor>) => {
return function(
return function patchedCursorCommand(
this: { ns: string },
...args: unknown[]
): mongodb.Cursor {
const currentSpan = plugin._tracer.getCurrentSpan();
if (currentSpan === null) {
return original.apply(this, (arguments as unknown) as unknown[]);
}
const resultHandler = args[0] as Func<unknown> | undefined;
if (resultHandler === undefined) {
if (currentSpan === null || resultHandler === undefined) {
return original.apply(this, (arguments as unknown) as unknown[]);
}
const span = plugin._tracer.startSpan(`${this.ns}.cursor`, {
const span = plugin._tracer.startSpan(`mongodb.cursor`, {
parent: currentSpan,
kind: SpanKind.CLIENT,
});
return original.call(this, plugin.patchEnd(span, resultHandler));
span.setAttribute('db', this.ns);
return original.call(this, plugin._patchEnd(span, resultHandler));
};
};
}
Expand All @@ -236,7 +182,7 @@ export class MongoDBPlugin extends BasePlugin<MongoDB> {
* @param span The created span to end.
* @param resultHandler A callback function.
*/
patchEnd(span: Span, resultHandler: Func<unknown>): Function {
private _patchEnd(span: Span, resultHandler: Func<unknown>): Function {
return function patchedEnd(this: {}, ...args: unknown[]) {
const error = args[0];
if (error instanceof Error) {
Expand All @@ -251,5 +197,4 @@ export class MongoDBPlugin extends BasePlugin<MongoDB> {
}
}

const plugin = new MongoDBPlugin('mongodb');
export { plugin };
export const plugin = new MongoDBPlugin('mongodb');
Loading

0 comments on commit 7bc2b08

Please sign in to comment.