-
Notifications
You must be signed in to change notification settings - Fork 824
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
feature(plugin): implement postgres plugin #417
Changes from 22 commits
0212dea
62ecb39
f8d885b
3ed0cf3
9b90fde
c14b33f
dd1a7d3
7a854fe
eafdb3d
7504ba7
9316ff5
bfb19d3
64a2a8b
10fd6ca
d81e4b0
297e699
208d3a9
f986476
644f5f1
b3a83b4
ccefe01
fefb04e
4e24474
0279784
bbdc02d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,9 +7,12 @@ | |
"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/**/*.test.ts'", | ||
"test:debug": "ts-mocha --inspect-brk --no-timeouts -p tsconfig.json 'test/**/*.test.ts'", | ||
"test:local": "cross-env RUN_POSTGRES_TESTS_LOCAL=true yarn test", | ||
"tdd": "yarn test -- --watch-extensions ts --watch", | ||
"clean": "rimraf build/*", | ||
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../", | ||
"check": "gts check", | ||
"compile": "tsc -p .", | ||
"fix": "gts fix", | ||
|
@@ -43,11 +46,14 @@ | |
"devDependencies": { | ||
"@types/mocha": "^5.2.7", | ||
"@types/node": "^12.6.9", | ||
"@types/pg": "^7.11.2", | ||
"@types/shimmer": "^1.0.1", | ||
"codecov": "^3.5.0", | ||
"gts": "^1.1.0", | ||
"gts": "^1.0.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why use the earlier version of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK most of the places we are using |
||
"mocha": "^6.2.0", | ||
"nyc": "^14.1.1", | ||
"rimraf": "^3.0.0", | ||
"pg": "^7.12.1", | ||
"tslint-microsoft-contrib": "^6.2.0", | ||
"tslint-consistent-codestyle": "^1.15.1", | ||
"ts-mocha": "^6.0.0", | ||
|
@@ -57,6 +63,8 @@ | |
"dependencies": { | ||
"@opentelemetry/core": "^0.1.1", | ||
"@opentelemetry/node": "^0.1.1", | ||
"@opentelemetry/types": "^0.1.1" | ||
"@opentelemetry/tracing": "^0.1.1", | ||
"@opentelemetry/types": "^0.1.1", | ||
"shimmer": "^1.2.1" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,36 @@ | |||||||||||||||||||||||||||||||||||||||||
/*! | |||||||||||||||||||||||||||||||||||||||||
* 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. | |||||||||||||||||||||||||||||||||||||||||
* You may obtain a copy of the License at | |||||||||||||||||||||||||||||||||||||||||
* | |||||||||||||||||||||||||||||||||||||||||
* https://www.apache.org/licenses/LICENSE-2.0 | |||||||||||||||||||||||||||||||||||||||||
* | |||||||||||||||||||||||||||||||||||||||||
* Unless required by applicable law or agreed to in writing, software | |||||||||||||||||||||||||||||||||||||||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||||||||||||||||||||||||||||||||||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||||||||||||||||||||||||||||||||||||||
* See the License for the specific language governing permissions and | |||||||||||||||||||||||||||||||||||||||||
* limitations under the License. | |||||||||||||||||||||||||||||||||||||||||
*/ | |||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
export enum AttributeNames { | |||||||||||||||||||||||||||||||||||||||||
// required by https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md#databases-client-calls | |||||||||||||||||||||||||||||||||||||||||
COMPONENT = 'component', | |||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a standard label in the spec? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the spec calls for:
For database client calls, peer information can be populated and interpreted as
|
|||||||||||||||||||||||||||||||||||||||||
DB_TYPE = 'db.type', | |||||||||||||||||||||||||||||||||||||||||
DB_INSTANCE = 'db.instance', | |||||||||||||||||||||||||||||||||||||||||
DB_STATEMENT = 'db.statement', | |||||||||||||||||||||||||||||||||||||||||
PEER_ADDRESS = 'peer.address', | |||||||||||||||||||||||||||||||||||||||||
PEER_HOSTNAME = 'peer.host', | |||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
// optional | |||||||||||||||||||||||||||||||||||||||||
DB_USER = 'db.user', | |||||||||||||||||||||||||||||||||||||||||
PEER_PORT = 'peer.port', | |||||||||||||||||||||||||||||||||||||||||
PEER_IPV4 = 'peer.ipv4', | |||||||||||||||||||||||||||||||||||||||||
PEER_IPV6 = 'peer.ipv6', | |||||||||||||||||||||||||||||||||||||||||
PEER_SERVICE = 'peer.service', | |||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
// PG specific -- not specified by spec | |||||||||||||||||||||||||||||||||||||||||
PG_VALUES = 'pg.values', | |||||||||||||||||||||||||||||||||||||||||
PG_PLAN = 'pg.plan', | |||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
/*! | ||
* 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { BasePlugin } from '@opentelemetry/core'; | ||
import { CanonicalCode, Span } from '@opentelemetry/types'; | ||
import { | ||
PostgresPluginOptions, | ||
PgClientExtended, | ||
PgPluginQueryConfig, | ||
PostgresCallback, | ||
} from './types'; | ||
import * as pgTypes from 'pg'; | ||
import * as shimmer from 'shimmer'; | ||
import * as utils from './utils'; | ||
|
||
export class PostgresPlugin extends BasePlugin<typeof pgTypes> { | ||
protected _config: PostgresPluginOptions; | ||
|
||
static readonly COMPONENT = 'pg'; | ||
static readonly DB_TYPE = 'sql'; | ||
|
||
static readonly BASE_SPAN_NAME = PostgresPlugin.COMPONENT + '.query'; | ||
|
||
readonly supportedVersions = ['^7.12.1']; | ||
|
||
constructor(readonly moduleName: string) { | ||
super(); | ||
this._config = {}; | ||
} | ||
|
||
protected patch(): typeof pgTypes { | ||
if (this._moduleExports.Client.prototype.query) { | ||
shimmer.wrap( | ||
this._moduleExports.Client.prototype, | ||
'query', | ||
this._getClientQueryPatch() as never | ||
); | ||
} | ||
return this._moduleExports; | ||
} | ||
|
||
protected unpatch(): void { | ||
if (this._moduleExports.Client.prototype.query) { | ||
shimmer.unwrap(this._moduleExports.Client.prototype, 'query'); | ||
} | ||
} | ||
|
||
private _getClientQueryPatch() { | ||
const plugin = this; | ||
return (original: typeof pgTypes.Client.prototype.query) => { | ||
plugin._logger.debug( | ||
`Patching ${PostgresPlugin.COMPONENT}.Client.prototype.query` | ||
); | ||
return function query( | ||
this: pgTypes.Client & PgClientExtended, | ||
...args: unknown[] | ||
) { | ||
let callbackProvided = false; | ||
let span: Span; | ||
|
||
// Handle different client.query(...) signatures | ||
if (typeof args[0] === 'string') { | ||
if (args.length > 1 && args[1] instanceof Array) { | ||
span = utils.handleParameterizedQuery.call( | ||
this, | ||
plugin._tracer, | ||
...args | ||
); | ||
} else { | ||
span = utils.handleTextQuery.call(this, plugin._tracer, ...args); | ||
} | ||
} else if (typeof args[0] === 'object') { | ||
span = utils.handleConfigQuery.call(this, plugin._tracer, ...args); | ||
} else { | ||
return original.apply(this, args as never); | ||
} | ||
|
||
// Bind callback to parent span | ||
if (args.length > 0) { | ||
const parentSpan = plugin._tracer.getCurrentSpan(); | ||
if (typeof args[args.length - 1] === 'function') { | ||
// Patch ParameterQuery callback | ||
args[args.length - 1] = utils.patchCallback(span, args[ | ||
args.length - 1 | ||
] as PostgresCallback); | ||
// If a parent span exists, bind the callback | ||
if (parentSpan) { | ||
args[args.length - 1] = plugin._tracer.bind( | ||
args[args.length - 1] | ||
); | ||
} | ||
callbackProvided = true; | ||
} else if ( | ||
typeof (args[0] as PgPluginQueryConfig).callback === 'function' | ||
) { | ||
// Patch ConfigQuery callback | ||
let callback = utils.patchCallback( | ||
span, | ||
(args[0] as PgPluginQueryConfig).callback! | ||
); | ||
// If a parent span existed, bind the callback | ||
if (parentSpan) { | ||
callback = plugin._tracer.bind(callback); | ||
} | ||
|
||
// Copy the callback instead of writing to args.callback so that we don't modify user's | ||
// original callback reference | ||
args[0] = { ...(args[0] as object), callback }; | ||
callbackProvided = true; | ||
} | ||
} | ||
|
||
// Perform the original query | ||
const result: unknown = original.apply(this, args as never); | ||
|
||
// Bind promise to parent span and end the span | ||
if (result instanceof Promise) { | ||
return result | ||
.then((result: unknown) => { | ||
// Return a pass-along promise which ends the span and then goes to user's orig resolvers | ||
return new Promise((resolve, _) => { | ||
span.setStatus({ code: CanonicalCode.OK }); | ||
span.end(); | ||
resolve(result); | ||
}); | ||
}) | ||
.catch((error: Error) => { | ||
return new Promise((_, reject) => { | ||
span.setStatus({ code: CanonicalCode.UNKNOWN }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we set something like |
||
span.end(); | ||
reject(error); | ||
}); | ||
}); | ||
} | ||
|
||
// If a promise was not returned and no callback is provided, we recieved invalid args | ||
if (!callbackProvided) { | ||
span.setStatus({ | ||
code: CanonicalCode.INVALID_ARGUMENT, | ||
message: 'Invalid query provided to the driver', | ||
}); | ||
span.end(); | ||
} | ||
|
||
// else returns void | ||
return result; // void | ||
}; | ||
}; | ||
} | ||
} | ||
|
||
export const plugin = new PostgresPlugin(PostgresPlugin.COMPONENT); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/*! | ||
* 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import * as pgTypes from 'pg'; | ||
|
||
export interface PostgresPluginOptions {} | ||
|
||
export type PostgresCallback = (err: Error, res: object) => unknown; | ||
|
||
// These are not included in @types/pg, so manually define them. | ||
// https://github.com/brianc/node-postgres/blob/fde5ec586e49258dfc4a2fcd861fcdecb4794fc3/lib/client.js#L25 | ||
export interface PgClientConnectionParams { | ||
database: string; | ||
host: string; | ||
port: number; | ||
user: string; | ||
} | ||
|
||
export interface PgClientExtended { | ||
connectionParameters: PgClientConnectionParams; | ||
} | ||
|
||
export interface PgPluginQueryConfig extends pgTypes.QueryConfig { | ||
callback?: PostgresCallback; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supported version is currently
^7.12.1
which is different. Please fix here orsupportedVersions
fieldThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 changed
supportedVersions
to7.*