diff --git a/package-lock.json b/package-lock.json index 0a8c28b..1923427 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1206,6 +1206,15 @@ "integrity": "sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=", "dev": true }, + "@types/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-NikKq+D2KJzmaWbcz4JLVT34ybtCC4c3jV6XRGWOQvOkXka9QblEmC+sPf3vRa3lmcURkVbCk2V2BDyCO7jbXg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/semver": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz", @@ -2615,6 +2624,12 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", @@ -3277,8 +3292,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "2.7.4", @@ -3295,6 +3309,15 @@ "wide-align": "^1.1.0" } }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npm.taobao.org/gensync/download/gensync-1.0.0-beta.2.tgz?cache=0&sync_timestamp=1603829637456&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgensync%2Fdownload%2Fgensync-1.0.0-beta.2.tgz", @@ -3651,7 +3674,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -3952,7 +3974,6 @@ "version": "2.2.0", "resolved": "https://registry.npm.taobao.org/is-core-module/download/is-core-module-2.2.0.tgz?cache=0&sync_timestamp=1606411621990&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-core-module%2Fdownload%2Fis-core-module-2.2.0.tgz", "integrity": "sha1-lwN+89UiJNhRY/VZeytj2a/tmBo=", - "dev": true, "requires": { "has": "^1.0.3" } @@ -4097,6 +4118,12 @@ "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", "dev": true }, + "is-property": { + "version": "1.0.2", + "resolved": "http://npm.meshkit.cn/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, "is-self-closing": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-self-closing/-/is-self-closing-1.0.1.tgz", @@ -5770,6 +5797,23 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npm.taobao.org/make-dir/download/make-dir-3.1.0.tgz", @@ -6025,6 +6069,66 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "mysql2": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", + "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", + "dev": true, + "requires": { + "denque": "^1.4.1", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.2", + "long": "^4.0.0", + "lru-cache": "^6.0.0", + "named-placeholders": "^1.1.2", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + } + } + }, + "named-placeholders": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", + "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", + "dev": true, + "requires": { + "lru-cache": "^4.1.3" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "http://npm.meshkit.cn/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", @@ -6515,8 +6619,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -6628,6 +6731,12 @@ "ipaddr.js": "1.9.1" } }, + "pseudomap": { + "version": "1.0.2", + "resolved": "http://npm.meshkit.cn/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz", @@ -6914,11 +7023,11 @@ "dev": true }, "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "requires": { + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -7093,6 +7202,12 @@ } } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "http://npm.meshkit.cn/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=", + "dev": true + }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz", @@ -7372,6 +7487,12 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, + "sqlstring": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", + "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==", + "dev": true + }, "ssh-remote-port-forward": { "version": "1.0.3", "resolved": "https://registry.npm.taobao.org/ssh-remote-port-forward/download/ssh-remote-port-forward-1.0.3.tgz", diff --git a/package.json b/package.json index f6c9d31..730a0e0 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@types/google-protobuf": "^3.7.2", "@types/jest": "^26.0.15", "@types/node": "^14.0.11", + "@types/resolve": "^1.19.0", "@types/semver": "^7.2.0", "@types/uuid": "^8.0.0", "axios": "^0.21.0", @@ -49,6 +50,7 @@ "grpc-tools": "^1.10.0", "grpc_tools_node_protoc_ts": "^4.0.0", "jest": "^26.6.3", + "mysql2": "^2.2.5", "prettier": "^2.0.5", "testcontainers": "^6.2.0", "ts-jest": "^26.4.4", @@ -62,6 +64,7 @@ "dependencies": { "google-protobuf": "^3.14.0", "grpc": "^1.10.1", + "resolve": "^1.19.0", "semver": "^7.3.2", "tslib": "^2.0.3", "uuid": "^8.1.0", diff --git a/src/Tag.ts b/src/Tag.ts index 95e8ab3..2a1c345 100644 --- a/src/Tag.ts +++ b/src/Tag.ts @@ -55,4 +55,25 @@ export default { val: `${val}`, } as Tag; }, + dbType: (val: string | undefined): Tag => { + return { + key: 'db.type', + overridable: true, + val: `${val}`, + }; + }, + dbInstance: (val: string | undefined): Tag => { + return { + key: 'db.instance', + overridable: true, + val: `${val}`, + }; + }, + dbStatement: (val: string | undefined): Tag => { + return { + key: 'db.statement', + overridable: true, + val: `${val}`, + }; + }, }; diff --git a/src/core/PluginInstaller.ts b/src/core/PluginInstaller.ts index 820b2bc..f9c5815 100644 --- a/src/core/PluginInstaller.ts +++ b/src/core/PluginInstaller.ts @@ -22,6 +22,7 @@ import * as path from 'path'; import SwPlugin from '../core/SwPlugin'; import { createLogger } from '../logging'; import * as semver from 'semver'; +import resolve from 'resolve'; const logger = createLogger(__filename); @@ -33,7 +34,7 @@ while (topModule.parent) { export default class PluginInstaller { private readonly pluginDir: string; readonly require: (name: string) => any = topModule.require.bind(topModule); - private readonly resolve = (request: string) => (module.constructor as any)._resolveFilename(request, topModule); + private readonly resolve = (request: string) => resolve.sync(request); constructor() { this.pluginDir = path.resolve(__dirname, '..', 'plugins'); @@ -57,7 +58,8 @@ export default class PluginInstaller { } const packageJsonPath = this.resolve(`${plugin.module}/package.json`); - const version = this.require(packageJsonPath).version; + + const version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version; if (!semver.satisfies(version, plugin.versions)) { logger.info(`Plugin ${plugin.module} ${version} doesn't satisfy the supported version ${plugin.versions}`); @@ -74,30 +76,30 @@ export default class PluginInstaller { install(): void { fs.readdirSync(this.pluginDir) - .filter((file) => !(file.endsWith('.d.ts') || file.endsWith('.js.map'))) - .forEach((file) => { - let plugin; - const pluginFile = path.join(this.pluginDir, file); + .filter((file) => !(file.endsWith('.d.ts') || file.endsWith('.js.map'))) + .forEach((file) => { + let plugin; + const pluginFile = path.join(this.pluginDir, file); - try { - plugin = require(pluginFile).default as SwPlugin; - const { isSupported, version } = this.checkModuleVersion(plugin); + try { + plugin = require(pluginFile).default as SwPlugin; + const { isSupported, version } = this.checkModuleVersion(plugin); - if (!isSupported) { - logger.info(`Plugin ${plugin.module} ${version} doesn't satisfy the supported version ${plugin.versions}`); - return; - } + if (!isSupported) { + logger.info(`Plugin ${plugin.module} ${version} doesn't satisfy the supported version ${plugin.versions}`); + return; + } - logger.info(`Installing plugin ${plugin.module} ${plugin.versions}`); + logger.info(`Installing plugin ${plugin.module} ${plugin.versions}`); - plugin.install(this); - } catch (e) { - if (plugin) { - logger.error(`Error installing plugin ${plugin.module} ${plugin.versions}`); - } else { - logger.error(`Error processing plugin ${pluginFile}`); + plugin.install(this); + } catch (e) { + if (plugin) { + logger.error(`Error installing plugin ${plugin.module} ${plugin.versions}`); + } else { + logger.error(`Error processing plugin ${pluginFile} ${e}`); + } } - } - }); + }); } } diff --git a/src/plugins/MysqlPlugin.ts b/src/plugins/MysqlPlugin.ts new file mode 100644 index 0000000..ae74412 --- /dev/null +++ b/src/plugins/MysqlPlugin.ts @@ -0,0 +1,66 @@ +/*! + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 PluginInstaller from '../core/PluginInstaller'; +import Connection from 'mysql2/typings/mysql/lib/Connection'; +import { SpanLayer } from '../proto/language-agent/Tracing_pb'; +import { Component } from '../trace/Component'; +import ContextManager from '../trace/context/ContextManager'; +import SwPlugin from '../core/SwPlugin'; +import Tag from '../Tag'; + +class MysqlPlugin implements SwPlugin { + readonly module = 'mysql2'; + readonly versions = '*'; + + install(installer: PluginInstaller): void { + this.interceptSql(installer); + } + + private interceptSql(installer: PluginInstaller) { + const mysql = installer.require('mysql2'); + const _query = mysql.Connection.prototype.query; + mysql.Connection.prototype.query = function (this: Connection, sql: any, values: any, cb: any) { + const span = ContextManager.current.newExitSpan('Mysql/query', `${this.config.host}:${this.config.port}`).start(); + const query = _query.apply(this, [sql, values, cb]); + span.component = Component.MYSQL; + span.layer = SpanLayer.DATABASE; + span.tag(Tag.dbType('mysql')); + if (this.config.database) { + span.tag(Tag.dbInstance(this.config.database)); + } + span.tag(Tag.dbStatement(query.sql)); + span.async(); + const _onResult = query.onResult; + query.onResult = function (err: Error, row: any, fields: any) { + span.resync(); + if (err) { + span.errored = true; + span.error(err) + } + span.stop(); + _onResult.apply(this, [err, row, fields]); + } + return query; + } + } + +} + +export default new MysqlPlugin(); diff --git a/src/trace/Component.ts b/src/trace/Component.ts index 1eb9597..2357467 100644 --- a/src/trace/Component.ts +++ b/src/trace/Component.ts @@ -20,10 +20,11 @@ export class Component { static readonly UNKNOWN = new Component(0); static readonly HTTP = new Component(2); + static readonly MYSQL = new Component(5); static readonly MONGODB = new Component(9); static readonly HTTP_SERVER = new Component(49); static readonly EXPRESS = new Component(4002); static readonly AXIOS = new Component(4005); - constructor(public readonly id: number) {} + constructor(public readonly id: number) { } } diff --git a/tests/plugins/mysql2/client.ts b/tests/plugins/mysql2/client.ts new file mode 100644 index 0000000..2f33af1 --- /dev/null +++ b/tests/plugins/mysql2/client.ts @@ -0,0 +1,38 @@ +/*! + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 http from 'http'; +import agent from '../../../src'; + +agent.start({ + serviceName: 'client', + maxBufferSize: 1000, +}) + +const server = http.createServer((req, res) => { + http + .request(`http://${process.env.SERVER || 'localhost:5000'}${req.url}`, (r) => { + let data = ''; + r.on('data', (chunk) => (data += chunk)); + r.on('end', () => res.end(data)); + }) + .end(); +}); + +server.listen(5001, () => console.info('Listening on port 5001...')); diff --git a/tests/plugins/mysql2/docker-compose.yml b/tests/plugins/mysql2/docker-compose.yml new file mode 100644 index 0000000..c103837 --- /dev/null +++ b/tests/plugins/mysql2/docker-compose.yml @@ -0,0 +1,88 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +version: "3.8" + +services: + collector: + extends: + file: ../common/base-compose.yml + service: collector + networks: + - traveling-light + + mysql: + environment: + MYSQL_ROOT_PASSWORD: "root" + MYSQL_DATABASE: "test" + ports: + - 3306:3306 + volumes: + - ./init:/docker-entrypoint-initdb.d + healthcheck: + test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/3306"] + interval: 5s + timeout: 60s + retries: 120 + image: "docker.io/mysql:latest" + networks: + - traveling-light + + server: + extends: + file: ../common/base-compose.yml + service: agent + ports: + - 5000:5000 + environment: + MYSQL_HOST: mysql + volumes: + - .:/app/tests/plugins/mysql2 + healthcheck: + test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/5000"] + interval: 5s + timeout: 60s + retries: 120 + entrypoint: + ["bash", "-c", "npx ts-node /app/tests/plugins/mysql2/server.ts"] + depends_on: + collector: + condition: service_healthy + mysql: + condition: service_healthy + + client: + extends: + file: ../common/base-compose.yml + service: agent + ports: + - 5001:5001 + environment: + SERVER: server:5000 + healthcheck: + test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/5001"] + interval: 5s + timeout: 60s + retries: 120 + entrypoint: + ["bash", "-c", "npx ts-node /app/tests/plugins/mysql2/client.ts"] + depends_on: + server: + condition: service_healthy + +networks: + traveling-light: diff --git a/tests/plugins/mysql2/expected.data.yaml b/tests/plugins/mysql2/expected.data.yaml new file mode 100644 index 0000000..85d082b --- /dev/null +++ b/tests/plugins/mysql2/expected.data.yaml @@ -0,0 +1,111 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# + +segmentItems: + - serviceName: server + segmentSize: 1 + segments: + - segmentId: not null + spans: + - operationName: /mysql + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 49 + spanType: Entry + peer: not null + skipAnalysis: false + tags: + - key: http.url + value: http://server:5000/mysql + - key: http.method + value: GET + - key: http.status.code + value: "200" + refs: + - parentEndpoint: "" + networkAddress: server:5000 + refType: CrossProcess + parentSpanId: 1 + parentTraceSegmentId: not null + parentServiceInstance: not null + parentService: client + traceId: not null + - operationName: Mysql/query + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Database + startTime: gt 0 + endTime: gt 0 + componentId: 5 + spanType: Exit + peer: mysql:3306 + skipAnalysis: false + tags: + - key: db.type + value: mysql + - key: db.instance + value: test + - key: db.statement + value: SELECT * FROM `user` WHERE `name` = "u1" + - serviceName: client + segmentSize: 1 + segments: + - segmentId: not null + spans: + - operationName: /mysql + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 49 + spanType: Entry + peer: not null + skipAnalysis: false + tags: + - key: http.url + value: http://localhost:5001/mysql + - key: http.method + value: GET + - key: http.status.code + value: "200" + - operationName: /mysql + operationId: 0 + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: gt 0 + endTime: gt 0 + componentId: 2 + spanType: Exit + peer: server:5000 + skipAnalysis: false + tags: + - key: http.url + value: server:5000/mysql + - key: http.method + value: GET + - key: http.status.code + value: "200" + - key: http.status.msg + value: OK diff --git a/tests/plugins/mysql2/init/init.sql b/tests/plugins/mysql2/init/init.sql new file mode 100644 index 0000000..6d9931d --- /dev/null +++ b/tests/plugins/mysql2/init/init.sql @@ -0,0 +1,7 @@ +use test; + +CREATE TABLE IF NOT EXISTS `user`( + `id` INT UNSIGNED AUTO_INCREMENT, + `name` VARCHAR(100) NOT NULL, + PRIMARY KEY( `id` ) +)ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/tests/plugins/mysql2/server.ts b/tests/plugins/mysql2/server.ts new file mode 100644 index 0000000..efecb96 --- /dev/null +++ b/tests/plugins/mysql2/server.ts @@ -0,0 +1,47 @@ +/*! + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 http from 'http'; +import mysql from 'mysql2'; +import agent from '../../../src'; + +agent.start({ + serviceName: 'server', + maxBufferSize: 1000, +}) + +const server = http.createServer((req, res) => { + const connection = mysql.createConnection({ + host: process.env.MYSQL_HOST || 'mysql', + user: 'root', + password: 'root', + database: 'test' + }); + connection.query( + 'SELECT * FROM `user` WHERE `name` = "u1"', + function (err, results, fields) { + res.end(JSON.stringify({ + results, + fields + })) + } + ); +}) + +server.listen(5000, () => console.info('Listening on port 5000...')); diff --git a/tests/plugins/mysql2/test.ts b/tests/plugins/mysql2/test.ts new file mode 100644 index 0000000..87087d3 --- /dev/null +++ b/tests/plugins/mysql2/test.ts @@ -0,0 +1,56 @@ +/*! + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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 path from 'path'; +import { DockerComposeEnvironment, StartedDockerComposeEnvironment, Wait } from 'testcontainers'; +import axios from 'axios'; +import waitForExpect from 'wait-for-expect'; +import { promises as fs } from 'fs'; + +const rootDir = path.resolve(__dirname); + +describe('plugin tests', () => { + let compose: StartedDockerComposeEnvironment; + + beforeAll(async () => { + compose = await new DockerComposeEnvironment(rootDir, 'docker-compose.yml') + .withWaitStrategy('client', Wait.forHealthCheck()) + .up(); + }); + + afterAll(async () => { + await compose.down(); + }); + + it(__filename, async () => { + await waitForExpect(async () => expect((await axios.get('http://localhost:5001/mysql')).status).toBe(200)); + + const expectedData = await fs.readFile(path.join(rootDir, 'expected.data.yaml'), 'utf8'); + + try { + await waitForExpect(async () => + expect((await axios.post('http://localhost:12800/dataValidate', expectedData)).status).toBe(200), + ); + } catch (e) { + const actualData = (await axios.get('http://localhost:12800/receiveData')).data; + console.info({ actualData }); + throw e; + } + }); +});