From ac59e7e1d2d8afd758720a26b9c0aba5c11dd4f4 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 2 May 2024 14:08:47 +0200 Subject: [PATCH] feat(ioredis): Add integration for `ioredis` (#11856) Integration for Node `integrations: [Sentry.experimental_redisIntegration()]` --------- Co-authored-by: Francesco Novy --- MIGRATION.md | 1 + .../node-integration-tests/package.json | 1 + .../suites/tracing/redis/docker-compose.yml | 9 ++ .../suites/tracing/redis/scenario-ioredis.js | 37 +++++++ .../suites/tracing/redis/test.ts | 40 +++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/package.json | 1 + packages/node/src/index.ts | 1 + .../node/src/integrations/tracing/index.ts | 2 + .../node/src/integrations/tracing/redis.ts | 25 +++++ packages/remix/src/index.server.ts | 1 + yarn.lock | 100 +++++++++++++++++- 15 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis/test.ts create mode 100644 packages/node/src/integrations/tracing/redis.ts diff --git a/MIGRATION.md b/MIGRATION.md index 082dede43593..0e9af3dc7fc3 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -351,6 +351,7 @@ We now support the following integrations out of the box without extra configura - `mongooseIntegration`: Automatically instruments Mongoose - `mysqlIntegration`: Automatically instruments MySQL - `mysql2Integration`: Automatically instruments MySQL2 +- `redisIntegration`: Automatically instruments Redis (supported clients: ioredis) - `nestIntegration`: Automatically instruments Nest.js - `postgresIntegration`: Automatically instruments PostgreSQL - `prismaIntegration`: Automatically instruments Prisma diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index c9a81caff541..42dcaba9679f 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -44,6 +44,7 @@ "express": "^4.17.3", "graphql": "^16.3.0", "http-terminator": "^3.2.0", + "ioredis": "^5.4.1", "mongodb": "^3.7.3", "mongodb-memory-server-global": "^7.6.3", "mongoose": "^5.13.22", diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml new file mode 100644 index 000000000000..164d5977e33d --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.9' + +services: + db: + image: redis:latest + restart: always + container_name: integration-tests-redis + ports: + - '6379:6379' diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js b/dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js new file mode 100644 index 000000000000..52df06b2a386 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js @@ -0,0 +1,37 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const Redis = require('ioredis'); + +const redis = new Redis({ port: 6379 }); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await redis.set('test-key', 'test-value'); + + await redis.get('test-key'); + } finally { + await redis.disconnect(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts new file mode 100644 index 000000000000..fd441201cebc --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts @@ -0,0 +1,40 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('redis auto instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('should auto-instrument `ioredis` package when using redis.set() and redis.get()', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'set test-key [1 other arguments]', + op: 'db', + data: expect.objectContaining({ + 'db.system': 'redis', + 'net.peer.name': 'localhost', + 'net.peer.port': 6379, + 'db.statement': 'set test-key [1 other arguments]', + }), + }), + expect.objectContaining({ + description: 'get test-key', + op: 'db', + data: expect.objectContaining({ + 'db.system': 'redis', + 'net.peer.name': 'localhost', + 'net.peer.port': 6379, + 'db.statement': 'get test-key', + }), + }), + ]), + }; + + createRunner(__dirname, 'scenario-ioredis.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port=6379'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index a200ca559f7a..71df2903e9eb 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -80,6 +80,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index dcf248f05112..28fa4229ec39 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -85,6 +85,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 47b40a9e19d7..b61dea4a4d77 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -106,6 +106,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index eb1e47b2fd05..c480589a5ef1 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -85,6 +85,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/node/package.json b/packages/node/package.json index 2a21d2b2d162..b9c75f3b9a1b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -63,6 +63,7 @@ "@opentelemetry/instrumentation-graphql": "0.39.0", "@opentelemetry/instrumentation-hapi": "0.36.0", "@opentelemetry/instrumentation-http": "0.48.0", + "@opentelemetry/instrumentation-ioredis": "0.40.0", "@opentelemetry/instrumentation-koa": "0.39.0", "@opentelemetry/instrumentation-mongodb": "0.39.0", "@opentelemetry/instrumentation-mongoose": "0.37.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index fd71dc874974..60b152f346e8 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -17,6 +17,7 @@ export { mongoIntegration } from './integrations/tracing/mongo'; export { mongooseIntegration } from './integrations/tracing/mongoose'; export { mysqlIntegration } from './integrations/tracing/mysql'; export { mysql2Integration } from './integrations/tracing/mysql2'; +export { redisIntegration } from './integrations/tracing/redis'; export { nestIntegration, setupNestErrorHandler } from './integrations/tracing/nest'; export { postgresIntegration } from './integrations/tracing/postgres'; export { prismaIntegration } from './integrations/tracing/prisma'; diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 446a23e91a3b..ec71ec7b8b60 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -12,6 +12,7 @@ import { mysqlIntegration } from './mysql'; import { mysql2Integration } from './mysql2'; import { nestIntegration } from './nest'; import { postgresIntegration } from './postgres'; +import { redisIntegration } from './redis'; /** * With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required. @@ -25,6 +26,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { mongooseIntegration(), mysqlIntegration(), mysql2Integration(), + redisIntegration(), postgresIntegration(), // For now, we do not include prisma by default because it has ESM issues // See https://github.com/prisma/prisma/issues/23410 diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts new file mode 100644 index 000000000000..8e1eebb83b6a --- /dev/null +++ b/packages/node/src/integrations/tracing/redis.ts @@ -0,0 +1,25 @@ +import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis'; +import { defineIntegration } from '@sentry/core'; +import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; +import type { IntegrationFn } from '@sentry/types'; + +const _redisIntegration = (() => { + return { + name: 'Redis', + setupOnce() { + addOpenTelemetryInstrumentation([ + new IORedisInstrumentation({}), + // todo: implement them gradually + // new LegacyRedisInstrumentation({}), + // new RedisInstrumentation({}), + ]); + }, + }; +}) satisfies IntegrationFn; + +/** + * Redis integration for "ioredis" + * + * Capture tracing data for redis and ioredis. + */ +export const redisIntegration = defineIntegration(_redisIntegration); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index fdb9878206f6..d528db5802f0 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -86,6 +86,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/yarn.lock b/yarn.lock index b4ea8976fe76..174a823c5361 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5077,6 +5077,11 @@ resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -6114,6 +6119,13 @@ dependencies: "@opentelemetry/api" "^1.0.0" +"@opentelemetry/api-logs@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.51.0.tgz#71f296661d2215167c748ca044ff184a65d9426b" + integrity sha512-m/jtfBPEIXS1asltl8fPQtO3Sb1qMpuL61unQajUmM8zIxeMF1AlqzWXM3QedcYgTTFiJCew5uJjyhpmqhc0+g== + dependencies: + "@opentelemetry/api" "^1.0.0" + "@opentelemetry/api@1.8.0", "@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.6.0", "@opentelemetry/api@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" @@ -6235,6 +6247,15 @@ "@opentelemetry/semantic-conventions" "1.21.0" semver "^7.5.2" +"@opentelemetry/instrumentation-ioredis@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.40.0.tgz#3a747dc44c6244d7f4c8cc98a6b75b9856241eaf" + integrity sha512-Jv/fH7KhpWe4KBirsiqeUJIYrsdR2iu2l4nWhfOlRvaZ+zYIiLEzTQR6QhBbyRoAbU4OuYJzjWusOmmpGBnwng== + dependencies: + "@opentelemetry/instrumentation" "^0.51.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@opentelemetry/instrumentation-koa@0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.39.0.tgz#9c01d40a444e592a95b6e39ba0bbe94e096bfc31" @@ -6301,6 +6322,24 @@ "@types/pg" "8.6.1" "@types/pg-pool" "2.0.4" +"@opentelemetry/instrumentation-redis-4@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.39.0.tgz#9f9950bca3eb7e2f1cfc53a003c4eef64d8846bc" + integrity sha512-Zpfqfi83KeKgVQ0C2083GZPon3ZPYQ5E59v9FAbhubtOoUb9Rh7n111YD8FPW3sgx6JKp1odXmBmfQhWCaTOpQ== + dependencies: + "@opentelemetry/instrumentation" "^0.51.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-redis@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.39.0.tgz#66a9d94a726deb0ef9c340ba504764ae457676a1" + integrity sha512-yjHWwufY7kfKtf20rliqlETgP32X3ZynGAfoP59NXSSHwTCZS7QMn+S+Hb0iLjwbca/iTM/BooiVFrB943kMrw== + dependencies: + "@opentelemetry/instrumentation" "^0.51.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.22.0" + "@opentelemetry/instrumentation@0.48.0", "@opentelemetry/instrumentation@^0.48.0": version "0.48.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz#a6dee936e973f1270c464657a55bb570807194aa" @@ -6335,6 +6374,18 @@ semver "^7.5.2" shimmer "^1.2.1" +"@opentelemetry/instrumentation@^0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.0.tgz#93dbe96c87da539081d0ccd07475cfc0b0c61233" + integrity sha512-Eg/+Od5bEvzpvZQGhvMyKIkrzB9S7jW+6z9LHEI2VXhl/GrqQ3oBqlzJt4tA6pGtxRmqQWKWGM1wAbwDdW/gUA== + dependencies: + "@opentelemetry/api-logs" "0.51.0" + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.7.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + "@opentelemetry/propagation-utils@^0.30.8": version "0.30.8" resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.8.tgz#5ae1468250e4f225be98b70aed994586248e2de3" @@ -6347,6 +6398,11 @@ dependencies: "@opentelemetry/core" "^1.0.0" +"@opentelemetry/redis-common@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47" + integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g== + "@opentelemetry/resources@1.23.0", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.8.0": version "1.23.0" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.23.0.tgz#4c71430f3e20c4d88b67ef5629759fae108485e5" @@ -12736,6 +12792,11 @@ clsx@^2.0.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + cmd-shim@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" @@ -18886,6 +18947,21 @@ invariant@^2.2.1, invariant@^2.2.2: dependencies: loose-envify "^1.0.0" +ioredis@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40" + integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -20915,6 +20991,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + lodash.defaultsdeep@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" @@ -20943,7 +21024,7 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== -lodash.isarguments@^3.0.0: +lodash.isarguments@^3.0.0, lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= @@ -25963,6 +26044,18 @@ redeyed@~1.0.0: dependencies: esprima "~3.0.0" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + redux@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" @@ -27830,6 +27923,11 @@ stagehand@^1.0.0: dependencies: debug "^4.1.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"