|
1 | | -import type { IntegrationFn, Span } from '@sentry/core'; |
2 | | -import { |
3 | | - defineIntegration, |
4 | | - SEMANTIC_ATTRIBUTE_CACHE_HIT, |
5 | | - SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE, |
6 | | - SEMANTIC_ATTRIBUTE_CACHE_KEY, |
7 | | - SEMANTIC_ATTRIBUTE_SENTRY_OP, |
8 | | - spanToJSON, |
9 | | - truncate, |
10 | | - waitForTracingChannelBinding, |
11 | | -} from '@sentry/core'; |
| 1 | +import type { IntegrationFn } from '@sentry/core'; |
| 2 | +import { defineIntegration, waitForTracingChannelBinding } from '@sentry/core'; |
12 | 3 | import * as dc from 'node:diagnostics_channel'; |
13 | 4 | import { subscribeRedisDiagnosticChannels, type RedisTracingChannelFactory } from '@sentry/server-utils'; |
14 | 5 | import { generateInstrumentOnce } from '@sentry/node-core'; |
15 | 6 | import { isDiagnosticsChannelInjectionEnabled } from '../../../sdk/diagnosticsChannelInjection'; |
16 | | -import type { IORedisCommandArgs } from '../../../utils/redisCache'; |
17 | | -import { |
18 | | - calculateCacheItemSize, |
19 | | - GET_COMMANDS, |
20 | | - getCacheKeySafely, |
21 | | - getCacheOperation, |
22 | | - isInCommands, |
23 | | - shouldConsiderForCache, |
24 | | -} from '../../../utils/redisCache'; |
25 | | -import type { IORedisResponseCustomAttributeFunction } from './vendored/types'; |
| 7 | +import { cacheResponseHook, type RedisOptions, setRedisOptions } from './cache'; |
26 | 8 | import { IORedisInstrumentation } from './vendored/ioredis-instrumentation'; |
27 | 9 | import { RedisInstrumentation } from './vendored/redis-instrumentation'; |
28 | 10 |
|
29 | | -interface RedisOptions { |
30 | | - /** |
31 | | - * Define cache prefixes for cache keys that should be captured as a cache span. |
32 | | - * |
33 | | - * Setting this to, for example, `['user:']` will capture cache keys that start with `user:`. |
34 | | - */ |
35 | | - cachePrefixes?: string[]; |
36 | | - /** |
37 | | - * Maximum length of the cache key added to the span description. If the key exceeds this length, it will be truncated. |
38 | | - * |
39 | | - * Passing `0` will use the full cache key without truncation. |
40 | | - * |
41 | | - * By default, the full cache key is used. |
42 | | - */ |
43 | | - maxCacheKeyLength?: number; |
44 | | -} |
| 11 | +// `cacheResponseHook`/`_redisOptions` live in `./cache` (which has no OTel |
| 12 | +// instrumentation imports) so the orchestrion opt-in can pull the hook without |
| 13 | +// dragging the OTel redis instrumentation in. Re-exported here for tests. |
| 14 | +export { _redisOptions, cacheResponseHook } from './cache'; |
45 | 15 |
|
46 | 16 | const INTEGRATION_NAME = 'Redis' as const; |
47 | 17 |
|
48 | | -/* Only exported for testing purposes */ |
49 | | -export let _redisOptions: RedisOptions = {}; |
50 | | - |
51 | | -/* Only exported for testing purposes */ |
52 | | -export const cacheResponseHook: IORedisResponseCustomAttributeFunction = ( |
53 | | - span: Span, |
54 | | - redisCommand: string, |
55 | | - cmdArgs: IORedisCommandArgs, |
56 | | - response: unknown, |
57 | | -) => { |
58 | | - const safeKey = getCacheKeySafely(redisCommand, cmdArgs); |
59 | | - const cacheOperation = getCacheOperation(redisCommand); |
60 | | - |
61 | | - if ( |
62 | | - !safeKey || |
63 | | - !cacheOperation || |
64 | | - !_redisOptions.cachePrefixes || |
65 | | - !shouldConsiderForCache(redisCommand, safeKey, _redisOptions.cachePrefixes) |
66 | | - ) { |
67 | | - // not relevant for cache |
68 | | - return; |
69 | | - } |
70 | | - |
71 | | - // otel/ioredis seems to be using the old standard, as there was a change to those params: https://github.com/open-telemetry/opentelemetry-specification/issues/3199 |
72 | | - // We are using params based on the docs: https://opentelemetry.io/docs/specs/semconv/attributes-registry/network/ |
73 | | - // Fall back to stable semconv attributes (server.address/server.port) when |
74 | | - // old-semconv ones are absent, eg OTEL_SEMCONV_STABILITY_OPT_IN=database |
75 | | - // set for node-redis v4/v5. |
76 | | - const spanData = spanToJSON(span).data; |
77 | | - const networkPeerAddress = spanData['net.peer.name'] ?? spanData['server.address']; |
78 | | - const networkPeerPort = spanData['net.peer.port'] ?? spanData['server.port']; |
79 | | - if (networkPeerPort && networkPeerAddress) { |
80 | | - span.setAttributes({ 'network.peer.address': networkPeerAddress, 'network.peer.port': networkPeerPort }); |
81 | | - } |
82 | | - |
83 | | - const cacheItemSize = calculateCacheItemSize(response); |
84 | | - |
85 | | - if (cacheItemSize) { |
86 | | - span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE, cacheItemSize); |
87 | | - } |
88 | | - |
89 | | - if (isInCommands(GET_COMMANDS, redisCommand) && cacheItemSize !== undefined) { |
90 | | - span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, cacheItemSize > 0); |
91 | | - } |
92 | | - |
93 | | - span.setAttributes({ |
94 | | - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: cacheOperation, |
95 | | - [SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey, |
96 | | - }); |
97 | | - |
98 | | - // todo: change to string[] once EAP supports it |
99 | | - const spanDescription = safeKey.join(', '); |
100 | | - |
101 | | - span.updateName( |
102 | | - _redisOptions.maxCacheKeyLength ? truncate(spanDescription, _redisOptions.maxCacheKeyLength) : spanDescription, |
103 | | - ); |
104 | | -}; |
105 | | - |
106 | 18 | const instrumentIORedis = generateInstrumentOnce(`${INTEGRATION_NAME}.IORedis`, () => { |
107 | 19 | return new IORedisInstrumentation({ |
108 | 20 | responseHook: cacheResponseHook, |
@@ -151,7 +63,7 @@ const _redisIntegration = ((options: RedisOptions = {}) => { |
151 | 63 | return { |
152 | 64 | name: INTEGRATION_NAME, |
153 | 65 | setupOnce() { |
154 | | - _redisOptions = options; |
| 66 | + setRedisOptions(options); |
155 | 67 | instrumentRedis(); |
156 | 68 | }, |
157 | 69 | }; |
|
0 commit comments