diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 85120136a8..d6ea67a435 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -72,6 +72,10 @@ components: - blumamir packages/instrumentation-socket.io: - mottibec + packages/instrumentation-sequelize: + - seemk + - t2t2 + - mhennoch packages/instrumentation-tedious: [] # Unmaintained packages/instrumentation-typeorm: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0a774e6f9d..2e5f624394 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -27,6 +27,7 @@ "packages/instrumentation-lru-memoizer": "0.52.0", "packages/instrumentation-mongoose": "0.54.0", "packages/instrumentation-runtime-node": "0.21.0", + "packages/instrumentation-sequelize": "0.1.0", "packages/instrumentation-socket.io": "0.54.0", "packages/instrumentation-tedious": "0.26.0", "packages/instrumentation-typeorm": "0.8.0", diff --git a/package-lock.json b/package-lock.json index 02236413db..357af68cce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2282,7 +2282,6 @@ "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.6", @@ -4172,7 +4171,6 @@ "integrity": "sha512-b7W4snvXYi1T2puUjxamASCCNhNzVSzb/fQUuGSkdjm/AFfJ24jo8kOHQyOcaoArCG71sVQci4vkZaITzl/V1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cucumber/ci-environment": "10.0.1", "@cucumber/cucumber-expressions": "18.0.1", @@ -4254,7 +4252,6 @@ "integrity": "sha512-659CCFsrsyvuBi/Eix1fnhSheMnojSfnBcqJ3IMPNawx7JlrNJDcXYSSdxcUw3n/nG05P+ptCjmiZY3i14p+tA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cucumber/messages": ">=19.1.4 <29" } @@ -4411,7 +4408,6 @@ "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@cucumber/messages": ">=17.1.1" } @@ -4422,7 +4418,6 @@ "integrity": "sha512-2LzZtOwYKNlCuNf31ajkrekoy2M4z0Z1QGiPH40n4gf5t8VOUFb7m1ojtR4LmGvZxBGvJZP8voOmRqDWzBzYKA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/uuid": "10.0.0", "class-transformer": "0.5.1", @@ -7344,7 +7339,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -7646,7 +7640,6 @@ "integrity": "sha512-pzGXp14KF2Q4CDZGQgPK4l8zEg7i6cNkb+10yc8ZA5K41cLe3ZbWW1YxtY2e/glHauOJwTLSVjH4tiRVtOTizg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "iterare": "1.2.1", "tslib": "2.8.1", @@ -7729,7 +7722,6 @@ "integrity": "sha512-UVSf0yaWFBC2Zn2FOWABXxCnyG8XNIXrNnvTFpbUFqaJu1YDdwJ7wQBBqxq9CtJT7ILqSmfhOU7HS0d/0EAxpw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.0.1", @@ -9197,7 +9189,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -9386,7 +9377,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -9926,6 +9916,10 @@ "resolved": "packages/instrumentation-runtime-node", "link": true }, + "node_modules/@opentelemetry/instrumentation-sequelize": { + "resolved": "packages/instrumentation-sequelize", + "link": true + }, "node_modules/@opentelemetry/instrumentation-socket.io": { "resolved": "packages/instrumentation-socket.io", "link": true @@ -10424,7 +10418,6 @@ "integrity": "sha512-MZVUE+l7LmMIYlIjubPosruJ9ltSLGFmJqsXApTqPLyHLjsJUSAbAJb/A3N34fEqean4ddiDkdWzNu4ZKPvRUg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2" }, @@ -12216,6 +12209,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -12447,6 +12450,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mysql": { "version": "2.15.27", "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", @@ -12461,7 +12471,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.9.tgz", "integrity": "sha512-5yBtK0k/q8PjkMXbTfeIEP/XVYnz1R9qZJ3yUicdEW7ppdDJfe+MqXEhpqDL3mtn4Wvs1u0KLEG0RXzCgNpsSg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -12543,7 +12552,6 @@ "integrity": "sha512-I98SaDCar5lvEYl80ClRIUztH/hyWHR+I2f+5yTVp/MQ205HgYkA2b5mVdry/+nsEIrf8I65KA5V/PASx68MsQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "^0.16", @@ -12705,6 +12713,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/validator": { + "version": "13.15.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz", + "integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -12813,7 +12828,6 @@ "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/types": "8.41.0", @@ -13861,7 +13875,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -13978,7 +13991,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14814,7 +14826,6 @@ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -15388,7 +15399,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -17455,8 +17465,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/di": { "version": "0.0.1", @@ -17566,6 +17575,13 @@ "url": "https://dotenvx.com" } }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==", + "dev": true, + "license": "MIT" + }, "node_modules/dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -18287,7 +18303,6 @@ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -21195,6 +21210,16 @@ "node": ">= 0.8.0" } }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "dev": true, + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -22951,7 +22976,6 @@ "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -24533,7 +24557,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -26669,7 +26692,19 @@ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "dev": true, "license": "MIT", - "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, "engines": { "node": "*" } @@ -27964,7 +27999,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -29390,7 +29424,6 @@ "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -30236,7 +30269,6 @@ "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -30699,8 +30731,7 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -31234,6 +31265,13 @@ "node": "*" } }, + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "dev": true, + "license": "MIT" + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -31335,7 +31373,6 @@ "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -31455,7 +31492,6 @@ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -31696,6 +31732,89 @@ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", "dev": true }, + "node_modules/sequelize": { + "version": "6.37.7", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz", + "integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/sequelize/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -33982,7 +34101,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -34453,6 +34571,13 @@ "dev": true, "license": "MIT" }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", + "dev": true, + "license": "MIT" + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -34588,7 +34713,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -35127,7 +35251,6 @@ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -35571,6 +35694,16 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -35686,7 +35819,6 @@ "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -35734,7 +35866,6 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -36142,6 +36273,16 @@ "node": ">=0.1.90" } }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -36681,8 +36822,7 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "packages/auto-configuration-propagators": { "name": "@opentelemetry/auto-configuration-propagators", @@ -39146,6 +39286,312 @@ "dev": true, "license": "MIT" }, + "packages/instrumentation-sequelize": { + "name": "@opentelemetry/instrumentation-sequelize", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.207.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "devDependencies": { + "@opentelemetry/api": "^1.3.0", + "@opentelemetry/contrib-test-utils": "^0.54.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/node": "18.18.14", + "nyc": "15.1.0", + "rimraf": "5.0.10", + "sequelize": "^6.37.3", + "typescript": "5.0.4" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "packages/instrumentation-sequelize/node_modules/@types/node": { + "version": "18.18.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.14.tgz", + "integrity": "sha512-iSOeNeXYNYNLLOMDSVPvIFojclvMZ/HDY2dU17kUlcsOsSQETbWIslJbYLZgA+ox8g2XQwSHKTkght1a5X26lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "packages/instrumentation-sequelize/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/instrumentation-sequelize/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "packages/instrumentation-sequelize/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/instrumentation-sequelize/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "packages/instrumentation-sequelize/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "packages/instrumentation-sequelize/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "packages/instrumentation-sequelize/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "packages/instrumentation-sequelize/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "packages/instrumentation-sequelize/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/instrumentation-sequelize/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "packages/instrumentation-sequelize/node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "packages/instrumentation-sequelize/node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/instrumentation-sequelize/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/instrumentation-sequelize/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "packages/instrumentation-sequelize/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "packages/instrumentation-sequelize/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/instrumentation-sequelize/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "packages/instrumentation-sequelize/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "packages/instrumentation-sequelize/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "packages/instrumentation-socket.io": { "name": "@opentelemetry/instrumentation-socket.io", "version": "0.54.0", diff --git a/packages/instrumentation-sequelize/.tav.yml b/packages/instrumentation-sequelize/.tav.yml new file mode 100644 index 0000000000..49020bf64e --- /dev/null +++ b/packages/instrumentation-sequelize/.tav.yml @@ -0,0 +1,5 @@ +sequelize: + versions: + include: '>=6 <7' + mode: max-7 + commands: npm run test diff --git a/packages/instrumentation-sequelize/LICENSE b/packages/instrumentation-sequelize/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/instrumentation-sequelize/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + 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. diff --git a/packages/instrumentation-sequelize/NOTICE b/packages/instrumentation-sequelize/NOTICE new file mode 100644 index 0000000000..4fee72c6a6 --- /dev/null +++ b/packages/instrumentation-sequelize/NOTICE @@ -0,0 +1,6 @@ +[Based on the instrumentation written by Aspecto](https://github.com/aspecto-io/opentelemetry-ext-js/tree/master/packages/instrumentation-sequelize). + +The library contains the following changes compared to the original: +* Removed `moduleVersionAttributeName` configuration option. +* Various type declaration fixes. +* Upgraded to latest instrumentation and semantic conventions. \ No newline at end of file diff --git a/packages/instrumentation-sequelize/README.md b/packages/instrumentation-sequelize/README.md new file mode 100644 index 0000000000..898611cce8 --- /dev/null +++ b/packages/instrumentation-sequelize/README.md @@ -0,0 +1,96 @@ +# OpenTelemetry `sequelize` Instrumentation for Node.js + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +This module provides automatic instrumentation for the [`sequelize`](https://www.npmjs.com/package/sequelize) package, which may be loaded using the [`@opentelemetry/sdk-trace-node`](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node) package and is included in the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle. + +If total installation size is not constrained, it is recommended to use the [`@opentelemetry/auto-instrumentations-node`](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) bundle with [@opentelemetry/sdk-node](`https://www.npmjs.com/package/@opentelemetry/sdk-node`) for the most seamless instrumentation experience. + +## Installation + +```bash +npm install --save @opentelemetry/instrumentation-sequelize +``` + +### Supported versions + +- [`sequelize`](https://www.npmjs.com/package/sequelize) versions `>=6 <7` + +## Usage + +```js +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); +const { SequelizeInstrumentation } = require('@opentelemetry/instrumentation-sequelize'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); + +const provider = new NodeTracerProvider(); +provider.register(); + +registerInstrumentations({ + instrumentations: [ + new SequelizeInstrumentation({ + // see below for available configuration + }), + ], +}); +export interface SequelizeInstrumentationConfig extends InstrumentationConfig { + /** Hook for adding custom attributes using the query */ + queryHook?: SequelizeQueryHook; + /** Hook for adding custom attributes using the response payload */ + responseHook?: SequelizeResponseCustomAttributesFunction; + /** Set to true if you only want to trace operation which has parent spans */ + ignoreOrphanedSpans?: boolean; + /** + * Sequelize operation use postgres/mysql/mariadb/etc. under the hood. + * If, for example, postgres instrumentation is enabled, a postgres operation will also create + * a postgres span describing the communication. + * Setting the `suppressInternalInstrumentation` config value to `true` will + * cause the instrumentation to suppress instrumentation of underlying operations. + */ + suppressInternalInstrumentation?: boolean; + * An identifier for the database management system (DBMS) product being used. See below for a list of well-known identifiers. +``` + +### Instrumentation Options + +You can set the following: + +| Options | Type | Description | +| --------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| `queryHook` | `SequelizeQueryHook` | Function called before running the query. Allows for adding custom attributes to the span. | +| `responseHook` | `SequelizeResponseCustomAttributesFunction` | Function called after a response is received. Allows for adding custom attributes to the span. | +| `ignoreOrphanedSpans` | `boolean` | Can be set to only produce spans which have parent spans. Default: `false` | +| `suppressInternalInstrumentation` | `boolean` | Set to ignore the underlying database library instrumentation. Default: `false` | + +## Semantic Conventions + +| Attribute | Short Description | +| ---------------------| --------------------------------------------------------------------------- | +| `db.namespace` | The name of the database being accessed. | +| `db.operation.name` | The name of the operation being executed (e.g. the SQL keyword). | +| `db.collection.name` | The name of the table being accessed. | +| `db.query.text` | The database statement being executed. | +| `db.system.name` | An identifier for the database management system (DBMS) product being used. | +| `server.address` | Remote address of the database. | +| `server.port` | Peer port number of the network connection. | +| `network transport` | OSI transport layer or inter-process communication method. | + +Attributes collected: + + +## Useful links + +- For more information on OpenTelemetry, visit: +- For more about OpenTelemetry JavaScript: +- For help or feedback on this project, join us in [GitHub Discussions][discussions-url] + +## License + +Apache 2.0 - See [LICENSE][license-url] for more information. + +[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions +[license-url]: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/LICENSE +[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat +[npm-url]: https://www.npmjs.com/package/@opentelemetry/instrumentation-sequelize +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Finstrumentation-sequelize.svg diff --git a/packages/instrumentation-sequelize/package.json b/packages/instrumentation-sequelize/package.json new file mode 100644 index 0000000000..486a89ac7b --- /dev/null +++ b/packages/instrumentation-sequelize/package.json @@ -0,0 +1,63 @@ +{ + "name": "@opentelemetry/instrumentation-sequelize", + "version": "0.1.0", + "description": "OpenTelemetry instrumentation for `sequelize` ORM", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/open-telemetry/opentelemetry-js-contrib.git", + "directory": "packages/instrumentation-sequelize" + }, + "scripts": { + "clean": "rimraf build/*", + "compile": "tsc -p .", + "compile:with-dependencies": "nx run-many -t compile -p @opentelemetry/instrumentation-sequelize", + "lint:readme": "node ../../scripts/lint-readme.js", + "prepublishOnly": "npm run compile", + "tdd": "yarn test -- --watch-extensions ts --watch", + "test": "nyc --no-clean mocha --require '@opentelemetry/contrib-test-utils' 'test/**/*.test.ts'", + "test-all-versions": "tav", + "version:update": "node ../../scripts/version-update.js", + "watch": "tsc -w" + }, + "keywords": [ + "sequelize", + "instrumentation", + "nodejs", + "opentelemetry", + "tracing" + ], + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "files": [ + "build/src/**/*.js", + "build/src/**/*.js.map", + "build/src/**/*.d.ts" + ], + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "devDependencies": { + "@opentelemetry/api": "^1.3.0", + "@opentelemetry/contrib-test-utils": "^0.54.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/node": "18.18.14", + "nyc": "15.1.0", + "rimraf": "5.0.10", + "sequelize": "^6.37.3", + "typescript": "5.0.4" + }, + "dependencies": { + "@opentelemetry/instrumentation": "^0.207.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-sequelize#readme" +} diff --git a/packages/instrumentation-sequelize/src/index.ts b/packages/instrumentation-sequelize/src/index.ts new file mode 100644 index 0000000000..0cf727963d --- /dev/null +++ b/packages/instrumentation-sequelize/src/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors, Aspecto + * + * 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 { SequelizeInstrumentation } from './instrumentation'; +export type { + SequelizeResponseCustomAttributesFunction, + SequelizeQueryHook, + SequelizeQueryHookParams, + SequelizeInstrumentationConfig, +} from './types'; diff --git a/packages/instrumentation-sequelize/src/instrumentation.ts b/packages/instrumentation-sequelize/src/instrumentation.ts new file mode 100644 index 0000000000..ce920bb11c --- /dev/null +++ b/packages/instrumentation-sequelize/src/instrumentation.ts @@ -0,0 +1,245 @@ +/* + * Copyright The OpenTelemetry Authors, Aspecto + * + * 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 { + context, + Span, + SpanKind, + SpanStatusCode, + trace, + diag, + Attributes, +} from '@opentelemetry/api'; +import { suppressTracing } from '@opentelemetry/core'; +import { + ATTR_DB_COLLECTION_NAME, + ATTR_DB_OPERATION_NAME, + ATTR_DB_NAMESPACE, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, + ATTR_SERVER_ADDRESS, + ATTR_SERVER_PORT, + ATTR_NETWORK_TRANSPORT, + NETWORK_TRANSPORT_VALUE_TCP, +} from '@opentelemetry/semantic-conventions'; +import type * as sequelize from 'sequelize'; +import { SequelizeInstrumentationConfig } from './types'; +/** @knipignore */ +import { PACKAGE_NAME, PACKAGE_VERSION } from './version'; +import { extractTableFromQuery } from './utils'; +import { + InstrumentationBase, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, + isWrapped, + safeExecuteInTheMiddle, +} from '@opentelemetry/instrumentation'; + +export class SequelizeInstrumentation extends InstrumentationBase { + static readonly component = 'sequelize'; + static readonly supportedVersions = '>=6 <7'; + + constructor(config: SequelizeInstrumentationConfig = {}) { + super(PACKAGE_NAME, PACKAGE_VERSION, config); + } + + protected init() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const unpatchConnectionManager = (moduleExports: any) => { + if ( + isWrapped(moduleExports?.ConnectionManager?.prototype?.getConnection) + ) { + this._unwrap( + moduleExports.ConnectionManager.prototype, + 'getConnection' + ); + } + return moduleExports; + }; + const connectionManagerInstrumentation = new InstrumentationNodeModuleFile( + 'sequelize/lib/dialects/abstract/connection-manager.js', + [SequelizeInstrumentation.supportedVersions], + moduleExports => { + if (moduleExports === undefined || moduleExports === null) { + return moduleExports; + } + unpatchConnectionManager(moduleExports); + this._wrap( + moduleExports.ConnectionManager.prototype, + 'getConnection', + this._getConnectionPatch() + ); + return moduleExports; + }, + unpatchConnectionManager + ); + + const unpatch = (moduleExports: typeof sequelize) => { + if (isWrapped(moduleExports.Sequelize.prototype.query)) { + this._unwrap(moduleExports.Sequelize.prototype, 'query'); + } + }; + const module = new InstrumentationNodeModuleDefinition( + SequelizeInstrumentation.component, + [SequelizeInstrumentation.supportedVersions], + moduleExports => { + if (moduleExports === undefined || moduleExports === null) { + return moduleExports; + } + + unpatch(moduleExports); + this._wrap( + moduleExports.Sequelize.prototype, + 'query', + this._createQueryPatch() + ); + + return moduleExports; + }, + unpatch, + [connectionManagerInstrumentation] + ); + return module; + } + + // run getConnection with suppressTracing, as it might call internally to `databaseVersion` function + // which calls `query` and create internal span which we don't need to instrument + private _getConnectionPatch() { + return (original: Function) => { + return function (this: unknown, ...args: unknown[]) { + return context.with(suppressTracing(context.active()), () => + original.apply(this, args) + ); + }; + }; + } + + private _createQueryPatch() { + const self = this; + return (original: sequelize.Sequelize['query']) => { + return function query( + this: sequelize.Sequelize, + ...args: Parameters + ) { + if ( + self.getConfig().ignoreOrphanedSpans && + !trace.getSpan(context.active()) + ) { + return original.apply(this, args); + } + + const sqlOrQuery = args[0]; + const extractStatement = (sql: typeof sqlOrQuery) => { + if (typeof sql === 'string') return sql; + return sql?.query || ''; + }; + const statement = extractStatement(args[0]).trim(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const option = args[1] as any; + let operation = option?.type; + + if (!operation) operation = statement.split(' ')[0]; + + const sequelizeInstance: sequelize.Sequelize = this; + const config = sequelizeInstance?.config; + + let tableName = option?.instance?.constructor?.tableName; + if (!tableName) { + if (Array.isArray(option?.tableNames) && option.tableNames.length > 0) + tableName = option?.tableNames.sort().join(','); + else tableName = extractTableFromQuery(statement); + } + + const attributes: Attributes = { + [ATTR_DB_SYSTEM_NAME]: sequelizeInstance.getDialect(), + [ATTR_DB_NAMESPACE]: config?.database, + [ATTR_DB_OPERATION_NAME]: operation, + [ATTR_DB_QUERY_TEXT]: statement, + [ATTR_DB_COLLECTION_NAME]: tableName, + [ATTR_SERVER_ADDRESS]: config?.host, + [ATTR_SERVER_PORT]: config?.port ? Number(config?.port) : undefined, + [ATTR_NETWORK_TRANSPORT]: self._getNetTransport(config?.protocol), + }; + + const newSpan: Span = self.tracer.startSpan(`Sequelize ${operation}`, { + kind: SpanKind.CLIENT, + attributes, + }); + + const activeContextWithSpan = trace.setSpan(context.active(), newSpan); + + const hook = self.getConfig().queryHook; + if (hook !== undefined && sqlOrQuery !== undefined) { + safeExecuteInTheMiddle( + () => hook(newSpan, { sql: sqlOrQuery, option }), + e => { + if (e) + diag.error('sequelize instrumentation: queryHook error', e); + }, + true + ); + } + + return ( + context + .with( + self.getConfig().suppressInternalInstrumentation + ? suppressTracing(activeContextWithSpan) + : activeContextWithSpan, + () => original.apply(this, args) + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .then((response: any) => { + const responseHook = self.getConfig().responseHook; + if (responseHook !== undefined) { + safeExecuteInTheMiddle( + () => responseHook(newSpan, response), + e => { + if (e) + diag.error( + 'sequelize instrumentation: responseHook error', + e + ); + }, + true + ); + } + return response; + }) + .catch((err: Error) => { + newSpan.setStatus({ + code: SpanStatusCode.ERROR, + message: err.message, + }); + throw err; + }) + .finally(() => { + newSpan.end(); + }) + ); + }; + }; + } + + private _getNetTransport(protocol: string) { + switch (protocol) { + case 'tcp': + return NETWORK_TRANSPORT_VALUE_TCP; + default: + return undefined; + } + } +} diff --git a/packages/instrumentation-sequelize/src/types.ts b/packages/instrumentation-sequelize/src/types.ts new file mode 100644 index 0000000000..287f0becd6 --- /dev/null +++ b/packages/instrumentation-sequelize/src/types.ts @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors, Aspecto + * + * 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 { Span } from '@opentelemetry/api'; +import { InstrumentationConfig } from '@opentelemetry/instrumentation'; + +export interface SequelizeQueryHookParams { + /** The type of sql parameter depends on the database dialect. */ + sql: string | { query: string; values: unknown[] }; + /** The type of option parameter depends on the database dialect. */ + option: any; +} + +export type SequelizeQueryHook = ( + span: Span, + params: T +) => void; + +export type SequelizeResponseCustomAttributesFunction = ( + span: Span, + response: any +) => void; + +export interface SequelizeInstrumentationConfig extends InstrumentationConfig { + /** Hook for adding custom attributes using the query */ + queryHook?: SequelizeQueryHook; + /** Hook for adding custom attributes using the response payload */ + responseHook?: SequelizeResponseCustomAttributesFunction; + /** Set to true if you only want to trace operation which has parent spans */ + ignoreOrphanedSpans?: boolean; + /** + * Sequelize operation use postgres/mysql/mariadb/etc. under the hood. + * If, for example, postgres instrumentation is enabled, a postgres operation will also create + * a postgres span describing the communication. + * Setting the `suppressInternalInstrumentation` config value to `true` will + * cause the instrumentation to suppress instrumentation of underlying operations. + */ + suppressInternalInstrumentation?: boolean; +} diff --git a/packages/instrumentation-sequelize/src/utils.ts b/packages/instrumentation-sequelize/src/utils.ts new file mode 100644 index 0000000000..d31f7541d0 --- /dev/null +++ b/packages/instrumentation-sequelize/src/utils.ts @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors, Aspecto + * + * 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 const extractTableFromQuery = (query: string | null | undefined) => { + try { + const result = query?.match(/(?<=from|join|truncate)\s+"?`?(\w+)"?`?/gi); + if (!Array.isArray(result)) return; + + return result + .map(table => + table + .trim() + .replace(/^"(.*)"$/, '$1') + .replace(/^`(.*)`$/, '$1') + ) + .sort() + .join(','); + } catch { + return; + } +}; diff --git a/packages/instrumentation-sequelize/test/sequelize.test.ts b/packages/instrumentation-sequelize/test/sequelize.test.ts new file mode 100644 index 0000000000..1f38005313 --- /dev/null +++ b/packages/instrumentation-sequelize/test/sequelize.test.ts @@ -0,0 +1,559 @@ +/* + * Copyright The 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 assert from 'assert'; +import { SequelizeInstrumentation } from '../src'; +import { extractTableFromQuery } from '../src/utils'; +import { ReadableSpan } from '@opentelemetry/sdk-trace-base'; +import { + context, + diag, + SpanStatusCode, + DiagConsoleLogger, + ROOT_CONTEXT, +} from '@opentelemetry/api'; +import { + ATTR_DB_COLLECTION_NAME, + ATTR_DB_OPERATION_NAME, + ATTR_DB_NAMESPACE, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, + ATTR_SERVER_ADDRESS, + ATTR_SERVER_PORT, +} from '@opentelemetry/semantic-conventions'; +import { + getTestSpans, + registerInstrumentationTesting, +} from '@opentelemetry/contrib-test-utils'; + +const instrumentation = registerInstrumentationTesting( + new SequelizeInstrumentation() +); + +import * as sequelize from 'sequelize'; + +type QueryFunction = typeof sequelize.Sequelize.prototype.query; + +describe('instrumentation-sequelize', () => { + const getSequelizeSpans = (): ReadableSpan[] => { + return getTestSpans().filter(s => + s.instrumentationScope.name.includes('sequelize') + ) as ReadableSpan[]; + }; + + beforeEach(() => { + instrumentation.enable(); + }); + + afterEach(() => { + instrumentation.disable(); + }); + + describe('postgres', () => { + const DB_SYSTEM = 'postgres'; + const DB_USER = 'some-user'; + const SERVER_ADDRESS = 'localhost'; + const SERVER_PORT = 12345; + const DB_NAME = 'my-db'; + + const instance = new sequelize.Sequelize( + `${DB_SYSTEM}://${DB_USER}@${SERVER_ADDRESS}:${SERVER_PORT}/${DB_NAME}`, + { logging: false } + ); + class User extends sequelize.Model { + firstName: string = ''; + } + + User.init( + { firstName: { type: sequelize.DataTypes.STRING } }, + { sequelize: instance } + ); + + it('create is instrumented', async () => { + try { + await User.create({ firstName: 'OpenTelemetry' }); + } catch { + // Error is thrown but we don't care + } + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + assert.strictEqual(spans[0].status.code, SpanStatusCode.ERROR); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], DB_SYSTEM); + assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], SERVER_ADDRESS); + assert.strictEqual(attributes[ATTR_SERVER_PORT], SERVER_PORT); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], DB_NAME); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'INSERT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.match( + attributes[ATTR_DB_QUERY_TEXT] as string, + /INSERT INTO "Users" \("id","firstName","createdAt","updatedAt"\) VALUES \(DEFAULT,\$1,\$2,\$3\) RETURNING (\*|"id","firstName","createdAt","updatedAt");/ + ); + }); + + it('findAll is instrumented', async () => { + await User.findAll().catch(() => {}); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT "id", "firstName", "createdAt", "updatedAt" FROM "Users" AS "User";' + ); + }); + + it('destroy is instrumented', async () => { + await User.destroy({ where: {}, truncate: true }).catch(() => {}); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'BULKDELETE'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], 'TRUNCATE "Users"'); + }); + + it('count is instrumented', async () => { + await User.count().catch(() => {}); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT count(*) AS "count" FROM "Users" AS "User";' + ); + }); + + it('handled complex query', async () => { + const Op = sequelize.Op; + await User.findOne({ + where: { + username: 'Shlomi', + rank: { + [Op.or]: { + [Op.lt]: 1000, + [Op.eq]: null, + }, + }, + }, + attributes: ['id', 'username'], + order: [['username', 'DESC']], + limit: 10, + offset: 5, + }).catch(() => {}); + + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT "id", "username" FROM "Users" AS "User" WHERE "User"."username" = \'Shlomi\' AND ("User"."rank" < 1000 OR "User"."rank" IS NULL) ORDER BY "User"."username" DESC LIMIT 10 OFFSET 5;' + ); + }); + + it('tableName is taken from init override', async () => { + class Planet extends sequelize.Model {} + const expectedTableName = 'solar-system'; + Planet.init({}, { sequelize: instance, tableName: expectedTableName }); + + await Planet.findAll().catch(() => {}); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + assert.strictEqual( + attributes[ATTR_DB_COLLECTION_NAME], + expectedTableName + ); + }); + + it('handles JOIN queries', async () => { + class Dog extends sequelize.Model { + firstName: string = ''; + } + + Dog.init( + { + firstName: { type: sequelize.DataTypes.STRING }, + owner: { type: sequelize.DataTypes.STRING }, + }, + { sequelize: instance } + ); + Dog.belongsTo(User, { foreignKey: 'firstName' }); + User.hasMany(Dog, { foreignKey: 'firstName' }); + + await Dog.findOne({ + attributes: ['firstName', 'owner'], + include: [ + { + model: User, + attributes: ['firstName'], + required: true, + }, + ], + }).catch(() => {}); + + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Dogs,Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT "Dog"."id", "Dog"."firstName", "Dog"."owner", "User"."id" AS "User.id", "User"."firstName" AS "User.firstName" FROM "Dogs" AS "Dog" INNER JOIN "Users" AS "User" ON "Dog"."firstName" = "User"."id" LIMIT 1;' + ); + }); + }); + + describe('mysql', () => { + const DB_SYSTEM = 'mysql'; + const DB_USER = 'RickSanchez'; + const SERVER_NAME = 'localhost'; + const SERVER_PORT = 34567; + const DB_NAME = 'mysql-db'; + + const instance = new sequelize.Sequelize(DB_NAME, DB_USER, 'password', { + host: SERVER_NAME, + port: SERVER_PORT, + dialect: DB_SYSTEM, + }); + + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + + it('create is instrumented', async () => { + await instance.models.User.create({ firstName: 'OpenTelemetry' }).catch( + () => {} + ); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + assert.strictEqual(spans[0].status.code, SpanStatusCode.ERROR); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], DB_SYSTEM); + assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], SERVER_NAME); + assert.strictEqual(attributes[ATTR_SERVER_PORT], SERVER_PORT); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], DB_NAME); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'INSERT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'INSERT INTO `Users` (`id`,`firstName`,`createdAt`,`updatedAt`) VALUES (DEFAULT,$1,$2,$3);' + ); + }); + + it('findAll is instrumented', async () => { + await instance.models.User.findAll().catch(() => {}); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT `id`, `firstName`, `createdAt`, `updatedAt` FROM `Users` AS `User`;' + ); + }); + + describe('query is instrumented', () => { + it('with options not specified', async () => { + try { + await instance.query('SELECT 1 + 1'); + } catch { + // Do not care about the error + } + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], 'SELECT 1 + 1'); + }); + it('with type not specified in options', async () => { + try { + await instance.query('SELECT 1 + 1', {}); + } catch { + // Do not care about the error + } + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'SELECT'); + assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], 'SELECT 1 + 1'); + }); + + it('with type specified in options', async () => { + try { + await instance.query('SELECT 1 + 1', { + type: sequelize.QueryTypes.RAW, + }); + } catch { + // Do not care about the error + } + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'RAW'); + assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], 'SELECT 1 + 1'); + }); + }); + }); + + describe('sqlite', () => { + const instance = new sequelize.Sequelize('sqlite:memory', { + logging: false, + }); + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + + it('create is instrumented', async () => { + await instance.models.User.create({ firstName: 'OpenTelemetry' }).catch( + () => {} + ); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], 'sqlite'); + assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], 'memory'); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'INSERT'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'Users'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'INSERT INTO `Users` (`id`,`firstName`,`createdAt`,`updatedAt`) VALUES (NULL,$1,$2,$3);' + ); + }); + }); + + describe('config', () => { + describe('queryHook', () => { + it('able to collect query', async () => { + instrumentation.disable(); + const instance = new sequelize.Sequelize( + 'postgres://john@$localhost:1111/my-name', + { logging: false } + ); + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + + const response = { john: 'doe' }; + sequelize.Sequelize.prototype.query = (() => { + return new Promise(resolve => resolve(response)); + }) as QueryFunction; + instrumentation.setConfig({ + queryHook: (span, { sql, option }: { sql: any; option: any }) => { + span.setAttribute('test-sql', 'any'); + span.setAttribute('test-option', 'any'); + }, + }); + instrumentation.enable(); + + await instance.models.User.findAll(); + const spans = getSequelizeSpans(); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes['test-sql'], 'any'); + assert.strictEqual(attributes['test-option'], 'any'); + }); + + it('query hook which throws does not affect span', async () => { + instrumentation.disable(); + const instance = new sequelize.Sequelize( + 'postgres://john@$localhost:1111/my-name', + { logging: false } + ); + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + + const response = { john: 'doe' }; + sequelize.Sequelize.prototype.query = (() => { + return new Promise(resolve => resolve(response)); + }) as QueryFunction; + const mockedLogger = (() => { + let message: string; + let error: Error; + return { + error: (_message: string, _err: Error) => { + message = _message; + error = _err; + }, + debug: () => {}, + getMessage: () => message, + getError: () => error, + }; + })(); + + instrumentation.setConfig({ + queryHook: () => { + throw new Error('Throwing'); + }, + }); + instrumentation.enable(); + diag.setLogger(mockedLogger as any); + await instance.models.User.findAll(); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + assert.strictEqual( + mockedLogger.getMessage(), + 'sequelize instrumentation: queryHook error' + ); + assert.strictEqual(mockedLogger.getError().message, 'Throwing'); + diag.setLogger(new DiagConsoleLogger()); + }); + }); + + describe('responseHook', () => { + it('able to collect response', async () => { + instrumentation.disable(); + const instance = new sequelize.Sequelize( + 'postgres://john@$localhost:1111/my-name', + { logging: false } + ); + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + + const response = { john: 'doe' }; + sequelize.Sequelize.prototype.query = (() => { + return new Promise(resolve => resolve(response)); + }) as QueryFunction; + instrumentation.setConfig({ + responseHook: (span, response) => { + span.setAttribute('test', JSON.stringify(response)); + }, + }); + instrumentation.enable(); + + await instance.models.User.findAll(); + const spans = getSequelizeSpans(); + const attributes = spans[0].attributes; + + assert.strictEqual(attributes['test'], JSON.stringify(response)); + }); + + it('response hook which throws does not affect span', async () => { + instrumentation.disable(); + const instance = new sequelize.Sequelize( + 'postgres://john@$localhost:1111/my-name', + { logging: false } + ); + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + + const response = { john: 'doe' }; + sequelize.Sequelize.prototype.query = (() => { + return new Promise(resolve => resolve(response)); + }) as QueryFunction; + const mockedLogger = (() => { + let message: string; + let error: Error; + return { + error: (_message: string, _err: Error) => { + message = _message; + error = _err; + }, + debug: () => {}, + getMessage: () => message, + getError: () => error, + }; + })(); + + instrumentation.setConfig({ + responseHook: () => { + throw new Error('Throwing'); + }, + }); + instrumentation.enable(); + diag.setLogger(mockedLogger as any); + await instance.models.User.findAll(); + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 1); + assert.strictEqual( + mockedLogger.getMessage(), + 'sequelize instrumentation: responseHook error' + ); + assert.strictEqual(mockedLogger.getError().message, 'Throwing'); + diag.setLogger(new DiagConsoleLogger()); + }); + }); + + describe('ignoreOrphanedSpans', () => { + it('skips when ignoreOrphanedSpans option is true', async () => { + instrumentation.disable(); + const instance = new sequelize.Sequelize( + 'postgres://john@$localhost:1111/my-name', + { logging: false } + ); + instance.define('User', { + firstName: { type: sequelize.DataTypes.STRING }, + }); + instrumentation.setConfig({ + ignoreOrphanedSpans: true, + }); + instrumentation.enable(); + + try { + await context.with(ROOT_CONTEXT, async () => { + await instance.models.User.create({ firstName: 'OpenTelemetry' }); + }); + } catch {} + + const spans = getSequelizeSpans(); + assert.strictEqual(spans.length, 0); + }); + }); + }); + + describe('misc', () => { + it('extractTableFromQuery', async () => { + assert.strictEqual( + extractTableFromQuery('FROM Users JOIN Dogs Where 1243'), + 'Dogs,Users' + ); + assert.strictEqual(extractTableFromQuery('FROM "Users"'), 'Users'); + assert.strictEqual( + extractTableFromQuery( + 'SELECT count(*) AS "count" FROM "Users" AS "User";' + ), + 'Users' + ); + assert.strictEqual( + extractTableFromQuery( + 'SELECT `id`, `firstName`, `createdAt`, `updatedAt` FROM `Users` AS `User`;' + ), + 'Users' + ); + assert.strictEqual(extractTableFromQuery(null), undefined); + assert.strictEqual(extractTableFromQuery(undefined), undefined); + }); + }); +}); diff --git a/packages/instrumentation-sequelize/tsconfig.json b/packages/instrumentation-sequelize/tsconfig.json new file mode 100644 index 0000000000..4078877ce6 --- /dev/null +++ b/packages/instrumentation-sequelize/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "outDir": "build" + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/release-please-config.json b/release-please-config.json index 3b1e3943d4..75a9dec7bc 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -34,6 +34,7 @@ "packages/instrumentation-lru-memoizer": {}, "packages/instrumentation-mongoose": {}, "packages/instrumentation-runtime-node": {}, + "packages/instrumentation-sequelize": {}, "packages/instrumentation-socket.io": {}, "packages/instrumentation-tedious": {}, "packages/instrumentation-typeorm": {},