-
Notifications
You must be signed in to change notification settings - Fork 837
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(plugin): implement postgres plugin (#417)
* feat(pg): implement postgres plugin * fix: linting * fix: docker starting not locally * fix: compile errors from merge * fix: linting * refactor: use helper functions for span building * fix: add callback patching to end span * fix: add required attributes, address comments * fix: lint errors * refactor: start named spans in query handlers * fix: linting errors * fix: circleci config, make pg helpers nonexported * fix: linting * docs: add supported versions * fix: pass PG env to spawned container * fix: remove hardcoded shouldTest * test: add span tests for pg driver errors * chore: remove hardcode shouldTest
- Loading branch information
1 parent
ff907cf
commit 5c49c6c
Showing
11 changed files
with
1,025 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
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', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/*! | ||
* 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.*']; | ||
|
||
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 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 utils.handleInvalidQuery.call( | ||
this, | ||
plugin._tracer, | ||
original, | ||
...args | ||
); | ||
} | ||
|
||
// 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] | ||
); | ||
} | ||
} 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 }; | ||
} | ||
} | ||
|
||
// 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, | ||
message: error.message, | ||
}); | ||
span.end(); | ||
reject(error); | ||
}); | ||
}); | ||
} | ||
|
||
// else returns void | ||
return result; // void | ||
}; | ||
}; | ||
} | ||
} | ||
|
||
export const plugin = new PostgresPlugin(PostgresPlugin.COMPONENT); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.