diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 94404286cd..53ad4775c1 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -72,6 +72,11 @@ components: - mottibec plugins/node/instrumentation-tedious: [] # Unmaintained + plugins/node/instrumentation-typeorm: + - seemk + - weyert + - t2t2 + - mhennoch plugins/node/opentelemetry-instrumentation-aws-lambda: - jj22ee plugins/node/opentelemetry-instrumentation-aws-sdk: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 16d91bc486..725b970f10 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -28,6 +28,7 @@ "plugins/node/instrumentation-runtime-node": "0.14.0", "plugins/node/instrumentation-socket.io": "0.47.0", "plugins/node/instrumentation-tedious": "0.19.0", + "plugins/node/instrumentation-typeorm": "0.0.1", "plugins/node/instrumentation-undici": "0.11.0", "plugins/node/opentelemetry-instrumentation-aws-lambda": "0.51.0", "plugins/node/opentelemetry-instrumentation-aws-sdk": "0.51.0", diff --git a/package-lock.json b/package-lock.json index 103ffbeed7..ffd91915b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8465,6 +8465,10 @@ "resolved": "plugins/node/instrumentation-tedious", "link": true }, + "node_modules/@opentelemetry/instrumentation-typeorm": { + "resolved": "plugins/node/instrumentation-typeorm", + "link": true + }, "node_modules/@opentelemetry/instrumentation-undici": { "resolved": "plugins/node/instrumentation-undici", "link": true @@ -10432,6 +10436,13 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "dev": true, + "license": "MIT" + }, "node_modules/@teppeis/multimaps": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz", @@ -12188,6 +12199,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -12219,6 +12240,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -14728,6 +14759,13 @@ "node": "*" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, + "license": "MIT" + }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -29230,6 +29268,20 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -30145,6 +30197,23 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/sql-highlight": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.0.0.tgz", + "integrity": "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==", + "dev": true, + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", @@ -34523,6 +34592,527 @@ "node": ">=12.20" } }, + "plugins/node/instrumentation-typeorm": { + "name": "@opentelemetry/instrumentation-typeorm", + "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.200.0" + }, + "devDependencies": { + "@opentelemetry/api": "^1.3.0", + "@opentelemetry/contrib-test-utils": "^0.46.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/mocha": "10.0.10", + "@types/node": "18.18.14", + "nyc": "15.1.0", + "rimraf": "5.0.10", + "sqlite3": "^5.0.2", + "typeorm": "^0.3.21", + "typescript": "5.0.4" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=14.20.1" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/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" + }, + "plugins/node/instrumentation-typeorm/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "plugins/node/instrumentation-typeorm/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": "*" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/nyc/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" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/nyc/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" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/reflect-metadata": { + "version": "0.2.2", + "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 + }, + "plugins/node/instrumentation-typeorm/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" + }, + "plugins/node/instrumentation-typeorm/node_modules/typeorm": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", + "integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^3.9.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.4.5", + "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", + "tslib": "^2.5.0", + "uuid": "^11.0.5", + "yargs": "^17.6.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0", + "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "reflect-metadata": "^0.1.14 || ^0.2.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "plugins/node/instrumentation-typeorm/node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "plugins/node/instrumentation-typeorm/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "plugins/node/instrumentation-typeorm/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" + } + }, + "plugins/node/instrumentation-typeorm/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" + }, + "plugins/node/instrumentation-typeorm/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" + } + }, "plugins/node/instrumentation-undici": { "name": "@opentelemetry/instrumentation-undici", "version": "0.11.0", @@ -44816,6 +45406,291 @@ } } }, + "@opentelemetry/instrumentation-typeorm": { + "version": "file:plugins/node/instrumentation-typeorm", + "requires": { + "@opentelemetry/api": "^1.3.0", + "@opentelemetry/contrib-test-utils": "^0.46.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.200.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/mocha": "10.0.10", + "@types/node": "18.18.14", + "nyc": "15.1.0", + "rimraf": "5.0.10", + "sqlite3": "^5.0.2", + "typeorm": "^0.3.21", + "typescript": "5.0.4" + }, + "dependencies": { + "@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, + "requires": { + "undici-types": "~5.26.4" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "dev": true, + "optional": true, + "peer": true + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "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 + }, + "dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true + }, + "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, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + } + }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "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" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "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, + "requires": { + "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" + } + } + } + }, + "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, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "peer": true + }, + "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 + }, + "typeorm": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", + "integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", + "dev": true, + "requires": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^3.9.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.4.5", + "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", + "tslib": "^2.5.0", + "uuid": "^11.0.5", + "yargs": "^17.6.2" + } + }, + "typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true + }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "dev": true + }, + "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, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "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 + }, + "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, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "@opentelemetry/instrumentation-undici": { "version": "file:plugins/node/instrumentation-undici", "requires": { @@ -47050,6 +47925,12 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, + "@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "dev": true + }, "@teppeis/multimaps": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz", @@ -48535,6 +49416,12 @@ "color-convert": "^2.0.1" } }, + "ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "dev": true + }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -48559,6 +49446,12 @@ } } }, + "app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "dev": true + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -50508,6 +51401,12 @@ "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true }, + "dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true + }, "debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", @@ -61732,6 +62631,16 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -62434,6 +63343,12 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "sql-highlight": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.0.0.tgz", + "integrity": "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==", + "dev": true + }, "sqlite3": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", diff --git a/plugins/node/instrumentation-typeorm/.eslintignore b/plugins/node/instrumentation-typeorm/.eslintignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/.eslintignore @@ -0,0 +1 @@ +build diff --git a/plugins/node/instrumentation-typeorm/.eslintrc.js b/plugins/node/instrumentation-typeorm/.eslintrc.js new file mode 100644 index 0000000000..f756f4488b --- /dev/null +++ b/plugins/node/instrumentation-typeorm/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + "env": { + "mocha": true, + "node": true + }, + ...require('../../../eslint.config.js') +} diff --git a/plugins/node/instrumentation-typeorm/.tav.yml b/plugins/node/instrumentation-typeorm/.tav.yml new file mode 100644 index 0000000000..1df7d3bd2f --- /dev/null +++ b/plugins/node/instrumentation-typeorm/.tav.yml @@ -0,0 +1,4 @@ +'typeorm': + versions: ">=0.3.0" + commands: + - npm run test diff --git a/plugins/node/instrumentation-typeorm/LICENSE b/plugins/node/instrumentation-typeorm/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/plugins/node/instrumentation-typeorm/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/plugins/node/instrumentation-typeorm/NOTICE b/plugins/node/instrumentation-typeorm/NOTICE new file mode 100644 index 0000000000..61844b567e --- /dev/null +++ b/plugins/node/instrumentation-typeorm/NOTICE @@ -0,0 +1,12 @@ +[Based on the instrumentation written by Aspecto](https://github.com/aspecto-io/opentelemetry-ext-js/tree/master/packages/instrumentation-typeorm). + +The library contains the following changes compared to the original: +* Removed `moduleVersionAttributeName` configuration option. +* Changed the function signature of `responseHook`. +* Removed dependency on `is-promise` package. +* Removed usage of SemanticAttributes in favor of explicit values. +* Removed parsing of db operation from raw queries. +* Changed db.database.parameters to db.typeorm.parameters. +* Updated to OpenTelemetry semantic conventions 1.32.0. + +Refer to the changelog for any future changes. diff --git a/plugins/node/instrumentation-typeorm/README.md b/plugins/node/instrumentation-typeorm/README.md new file mode 100644 index 0000000000..5746935151 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/README.md @@ -0,0 +1,80 @@ +# OpenTelemetry `typeorm` Instrumentation for Node.js + +[![NPM Published Version][npm-img]][npm-url] +[![Apache License][license-image]][license-image] + +This module provides automatic instrumentation for the [`typeorm`](https://www.npmjs.com/package/typeorm) package, which may be loaded using the [`@opentelemetry/instrumentation`](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation) package. + +If total installation size is not constrained, it is recommended to use [@opentelemetry/sdk-node](`https://www.npmjs.com/package/@opentelemetry/sdk-node`) for the most seamless instrumentation experience. + +Compatible with OpenTelemetry JS API `^1.3.0` and SDK `2.0+`. + +## Installation + +```bash +npm install --save @opentelemetry/instrumentation-typeorm +``` + +### Supported versions + +- `>=0.3.0 <1` + +## Usage + +```js +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); +const { TypeormInstrumentation } = require('@opentelemetry/instrumentation-typeorm'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); + +const provider = new NodeTracerProvider(); +provider.register(); + +registerInstrumentations({ + instrumentations: [ + new TypeormInstrumentation({ + // see below for available configuration + }), + ], +}); +``` + +### Instrumentation Options + +You can set the following: + +| Options | Type | Description | +| ---------------------------- | -------------------------------------- | ------------------------------------------------------------------------------| +| `responseHook` | `TypeormResponseCustomAttributesFunction` | Hook called before response is returned, which allows to add custom attributes to span. | +| `suppressInternalInstrumentation` | boolean | Typeorm uses mongodb/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. | +| `enableInternalInstrumentation` | boolean | Some methods such as `getManyAndCount` can generate internally multiple spans. To instrument those set this to `true` | +| `enhancedDatabaseReporting` | boolean | set to `true` if you want to capture the parameter values for parameterized SQL queries (**may leak sensitive information**) | + +## Semantic Conventions + +Attributes collected: + +| 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. | + +## 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-typeorm +[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Finstrumentation-typeorm.svg diff --git a/plugins/node/instrumentation-typeorm/package.json b/plugins/node/instrumentation-typeorm/package.json new file mode 100644 index 0000000000..393ea7b784 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/package.json @@ -0,0 +1,63 @@ +{ + "name": "@opentelemetry/instrumentation-typeorm", + "version": "0.0.1", + "description": "OpenTelemetry instrumentation for `typeorm` database data-mapper ORM", + "main": "build/src/index.js", + "types": "build/src/index.d.ts", + "repository": "open-telemetry/opentelemetry-js-contrib", + "scripts": { + "clean": "rimraf build/*", + "compile": "tsc -p .", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "lint:readme": "node ../../../scripts/lint-readme.js", + "prewatch": "npm run precompile", + "prepublishOnly": "npm run compile", + "tdd": "npm run test -- --watch-extensions ts --watch", + "test": "nyc mocha --require '@opentelemetry/contrib-test-utils' 'test/**/*.test.ts'", + "test-all-versions": "tav", + "test:debug": "mocha --inspect-brk --no-timeouts 'test/**/*.test.ts'", + "version:update": "node ../../../scripts/version-update.js", + "watch": "tsc -w" + }, + "keywords": [ + "typeorm", + "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.46.0", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@types/mocha": "10.0.10", + "@types/node": "18.18.14", + "typeorm": "^0.3.21", + "nyc": "15.1.0", + "rimraf": "5.0.10", + "sqlite3": "^5.0.2", + "typescript": "5.0.4" + }, + "dependencies": { + "@opentelemetry/instrumentation": "^0.200.0", + "@opentelemetry/core": "^2.0.0" + }, + "homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/plugins/node/instrumentation-typeorm#readme" +} diff --git a/plugins/node/instrumentation-typeorm/src/index.ts b/plugins/node/instrumentation-typeorm/src/index.ts new file mode 100644 index 0000000000..a0cf02263c --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/index.ts @@ -0,0 +1,17 @@ +/* + * 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 * from './typeorm'; +export * from './types'; diff --git a/plugins/node/instrumentation-typeorm/src/semconv.ts b/plugins/node/instrumentation-typeorm/src/semconv.ts new file mode 100644 index 0000000000..df934ac462 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/semconv.ts @@ -0,0 +1,23 @@ +/* + * 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 ATTR_DB_NAMESPACE = 'db.namespace' as const; +export const ATTR_DB_COLLECTION_NAME = 'db.collection.name' as const; +export const ATTR_DB_OPERATION_NAME = 'db.operation.name' as const; +export const ATTR_DB_QUERY_TEXT = 'db.query.text' as const; +export const ATTR_DB_SYSTEM_NAME = 'db.system.name' as const; +export const ATTR_SERVER_ADDRESS = 'server.address' as const; +export const ATTR_SERVER_PORT = 'server.port' as const; diff --git a/plugins/node/instrumentation-typeorm/src/typeorm.ts b/plugins/node/instrumentation-typeorm/src/typeorm.ts new file mode 100644 index 0000000000..b655551eac --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/typeorm.ts @@ -0,0 +1,439 @@ +/* + * 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, + SpanKind, + SpanStatusCode, + trace, + context, +} from '@opentelemetry/api'; +import { suppressTracing } from '@opentelemetry/core'; +import { + ATTR_DB_COLLECTION_NAME, + ATTR_DB_NAMESPACE, + ATTR_DB_OPERATION_NAME, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, + ATTR_SERVER_ADDRESS, + ATTR_SERVER_PORT, +} from './semconv'; +import { + ExtendedDatabaseAttribute, + TypeormInstrumentationConfig, +} from './types'; +import { + getParamNames, + isTypeormInternalTracingSuppressed, + suppressTypeormInternalTracing, +} from './utils'; +/** @knipignore */ +import { PACKAGE_NAME, PACKAGE_VERSION } from './version'; +import type * as typeorm from 'typeorm'; +import { + InstrumentationBase, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, + isWrapped, + safeExecuteInTheMiddle, +} from '@opentelemetry/instrumentation'; +import * as util from 'util'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type SelectQueryBuilderMethod = keyof typeorm.SelectQueryBuilder; +const selectQueryBuilderExecuteMethods: SelectQueryBuilderMethod[] = [ + 'getRawOne', + 'getCount', + 'getManyAndCount', + 'stream', + 'getMany', + 'getOneOrFail', + 'getOne', + 'getRawAndEntities', + 'getRawMany', +]; +const rawQueryFuncName = 'query'; +type EntityManagerMethods = keyof typeorm.EntityManager; +const functionsUsingEntityPersistExecutor: EntityManagerMethods[] = [ + 'save', + 'remove', + 'softRemove', + 'recover', +]; +const functionsUsingQueryBuilder: EntityManagerMethods[] = [ + 'insert', + 'update', + 'delete', + 'softDelete', + 'restore', + 'count', + 'find', + 'findAndCount', + 'findByIds', + 'findOne', + 'increment', + 'decrement', +]; +const entityManagerMethods: EntityManagerMethods[] = [ + ...functionsUsingEntityPersistExecutor, + ...functionsUsingQueryBuilder, +]; + +export class TypeormInstrumentation extends InstrumentationBase { + constructor(config: TypeormInstrumentationConfig = {}) { + super(PACKAGE_NAME, PACKAGE_VERSION, config); + } + + protected init() { + const selectQueryBuilder = new InstrumentationNodeModuleFile( + 'typeorm/query-builder/SelectQueryBuilder.js', + ['>=0.3.0 <1'], + moduleExports => { + selectQueryBuilderExecuteMethods.map(method => { + if (isWrapped(moduleExports.SelectQueryBuilder.prototype?.[method])) { + this._unwrap(moduleExports.SelectQueryBuilder.prototype, method); + } + this._wrap( + moduleExports.SelectQueryBuilder.prototype, + method, + this._patchQueryBuilder() + ); + }); + + return moduleExports; + }, + moduleExports => { + selectQueryBuilderExecuteMethods.map(method => { + if (isWrapped(moduleExports.SelectQueryBuilder.prototype?.[method])) { + this._unwrap(moduleExports.SelectQueryBuilder.prototype, method); + } + }); + return moduleExports; + } + ); + + const dataSource = new InstrumentationNodeModuleFile( + 'typeorm/data-source/DataSource.js', + ['>=0.3.0 <1'], + moduleExports => { + if (isWrapped(moduleExports.DataSource.prototype?.[rawQueryFuncName])) { + this._unwrap(moduleExports.DataSource.prototype, rawQueryFuncName); + } + this._wrap( + moduleExports.DataSource.prototype, + rawQueryFuncName, + this._patchRawQuery() + ); + + return moduleExports; + }, + moduleExports => { + if (isWrapped(moduleExports.DataSource.prototype?.[rawQueryFuncName])) { + this._unwrap(moduleExports.DataSource.prototype, rawQueryFuncName); + } + return moduleExports; + } + ); + + const entityManager = new InstrumentationNodeModuleFile( + 'typeorm/entity-manager/EntityManager.js', + ['>=0.3.0 <1'], + moduleExports => { + entityManagerMethods.map(method => { + if (isWrapped(moduleExports.EntityManager.prototype?.[method])) { + this._unwrap(moduleExports.EntityManager.prototype, method); + } + this._wrap( + moduleExports.EntityManager.prototype, + method, + this._patchEntityManagerFunction(method) + ); + }); + + return moduleExports; + }, + moduleExports => { + entityManagerMethods.map(method => { + if (isWrapped(moduleExports.EntityManager.prototype?.[method])) { + this._unwrap(moduleExports.EntityManager.prototype, method); + } + }); + return moduleExports; + } + ); + + const module = new InstrumentationNodeModuleDefinition( + 'typeorm', + ['>=0.3.0 <1'], + undefined, + undefined, + [selectQueryBuilder, entityManager, dataSource] + ); + return module; + } + + private _patchEntityManagerFunction(opName: string) { + const self = this; + return (original: Function) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (this: any, ...args: unknown[]) { + if (isTypeormInternalTracingSuppressed(context.active())) { + return original.apply(this, args); + } + const connectionOptions = this?.connection?.options ?? {}; + const attributes: Record = { + [ATTR_DB_SYSTEM_NAME]: connectionOptions.type, + [ATTR_SERVER_ADDRESS]: connectionOptions.host, + [ATTR_SERVER_PORT]: connectionOptions.port, + [ATTR_DB_NAMESPACE]: connectionOptions.database, + [ATTR_DB_OPERATION_NAME]: opName, + [ATTR_DB_QUERY_TEXT]: JSON.stringify(buildStatement(original, args)), + }; + + //ignore EntityMetadataNotFoundError + try { + if (this.metadata) { + attributes[ATTR_DB_COLLECTION_NAME] = this.metadata.tableName; + } else { + const entity = args[0]; + const name = + typeof entity === 'object' ? entity?.constructor?.name : entity; + const metadata = this.connection.getMetadata(name); + if (metadata?.tableName) { + attributes[ATTR_DB_COLLECTION_NAME] = metadata.tableName; + } + } + } catch { + /* */ + } + + Object.entries(attributes).forEach(([key, value]) => { + if (value === undefined) delete attributes[key]; + }); + + const span: Span = self.tracer.startSpan( + buildSpanName(opName, attributes[ATTR_DB_COLLECTION_NAME]), + { + kind: SpanKind.CLIENT, + attributes, + } + ); + + const contextWithSpan = trace.setSpan(context.active(), span); + + const traceContext = self.getConfig().enableInternalInstrumentation + ? contextWithSpan + : suppressTypeormInternalTracing(contextWithSpan); + + const contextWithSuppressTracing = self.getConfig() + .suppressInternalInstrumentation + ? suppressTracing(traceContext) + : traceContext; + + return context.with(contextWithSuppressTracing, () => + self._endSpan(() => original.apply(this, args), span) + ); + }; + }; + } + + private _patchQueryBuilder() { + const self = this; + return (original: Function) => { + return function ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this: typeorm.SelectQueryBuilder, + ...args: unknown[] + ) { + if (isTypeormInternalTracingSuppressed(context.active())) { + return original.apply(this, args); + } + const sql = this.getQuery(); + const parameters = this.getParameters(); + const mainTableName = this.getMainTableName(); + const operation = this.expressionMap.queryType; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const connectionOptions: any = this.connection?.options; + const attributes: Record = { + [ATTR_DB_SYSTEM_NAME]: connectionOptions.type, + [ATTR_SERVER_ADDRESS]: connectionOptions.host, + [ATTR_SERVER_PORT]: connectionOptions.port, + [ATTR_DB_NAMESPACE]: connectionOptions.database, + [ATTR_DB_OPERATION_NAME]: operation, + [ATTR_DB_QUERY_TEXT]: sql, + [ATTR_DB_COLLECTION_NAME]: mainTableName, + }; + if (self.getConfig().enhancedDatabaseReporting) { + try { + attributes[ExtendedDatabaseAttribute.DB_STATEMENT_PARAMETERS] = + JSON.stringify(parameters); + } catch (err) { + /* */ + } + } + const span: Span = self.tracer.startSpan( + buildSpanName(operation, attributes[ATTR_DB_COLLECTION_NAME]), + { + kind: SpanKind.CLIENT, + attributes, + } + ); + + const contextWithSpan = trace.setSpan(context.active(), span); + + const traceContext = self.getConfig().enableInternalInstrumentation + ? contextWithSpan + : suppressTypeormInternalTracing(contextWithSpan); + + const contextWithSuppressTracing = self.getConfig() + ?.suppressInternalInstrumentation + ? suppressTracing(traceContext) + : traceContext; + + return context.with(contextWithSuppressTracing, () => + self._endSpan(() => original.apply(this, args), span) + ); + }; + }; + } + + private _patchRawQuery() { + const self = this; + return (original: Function) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function (this: any, ...args: unknown[]) { + if (isTypeormInternalTracingSuppressed(context.active())) { + return original.apply(this, args); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sql = args[0] as any; + const operation = 'raw query'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const connectionOptions: any = this.options; + const attributes = { + [ATTR_DB_SYSTEM_NAME]: connectionOptions.type, + [ATTR_SERVER_ADDRESS]: connectionOptions.host, + [ATTR_SERVER_PORT]: connectionOptions.port, + [ATTR_DB_NAMESPACE]: connectionOptions.database, + [ATTR_DB_OPERATION_NAME]: operation, + [ATTR_DB_QUERY_TEXT]: sql, + }; + + const span: Span = self.tracer.startSpan(operation, { + kind: SpanKind.CLIENT, + attributes, + }); + + const contextWithSpan = trace.setSpan(context.active(), span); + + const traceContext = self.getConfig().enableInternalInstrumentation + ? contextWithSpan + : suppressTypeormInternalTracing(contextWithSpan); + + const contextWithSuppressTracing = self.getConfig() + ?.suppressInternalInstrumentation + ? suppressTracing(traceContext) + : traceContext; + + return context.with(contextWithSuppressTracing, () => + self._endSpan(() => original.apply(this, args), span) + ); + }; + }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _endSpan(traced: any, span: Span) { + const executeResponseHook = (response: unknown) => { + const hook = this.getConfig().responseHook; + if (hook !== undefined) { + safeExecuteInTheMiddle( + () => hook(span, { response }), + e => { + if (e) this._diag.error('responseHook error', e); + }, + true + ); + } + return response; + }; + try { + const response = traced(); + if (util.types.isPromise(response)) { + return Promise.resolve(response) + .then(response => executeResponseHook(response)) + .catch(err => { + if (err) { + if (typeof err === 'string') { + span.setStatus({ code: SpanStatusCode.ERROR, message: err }); + } else { + span.recordException(err); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: err?.message, + }); + } + } + throw err; + }) + .finally(() => span.end()); + } else { + span.end(); + return executeResponseHook(response); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + span.recordException(error); + span.setStatus({ code: SpanStatusCode.ERROR, message: error?.message }); + span.end(); + throw error; + } + } +} + +const buildStatement = (func: Function, args: any[]) => { + const paramNames = getParamNames(func) || []; + const statement: Record = {}; + paramNames.forEach((pName, i) => { + const value = args[i]; + if (!value) return; + + try { + const stringified = JSON.stringify(value); + if (stringified) { + statement[pName] = args[i]; + return; + } + } catch (_err) { + /* */ + } + if (value?.name) { + statement[pName] = value.name; + return; + } + if (value?.constructor?.name) { + statement[pName] = value.constructor.name; + } + }); + return statement; +}; + +function buildSpanName(operation: string, target: string | undefined): string { + if (target !== undefined) { + return `${operation} ${target}`; + } + + return operation; +} diff --git a/plugins/node/instrumentation-typeorm/src/types.ts b/plugins/node/instrumentation-typeorm/src/types.ts new file mode 100644 index 0000000000..88eb5e8c3d --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/types.ts @@ -0,0 +1,49 @@ +/* + * 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 type { Span } from '@opentelemetry/api'; +import { InstrumentationConfig } from '@opentelemetry/instrumentation'; + +export enum ExtendedDatabaseAttribute { + DB_STATEMENT_PARAMETERS = 'db.typeorm.parameters', +} + +export interface HookInfo { + response: any; +} + +export type TypeormResponseCustomAttributesFunction = ( + span: Span, + info: HookInfo +) => void; + +export interface TypeormInstrumentationConfig extends InstrumentationConfig { + /** hook for adding custom attributes using the response payload */ + responseHook?: TypeormResponseCustomAttributesFunction; + /** + * Typeorm operation use mongodb/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; + /** Some methods such as `getManyAndCount` can generate internally multiple spans. + * To instrument those set this to `true` + */ + enableInternalInstrumentation?: boolean; + /** set to `true` if you want to capture the parameter values for parameterized SQL queries (**may leak sensitive information**) */ + enhancedDatabaseReporting?: boolean; +} diff --git a/plugins/node/instrumentation-typeorm/src/utils/get-func-param-names.ts b/plugins/node/instrumentation-typeorm/src/utils/get-func-param-names.ts new file mode 100644 index 0000000000..61a9cfa33c --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/utils/get-func-param-names.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ +const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm; +const ARGUMENT_NAMES = /([^\s,]+)/g; + +export function getParamNames(func: Function) { + const fnStr = func.toString().replace(STRIP_COMMENTS, ''); + return fnStr + .slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')) + .match(ARGUMENT_NAMES); +} diff --git a/plugins/node/instrumentation-typeorm/src/utils/index.ts b/plugins/node/instrumentation-typeorm/src/utils/index.ts new file mode 100644 index 0000000000..1667497fa2 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/utils/index.ts @@ -0,0 +1,17 @@ +/* + * 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 * from './get-func-param-names'; +export * from './suppressTracing'; diff --git a/plugins/node/instrumentation-typeorm/src/utils/suppressTracing.ts b/plugins/node/instrumentation-typeorm/src/utils/suppressTracing.ts new file mode 100644 index 0000000000..eb871c1991 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/src/utils/suppressTracing.ts @@ -0,0 +1,26 @@ +/* + * 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 { createContextKey, Context } from '@opentelemetry/api'; + +const SUPPRESS_TYPEORM_INTERNAL_TRACING_KEY = createContextKey( + 'instrumentation-typeorm Context Key SUPPRESS_TYPEORM_INTERNAL_TRACING' +); + +export const suppressTypeormInternalTracing = (context: Context) => + context.setValue(SUPPRESS_TYPEORM_INTERNAL_TRACING_KEY, true); + +export const isTypeormInternalTracingSuppressed = (context: Context) => + context.getValue(SUPPRESS_TYPEORM_INTERNAL_TRACING_KEY) === true; diff --git a/plugins/node/instrumentation-typeorm/test/Connection.test.ts b/plugins/node/instrumentation-typeorm/test/Connection.test.ts new file mode 100644 index 0000000000..8999c67576 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/test/Connection.test.ts @@ -0,0 +1,68 @@ +/* + * 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 * as assert from 'assert'; +import { SpanStatusCode } from '@opentelemetry/api'; +import { + ATTR_DB_SYSTEM_NAME, + ATTR_DB_NAMESPACE, + ATTR_DB_OPERATION_NAME, + ATTR_DB_QUERY_TEXT, +} from '../src/semconv'; +import { TypeormInstrumentation } from '../src'; +import { + getTestSpans, + registerInstrumentationTesting, +} from '@opentelemetry/contrib-test-utils'; + +const instrumentation = registerInstrumentationTesting( + new TypeormInstrumentation() +); + +import * as typeorm from 'typeorm'; +import { defaultOptions } from './utils'; + +describe('Connection', () => { + after(() => { + instrumentation.enable(); + }); + beforeEach(() => { + instrumentation.enable(); + }); + afterEach(() => { + instrumentation.disable(); + }); + + describe('single connection', () => { + it('raw query', async () => { + const options = { ...defaultOptions, name: 'rawQuery' }; + const ds = new typeorm.DataSource(options); + await ds.initialize(); + const query = 'select * from user'; + await ds.query(query); + const typeOrmSpans = getTestSpans(); + + assert.strictEqual(typeOrmSpans.length, 1); + assert.strictEqual(typeOrmSpans[0].name, 'raw query'); + assert.strictEqual(typeOrmSpans[0].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], options.type); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], options.database); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'raw query'); + assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], query); + await ds.destroy(); + }); + }); +}); diff --git a/plugins/node/instrumentation-typeorm/test/EntityManager.test.ts b/plugins/node/instrumentation-typeorm/test/EntityManager.test.ts new file mode 100644 index 0000000000..605ce93594 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/test/EntityManager.test.ts @@ -0,0 +1,241 @@ +/* + * 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 * as assert from 'assert'; +import { SpanStatusCode } from '@opentelemetry/api'; +import { + ATTR_DB_COLLECTION_NAME, + ATTR_DB_SYSTEM_NAME, + ATTR_DB_NAMESPACE, + ATTR_DB_OPERATION_NAME, + ATTR_DB_QUERY_TEXT, +} from '../src/semconv'; +import { TypeormInstrumentation } from '../src'; +import { + getTestSpans, + registerInstrumentationTesting, +} from '@opentelemetry/contrib-test-utils'; + +const instrumentation = registerInstrumentationTesting( + new TypeormInstrumentation() +); +import * as typeorm from 'typeorm'; +import { defaultOptions, User } from './utils'; + +describe('EntityManager', () => { + after(() => { + instrumentation.enable(); + }); + beforeEach(() => { + instrumentation.enable(); + }); + afterEach(() => { + instrumentation.disable(); + }); + + describe('single connection', () => { + it('save using connection.manager', async () => { + const options = defaultOptions; + const ds = new typeorm.DataSource(options); + await ds.initialize(); + + const user = new User(1, 'opentelemetry', 'io'); + await ds.manager.save(user); + const typeOrmSpans = getTestSpans(); + + assert.strictEqual(typeOrmSpans.length, 1); + assert.strictEqual(typeOrmSpans[0].name, 'save user'); + assert.strictEqual(typeOrmSpans[0].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], options.type); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], options.database); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'save'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + JSON.stringify({ targetOrEntity: user }) + ); + await ds.destroy(); + }); + + it('save', async () => { + const options = defaultOptions; + const ds = new typeorm.DataSource(options); + await ds.initialize(); + const manager = ds.manager; + const user = new User(1, 'opentelemetry', 'io'); + await manager.save(user); + const typeOrmSpans = getTestSpans(); + + assert.strictEqual(typeOrmSpans.length, 1); + assert.strictEqual(typeOrmSpans[0].name, 'save user'); + assert.strictEqual(typeOrmSpans[0].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], options.type); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], options.database); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'save'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + JSON.stringify({ targetOrEntity: user }) + ); + await ds.destroy(); + }); + + it('remove', async () => { + const options = defaultOptions; + const ds = new typeorm.DataSource(options); + await ds.initialize(); + const manager = ds.manager; + + const user = new User(56, 'opentelemetry', 'io'); + await manager.save(user); + await manager.remove(user); + const typeOrmSpans = getTestSpans(); + + assert.strictEqual(typeOrmSpans.length, 2); + assert.strictEqual(typeOrmSpans[1].name, 'remove user'); + assert.strictEqual(typeOrmSpans[1].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[1].attributes; + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], options.type); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], options.database); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'remove'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + JSON.stringify({ + targetOrEntity: { + id: 56, + firstName: 'opentelemetry', + lastName: 'io', + }, + }) + ); + await ds.destroy(); + }); + + it('update', async () => { + const options = defaultOptions; + const ds = new typeorm.DataSource(options); + await ds.initialize(); + const manager = ds.manager; + const user = new User(56, 'opentelemetry', 'io'); + await manager.save(user); + const partialEntity = { lastName: '.io' }; + await manager.update(User, 56, partialEntity); + const typeOrmSpans = getTestSpans(); + + assert.strictEqual(typeOrmSpans.length, 2); + assert.strictEqual(typeOrmSpans[1].name, 'update user'); + assert.strictEqual(typeOrmSpans[1].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[1].attributes; + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], options.type); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], options.database); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'update'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + JSON.stringify({ target: 'User', criteria: 56, partialEntity }) + ); + await ds.destroy(); + }); + + it('Sets failure status when function throws', async () => { + const ds = new typeorm.DataSource(defaultOptions); + await ds.initialize(); + const manager = ds.manager; + try { + await manager.find({} as any); + } catch (err) {} + + const typeOrmSpans = getTestSpans(); + assert.strictEqual(typeOrmSpans.length, 1); + assert.strictEqual(typeOrmSpans[0].name, 'find'); + assert.strictEqual(typeOrmSpans[0].status.code, SpanStatusCode.ERROR); + assert.strictEqual( + typeOrmSpans[0].status.message, + 'No metadata for "[object Object]" was found.' + ); + await ds.destroy(); + }); + }); + + describe('multiple connections', () => { + const options2: any = { + name: 'connection2', + type: 'sqlite', + database: 'connection2.db', + entities: [User], + synchronize: true, + }; + + it('appends matching connection details to span', async () => { + const ds1 = new typeorm.DataSource(defaultOptions); + await ds1.initialize(); + const ds2 = new typeorm.DataSource(options2); + await ds2.initialize(); + + const manager1 = ds1.manager; + const manager2 = ds2.manager; + + const user = new User(1, 'opentelemetry', 'io'); + await manager1.save(user); + await manager2.remove(user); + + const spans = getTestSpans(); + assert.strictEqual(spans.length, 2); + const sqlite1Span = spans[0]; + const sqlite2Span = spans[1]; + + assert.strictEqual(sqlite1Span.name, 'save user'); + assert.strictEqual( + sqlite1Span.attributes[ATTR_DB_SYSTEM_NAME], + defaultOptions.type + ); + assert.strictEqual( + sqlite1Span.attributes[ATTR_DB_NAMESPACE], + defaultOptions.database + ); + assert.strictEqual( + sqlite1Span.attributes[ATTR_DB_OPERATION_NAME], + 'save' + ); + assert.strictEqual( + sqlite1Span.attributes[ATTR_DB_COLLECTION_NAME], + 'user' + ); + + assert.strictEqual(sqlite2Span.name, 'remove user'); + assert.strictEqual( + sqlite2Span.attributes[ATTR_DB_SYSTEM_NAME], + options2.type + ); + assert.strictEqual( + sqlite2Span.attributes[ATTR_DB_NAMESPACE], + options2.database + ); + assert.strictEqual( + sqlite2Span.attributes[ATTR_DB_OPERATION_NAME], + 'remove' + ); + assert.strictEqual( + sqlite2Span.attributes[ATTR_DB_COLLECTION_NAME], + 'user' + ); + await ds1.destroy(); + await ds2.destroy(); + }); + }); +}); diff --git a/plugins/node/instrumentation-typeorm/test/QueryBuilder.test.ts b/plugins/node/instrumentation-typeorm/test/QueryBuilder.test.ts new file mode 100644 index 0000000000..710155d7cf --- /dev/null +++ b/plugins/node/instrumentation-typeorm/test/QueryBuilder.test.ts @@ -0,0 +1,72 @@ +/* + * 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 * as assert from 'assert'; +import { SpanStatusCode } from '@opentelemetry/api'; +import { + ATTR_DB_NAMESPACE, + ATTR_DB_COLLECTION_NAME, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, + ATTR_SERVER_ADDRESS, + ATTR_SERVER_PORT, +} from '../src/semconv'; +import { TypeormInstrumentation } from '../src'; +import { + getTestSpans, + registerInstrumentationTesting, +} from '@opentelemetry/contrib-test-utils'; + +const instrumentation = registerInstrumentationTesting( + new TypeormInstrumentation() +); + +import * as typeorm from 'typeorm'; +import { defaultOptions, User } from './utils'; + +describe('QueryBuilder', () => { + beforeEach(() => { + instrumentation.enable(); + }); + + afterEach(() => { + instrumentation.disable(); + }); + + it('getManyAndCount', async () => { + const ds = new typeorm.DataSource(defaultOptions); + await ds.initialize(); + const queryBuilder = ds.getRepository(User).createQueryBuilder('user'); + const users = await queryBuilder + .where('user.id = :userId', { userId: '1' }) + .getManyAndCount(); + assert.strictEqual(users.length, 2); + const typeOrmSpans = getTestSpans(); + assert.strictEqual(typeOrmSpans.length, 1); + assert.strictEqual(typeOrmSpans[0].name, 'select user'); + assert.strictEqual(typeOrmSpans[0].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], defaultOptions.type); + assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], defaultOptions.host); + assert.strictEqual(attributes[ATTR_SERVER_PORT], defaultOptions.port); + assert.strictEqual(attributes[ATTR_DB_NAMESPACE], defaultOptions.database); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT "user"."id" AS "user_id", "user"."firstName" AS "user_firstName", "user"."lastName" AS "user_lastName" FROM "user" "user" WHERE "user"."id" = :userId' + ); + await ds.destroy(); + }); +}); diff --git a/plugins/node/instrumentation-typeorm/test/Repository.test.ts b/plugins/node/instrumentation-typeorm/test/Repository.test.ts new file mode 100644 index 0000000000..8dec19a1f1 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/test/Repository.test.ts @@ -0,0 +1,54 @@ +/* + * 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 * as assert from 'assert'; +import { TypeormInstrumentation } from '../src'; +import { + getTestSpans, + registerInstrumentationTesting, +} from '@opentelemetry/contrib-test-utils'; + +const instrumentation = registerInstrumentationTesting( + new TypeormInstrumentation() +); + +import { defaultOptions, User } from './utils'; +import * as typeorm from 'typeorm'; +import { ATTR_DB_COLLECTION_NAME } from '../src/semconv'; + +describe('Repository', () => { + beforeEach(() => { + instrumentation.enable(); + }); + + afterEach(() => { + instrumentation.disable(); + }); + + it('findAndCount', async () => { + const ds = new typeorm.DataSource(defaultOptions); + await ds.initialize(); + const repo = ds.getRepository(User); + const [_users, count] = await repo.findAndCount(); + assert(count === 0); + const spans = getTestSpans(); + assert.strictEqual(spans.length, 1); + const span = spans[0]; + const attributes = span.attributes; + assert.strictEqual(span.name, 'findAndCount user'); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + await ds.destroy(); + }); +}); diff --git a/plugins/node/instrumentation-typeorm/test/config.test.ts b/plugins/node/instrumentation-typeorm/test/config.test.ts new file mode 100644 index 0000000000..f1854002b8 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/test/config.test.ts @@ -0,0 +1,159 @@ +/* + * 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 * as assert from 'assert'; +import { + ATTR_SERVER_PORT, + ATTR_SERVER_ADDRESS, + ATTR_DB_COLLECTION_NAME, + ATTR_DB_NAMESPACE, + ATTR_DB_QUERY_TEXT, + ATTR_DB_SYSTEM_NAME, + ATTR_DB_OPERATION_NAME, +} from '../src/semconv'; +import { + ExtendedDatabaseAttribute, + TypeormInstrumentation, + TypeormInstrumentationConfig, +} from '../src'; + +import { + getTestSpans, + registerInstrumentationTesting, +} from '@opentelemetry/contrib-test-utils'; + +const instrumentation = registerInstrumentationTesting( + new TypeormInstrumentation() +); + +import * as typeorm from 'typeorm'; +import { defaultOptions, User } from './utils'; +import { SpanStatusCode } from '@opentelemetry/api'; + +describe('TypeormInstrumentationConfig', () => { + it('responseHook', async function () { + this.timeout(3_000); + instrumentation.disable(); + const config: TypeormInstrumentationConfig = { + responseHook: (span, { response }) => { + span.setAttribute('test', JSON.stringify(response)); + }, + }; + instrumentation.setConfig(config); + instrumentation.enable(); + + const ds = new typeorm.DataSource(defaultOptions); + await ds.initialize(); + const user = new User(1, 'opentelemetry', 'io'); + await ds.manager.save(user); + const typeOrmSpans = getTestSpans(); + assert.strictEqual(typeOrmSpans.length, 1); + const attributes = typeOrmSpans[0].attributes; + + assert.strictEqual(attributes['test'], JSON.stringify(user)); + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'save'); + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], defaultOptions.type); + await ds.destroy(); + }); + + it('enableInternalInstrumentation:true', async () => { + const config: TypeormInstrumentationConfig = { + enableInternalInstrumentation: true, + }; + instrumentation.setConfig(config); + const ds = new typeorm.DataSource(defaultOptions); + await ds.initialize(); + await ds.manager.findAndCount(User); + const spans = getTestSpans(); + assert.strictEqual(spans.length, 2); + + const findAndCountSpan = spans.find( + s => s.name.indexOf('findAndCount') !== -1 + ); + assert.notStrictEqual(findAndCountSpan, undefined); + assert.strictEqual( + findAndCountSpan?.attributes[ATTR_DB_OPERATION_NAME], + 'findAndCount' + ); + assert.strictEqual( + findAndCountSpan?.attributes[ATTR_DB_COLLECTION_NAME], + 'user' + ); + + const selectSpan = spans.find(s => s.name.indexOf('select') !== -1); + assert.notStrictEqual(selectSpan, undefined); + assert.strictEqual( + selectSpan?.attributes[ATTR_DB_OPERATION_NAME], + 'select' + ); + assert.strictEqual(selectSpan?.attributes[ATTR_DB_COLLECTION_NAME], 'user'); + await ds.destroy(); + }); + + it('enableInternalInstrumentation:false', async () => { + const config: TypeormInstrumentationConfig = { + enableInternalInstrumentation: false, + }; + instrumentation.setConfig(config); + const ds = new typeorm.DataSource(defaultOptions); + await ds.initialize(); + await ds.manager.findAndCount(User); + const spans = getTestSpans(); + assert.strictEqual(spans.length, 1); + const attributes = spans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_OPERATION_NAME], 'findAndCount'); + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], defaultOptions.type); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + await ds.destroy(); + }); + + it('enhancedDatabaseReporting:true', async () => { + const config: TypeormInstrumentationConfig = { + enhancedDatabaseReporting: true, + }; + instrumentation.setConfig(config); + const connectionOptions = defaultOptions; + const ds = new typeorm.DataSource(connectionOptions); + await ds.initialize(); + await ds + .getRepository(User) + .createQueryBuilder('user') + .where('user.id = :userId', { userId: '1' }) + .andWhere('user.firstName = :firstName', { firstName: 'bob' }) + .andWhere('user.lastName = :lastName', { lastName: 'dow' }) + .getMany(); + const typeOrmSpans = getTestSpans(); + assert.strictEqual(typeOrmSpans.length, 1); + assert.strictEqual(typeOrmSpans[0].status.code, SpanStatusCode.UNSET); + const attributes = typeOrmSpans[0].attributes; + assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], connectionOptions.type); + assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], connectionOptions.host); + assert.strictEqual(attributes[ATTR_SERVER_PORT], connectionOptions.port); + assert.strictEqual( + attributes[ATTR_DB_NAMESPACE], + connectionOptions.database + ); + assert.strictEqual(attributes[ATTR_DB_COLLECTION_NAME], 'user'); + assert.strictEqual( + attributes[ATTR_DB_QUERY_TEXT], + 'SELECT "user"."id" AS "user_id", "user"."firstName" AS "user_firstName", "user"."lastName" AS "user_lastName" FROM "user" "user" WHERE "user"."id" = :userId AND "user"."firstName" = :firstName AND "user"."lastName" = :lastName' + ); + assert.strictEqual( + attributes[ExtendedDatabaseAttribute.DB_STATEMENT_PARAMETERS], + JSON.stringify({ userId: '1', firstName: 'bob', lastName: 'dow' }) + ); + await ds.destroy(); + }); +}); diff --git a/plugins/node/instrumentation-typeorm/test/utils.ts b/plugins/node/instrumentation-typeorm/test/utils.ts new file mode 100644 index 0000000000..9c039e4685 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/test/utils.ts @@ -0,0 +1,42 @@ +/* + * 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 * as typeorm from 'typeorm'; + +@typeorm.Entity() +export class User { + @typeorm.PrimaryColumn() + id: number; + + @typeorm.Column() + firstName: string; + + @typeorm.Column() + lastName: string; + + constructor(id: number, firstName: string, lastName: string) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } +} + +export const defaultOptions: any = { + type: 'sqlite', + database: ':memory:', + dropSchema: true, + synchronize: true, + entities: [User], +}; diff --git a/plugins/node/instrumentation-typeorm/tsconfig.json b/plugins/node/instrumentation-typeorm/tsconfig.json new file mode 100644 index 0000000000..9078465762 --- /dev/null +++ b/plugins/node/instrumentation-typeorm/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.base", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": [ + "src/**/*.ts", + "test/**/*.ts" + ] +} diff --git a/release-please-config.json b/release-please-config.json index a55d06f9ec..c66d81cda9 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -35,6 +35,7 @@ "plugins/node/instrumentation-runtime-node": {}, "plugins/node/instrumentation-socket.io": {}, "plugins/node/instrumentation-tedious": {}, + "plugins/node/instrumentation-typeorm": {}, "plugins/node/instrumentation-undici": {}, "plugins/node/opentelemetry-instrumentation-aws-lambda": {}, "plugins/node/opentelemetry-instrumentation-aws-sdk": {},