Skip to content

Commit

Permalink
fix: updated prisma instrumentation to properly parse database connec…
Browse files Browse the repository at this point in the history
…tion strings that work across all versions of prisma (#1634)
  • Loading branch information
bizob2828 committed May 11, 2023
1 parent f1a00e5 commit b2101fd
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 176 deletions.
29 changes: 29 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic

* [@grpc/grpc-js](#grpcgrpc-js)
* [@grpc/proto-loader](#grpcproto-loader)
* [@mrleebo/prisma-ast](#mrleeboprisma-ast)
* [@newrelic/aws-sdk](#newrelicaws-sdk)
* [@newrelic/koa](#newrelickoa)
* [@newrelic/superagent](#newrelicsuperagent)
Expand Down Expand Up @@ -504,6 +505,34 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
```

### @mrleebo/prisma-ast

This product includes source derived from [@mrleebo/prisma-ast](https://github.com/MrLeebo/prisma-ast) ([v0.5.2](https://github.com/MrLeebo/prisma-ast/tree/v0.5.2)), distributed under the [MIT License](https://github.com/MrLeebo/prisma-ast/blob/v0.5.2/LICENSE):

```
MIT License
Copyright (c) 2021 Jeremy Liberman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

### @newrelic/aws-sdk

This product includes source derived from [@newrelic/aws-sdk](https://github.com/newrelic/node-newrelic-aws-sdk) ([v5.0.2](https://github.com/newrelic/node-newrelic-aws-sdk/tree/v5.0.2)), distributed under the [Apache-2.0 License](https://github.com/newrelic/node-newrelic-aws-sdk/blob/v5.0.2/LICENSE):
Expand Down
75 changes: 48 additions & 27 deletions lib/instrumentation/@prisma/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,53 @@ const parseSql = require('../../db/query-parsers/sql')
const RAW_COMMANDS = ['executeRaw', 'queryRaw']

const semver = require('semver')
const { getSchema } = require('@mrleebo/prisma-ast')

/**
* Extracts the connection url from env var or the .value prop
* Very similar to this helper: https://github.com/prisma/prisma/blob/main/packages/internals/src/utils/parseEnvValue.ts
* The library we use to parse the prisma schema retains double quotes around
* strings, and they need to be stripped
*
* @param {string} datasource object from prisma config { url, fromEnvVar }
* @returns {string} connection string
* @param {string} [str=''] string to strip double-quotes from
* @returns {string} stripped string
*/
function extractConnectionString(datasource = {}) {
return process.env[datasource.fromEnvVar] || datasource.value
function trimQuotes(str = '') {
return str.match(/"(.*)"/)[1]
}

/**
* You can set the connection string in schema as raw string,
* env var mapping, or an override at client instantiation time.
*
* @param {*} url string/object value of url in datsource stanza of schema
* @param {string} overrideUrl value of url in overrides at client instantiation
* @returns {string} properly parsed connection url
*/
function parseDataModelUrl(url, overrideUrl) {
let parsedUrl = ''

if (overrideUrl) {
parsedUrl = overrideUrl
} else if (typeof url === 'string') {
parsedUrl = trimQuotes(url)
} else if (url.name && url.name === 'env') {
const envVar = trimQuotes(url.params[0])
parsedUrl = process.env[envVar]
}

return parsedUrl
}

/**
* Parses a connection string. Most database engines in prisma are SQL and all
* have similar engine strings.
*
* **Note**: This will not parse ms sql server, instead will log a warning
*
* @param {string} provider prisma provider(i.e. mysql, postgres, mongodb)
* @param {string} datasource object from prisma config { url, fromEnvVar }
* @param {string} connectionUrl connection string to db
* @returns {object} { host, port, dbName }
*/
function parseConnectionString(provider, datasource) {
const connectionUrl = extractConnectionString(datasource)

function parseConnectionString(provider, connectionUrl) {
let parameters = {}
try {
const parsedUrl = new URL(connectionUrl)
Expand Down Expand Up @@ -124,24 +147,23 @@ function queryParser(query) {
}

/**
* Extracts the prisma connection information from the engine. In pre 4.11.0 this existed
* on a different object and was also a promise.
* Extracts the prisma connection information from the engine. This used to use
* prisma functions available on engine `getConfig` but that's no longer accessible.
* Instead we went the route of parsing the schema DSL.
*
* @param {object} client prisma client instance
* @param {string} pkgVersion prisma version
* @returns {Promise} returns prisma connection configuration
* @returns {Promise} returns prisma datasource connection configuration { provider, url }
*/
function extractPrismaConfig(client, pkgVersion) {
if (semver.gte(pkgVersion, '4.11.0')) {
// wait for the library promise to resolve before getting the config
return client._engine.libraryInstantiationPromise.then(() => {
return client._engine.library.getConfig({
datamodel: client._engine.datamodel,
ignoreEnvVarErrors: true
})
})
function extractPrismaDatasource(client) {
const { datamodel, datasourceOverrides: overrides } = client._engine
const schema = getSchema(datamodel)
const datasource = schema.list.filter(({ type }) => type === 'datasource')[0]
const urlData = datasource.assignments.filter(({ key }) => key === 'url')[0].value
const url = parseDataModelUrl(urlData, overrides[datasource.name])
return {
provider: trimQuotes(datasource.assignments.filter(({ key }) => key === 'provider')[0].value),
url
}
return client._engine.getConfig()
}

/**
Expand Down Expand Up @@ -179,11 +201,10 @@ module.exports = async function initialize(_agent, prisma, _moduleName, shim) {
* Adds the relevant host, port, database_name parameters
* to the active segment
*/
inContext: async function inContext() {
inContext: function inContext() {
if (!client[prismaConnection]) {
try {
const prismaConfig = await extractPrismaConfig(client, pkgVersion)
const activeDatasource = prismaConfig?.datasources[0]
const activeDatasource = extractPrismaDatasource(client)
const dbParams = parseConnectionString(
activeDatasource?.provider,
activeDatasource?.url
Expand Down
Loading

0 comments on commit b2101fd

Please sign in to comment.