Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 3a613a7

Browse files
fabiogomessilvakjin
authored andcommitted
feat: add mechanism to load internal module files to patch (#52)
* feat: add mechanism to load internal module files to patch * refactor: change Plugin Interface and BasePlugin to implement GoF Template Method * refactor: change http plugin to new interface * refactor: change https plugin to new interface * refactor: change http2 plugin to new interface * refactor: change mongodb plugin to new interface * refactor: improve load internal file test case * refactor: rename methods of Plugin interface
1 parent 97a7790 commit 3a613a7

21 files changed

Lines changed: 325 additions & 116 deletions

File tree

packages/opencensus-core/src/trace/instrumentation/base-plugin.ts

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,23 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import * as shimmer from 'shimmer';
16+
import * as path from 'path';
17+
import * as semver from 'semver';
18+
19+
import {logger} from '../../common/console-logger';
20+
import {Logger} from '../../common/types';
1721
import * as modelTypes from '../model/types';
22+
1823
import * as types from './types';
1924

20-
// TODO: improve Jsdoc comments
25+
/**
26+
* Maps a name (key) representing a internal file module and its exports
27+
*/
28+
export type ModuleExportsMapping = {
29+
// tslint:disable:no-any
30+
[key: string]: any;
31+
};
32+
2133

2234
/** This class represent the base to patch plugin. */
2335
export abstract class BasePlugin implements types.Plugin {
@@ -30,6 +42,14 @@ export abstract class BasePlugin implements types.Plugin {
3042
protected tracer: modelTypes.Tracer;
3143
/** The module version. */
3244
protected version: string;
45+
/** a logger */
46+
protected logger: Logger;
47+
/** list of internal files that need patch and are not exported by default */
48+
protected readonly internalFileList: types.PluginInternalFiles;
49+
/** internal files loaded */
50+
protected internalFilesExports: ModuleExportsMapping;
51+
/** module directory - used to load internal files */
52+
protected basedir: string;
3353

3454
/**
3555
* Constructs a new BasePlugin instance.
@@ -44,26 +64,109 @@ export abstract class BasePlugin implements types.Plugin {
4464
* @param moduleExports nodejs module exports to set as context
4565
* @param tracer tracer relating to context
4666
* @param version module version description
67+
* @param basedir module absolute path
4768
*/
4869
// tslint:disable:no-any
49-
protected setPluginContext(
50-
moduleExports: any, tracer: modelTypes.Tracer, version: string) {
70+
private setPluginContext(
71+
moduleExports: any, tracer: modelTypes.Tracer, version: string,
72+
basedir?: string) {
5173
this.moduleExports = moduleExports;
5274
this.tracer = tracer;
5375
this.version = version;
76+
this.basedir = basedir;
77+
this.logger = tracer.logger;
78+
this.internalFilesExports = this.loadInternalFiles();
5479
}
5580

5681

57-
// TODO: review this implementation
58-
// From the perspective of an instrumentation module author,
59-
// that applyUnpatch is abstract makes it seem like patching is optional,
60-
// while unpatching is not. It should be the other way around
82+
/**
83+
* Method that enables the instrumentation patch.
84+
*
85+
* This method implements the GoF Template Method Pattern
86+
* 'enable' is the invariant part of the pattern and
87+
* 'applyPatch' the variant.
88+
*
89+
* @param moduleExports nodejs module exports from the module to patch
90+
* @param tracer a tracer instance
91+
* @param version version of the current instaled module to patch
92+
* @param basedir module absolute path
93+
*/
94+
enable(
95+
// tslint:disable:no-any
96+
moduleExports: any, tracer: modelTypes.Tracer, version: string,
97+
basedir: string) {
98+
this.setPluginContext(moduleExports, tracer, version, basedir);
99+
return this.applyPatch();
100+
}
101+
102+
/** Method to disable the instrumentation */
103+
disable() {
104+
this.applyUnpatch();
105+
}
61106

107+
/**
108+
* This method implements the GoF Template Method Pattern,
109+
* 'applyPatch' is the variant part, each instrumentation should
110+
* implement its own version, 'enable' method is the invariant.
111+
* Wil be called when enable is called.
112+
*
113+
*/
62114
// tslint:disable:no-any
63-
applyPatch(moduleExports: any, tracer: modelTypes.Tracer, version: string):
64-
any {
65-
this.setPluginContext(moduleExports, tracer, version);
115+
protected abstract applyPatch(): any;
116+
protected abstract applyUnpatch(): void;
117+
118+
119+
/**
120+
* Load internal files according to version range
121+
*/
122+
private loadInternalFiles(): ModuleExportsMapping {
123+
let result: ModuleExportsMapping = null;
124+
if (this.internalFileList) {
125+
this.logger.debug('loadInternalFiles %o', this.internalFileList);
126+
Object.keys(this.internalFileList).forEach(versionRange => {
127+
if (semver.satisfies(this.version, versionRange)) {
128+
if (result) {
129+
this.logger.warn(
130+
'Plugin for %s@%s, has overlap version range (%s) for internal files: %o',
131+
this.moduleName, this.version, versionRange,
132+
this.internalFileList);
133+
}
134+
result = this.loadInternalModuleFiles(
135+
this.internalFileList[versionRange], this.basedir);
136+
}
137+
});
138+
if (!result) {
139+
this.logger.debug(
140+
'No internal file could be loaded for %s@%s', this.moduleName,
141+
this.version);
142+
}
143+
}
144+
145+
return result;
66146
}
67147

68-
abstract applyUnpatch(): void;
148+
149+
/**
150+
* Load internal files from a module and set internalFilesExports
151+
*/
152+
private loadInternalModuleFiles(
153+
extraModulesList: types.PluginNames,
154+
basedir: string): ModuleExportsMapping {
155+
const extraModules: ModuleExportsMapping = {};
156+
if (extraModulesList) {
157+
Object.keys(extraModulesList).forEach(moduleName => {
158+
try {
159+
this.logger.debug('loading File %s', extraModulesList[moduleName]);
160+
extraModules[moduleName] =
161+
require(path.join(basedir, extraModulesList[moduleName]));
162+
} catch (e) {
163+
this.logger.error(
164+
'Could not load internal file %s of module %s. Error: %s',
165+
path.join(basedir, extraModulesList[moduleName]), this.moduleName,
166+
e.message);
167+
}
168+
});
169+
}
170+
return extraModules;
171+
}
69172
}

packages/opencensus-core/src/trace/instrumentation/types.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@ import {Tracer} from '../model/types';
1919
/** Interface Plugin to apply patch. */
2020
export interface Plugin {
2121
/**
22-
* Method to apply the instrumentation patch
22+
* Method that enables the instrumentation patch.
23+
*
2324
* @param moduleExports nodejs module exports from the module to patch
2425
* @param tracer a tracer instance
2526
* @param version version of the current instaled module to patch
27+
* @param basedir module absolute path
2628
*/
27-
// tslint:disable:no-any
28-
applyPatch(moduleExports: any, tracer: Tracer, version: string): any;
29-
/** Method to unpatch the instrumentation */
30-
applyUnpatch(): void;
29+
enable(
30+
// tslint:disable-next-line:no-any
31+
moduleExports: any, tracer: Tracer, version: string,
32+
// tslint:disable-next-line:no-any
33+
basedir?: string): any;
34+
/** Method to disable the instrumentation */
35+
disable(): void;
3136
}
3237

3338

@@ -39,3 +44,11 @@ export interface Plugin {
3944
export type PluginNames = {
4045
[pluginName: string]: string;
4146
};
47+
48+
/**
49+
* Each key should be the name of the module to trace, and its value
50+
* a mapping of a property name to a internal plugin file name.
51+
*/
52+
export type PluginInternalFiles = {
53+
[versions: string]: PluginNames;
54+
};

packages/opencensus-instrumentation-http/src/http.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export class HttpPlugin extends classes.BasePlugin {
4444
static ATTRIBUTE_HTTP_ERROR_NAME = 'http.error_name';
4545
static ATTRIBUTE_HTTP_ERROR_MESSAGE = 'http.error_message';
4646

47-
logger: types.Logger;
4847

4948
/** Constructs a new HttpPlugin instance. */
5049
constructor(moduleName: string) {
@@ -54,44 +53,37 @@ export class HttpPlugin extends classes.BasePlugin {
5453

5554
/**
5655
* Patches HTTP incoming and outcoming request functions.
57-
* @param moduleExports The http module exports
58-
* @param tracer A tracer instance to create spans on.
59-
* @param version The package version.
6056
*/
61-
// tslint:disable-next-line:no-any
62-
applyPatch(moduleExports: HttpModule, tracer: types.Tracer, version: string) {
63-
this.setPluginContext(moduleExports, tracer, version);
64-
this.logger = tracer.logger || logger.logger('debug');
65-
57+
protected applyPatch() {
6658
this.logger.debug('applying pacth to %s@%s', this.moduleName, this.version);
6759

6860
shimmer.wrap(
69-
moduleExports, 'request', this.getPatchOutgoingRequestFunction());
61+
this.moduleExports, 'request', this.getPatchOutgoingRequestFunction());
7062

7163
// In Node 8, http.get calls a private request method, therefore we patch it
7264
// here too.
73-
if (semver.satisfies(version, '>=8.0.0')) {
65+
if (semver.satisfies(this.version, '>=8.0.0')) {
7466
shimmer.wrap(
75-
moduleExports, 'get', this.getPatchOutgoingRequestFunction());
67+
this.moduleExports, 'get', this.getPatchOutgoingRequestFunction());
7668
}
7769

78-
if (moduleExports && moduleExports.Server &&
79-
moduleExports.Server.prototype) {
70+
if (this.moduleExports && this.moduleExports.Server &&
71+
this.moduleExports.Server.prototype) {
8072
shimmer.wrap(
81-
moduleExports.Server.prototype, 'emit',
73+
this.moduleExports.Server.prototype, 'emit',
8274
this.getPatchIncomingRequestFunction());
8375
} else {
8476
this.logger.error(
8577
'Could not apply patch to %s.emit. Interface is not as expected.',
8678
this.moduleName);
8779
}
8880

89-
return moduleExports;
81+
return this.moduleExports;
9082
}
9183

9284

9385
/** Unpatches all HTTP patched function. */
94-
applyUnpatch(): void {
86+
protected applyUnpatch(): void {
9587
shimmer.unwrap(this.moduleExports, 'request');
9688
if (semver.satisfies(this.version, '>=8.0.0')) {
9789
shimmer.unwrap(this.moduleExports, 'get');

packages/opencensus-instrumentation-http/test/test-http.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ describe('HttpPlugin', () => {
100100
});
101101

102102
before(() => {
103-
plugin.applyPatch(http, tracer, VERSION);
103+
plugin.enable(http, tracer, VERSION, null);
104104
tracer.registerSpanEventListener(rootSpanVerifier);
105105
server = http.createServer((request, response) => {
106106
response.end('Test Server Response');
@@ -283,7 +283,7 @@ describe('HttpPlugin', () => {
283283
/** Should not intercept incoming and outgoing requests */
284284
describe('applyUnpatch()', () => {
285285
it('should not create a root span for incoming requests', async () => {
286-
plugin.applyUnpatch();
286+
plugin.disable();
287287
const testPath = '/incoming/unpatch/';
288288
nock.enableNetConnect();
289289

packages/opencensus-instrumentation-http2/src/http2.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,28 +42,22 @@ export class Http2Plugin extends HttpPlugin {
4242

4343
/**
4444
* Patches HTTP2 incoming and outcoming request functions.
45-
* @param moduleExporters The HTTP2 package.
46-
* @param tracer A tracer instance to create spans on.
47-
* @param version The package version.
4845
*/
49-
// tslint:disable-next-line:no-any
50-
applyPatch(moduleExports: any, tracer: types.Tracer, version: string) {
51-
this.setPluginContext(moduleExports, tracer, version);
52-
this.logger = tracer.logger || logger.logger('debug');
53-
46+
protected applyPatch() {
5447
shimmer.wrap(
55-
moduleExports, 'createServer', this.getPatchCreateServerFunction());
48+
this.moduleExports, 'createServer',
49+
this.getPatchCreateServerFunction());
5650
shimmer.wrap(
57-
moduleExports, 'createSecureServer',
51+
this.moduleExports, 'createSecureServer',
5852
this.getPatchCreateServerFunction());
5953

60-
shimmer.wrap(moduleExports, 'connect', this.getPatchConnectFunction());
54+
shimmer.wrap(this.moduleExports, 'connect', this.getPatchConnectFunction());
6155

62-
return moduleExports;
56+
return this.moduleExports;
6357
}
6458

6559
/** Unpatches all HTTP2 patched function. */
66-
applyUnpatch(): void {
60+
protected applyUnpatch(): void {
6761
// Only Client and Server constructors will be unwrapped. Any existing
6862
// Client or Server instances will still trace
6963
shimmer.unwrap(this.moduleExports, 'createServer');

packages/opencensus-instrumentation-http2/test/test-http2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ describe('Http2Plugin', () => {
100100
before(() => {
101101
tracer.registerSpanEventListener(rootSpanVerifier);
102102

103-
plugin.applyPatch(http2, tracer, VERSION);
103+
plugin.enable(http2, tracer, VERSION, null);
104104
server = http2.createServer();
105105
server.on('stream', (stream, requestHeaders) => {
106106
const statusCode = requestHeaders[':path'].length > 1 ?

packages/opencensus-instrumentation-https/src/https.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,15 @@ export class HttpsPlugin extends HttpPlugin {
3030

3131
/**
3232
* Patches HTTPS incoming and outcoming request functions.
33-
* @param moduleExports The HTTPS package.
34-
* @param tracer A tracer instance to create spans on.
35-
* @param version The package version.
3633
*/
37-
// tslint:disable:no-any
38-
applyPatch(moduleExports: any, tracer: types.Tracer, version: string) {
39-
this.setPluginContext(moduleExports, tracer, version);
40-
this.logger = tracer.logger || logger.logger('debug');
41-
34+
protected applyPatch() {
4235
this.logger.debug('applying pacth to %s@%s', this.moduleName, this.version);
4336

44-
if (moduleExports && moduleExports.Server &&
45-
moduleExports.Server.prototype) {
37+
if (this.moduleExports && this.moduleExports.Server &&
38+
this.moduleExports.Server.prototype) {
4639
shimmer.wrap(
47-
moduleExports && moduleExports.Server &&
48-
moduleExports.Server.prototype,
40+
this.moduleExports && this.moduleExports.Server &&
41+
this.moduleExports.Server.prototype,
4942
'emit', this.getPatchIncomingRequestFunction());
5043
} else {
5144
this.logger.error(
@@ -55,13 +48,14 @@ export class HttpsPlugin extends HttpPlugin {
5548

5649
// TODO: review the need to patch 'request'
5750

58-
shimmer.wrap(moduleExports, 'get', this.getPatchOutgoingRequestFunction());
51+
shimmer.wrap(
52+
this.moduleExports, 'get', this.getPatchOutgoingRequestFunction());
5953

60-
return moduleExports;
54+
return this.moduleExports;
6155
}
6256

6357
/** Unpatches all HTTPS patched function. */
64-
applyUnpatch(): void {
58+
protected applyUnpatch(): void {
6559
if (this.moduleExports && this.moduleExports.Server &&
6660
this.moduleExports.Server.prototype) {
6761
shimmer.unwrap(

packages/opencensus-instrumentation-https/test/test-https.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ describe('HttpsPlugin', () => {
108108
});
109109

110110
before(() => {
111-
plugin.applyPatch(https, tracer, VERSION);
111+
plugin.enable(https, tracer, VERSION, null);
112112
tracer.registerSpanEventListener(rootSpanVerifier);
113113
server = https.createServer(httpsOptions, (request, response) => {
114114
response.end('Test Server Response');
@@ -289,7 +289,7 @@ describe('HttpsPlugin', () => {
289289
/** Should not intercept incoming and outgoing requests */
290290
describe('applyUnpatch()', () => {
291291
it('should not create a root span for incoming requests', async () => {
292-
plugin.applyUnpatch();
292+
plugin.disable();
293293
const testPath = '/incoming/unpatch/';
294294

295295
const options = {host: 'localhost', path: testPath, port: serverPort};

0 commit comments

Comments
 (0)