1
+ import type { Span } from '@opentelemetry/api' ;
2
+ import type { RedisResponseCustomAttributeFunction } from '@opentelemetry/instrumentation-ioredis' ;
1
3
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis' ;
4
+ import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4' ;
2
5
import {
3
6
SEMANTIC_ATTRIBUTE_CACHE_HIT ,
4
7
SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE ,
@@ -9,12 +12,14 @@ import {
9
12
spanToJSON ,
10
13
} from '@sentry/core' ;
11
14
import type { IntegrationFn } from '@sentry/types' ;
15
+ import { truncate } from '@sentry/utils' ;
12
16
import { generateInstrumentOnce } from '../../otel/instrument' ;
13
17
import {
14
18
GET_COMMANDS ,
15
19
calculateCacheItemSize ,
16
20
getCacheKeySafely ,
17
21
getCacheOperation ,
22
+ isInCommands ,
18
23
shouldConsiderForCache ,
19
24
} from '../../utils/redisCache' ;
20
25
@@ -26,64 +31,80 @@ const INTEGRATION_NAME = 'Redis';
26
31
27
32
let _redisOptions : RedisOptions = { } ;
28
33
29
- export const instrumentRedis = generateInstrumentOnce ( INTEGRATION_NAME , ( ) => {
34
+ const cacheResponseHook : RedisResponseCustomAttributeFunction = ( span : Span , redisCommand , cmdArgs , response ) => {
35
+ span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , 'auto.db.otel.redis' ) ;
36
+
37
+ const safeKey = getCacheKeySafely ( redisCommand , cmdArgs ) ;
38
+ const cacheOperation = getCacheOperation ( redisCommand ) ;
39
+
40
+ if (
41
+ ! safeKey ||
42
+ ! cacheOperation ||
43
+ ! _redisOptions ?. cachePrefixes ||
44
+ ! shouldConsiderForCache ( redisCommand , safeKey , _redisOptions . cachePrefixes )
45
+ ) {
46
+ // not relevant for cache
47
+ return ;
48
+ }
49
+
50
+ // 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
51
+ // We are using params based on the docs: https://opentelemetry.io/docs/specs/semconv/attributes-registry/network/
52
+ const networkPeerAddress = spanToJSON ( span ) . data ?. [ 'net.peer.name' ] ;
53
+ const networkPeerPort = spanToJSON ( span ) . data ?. [ 'net.peer.port' ] ;
54
+ if ( networkPeerPort && networkPeerAddress ) {
55
+ span . setAttributes ( { 'network.peer.address' : networkPeerAddress , 'network.peer.port' : networkPeerPort } ) ;
56
+ }
57
+
58
+ const cacheItemSize = calculateCacheItemSize ( response ) ;
59
+
60
+ if ( cacheItemSize ) {
61
+ span . setAttribute ( SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE , cacheItemSize ) ;
62
+ }
63
+
64
+ if ( isInCommands ( GET_COMMANDS , redisCommand ) && cacheItemSize !== undefined ) {
65
+ span . setAttribute ( SEMANTIC_ATTRIBUTE_CACHE_HIT , cacheItemSize > 0 ) ;
66
+ }
67
+
68
+ span . setAttributes ( {
69
+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : cacheOperation ,
70
+ [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] : safeKey ,
71
+ } ) ;
72
+
73
+ const spanDescription = safeKey . join ( ', ' ) ;
74
+
75
+ span . updateName ( truncate ( spanDescription , 1024 ) ) ;
76
+ } ;
77
+
78
+ const instrumentIORedis = generateInstrumentOnce ( 'IORedis' , ( ) => {
30
79
return new IORedisInstrumentation ( {
31
- responseHook : ( span , redisCommand , cmdArgs , response ) => {
32
- span . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , 'auto.db.otel.redis' ) ;
33
-
34
- const safeKey = getCacheKeySafely ( redisCommand , cmdArgs ) ;
35
- const cacheOperation = getCacheOperation ( redisCommand ) ;
36
-
37
- if (
38
- ! safeKey ||
39
- ! cacheOperation ||
40
- ! _redisOptions ?. cachePrefixes ||
41
- ! shouldConsiderForCache ( redisCommand , safeKey , _redisOptions . cachePrefixes )
42
- ) {
43
- // not relevant for cache
44
- return ;
45
- }
46
-
47
- // 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
48
- // We are using params based on the docs: https://opentelemetry.io/docs/specs/semconv/attributes-registry/network/
49
- const networkPeerAddress = spanToJSON ( span ) . data ?. [ 'net.peer.name' ] ;
50
- const networkPeerPort = spanToJSON ( span ) . data ?. [ 'net.peer.port' ] ;
51
- if ( networkPeerPort && networkPeerAddress ) {
52
- span . setAttributes ( { 'network.peer.address' : networkPeerAddress , 'network.peer.port' : networkPeerPort } ) ;
53
- }
54
-
55
- const cacheItemSize = calculateCacheItemSize ( response ) ;
56
-
57
- if ( cacheItemSize ) {
58
- span . setAttribute ( SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE , cacheItemSize ) ;
59
- }
60
-
61
- if ( GET_COMMANDS . includes ( redisCommand ) && cacheItemSize !== undefined ) {
62
- span . setAttribute ( SEMANTIC_ATTRIBUTE_CACHE_HIT , cacheItemSize > 0 ) ;
63
- }
64
-
65
- span . setAttributes ( {
66
- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : cacheOperation ,
67
- [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] : safeKey ,
68
- } ) ;
69
-
70
- const spanDescription = safeKey . join ( ', ' ) ;
71
-
72
- span . updateName ( spanDescription . length > 1024 ? `${ spanDescription . substring ( 0 , 1024 ) } ...` : spanDescription ) ;
73
- } ,
80
+ responseHook : cacheResponseHook ,
74
81
} ) ;
75
82
} ) ;
76
83
84
+ const instrumentRedis4 = generateInstrumentOnce ( 'Redis-4' , ( ) => {
85
+ return new RedisInstrumentation ( {
86
+ responseHook : cacheResponseHook ,
87
+ } ) ;
88
+ } ) ;
89
+
90
+ /** To be able to preload all Redis OTel instrumentations with just one ID ("Redis"), all the instrumentations are generated in this one function */
91
+ export const instrumentRedis = Object . assign (
92
+ ( ) : void => {
93
+ instrumentIORedis ( ) ;
94
+ instrumentRedis4 ( ) ;
95
+
96
+ // todo: implement them gradually
97
+ // new LegacyRedisInstrumentation({}),
98
+ } ,
99
+ { id : INTEGRATION_NAME } ,
100
+ ) ;
101
+
77
102
const _redisIntegration = ( ( options : RedisOptions = { } ) => {
78
103
return {
79
104
name : INTEGRATION_NAME ,
80
105
setupOnce ( ) {
81
106
_redisOptions = options ;
82
107
instrumentRedis ( ) ;
83
-
84
- // todo: implement them gradually
85
- // new LegacyRedisInstrumentation({}),
86
- // new RedisInstrumentation({}),
87
108
} ,
88
109
} ;
89
110
} ) satisfies IntegrationFn ;
0 commit comments