newrelic_rpm 9.12.0 → 9.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +16 -20
  4. data/lib/new_relic/agent/configuration/default_source.rb +30 -2
  5. data/lib/new_relic/agent/configuration/manager.rb +8 -0
  6. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  7. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -19
  8. data/lib/new_relic/agent/instrumentation/excon.rb +0 -16
  9. data/lib/new_relic/agent/instrumentation/grape.rb +3 -1
  10. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  11. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  12. data/lib/new_relic/agent/instrumentation/opensearch/prepend.rb +13 -0
  13. data/lib/new_relic/agent/instrumentation/opensearch.rb +25 -0
  14. data/lib/new_relic/agent/instrumentation/redis.rb +7 -5
  15. data/lib/new_relic/agent/instrumentation/sidekiq.rb +0 -14
  16. data/lib/new_relic/agent/instrumentation/sinatra.rb +0 -13
  17. data/lib/new_relic/agent/serverless_handler.rb +241 -12
  18. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  19. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  20. data/lib/new_relic/agent/system_info.rb +14 -0
  21. data/lib/new_relic/agent/transaction/trace_context.rb +1 -1
  22. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  23. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  24. data/lib/new_relic/control/frameworks/rails4.rb +4 -2
  25. data/lib/new_relic/environment_report.rb +6 -2
  26. data/lib/new_relic/language_support.rb +7 -1
  27. data/lib/new_relic/local_environment.rb +1 -4
  28. data/lib/new_relic/version.rb +1 -1
  29. data/lib/tasks/helpers/newrelicyml.rb +73 -11
  30. data/lib/tasks/instrumentation_generator/instrumentation.thor +1 -1
  31. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +3 -3
  32. data/newrelic.yml +63 -54
  33. metadata +10 -2
@@ -4,13 +4,13 @@
4
4
 
5
5
  require 'json'
6
6
  require 'new_relic/base64'
7
+ require 'uri'
8
+
9
+ require_relative 'serverless_handler_event_sources'
7
10
 
8
11
  module NewRelic
9
12
  module Agent
10
13
  class ServerlessHandler
11
- ATTRIBUTE_ARN = 'aws.lambda.arn'
12
- ATTRIBUTE_COLD_START = 'aws.lambda.coldStart'
13
- ATTRIBUTE_REQUEST_ID = 'aws.requestId'
14
14
  AGENT_ATTRIBUTE_DESTINATIONS = NewRelic::Agent::AttributeFilter::DST_TRANSACTION_TRACER |
15
15
  NewRelic::Agent::AttributeFilter::DST_TRANSACTION_EVENTS
16
16
  EXECUTION_ENVIRONMENT = "AWS_Lambda_ruby#{RUBY_VERSION.rpartition('.').first}".freeze
@@ -22,12 +22,15 @@ module NewRelic
22
22
  SUPPORTABILITY_METRIC = 'Supportability/AWSLambda/HandlerInvocation'
23
23
  FUNCTION_NAME = 'lambda_function'
24
24
  PAYLOAD_VERSION = ENV.fetch('NEW_RELIC_SERVERLESS_PAYLOAD_VERSION', 2)
25
+ DIGIT = /\d/
26
+ EVENT_SOURCES = NewRelic::Agent::ServerlessHandlerEventSources.to_hash
25
27
 
26
28
  def self.env_var_set?
27
29
  ENV.key?(LAMBDA_ENVIRONMENT_VARIABLE)
28
30
  end
29
31
 
30
32
  def initialize
33
+ @event = nil
31
34
  @context = nil
32
35
  @payloads = {}
33
36
  end
@@ -35,12 +38,13 @@ module NewRelic
35
38
  def invoke_lambda_function_with_new_relic(event:, context:, method_name:, namespace: nil)
36
39
  NewRelic::Agent.increment_metric(SUPPORTABILITY_METRIC)
37
40
 
38
- @context = context
41
+ @event, @context = event, context
39
42
 
40
- NewRelic::Agent::Tracer.in_transaction(category: :other, name: function_name) do
41
- add_agent_attributes
43
+ NewRelic::Agent::Tracer.in_transaction(category: category, name: function_name) do
44
+ prep_transaction
42
45
 
43
- NewRelic::LanguageSupport.constantize(namespace).send(method_name, event: event, context: context)
46
+ process_response(NewRelic::LanguageSupport.constantize(namespace)
47
+ .send(method_name, event: event, context: context))
44
48
  end
45
49
  ensure
46
50
  harvest!
@@ -86,6 +90,12 @@ module NewRelic
86
90
 
87
91
  private
88
92
 
93
+ def prep_transaction
94
+ process_api_gateway_info
95
+ process_headers
96
+ add_agent_attributes
97
+ end
98
+
89
99
  def harvest!
90
100
  NewRelic::Agent.instance.harvest_and_send_analytic_event_data
91
101
  NewRelic::Agent.instance.harvest_and_send_custom_event_data
@@ -109,6 +119,11 @@ module NewRelic
109
119
  ENV.fetch(LAMBDA_ENVIRONMENT_VARIABLE, FUNCTION_NAME)
110
120
  end
111
121
 
122
+ def category
123
+ @category ||=
124
+ @event&.dig('requestContext', 'http', 'method') || @event&.fetch('httpMethod', nil) ? :web : :other
125
+ end
126
+
112
127
  def write_output
113
128
  string = PAYLOAD_VERSION == 1 ? payload_v1 : payload_v2
114
129
 
@@ -120,7 +135,7 @@ module NewRelic
120
135
  "BEGIN PAYLOAD>>>\n#{string}\n<<<END PAYLOAD"
121
136
  end
122
137
 
123
- def payload_v1
138
+ def payload_v1 # New Relic serverless payload v1
124
139
  payload_hash = {'metadata' => metadata, 'data' => @payloads}
125
140
  json = NewRelic::Agent.agent.service.marshaller.dump(payload_hash)
126
141
  gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
@@ -129,7 +144,7 @@ module NewRelic
129
144
  ::JSON.dump(array)
130
145
  end
131
146
 
132
- def payload_v2
147
+ def payload_v2 # New Relic serverless payload v2
133
148
  json = NewRelic::Agent.agent.service.marshaller.dump(@payloads)
134
149
  gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
135
150
  base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
@@ -137,6 +152,88 @@ module NewRelic
137
152
  ::JSON.dump(array)
138
153
  end
139
154
 
155
+ def determine_api_gateway_version
156
+ return unless @event
157
+
158
+ version = @event.fetch('version', '')
159
+ if version.start_with?('2.')
160
+ return 2
161
+ elsif version.start_with?('1.')
162
+ return 1
163
+ end
164
+
165
+ headers = headers_from_event
166
+ return unless headers
167
+
168
+ if @event.dig('requestContext', 'http', 'path') && @event.dig('requestContext', 'http', 'method')
169
+ 2
170
+ elsif @event.fetch('path', nil) && @event.fetch('httpMethod', nil)
171
+ 1
172
+ end
173
+ end
174
+
175
+ def process_api_gateway_info
176
+ api_v = determine_api_gateway_version
177
+ return unless api_v
178
+
179
+ info = api_v == 2 ? info_for_api_gateway_v2 : info_for_api_gateway_v1
180
+ info[:query_parameters] = @event.fetch('queryStringParameters', nil)
181
+
182
+ @http_method = info[:method]
183
+ @http_uri = http_uri(info)
184
+ end
185
+
186
+ def http_uri(info)
187
+ return unless info[:host] && info[:path]
188
+
189
+ url_str = "https://#{info[:host]}"
190
+ url_str += ":#{info[:port]}" unless info[:host].match?(':')
191
+ url_str += "#{info[:path]}"
192
+
193
+ if info[:query_parameters]
194
+ qp = info[:query_parameters].map { |k, v| "#{k}=#{v}" }.join('&')
195
+ url_str += "?#{qp}"
196
+ end
197
+
198
+ URI.parse(url_str)
199
+ rescue StandardError => e
200
+ NewRelic::Agent.logger.error "ServerlessHandler failed to parse the source HTTP URI: #{e}"
201
+ end
202
+
203
+ def info_for_api_gateway_v2
204
+ ctx = @event.fetch('requestContext', nil)
205
+ return {} unless ctx
206
+
207
+ {method: ctx.dig('http', 'method'),
208
+ path: ctx.dig('http', 'path'),
209
+ host: ctx.fetch('domainName', @event.dig('headers', 'Host')),
210
+ port: @event.dig('headers', 'X-Forwarded-Port') || 443}
211
+ end
212
+
213
+ def info_for_api_gateway_v1
214
+ headers = headers_from_event
215
+ {method: @event.fetch('httpMethod', nil),
216
+ path: @event.fetch('path', nil),
217
+ host: headers.fetch('Host', nil),
218
+ port: headers.fetch('X-Forwarded-Port', 443)}
219
+ end
220
+
221
+ def process_headers
222
+ return unless ::NewRelic::Agent.config[:'distributed_tracing.enabled']
223
+
224
+ headers = headers_from_event
225
+ return unless headers && !headers.empty?
226
+
227
+ dt_headers = headers.fetch(NewRelic::NEWRELIC_KEY, nil)
228
+ return unless dt_headers
229
+
230
+ ::NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(dt_headers, 'Other')
231
+ end
232
+
233
+ def headers_from_event
234
+ @headers ||= @event&.dig('requestContext', 'http') || @event&.dig('headers')
235
+ end
236
+
140
237
  def use_named_pipe?
141
238
  return @use_named_pipe if defined?(@use_named_pipe)
142
239
 
@@ -146,15 +243,142 @@ module NewRelic
146
243
  def add_agent_attributes
147
244
  return unless NewRelic::Agent::Tracer.current_transaction
148
245
 
149
- add_agent_attribute(ATTRIBUTE_COLD_START, true) if cold?
150
- add_agent_attribute(ATTRIBUTE_ARN, @context.invoked_function_arn)
151
- add_agent_attribute(ATTRIBUTE_REQUEST_ID, @context.aws_request_id)
246
+ add_agent_attribute('aws.lambda.coldStart', true) if cold?
247
+ add_agent_attribute('aws.lambda.arn', @context.invoked_function_arn)
248
+ add_agent_attribute('aws.requestId', @context.aws_request_id)
249
+
250
+ add_event_source_attributes
251
+ add_http_attributes if api_gateway_event?
252
+ end
253
+
254
+ def add_http_attributes
255
+ return unless category == :web
256
+
257
+ if @http_uri
258
+ add_agent_attribute('uri.host', @http_uri.host)
259
+ add_agent_attribute('uri.port', @http_uri.port)
260
+ if NewRelic::Agent.instance.attribute_filter.allows_key?('http.url', AttributeFilter::DST_SPAN_EVENTS)
261
+ add_agent_attribute('http.url', @http_uri.to_s)
262
+ end
263
+ end
264
+
265
+ if @http_method
266
+ add_agent_attribute('http.method', @http_method)
267
+ add_agent_attribute('http.request.method', @http_method)
268
+ end
269
+ end
270
+
271
+ def api_gateway_event?
272
+ return false unless @event
273
+
274
+ # '1.0' for API Gateway V1, '2.0' for API Gateway V2
275
+ return true if @event.fetch('version', '').start_with?(DIGIT)
276
+
277
+ return false unless headers_from_event
278
+
279
+ # API Gateway V1 - look for toplevel 'path' and 'httpMethod' keys if a version is unset
280
+ return true if @event.fetch('path', nil) && @event.fetch('httpMethod', nil)
281
+
282
+ # API Gateway V2 - look for 'requestContext/http' inner nested 'path' and 'method' keys if a version is unset
283
+ return true if @event.dig('requestContext', 'http', 'path') && @event.dig('requestContext', 'http', 'method')
284
+
285
+ false
286
+ end
287
+
288
+ def add_event_source_attributes
289
+ arn = event_source_arn
290
+ add_agent_attribute('aws.lambda.eventSource.arn', arn) if arn
291
+
292
+ info = event_source_event_info
293
+ return unless info
294
+
295
+ add_agent_attribute('aws.lambda.eventSource.eventType', info['name'])
296
+
297
+ info['attributes'].each do |name, elements|
298
+ next if elements.empty?
299
+
300
+ size = false
301
+ if elements.last.eql?('#size')
302
+ elements = elements.dup
303
+ elements.pop
304
+ size = true
305
+ end
306
+ value = @event.dig(*elements)
307
+ value = value.size if size
308
+ next unless value
309
+
310
+ add_agent_attribute(name, value)
311
+ end
312
+ end
313
+
314
+ def event_source_arn
315
+ return unless @event
316
+
317
+ # SQS/Kinesis Stream/DynamoDB/CodeCommit/S3/SNS
318
+ return event_source_arn_for_records if @event.fetch('Records', nil)
319
+
320
+ # Kinesis Firehose
321
+ ds_arn = @event.fetch('deliveryStreamArn', nil) if @event.fetch('records', nil)
322
+ return ds_arn if ds_arn
323
+
324
+ # ELB
325
+ elb_arn = @event.dig('requestContext', 'elb', 'targetGroupArn')
326
+ return elb_arn if elb_arn
327
+
328
+ # (other)
329
+ es_arn = @event.dig('resources', 0)
330
+ return es_arn if es_arn
331
+
332
+ NewRelic::Agent.logger.debug 'Unable to determine an event source arn'
333
+
334
+ nil
335
+ end
336
+
337
+ def event_source_event_info
338
+ return unless @event
339
+
340
+ # if every required key for a source is found, consider that source
341
+ # to be a match
342
+ EVENT_SOURCES.each_value do |info|
343
+ return info unless info['required_keys'].detect { |r| @event.dig(*r).nil? }
344
+ end
345
+
346
+ nil
347
+ end
348
+
349
+ def event_source_arn_for_records
350
+ record = @event['Records'].first
351
+ unless record
352
+ NewRelic::Agent.logger.debug "Unable to find any records in the event's 'Records' array"
353
+ return
354
+ end
355
+
356
+ arn = record.fetch('eventSourceARN', nil) || # SQS/Kinesis Stream/DynamoDB/CodeCommit
357
+ record.dig('s3', 'bucket', 'arn') || # S3
358
+ record.fetch('EventSubscriptionArn', nil) # SNS
359
+
360
+ unless arn
361
+ NewRelic::Agent.logger.debug "Unable to determine an event source arn from the event's 'Records' array"
362
+ end
363
+
364
+ arn
152
365
  end
153
366
 
154
367
  def add_agent_attribute(attribute, value)
155
368
  NewRelic::Agent::Tracer.current_transaction.add_agent_attribute(attribute, value, AGENT_ATTRIBUTE_DESTINATIONS)
156
369
  end
157
370
 
371
+ def process_response(response)
372
+ return response unless category == :web && response.respond_to?(:fetch)
373
+
374
+ http_status = response.fetch(:statusCode, response.fetch('statusCode', nil))
375
+ return unless http_status
376
+
377
+ add_agent_attribute('http.statusCode', http_status)
378
+
379
+ response
380
+ end
381
+
158
382
  def cold?
159
383
  return @cold if defined?(@cold)
160
384
 
@@ -163,7 +387,12 @@ module NewRelic
163
387
  end
164
388
 
165
389
  def reset!
390
+ @event = nil
391
+ @category = nil
166
392
  @context = nil
393
+ @headers = nil
394
+ @http_method = nil
395
+ @http_uri = nil
167
396
  @payloads.replace({})
168
397
  end
169
398
  end
@@ -0,0 +1,155 @@
1
+ {
2
+ "alb": {
3
+ "attributes": {},
4
+ "name": "alb",
5
+ "required_keys": [
6
+ "httpMethod",
7
+ "requestContext.elb"
8
+ ]
9
+ },
10
+
11
+ "apiGateway": {
12
+ "attributes": {
13
+ "aws.lambda.eventSource.accountId": "requestContext.accountId",
14
+ "aws.lambda.eventSource.apiId": "requestContext.apiId",
15
+ "aws.lambda.eventSource.resourceId": "requestContext.resourceId",
16
+ "aws.lambda.eventSource.resourcePath": "requestContext.resourcePath",
17
+ "aws.lambda.eventSource.stage": "requestContext.stage"
18
+ },
19
+ "name": "apiGateway",
20
+ "required_keys": [
21
+ "headers",
22
+ "httpMethod",
23
+ "path",
24
+ "requestContext",
25
+ "requestContext.stage"
26
+ ]
27
+ },
28
+
29
+ "apiGatewayV2": {
30
+ "attributes": {
31
+ "aws.lambda.eventSource.accountId": "requestContext.accountId",
32
+ "aws.lambda.eventSource.apiId": "requestContext.apiId",
33
+ "aws.lambda.eventSource.stage": "requestContext.stage"
34
+ },
35
+ "name": "apiGatewayV2",
36
+ "required_keys": [
37
+ "version",
38
+ "headers",
39
+ "requestContext.http",
40
+ "requestContext.http.path",
41
+ "requestContext.http.method",
42
+ "requestContext.stage"
43
+ ]
44
+ },
45
+
46
+ "cloudFront": {
47
+ "attributes": {},
48
+ "name": "cloudFront",
49
+ "required_keys": [
50
+ "Records[0].cf"
51
+ ]
52
+ },
53
+
54
+ "cloudWatchScheduled": {
55
+ "attributes": {
56
+ "aws.lambda.eventSource.account": "account",
57
+ "aws.lambda.eventSource.id": "id",
58
+ "aws.lambda.eventSource.region": "region",
59
+ "aws.lambda.eventSource.resource": "resources[0]",
60
+ "aws.lambda.eventSource.time": "time"
61
+ },
62
+ "name": "cloudWatch_scheduled",
63
+ "required_keys": [
64
+ "detail-type",
65
+ "source"
66
+ ]
67
+ },
68
+
69
+ "dynamoStreams": {
70
+ "attributes": {
71
+ "aws.lambda.eventSource.length": "Records.length"
72
+ },
73
+ "name": "dynamo_streams",
74
+ "required_keys": [
75
+ "Records[0].dynamodb"
76
+ ]
77
+ },
78
+
79
+ "firehose": {
80
+ "attributes": {
81
+ "aws.lambda.eventSource.length": "records.length",
82
+ "aws.lambda.eventSource.region": "region"
83
+ },
84
+ "name": "firehose",
85
+ "required_keys": [
86
+ "deliveryStreamArn",
87
+ "records[0].kinesisRecordMetadata"
88
+ ]
89
+ },
90
+
91
+ "kinesis": {
92
+ "attributes": {
93
+ "aws.lambda.eventSource.length": "Records.length",
94
+ "aws.lambda.eventSource.region": "Records[0].awsRegion"
95
+ },
96
+ "name": "kinesis",
97
+ "required_keys": [
98
+ "Records[0].kinesis"
99
+ ]
100
+ },
101
+
102
+ "s3": {
103
+ "attributes": {
104
+ "aws.lambda.eventSource.bucketName": "Records[0].s3.bucket.name",
105
+ "aws.lambda.eventSource.eventName": "Records[0].eventName",
106
+ "aws.lambda.eventSource.eventTime": "Records[0].eventTime",
107
+ "aws.lambda.eventSource.length": "Records.length",
108
+ "aws.lambda.eventSource.objectKey": "Records[0].s3.object.key",
109
+ "aws.lambda.eventSource.objectSequencer": "Records[0].s3.object.sequencer",
110
+ "aws.lambda.eventSource.objectSize": "Records[0].s3.object.size",
111
+ "aws.lambda.eventSource.region": "Records[0].awsRegion"
112
+ },
113
+ "name": "s3",
114
+ "required_keys": [
115
+ "Records[0].s3"
116
+ ]
117
+ },
118
+
119
+ "ses": {
120
+ "attributes": {
121
+ "aws.lambda.eventSource.date": "Records[0].ses.mail.commonHeaders.date",
122
+ "aws.lambda.eventSource.length": "Records.length",
123
+ "aws.lambda.eventSource.messageId": "Records[0].ses.mail.commonHeaders.messageId",
124
+ "aws.lambda.eventSource.returnPath": "Records[0].ses.mail.commonHeaders.returnPath"
125
+ },
126
+ "name": "ses",
127
+ "required_keys": [
128
+ "Records[0].ses"
129
+ ]
130
+ },
131
+
132
+ "sns": {
133
+ "attributes": {
134
+ "aws.lambda.eventSource.length": "Records.length",
135
+ "aws.lambda.eventSource.messageId": "Records[0].Sns.MessageId",
136
+ "aws.lambda.eventSource.timestamp": "Records[0].Sns.Timestamp",
137
+ "aws.lambda.eventSource.topicArn": "Records[0].Sns.TopicArn",
138
+ "aws.lambda.eventSource.type": "Records[0].Sns.Type"
139
+ },
140
+ "name": "sns",
141
+ "required_keys": [
142
+ "Records[0].Sns"
143
+ ]
144
+ },
145
+
146
+ "sqs": {
147
+ "attributes": {
148
+ "aws.lambda.eventSource.length": "Records.length"
149
+ },
150
+ "name": "sqs",
151
+ "required_keys": [
152
+ "Records[0].receiptHandle"
153
+ ]
154
+ }
155
+ }
@@ -0,0 +1,49 @@
1
+ # This file is distributed under New Relic's license terms.
2
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
+ # frozen_string_literal: true
4
+
5
+ require 'json'
6
+
7
+ module NewRelic
8
+ module Agent
9
+ # ServerlessHandlerEventSources - New Relic's language agent devs maintain
10
+ # a cross-agent JSON map of all AWS resources with the potential to invoke
11
+ # an AWS Lambda function by issuing it an event. This map is used to glean
12
+ # source specific attributes while instrumenting the function's invocation.
13
+ #
14
+ # Given that the event arrives as a Ruby hash argument to the AWS Lambda
15
+ # function, the JSON map's values need to be converted into arrays that can
16
+ # be passed to `Hash#dig`. So a value such as `'records[0].name'` needs to
17
+ # be converted to `['records', 0, 'name']`. This class's `.to_hash` method
18
+ # yields the converted data.
19
+ #
20
+ # Furthermore, `.length` calls are converted to Ruby `#size` notation to
21
+ # denote that a method call must be performed on the dug value.
22
+ class ServerlessHandlerEventSources
23
+ JSON_SOURCE = File.join(File.dirname(__FILE__), 'serverless_handler_event_sources.json').freeze
24
+ JSON_RAW = JSON.parse(File.read(JSON_SOURCE)).freeze
25
+
26
+ def self.to_hash
27
+ JSON_RAW.each_with_object({}) do |(type, info), hash|
28
+ hash[type] = {'attributes' => {},
29
+ 'name' => info['name'],
30
+ 'required_keys' => []}
31
+ info['attributes'].each { |attr, value| hash[type]['attributes'][attr] = transform(value) }
32
+ info['required_keys'].each { |key| hash[type]['required_keys'].push(transform(key)) }
33
+ end.freeze
34
+ end
35
+
36
+ def self.transform(value)
37
+ value.gsub(/\[(\d+)\]/, '.\1').split('.').map do |e|
38
+ if e.match?(/^\d+$/)
39
+ e.to_i
40
+ elsif e == 'length'
41
+ '#size'
42
+ else
43
+ e
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -19,6 +19,20 @@ module NewRelic
19
19
  RbConfig::CONFIG['target_os']
20
20
  end
21
21
 
22
+ def self.os_distribution
23
+ case
24
+ when darwin? then :darwin
25
+ when linux? then :linux
26
+ when bsd? then :bsd
27
+ when windows? then :windows
28
+ else ruby_os_identifier
29
+ end
30
+ end
31
+
32
+ def self.windows?
33
+ !!(ruby_os_identifier[/mingw|mswin/i])
34
+ end
35
+
22
36
  def self.darwin?
23
37
  !!(ruby_os_identifier =~ /darwin/i)
24
38
  end
@@ -95,7 +95,7 @@ module NewRelic
95
95
 
96
96
  def create_trace_state_payload
97
97
  unless Agent.config[:'distributed_tracing.enabled']
98
- NewRelic::Agent.logger.warn('Not configured to create WC3 trace context payload')
98
+ NewRelic::Agent.logger.warn('Not configured to create W3C trace context payload')
99
99
  return
100
100
  end
101
101
 
@@ -0,0 +1,14 @@
1
+ # This file is distributed under New Relic's license terms.
2
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
+ # frozen_string_literal: true
4
+
5
+ require 'new_relic/control/frameworks/ruby'
6
+ module NewRelic
7
+ class Control
8
+ module Frameworks
9
+ # Contains basic control logic for Grape
10
+ class Grape < NewRelic::Control::Frameworks::Ruby
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # This file is distributed under New Relic's license terms.
2
+ # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
+ # frozen_string_literal: true
4
+
5
+ require 'new_relic/control/frameworks/sinatra'
6
+ module NewRelic
7
+ class Control
8
+ module Frameworks
9
+ # Contains basic control logic for Padrino
10
+ class Padrino < NewRelic::Control::Frameworks::Sinatra
11
+ end
12
+ end
13
+ end
14
+ end
@@ -9,8 +9,10 @@ module NewRelic
9
9
  module Frameworks
10
10
  class Rails4 < NewRelic::Control::Frameworks::Rails3
11
11
  def rails_gem_list
12
- Bundler.rubygems.all_specs.map do |gem|
13
- "#{gem.name} (#{gem.version})"
12
+ if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0')
13
+ Bundler.rubygems.installed_specs.map { |gem| "#{gem.name} (#{gem.version})" }
14
+ else
15
+ Bundler.rubygems.all_specs.map { |gem| "#{gem.name} (#{gem.version})" }
14
16
  end
15
17
  end
16
18
 
@@ -44,7 +44,11 @@ module NewRelic
44
44
  ####################################
45
45
  report_on('Gems') do
46
46
  begin
47
- Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" }
47
+ if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0')
48
+ Bundler.rubygems.installed_specs.map { |gem| "#{gem.name}(#{gem.version})" }
49
+ else
50
+ Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" }
51
+ end
48
52
  rescue
49
53
  # There are certain rubygem, bundler, rails combinations (e.g. gem
50
54
  # 1.6.2, rails 2.3, bundler 1.2.3) where the code above throws an error
@@ -67,7 +71,7 @@ module NewRelic
67
71
  report_on('Physical Cores') { ::NewRelic::Agent::SystemInfo.num_physical_cores }
68
72
  report_on('Arch') { ::NewRelic::Agent::SystemInfo.processor_arch }
69
73
  report_on('OS version') { ::NewRelic::Agent::SystemInfo.os_version }
70
- report_on('OS') { ::NewRelic::Agent::SystemInfo.ruby_os_identifier }
74
+ report_on('OS') { ::NewRelic::Agent::SystemInfo.os_distribution }
71
75
  report_on('Database adapter') { ::NewRelic::Agent::DatabaseAdapter.value }
72
76
  report_on('Framework') { Agent.config[:framework].to_s }
73
77
  report_on('Dispatcher') { Agent.config[:dispatcher].to_s }
@@ -88,7 +88,13 @@ module NewRelic
88
88
  end
89
89
 
90
90
  def bundled_gem?(gem_name)
91
- defined?(Bundler) && Bundler.rubygems.all_specs.map(&:name).include?(gem_name)
91
+ return false unless defined?(Bundler)
92
+
93
+ if Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('2.0.0')
94
+ Bundler.rubygems.installed_specs.map(&:name).include?(gem_name)
95
+ else
96
+ Bundler.rubygems.all_specs.map(&:name).include?(gem_name)
97
+ end
92
98
  rescue => e
93
99
  ::NewRelic::Agent.logger.info("Could not determine if third party #{gem_name} gem is installed", e)
94
100
  false
@@ -142,10 +142,7 @@ module NewRelic
142
142
  end
143
143
 
144
144
  def check_for_falcon
145
- return unless defined?(::Falcon::Server) &&
146
- NewRelic::LanguageSupport.object_space_usable?
147
-
148
- @discovered_dispatcher = :falcon if find_class_in_object_space(::Falcon::Server)
145
+ @discovered_dispatcher = :falcon if defined?(::Falcon::Server) && File.basename($PROGRAM_NAME) == 'falcon'
149
146
  end
150
147
 
151
148
  def check_for_delayed_job
@@ -6,7 +6,7 @@
6
6
  module NewRelic
7
7
  module VERSION # :nodoc:
8
8
  MAJOR = 9
9
- MINOR = 12
9
+ MINOR = 13
10
10
  TINY = 0
11
11
 
12
12
  STRING = "#{MAJOR}.#{MINOR}.#{TINY}"