1+ import type { Span } from '@opentelemetry/api' ;
2+ import type { RedisResponseCustomAttributeFunction } from '@opentelemetry/instrumentation-ioredis' ;
13import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis' ;
4+ import { RedisInstrumentation } from '@opentelemetry/instrumentation-redis-4' ;
25import {
36 SEMANTIC_ATTRIBUTE_CACHE_HIT ,
47 SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE ,
@@ -9,12 +12,14 @@ import {
912 spanToJSON ,
1013} from '@sentry/core' ;
1114import type { IntegrationFn } from '@sentry/types' ;
15+ import { truncate } from '@sentry/utils' ;
1216import { generateInstrumentOnce } from '../../otel/instrument' ;
1317import {
1418 GET_COMMANDS ,
1519 calculateCacheItemSize ,
1620 getCacheKeySafely ,
1721 getCacheOperation ,
22+ isInCommands ,
1823 shouldConsiderForCache ,
1924} from '../../utils/redisCache' ;
2025
@@ -26,64 +31,80 @@ const INTEGRATION_NAME = 'Redis';
2631
2732let _redisOptions : RedisOptions = { } ;
2833
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' , ( ) => {
3079 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 ,
7481 } ) ;
7582} ) ;
7683
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+
77102const _redisIntegration = ( ( options : RedisOptions = { } ) => {
78103 return {
79104 name : INTEGRATION_NAME ,
80105 setupOnce ( ) {
81106 _redisOptions = options ;
82107 instrumentRedis ( ) ;
83-
84- // todo: implement them gradually
85- // new LegacyRedisInstrumentation({}),
86- // new RedisInstrumentation({}),
87108 } ,
88109 } ;
89110} ) satisfies IntegrationFn ;
0 commit comments