newrelic_rpm 4.2.0.334 → 4.3.0.335

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,414 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ require 'new_relic/agent/transaction'
5
+
6
+ module NewRelic
7
+ module Agent
8
+ #
9
+ # This module contains helper methods to facilitate instrumentation of
10
+ # message brokers.
11
+ #
12
+ # @api public
13
+ module Messaging
14
+ extend self
15
+
16
+ ATTR_DESTINATION = AttributeFilter::DST_TRANSACTION_EVENTS |
17
+ AttributeFilter::DST_TRANSACTION_TRACER |
18
+ AttributeFilter::DST_ERROR_COLLECTOR
19
+
20
+ EMPTY_STRING = ''.freeze
21
+
22
+ # Start a MessageBroker segment configured to trace a messaging action.
23
+ # Finishing this segment will handle timing and recording of the proper
24
+ # metrics for New Relic's messaging features..
25
+ #
26
+ # @param action [Symbol] The message broker action being traced (see
27
+ # NewRelic::Agent::Transaction::MessageBrokerSegment::ACTIONS) for
28
+ # all options.
29
+ #
30
+ # @param library [String] The name of the library being instrumented
31
+ #
32
+ # @param destination_type [Symbol] Type of destination (see
33
+ # NewRelic::Agent::Transaction::MessageBrokerSegment::DESTINATION_TYPES)
34
+ # for all options.
35
+ #
36
+ # @param destination_name [String] Name of destination (queue or
37
+ # exchange name)
38
+ #
39
+ # @param headers [Hash] Metadata about the message and opaque
40
+ # application-level data (optional)
41
+ #
42
+ # @param parameters [Hash] A hash of parameters to be attached to this
43
+ # segment (optional)
44
+ #
45
+ # @param start_time [Time] An instance of Time class denoting the start
46
+ # time of the segment. Value is set by AbstractSegment#start if not
47
+ # given. (optional)
48
+ #
49
+ # @return [NewRelic::Agent::Transaction::MessageBrokerSegment]
50
+ #
51
+ # @api public
52
+ #
53
+ def start_message_broker_segment(action: nil,
54
+ library: nil,
55
+ destination_type: nil,
56
+ destination_name: nil,
57
+ headers: nil,
58
+ parameters: nil,
59
+ start_time: nil)
60
+
61
+ Transaction.start_message_broker_segment(
62
+ action: action,
63
+ library: library,
64
+ destination_type: destination_type,
65
+ destination_name: destination_name,
66
+ headers: headers,
67
+ parameters: parameters,
68
+ start_time: start_time
69
+ )
70
+ end
71
+
72
+ # Wrap a MessageBroker transaction trace around a messaging handling block.
73
+ # This API is intended to be used in library instrumentation when a "push"-
74
+ # style callback is invoked to handle an incoming message.
75
+ #
76
+ # @param library [String] The name of the library being instrumented
77
+ #
78
+ # @param destination_type [Symbol] Type of destination (see
79
+ # +NewRelic::Agent::Transaction::MessageBrokerSegment::DESTINATION_TYPES+)
80
+ # for all options.
81
+ #
82
+ # @param destination_name [String] Name of destination (queue or
83
+ # exchange name)
84
+ #
85
+ # @param headers [Hash] Metadata about the message and opaque
86
+ # application-level data (optional)
87
+ #
88
+ # @param routing_key [String] Value used by AMQP message brokers to route
89
+ # messages to queues
90
+ #
91
+ # @param queue_name [String] Name of AMQP queue that received the
92
+ # message (optional)
93
+ #
94
+ # @param exchange_type [Symbol] Type of last AMQP exchange to deliver the
95
+ # message (optional)
96
+ #
97
+ # @param reply_to [String] Routing key to be used to send AMQP-based RPC
98
+ # response messages (optional)
99
+ #
100
+ # @param correlation_id [String] Application-level value used to correlate
101
+ # AMQP-based RPC response messages to request messages (optional)
102
+ #
103
+ # @param &block [Proc] The block should handle calling the original subscribed
104
+ # callback function
105
+ #
106
+ # @return return value of given block, which will be the same as the
107
+ # return value of an un-instrumented subscribed callback
108
+ #
109
+ # @api public
110
+ #
111
+ def wrap_message_broker_consume_transaction library: nil,
112
+ destination_type: nil,
113
+ destination_name: nil,
114
+ headers: nil,
115
+ routing_key: nil,
116
+ queue_name: nil,
117
+ exchange_type: nil,
118
+ reply_to: nil,
119
+ correlation_id: nil
120
+
121
+ # ruby 2.0.0 does not support required kwargs
122
+ raise ArgumentError, 'missing required argument: library' if library.nil?
123
+ raise ArgumentError, 'missing required argument: destination_type' if destination_type.nil?
124
+ raise ArgumentError, 'missing required argument: destination_name' if destination_name.nil?
125
+
126
+ state = TransactionState.tl_get
127
+ return yield if state.current_transaction
128
+ txn = nil
129
+
130
+ begin
131
+ txn_name = transaction_name library, destination_type, destination_name
132
+ txn = Transaction.start state, :background, transaction_name: txn_name
133
+ consume_message_headers headers, txn, state
134
+
135
+ CrossAppTracing.reject_messaging_cat_headers(headers).each do |k, v|
136
+ txn.add_agent_attribute :"message.headers.#{k}", v, AttributeFilter::DST_NONE unless v.nil?
137
+ end if headers
138
+
139
+ txn.add_agent_attribute :'message.routingKey', routing_key, ATTR_DESTINATION if routing_key
140
+ txn.add_agent_attribute :'message.exchangeType', exchange_type, AttributeFilter::DST_NONE if exchange_type
141
+ txn.add_agent_attribute :'message.correlationId', correlation_id, AttributeFilter::DST_NONE if correlation_id
142
+ txn.add_agent_attribute :'message.queueName', queue_name, ATTR_DESTINATION if queue_name
143
+ txn.add_agent_attribute :'message.replyTo', reply_to, AttributeFilter::DST_NONE if reply_to
144
+ rescue => e
145
+ NewRelic::Agent.logger.error "Error starting Message Broker consume transaction", e
146
+ end
147
+
148
+ yield
149
+ ensure
150
+ begin
151
+ Transaction.stop(state) if txn
152
+ rescue => e
153
+ NewRelic::Agent.logger.error "Error stopping Message Broker consume transaction", e
154
+ end
155
+ end
156
+
157
+ # Start a MessageBroker segment configured to trace an AMQP publish.
158
+ # Finishing this segment will handle timing and recording of the proper
159
+ # metrics for New Relic's messaging features. This method is a convenience
160
+ # wrapper around NewRelic::Agent::Transaction.start_message_broker_segment.
161
+ #
162
+ # @param library [String] The name of the library being instrumented
163
+ #
164
+ # @param destination_name [String] Name of destination (exchange name)
165
+ #
166
+ # @param headers [Hash] The message headers
167
+ #
168
+ # @param routing_key [String] The routing key used for the message (optional)
169
+ #
170
+ # @param reply_to [String] A routing key for use in RPC-models for the
171
+ # receiver to publish a response to (optional)
172
+ #
173
+ # @param correlation_id [String] An application-generated value to link up
174
+ # request and responses in RPC-models (optional)
175
+ #
176
+ # @param exchange_type [String] Type of exchange which determines how
177
+ # message are routed (optional)
178
+ #
179
+ # @return [NewRelic::Agent::Transaction::MessageBrokerSegment]
180
+ #
181
+ # @api public
182
+ #
183
+ def start_amqp_publish_segment(library: nil,
184
+ destination_name: nil,
185
+ headers: nil,
186
+ routing_key: nil,
187
+ reply_to: nil,
188
+ correlation_id: nil,
189
+ exchange_type: nil)
190
+
191
+ # ruby 2.0.0 does not support required kwargs
192
+ raise ArgumentError, 'missing required argument: library' if library.nil?
193
+ raise ArgumentError, 'missing required argument: destination_name' if destination_name.nil?
194
+ raise ArgumentError, 'missing required argument: headers' if headers.nil? && CrossAppTracing.cross_app_enabled?
195
+
196
+ original_headers = headers.nil? ? nil : headers.dup
197
+
198
+ segment = Transaction.start_message_broker_segment(
199
+ action: :produce,
200
+ library: library,
201
+ destination_type: :exchange,
202
+ destination_name: destination_name,
203
+ headers: headers
204
+ )
205
+
206
+ if segment_parameters_enabled?
207
+ segment.params[:headers] = original_headers if original_headers && !original_headers.empty?
208
+ segment.params[:routing_key] = routing_key if routing_key
209
+ segment.params[:reply_to] = reply_to if reply_to
210
+ segment.params[:correlation_id] = correlation_id if correlation_id
211
+ segment.params[:exchange_type] = exchange_type if exchange_type
212
+ end
213
+
214
+ segment
215
+ end
216
+
217
+ # Start a MessageBroker segment configured to trace an AMQP consume.
218
+ # Finishing this segment will handle timing and recording of the proper
219
+ # metrics for New Relic's messaging features. This method is a convenience
220
+ # wrapper around NewRelic::Agent::Transaction.start_message_broker_segment.
221
+ #
222
+ # @param library [String] The name of the library being instrumented
223
+ #
224
+ # @param destination_name [String] Name of destination (exchange name)
225
+ #
226
+ # @param delivery_info [Hash] Metadata about how the message was delivered
227
+ #
228
+ # @param message_properties [Hash] AMQP-specific metadata about the message
229
+ # including headers and opaque application-level data
230
+ #
231
+ # @param exchange_type [String] Type of exchange which determines how
232
+ # messages are routed (optional)
233
+ #
234
+ # @param queue_name [String] The name of the queue the message was
235
+ # consumed from (optional)
236
+ #
237
+ # @param start_time [Time] An instance of Time class denoting the start
238
+ # time of the segment. Value is set by AbstractSegment#start if not
239
+ # given. (optional)
240
+ #
241
+ # @return [NewRelic::Agent::Transaction::MessageBrokerSegment]
242
+ #
243
+ # @api public
244
+ #
245
+ def start_amqp_consume_segment(library: nil,
246
+ destination_name: nil,
247
+ delivery_info: nil,
248
+ message_properties: nil,
249
+ exchange_type: nil,
250
+ queue_name: nil,
251
+ start_time: nil)
252
+
253
+ # ruby 2.0.0 does not support required kwargs
254
+ raise ArgumentError, 'missing required argument: library' if library.nil?
255
+ raise ArgumentError, 'missing required argument: destination_name' if destination_name.nil?
256
+ raise ArgumentError, 'missing required argument: delivery_info' if delivery_info.nil?
257
+ raise ArgumentError, 'missing required argument: message_properties' if message_properties.nil?
258
+
259
+ segment = Transaction.start_message_broker_segment(
260
+ action: :consume,
261
+ library: library,
262
+ destination_name: destination_name,
263
+ destination_type: :exchange,
264
+ headers: message_properties[:headers],
265
+ start_time: start_time
266
+ )
267
+
268
+ if segment_parameters_enabled?
269
+ if message_properties[:headers] && !message_properties[:headers].empty?
270
+ non_cat_headers = CrossAppTracing.reject_messaging_cat_headers message_properties[:headers]
271
+ non_synth_headers = SyntheticsMonitor.reject_messaging_synthetics_header non_cat_headers
272
+ segment.params[:headers] = non_synth_headers unless non_synth_headers.empty?
273
+ end
274
+
275
+ segment.params[:routing_key] = delivery_info[:routing_key] if delivery_info[:routing_key]
276
+ segment.params[:reply_to] = message_properties[:reply_to] if message_properties[:reply_to]
277
+ segment.params[:queue_name] = queue_name if queue_name
278
+ segment.params[:exchange_type] = exchange_type if exchange_type
279
+ segment.params[:exchange_name] = delivery_info[:exchange_name] if delivery_info[:exchange_name]
280
+ segment.params[:correlation_id] = message_properties[:correlation_id] if message_properties[:correlation_id]
281
+ end
282
+
283
+ segment
284
+ end
285
+
286
+ # Wrap a MessageBroker transaction trace around a AMQP messaging handling block.
287
+ # This API is intended to be used in AMQP-specific library instrumentation when a
288
+ # "push"-style callback is invoked to handle an incoming message.
289
+ #
290
+ # @param library [String] The name of the library being instrumented
291
+ #
292
+ # @param destination_name [String] Name of destination (queue or
293
+ # exchange name)
294
+ #
295
+ # @param message_properties [Hash] Metadata about the message and opaque
296
+ # application-level data (optional)
297
+ #
298
+ # @param exchange_type [Symbol] Type of AMQP exchange the message was recieved
299
+ # from (see NewRelic::Agent::Transaction::MessageBrokerSegment::DESTINATION_TYPES)
300
+ #
301
+ # @param queue_name [String] name of the AMQP queue on which the message was
302
+ # received
303
+ #
304
+ # @param &block [Proc] The block should handle calling the original subscribed
305
+ # callback function
306
+ #
307
+ # @return return value of given block, which will be the same as the
308
+ # return value of an un-instrumented subscribed callback
309
+ #
310
+ # @api public
311
+ #
312
+ def wrap_amqp_consume_transaction library: nil,
313
+ destination_name: nil,
314
+ delivery_info: nil,
315
+ message_properties: nil,
316
+ exchange_type: nil,
317
+ queue_name: nil,
318
+ &block
319
+
320
+ wrap_message_broker_consume_transaction library: library,
321
+ destination_type: :exchange,
322
+ destination_name: Instrumentation::Bunny.exchange_name(destination_name),
323
+ routing_key: delivery_info[:routing_key],
324
+ reply_to: message_properties[:reply_to],
325
+ queue_name: queue_name,
326
+ exchange_type: exchange_type,
327
+ headers: message_properties[:headers],
328
+ correlation_id: message_properties[:correlation_id],
329
+ &block
330
+ end
331
+
332
+ private
333
+
334
+ def segment_parameters_enabled?
335
+ NewRelic::Agent.config[:'message_tracer.segment_parameters.enabled']
336
+ end
337
+
338
+ def transaction_name library, destination_type, destination_name
339
+ transaction_name = Transaction::MESSAGE_PREFIX + library
340
+ transaction_name << Transaction::MessageBrokerSegment::SLASH
341
+ transaction_name << Transaction::MessageBrokerSegment::TYPES[destination_type]
342
+ transaction_name << Transaction::MessageBrokerSegment::SLASH
343
+
344
+ case destination_type
345
+ when :queue
346
+ transaction_name << Transaction::MessageBrokerSegment::NAMED
347
+ transaction_name << destination_name
348
+
349
+ when :topic
350
+ transaction_name << Transaction::MessageBrokerSegment::NAMED
351
+ transaction_name << destination_name
352
+
353
+ when :temporary_queue, :temporary_topic
354
+ transaction_name << Transaction::MessageBrokerSegment::TEMP
355
+
356
+ when :exchange
357
+ transaction_name << Transaction::MessageBrokerSegment::NAMED
358
+ transaction_name << destination_name
359
+
360
+ end
361
+
362
+ transaction_name
363
+ end
364
+
365
+ def consume_message_headers headers, transaction, state
366
+ if CrossAppTracing.cross_app_enabled? && CrossAppTracing.message_has_crossapp_request_header?(headers)
367
+ decode_id headers[CrossAppTracing::NR_MESSAGE_BROKER_ID_HEADER], state
368
+ decode_txn_info headers[CrossAppTracing::NR_MESSAGE_BROKER_TXN_HEADER], state
369
+ CrossAppTracing.assign_intrinsic_transaction_attributes state
370
+ end
371
+
372
+ assign_synthetics_header headers[CrossAppTracing::NR_MESSAGE_BROKER_SYNTHETICS_HEADER], transaction
373
+ rescue => e
374
+ NewRelic::Agent.logger.error "Error in consume_message_headers", e
375
+ end
376
+
377
+ def decode_id encoded_id, transaction_state
378
+ decoded_id = if encoded_id.nil?
379
+ EMPTY_STRING
380
+ else
381
+ CrossAppTracing.obfuscator.deobfuscate(encoded_id)
382
+ end
383
+ if CrossAppTracing.trusted_valid_cross_app_id? decoded_id
384
+ transaction_state.client_cross_app_id = decoded_id
385
+ end
386
+ end
387
+
388
+ def decode_txn_info txn_header, transaction_state
389
+ begin
390
+ txn_info = ::JSON.load(CrossAppTracing.obfuscator.deobfuscate(txn_header))
391
+ transaction_state.referring_transaction_info = txn_info
392
+ rescue => e
393
+ NewRelic::Agent.logger.debug("Failure deserializing encoded header in #{self.class}, #{e.class}, #{e.message}")
394
+ nil
395
+ end
396
+ end
397
+
398
+ def assign_synthetics_header synthetics_header, transaction
399
+ if synthetics_header and
400
+ incoming_payload = ::JSON.load(CrossAppTracing.obfuscator.deobfuscate(synthetics_header)) and
401
+ SyntheticsMonitor.is_valid_payload?(incoming_payload) and
402
+ SyntheticsMonitor.is_supported_version?(incoming_payload) and
403
+ SyntheticsMonitor.is_trusted?(incoming_payload)
404
+
405
+ transaction.raw_synthetics_header = synthetics_header
406
+ transaction.synthetics_payload = incoming_payload
407
+ end
408
+ rescue => e
409
+ NewRelic::Agent.logger.error "Error in assign_synthetics_header", e
410
+ end
411
+
412
+ end
413
+ end
414
+ end
@@ -23,9 +23,9 @@ module NewRelic
23
23
  incoming_payload = deserialize_header(encoded_header, SYNTHETICS_HEADER_KEY)
24
24
 
25
25
  return unless incoming_payload &&
26
- is_valid_payload?(incoming_payload) &&
27
- is_supported_version?(incoming_payload) &&
28
- is_trusted?(incoming_payload)
26
+ SyntheticsMonitor.is_valid_payload?(incoming_payload) &&
27
+ SyntheticsMonitor.is_supported_version?(incoming_payload) &&
28
+ SyntheticsMonitor.is_trusted?(incoming_payload)
29
29
 
30
30
  state = NewRelic::Agent::TransactionState.tl_get
31
31
  txn = state.current_transaction
@@ -33,18 +33,27 @@ module NewRelic
33
33
  txn.synthetics_payload = incoming_payload
34
34
  end
35
35
 
36
- def is_supported_version?(incoming_payload)
37
- incoming_payload.first == SUPPORTED_VERSION
38
- end
36
+ class << self
39
37
 
40
- def is_trusted?(incoming_payload)
41
- account_id = incoming_payload[1]
42
- NewRelic::Agent.config[:trusted_account_ids].include?(account_id)
43
- end
38
+ def is_supported_version?(incoming_payload)
39
+ incoming_payload.first == SUPPORTED_VERSION
40
+ end
41
+
42
+ def is_trusted?(incoming_payload)
43
+ account_id = incoming_payload[1]
44
+ NewRelic::Agent.config[:trusted_account_ids].include?(account_id)
45
+ end
46
+
47
+ def is_valid_payload?(incoming_payload)
48
+ incoming_payload.length == EXPECTED_PAYLOAD_LENGTH
49
+ end
50
+
51
+ def reject_messaging_synthetics_header headers
52
+ headers.reject {|k,_| k == NewRelic::Agent::CrossAppTracing::NR_MESSAGE_BROKER_SYNTHETICS_HEADER}
53
+ end
44
54
 
45
- def is_valid_payload?(incoming_payload)
46
- incoming_payload.length == EXPECTED_PAYLOAD_LENGTH
47
55
  end
56
+
48
57
  end
49
58
  end
50
59
  end