1010require_relative '../../../../../lib/opentelemetry/instrumentation/redis/patches/redis_v4_client'
1111
1212describe OpenTelemetry ::Instrumentation ::Redis ::Patches ::RedisV4Client do
13+ # NOTE: These tests should be run for redis v4 and redis v5, even though the patches won't be installed on v5.
14+ # Perhaps these tests should live in a different file?
1315 let ( :instrumentation ) { OpenTelemetry ::Instrumentation ::Redis ::Instrumentation . instance }
1416 let ( :exporter ) { EXPORTER }
1517 let ( :password ) { 'passw0rd' }
2123 # will generate one extra span on connect because the Redis client will
2224 # send an AUTH command before doing anything else.
2325 def redis_with_auth ( redis_options = { } )
24- redis_options [ :password ] = password
25- redis_options [ :host ] = redis_host
26- redis_options [ :port ] = redis_port
26+ redis_options [ :password ] || = password
27+ redis_options [ :host ] || = redis_host
28+ redis_options [ :port ] || = redis_port
2729 Redis . new ( redis_options )
2830 end
2931
32+ def redis_version
33+ Gem . loaded_specs [ 'redis' ] &.version
34+ end
35+
36+ def redis_version_major
37+ redis_version &.segments &.first
38+ end
39+
40+ def redis_gte_5?
41+ redis_version_major &.>=( 5 )
42+ end
43+
3044 before do
3145 # ensure obfuscation is off if it was previously set in a different test
3246 config = { db_statement : :include }
@@ -98,16 +112,27 @@ def redis_with_auth(redis_options = {})
98112 redis = redis_with_auth ( db : 1 )
99113 redis . get ( 'K' )
100114
101- _ ( exporter . finished_spans . size ) . must_equal 3
115+ if redis_gte_5?
116+ _ ( exporter . finished_spans . size ) . must_equal 2
117+ select_span = exporter . finished_spans . first
118+ get_span = exporter . finished_spans . last
119+ _ ( select_span . name ) . must_equal 'PIPELINED'
120+ _ ( select_span . attributes [ 'db.statement' ] ) . must_equal ( "AUTH ?\n SELECT 1" )
121+ else
122+ _ ( exporter . finished_spans . size ) . must_equal 3
123+
124+ select_span = exporter . finished_spans [ 1 ]
125+ _ ( select_span . name ) . must_equal 'SELECT'
126+ _ ( select_span . attributes [ 'db.statement' ] ) . must_equal ( 'SELECT 1' )
127+
128+ get_span = exporter . finished_spans . last
129+ end
102130
103- select_span = exporter . finished_spans [ 1 ]
104- _ ( select_span . name ) . must_equal 'SELECT'
105131 _ ( select_span . attributes [ 'db.system' ] ) . must_equal 'redis'
106- _ ( select_span . attributes [ 'db.statement' ] ) . must_equal ( 'SELECT 1' )
107132 _ ( select_span . attributes [ 'net.peer.name' ] ) . must_equal redis_host
108133 _ ( select_span . attributes [ 'net.peer.port' ] ) . must_equal redis_port
134+ _ ( select_span . attributes [ 'db.redis.database_index' ] ) . must_equal 1
109135
110- get_span = exporter . finished_spans . last
111136 _ ( get_span . name ) . must_equal 'GET'
112137 _ ( get_span . attributes [ 'db.system' ] ) . must_equal 'redis'
113138 _ ( get_span . attributes [ 'db.statement' ] ) . must_equal ( 'GET K' )
@@ -150,21 +175,35 @@ def redis_with_auth(redis_options = {})
150175 _ ( last_span . status . code ) . must_equal (
151176 OpenTelemetry ::Trace ::Status ::ERROR
152177 )
153- _ ( last_span . status . description . tr ( '`' , "'" ) ) . must_include (
154- "ERR unknown command 'THIS_IS_NOT_A_REDIS_FUNC', with args beginning with: 'THIS_IS_NOT_A_VALID_ARG"
155- )
178+
179+ if redis_gte_5?
180+ _ ( last_span . status . description . tr ( '`' , "'" ) ) . must_include (
181+ 'Unhandled exception of type: RedisClient::CommandError'
182+ )
183+ else
184+ _ ( last_span . status . description . tr ( '`' , "'" ) ) . must_include (
185+ "ERR unknown command 'THIS_IS_NOT_A_REDIS_FUNC', with args beginning with: 'THIS_IS_NOT_A_VALID_ARG"
186+ )
187+ end
156188 end
157189
158190 it 'records net.peer.name and net.peer.port attributes' do
159- expect do
160- Redis . new ( host : 'example.com' , port : 8321 , timeout : 0.01 ) . auth ( password )
161- end . must_raise Redis ::CannotConnectError
162-
163- _ ( last_span . name ) . must_equal 'AUTH'
164- _ ( last_span . attributes [ 'db.system' ] ) . must_equal 'redis'
165- _ ( last_span . attributes [ 'db.statement' ] ) . must_equal 'AUTH ?'
166- _ ( last_span . attributes [ 'net.peer.name' ] ) . must_equal 'example.com'
167- _ ( last_span . attributes [ 'net.peer.port' ] ) . must_equal 8321
191+ client = Redis . new ( host : 'example.com' , port : 8321 , timeout : 0.01 )
192+ expect { client . auth ( password ) } . must_raise Redis ::CannotConnectError
193+
194+ if redis_gte_5?
195+ skip (
196+ 'Redis 5 is a wrapper around RedisClient, which calls' \
197+ '`ensure_connected` before any of the middlewares are invoked.' \
198+ 'This is more appropriately instrumented via a `#connect` hook in the middleware.'
199+ )
200+ else
201+ _ ( last_span . name ) . must_equal 'AUTH'
202+ _ ( last_span . attributes [ 'db.system' ] ) . must_equal 'redis'
203+ _ ( last_span . attributes [ 'db.statement' ] ) . must_equal 'AUTH ?'
204+ _ ( last_span . attributes [ 'net.peer.name' ] ) . must_equal 'example.com'
205+ _ ( last_span . attributes [ 'net.peer.port' ] ) . must_equal 8321
206+ end
168207 end
169208
170209 it 'traces pipelined commands' do
@@ -185,10 +224,11 @@ def redis_with_auth(redis_options = {})
185224
186225 it 'traces pipelined commands on commit' do
187226 redis = redis_with_auth
188- redis . queue ( [ :set , 'v1' , '0' ] )
189- redis . queue ( [ :incr , 'v1' ] )
190- redis . queue ( [ :get , 'v1' ] )
191- redis . commit
227+ redis . pipelined do |pipeline |
228+ pipeline . set ( 'v1' , '0' )
229+ pipeline . incr ( 'v1' )
230+ pipeline . get ( 'v1' )
231+ end
192232
193233 _ ( exporter . finished_spans . size ) . must_equal 2
194234 _ ( last_span . name ) . must_equal 'PIPELINED'
@@ -225,16 +265,17 @@ def redis_with_auth(redis_options = {})
225265 it 'truncates long db.statements' do
226266 redis = redis_with_auth
227267 the_long_value = 'y' * 100
228- redis . queue ( [ :set , 'v1' , the_long_value ] )
229- redis . queue ( [ :set , 'v1' , the_long_value ] )
230- redis . queue ( [ :set , 'v1' , the_long_value ] )
231- redis . queue ( [ :set , 'v1' , the_long_value ] )
232- redis . queue ( [ :set , 'v1' , the_long_value ] )
233- redis . queue ( [ :set , 'v1' , the_long_value ] )
234- redis . queue ( [ :set , 'v1' , the_long_value ] )
235- redis . queue ( [ :set , 'v1' , the_long_value ] )
236- redis . queue ( [ :set , 'v1' , the_long_value ] )
237- redis . commit
268+ redis . pipelined do |pipeline |
269+ pipeline . set ( 'v1' , the_long_value )
270+ pipeline . set ( 'v1' , the_long_value )
271+ pipeline . set ( 'v1' , the_long_value )
272+ pipeline . set ( 'v1' , the_long_value )
273+ pipeline . set ( 'v1' , the_long_value )
274+ pipeline . set ( 'v1' , the_long_value )
275+ pipeline . set ( 'v1' , the_long_value )
276+ pipeline . set ( 'v1' , the_long_value )
277+ pipeline . set ( 'v1' , the_long_value )
278+ end
238279
239280 expected_db_statement = <<~HEREDOC . chomp
240281 SET v1 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
@@ -297,7 +338,7 @@ def redis_with_auth(redis_options = {})
297338 _ ( exporter . finished_spans . size ) . must_equal 3
298339
299340 set_span = exporter . finished_spans [ 0 ]
300- _ ( set_span . name ) . must_equal ' AUTH'
341+ _ ( set_span . name ) . must_equal ( redis_gte_5? ? 'PIPELINED' : ' AUTH')
301342 _ ( set_span . attributes [ 'db.system' ] ) . must_equal 'redis'
302343 _ ( set_span . attributes ) . wont_include ( 'db.statement' )
303344
@@ -326,7 +367,7 @@ def redis_with_auth(redis_options = {})
326367 _ ( exporter . finished_spans . size ) . must_equal 3
327368
328369 set_span = exporter . finished_spans [ 0 ]
329- _ ( set_span . name ) . must_equal ' AUTH'
370+ _ ( set_span . name ) . must_equal ( redis_gte_5? ? 'PIPELINED' : ' AUTH')
330371 _ ( set_span . attributes [ 'db.system' ] ) . must_equal 'redis'
331372 _ ( set_span . attributes [ 'db.statement' ] ) . must_equal (
332373 'AUTH ?'
0 commit comments