newrelic_rpm 9.9.0 → 9.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +1 -0
  3. data/CHANGELOG.md +463 -1
  4. data/CONTRIBUTING.md +2 -2
  5. data/README.md +16 -17
  6. data/Rakefile +1 -1
  7. data/lib/boot/strap.rb +102 -0
  8. data/lib/new_relic/agent/agent.rb +6 -0
  9. data/lib/new_relic/agent/agent_helpers/connect.rb +3 -0
  10. data/lib/new_relic/agent/agent_helpers/harvest.rb +3 -0
  11. data/lib/new_relic/agent/agent_helpers/shutdown.rb +3 -0
  12. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -0
  13. data/lib/new_relic/agent/agent_helpers/startup.rb +7 -0
  14. data/lib/new_relic/agent/agent_logger.rb +1 -0
  15. data/lib/new_relic/agent/aws.rb +68 -0
  16. data/lib/new_relic/agent/configuration/default_source.rb +603 -105
  17. data/lib/new_relic/agent/configuration/environment_source.rb +5 -1
  18. data/lib/new_relic/agent/configuration/manager.rb +28 -2
  19. data/lib/new_relic/agent/configuration/yaml_source.rb +7 -2
  20. data/lib/new_relic/agent/database/obfuscation_helpers.rb +11 -11
  21. data/lib/new_relic/agent/database/obfuscator.rb +1 -0
  22. data/lib/new_relic/agent/database.rb +41 -1
  23. data/lib/new_relic/agent/database_adapter.rb +1 -1
  24. data/lib/new_relic/agent/datastores/redis.rb +1 -1
  25. data/lib/new_relic/agent/distributed_tracing/cross_app_tracing.rb +1 -1
  26. data/lib/new_relic/agent/distributed_tracing.rb +4 -2
  27. data/lib/new_relic/agent/error_collector.rb +37 -10
  28. data/lib/new_relic/agent/external.rb +2 -0
  29. data/lib/new_relic/agent/health_check.rb +136 -0
  30. data/lib/new_relic/agent/http_clients/uri_util.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/action_dispatch.rb +1 -1
  32. data/lib/new_relic/agent/instrumentation/action_dispatch_subscriber.rb +1 -1
  33. data/lib/new_relic/agent/instrumentation/action_mailbox.rb +1 -1
  34. data/lib/new_relic/agent/instrumentation/action_mailer.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/active_job.rb +1 -1
  36. data/lib/new_relic/agent/instrumentation/active_job_subscriber.rb +6 -2
  37. data/lib/new_relic/agent/instrumentation/active_merchant.rb +0 -13
  38. data/lib/new_relic/agent/instrumentation/active_record.rb +7 -12
  39. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +7 -3
  40. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +11 -9
  41. data/lib/new_relic/agent/instrumentation/active_record_prepend.rb +2 -2
  42. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +9 -16
  43. data/lib/new_relic/agent/instrumentation/active_support_broadcast_logger.rb +0 -2
  44. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +0 -2
  45. data/lib/new_relic/agent/instrumentation/async_http.rb +2 -3
  46. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/chain.rb +21 -0
  47. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/instrumentation.rb +66 -0
  48. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose/prepend.rb +15 -0
  49. data/lib/new_relic/agent/instrumentation/aws_sdk_firehose.rb +22 -0
  50. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/chain.rb +21 -0
  51. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/instrumentation.rb +91 -0
  52. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/prepend.rb +15 -0
  53. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis.rb +22 -0
  54. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/chain.rb +33 -0
  55. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/instrumentation.rb +93 -0
  56. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda/prepend.rb +23 -0
  57. data/lib/new_relic/agent/instrumentation/aws_sdk_lambda.rb +23 -0
  58. data/lib/new_relic/agent/instrumentation/aws_sqs/chain.rb +37 -0
  59. data/lib/new_relic/agent/instrumentation/aws_sqs/instrumentation.rb +67 -0
  60. data/lib/new_relic/agent/instrumentation/aws_sqs/prepend.rb +21 -0
  61. data/lib/new_relic/agent/instrumentation/aws_sqs.rb +23 -0
  62. data/lib/new_relic/agent/instrumentation/bunny/instrumentation.rb +14 -0
  63. data/lib/new_relic/agent/instrumentation/bunny.rb +3 -4
  64. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -3
  65. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +4 -0
  66. data/lib/new_relic/agent/instrumentation/curb.rb +4 -5
  67. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +0 -23
  68. data/lib/new_relic/agent/instrumentation/dynamodb/chain.rb +27 -0
  69. data/lib/new_relic/agent/instrumentation/dynamodb/instrumentation.rb +64 -0
  70. data/lib/new_relic/agent/instrumentation/dynamodb/prepend.rb +19 -0
  71. data/lib/new_relic/agent/instrumentation/dynamodb.rb +23 -0
  72. data/lib/new_relic/agent/instrumentation/elasticsearch/chain.rb +1 -2
  73. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +53 -7
  74. data/lib/new_relic/agent/instrumentation/elasticsearch.rb +1 -3
  75. data/lib/new_relic/agent/instrumentation/ethon.rb +1 -5
  76. data/lib/new_relic/agent/instrumentation/excon.rb +1 -17
  77. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +1 -1
  78. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +1 -1
  79. data/lib/new_relic/agent/instrumentation/fiber.rb +0 -2
  80. data/lib/new_relic/agent/instrumentation/grape/instrumentation.rb +0 -3
  81. data/lib/new_relic/agent/instrumentation/grape.rb +1 -1
  82. data/lib/new_relic/agent/instrumentation/grpc/client/instrumentation.rb +0 -1
  83. data/lib/new_relic/agent/instrumentation/httpclient.rb +1 -5
  84. data/lib/new_relic/agent/instrumentation/httprb.rb +0 -1
  85. data/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb +1 -1
  86. data/lib/new_relic/agent/instrumentation/httpx.rb +1 -5
  87. data/lib/new_relic/agent/instrumentation/logger.rb +1 -3
  88. data/lib/new_relic/agent/instrumentation/logstasher/chain.rb +21 -0
  89. data/lib/new_relic/agent/instrumentation/logstasher/instrumentation.rb +24 -0
  90. data/lib/new_relic/agent/instrumentation/logstasher/prepend.rb +13 -0
  91. data/lib/new_relic/agent/instrumentation/logstasher.rb +25 -0
  92. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +1 -1
  93. data/lib/new_relic/agent/instrumentation/memcache/helper.rb +2 -2
  94. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +1 -1
  95. data/lib/new_relic/agent/instrumentation/memcache/prepend.rb +1 -1
  96. data/lib/new_relic/agent/instrumentation/memcache.rb +0 -1
  97. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +1 -1
  98. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +3 -3
  99. data/lib/new_relic/agent/instrumentation/net_http.rb +2 -1
  100. data/lib/new_relic/agent/instrumentation/notifications_subscriber.rb +0 -2
  101. data/lib/new_relic/agent/instrumentation/opensearch/chain.rb +21 -0
  102. data/lib/new_relic/agent/instrumentation/opensearch/instrumentation.rb +66 -0
  103. data/lib/{tasks/instrumentation_generator/templates/instrumentation.tt → new_relic/agent/instrumentation/opensearch/prepend.rb} +4 -4
  104. data/lib/new_relic/agent/instrumentation/opensearch.rb +23 -0
  105. data/lib/new_relic/agent/instrumentation/padrino.rb +3 -3
  106. data/lib/new_relic/agent/instrumentation/rack/instrumentation.rb +3 -0
  107. data/lib/new_relic/agent/instrumentation/rails_notifications/action_controller.rb +9 -5
  108. data/lib/new_relic/agent/instrumentation/rake.rb +1 -2
  109. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +72 -0
  110. data/lib/new_relic/agent/instrumentation/rdkafka/instrumentation.rb +70 -0
  111. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +67 -0
  112. data/lib/new_relic/agent/instrumentation/rdkafka.rb +25 -0
  113. data/lib/new_relic/agent/instrumentation/redis/cluster_middleware.rb +26 -0
  114. data/lib/new_relic/agent/instrumentation/redis/constants.rb +2 -2
  115. data/lib/new_relic/agent/instrumentation/redis/instrumentation.rb +14 -11
  116. data/lib/new_relic/agent/instrumentation/redis/middleware.rb +3 -0
  117. data/lib/new_relic/agent/instrumentation/redis.rb +11 -5
  118. data/lib/new_relic/agent/instrumentation/resque.rb +8 -6
  119. data/lib/new_relic/agent/instrumentation/roda.rb +5 -5
  120. data/lib/new_relic/agent/instrumentation/ruby_kafka/chain.rb +55 -0
  121. data/lib/new_relic/agent/instrumentation/ruby_kafka/instrumentation.rb +67 -0
  122. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +60 -0
  123. data/lib/new_relic/agent/instrumentation/ruby_kafka.rb +25 -0
  124. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +2 -2
  125. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delay_extensions.rb +24 -0
  126. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delayed_class.rb +2 -2
  127. data/lib/new_relic/agent/instrumentation/sidekiq.rb +9 -15
  128. data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -19
  129. data/lib/new_relic/agent/instrumentation/stripe.rb +1 -1
  130. data/lib/new_relic/agent/instrumentation/stripe_subscriber.rb +22 -1
  131. data/lib/new_relic/agent/instrumentation/thread.rb +0 -2
  132. data/lib/new_relic/agent/instrumentation/tilt.rb +0 -4
  133. data/lib/new_relic/agent/instrumentation/typhoeus/instrumentation.rb +2 -2
  134. data/lib/new_relic/agent/instrumentation/typhoeus.rb +0 -1
  135. data/lib/new_relic/agent/instrumentation/view_component/instrumentation.rb +11 -5
  136. data/lib/new_relic/agent/instrumentation/view_component.rb +0 -2
  137. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -3
  138. data/lib/new_relic/agent/llm/chat_completion_summary.rb +1 -1
  139. data/lib/new_relic/agent/llm/embedding.rb +1 -1
  140. data/lib/new_relic/agent/local_log_decorator.rb +20 -3
  141. data/lib/new_relic/agent/log_event_aggregator.rb +119 -28
  142. data/lib/new_relic/agent/logging.rb +1 -1
  143. data/lib/new_relic/agent/messaging.rb +16 -5
  144. data/lib/new_relic/agent/method_tracer.rb +3 -0
  145. data/lib/new_relic/agent/monitors/inbound_request_monitor.rb +1 -1
  146. data/lib/new_relic/agent/monitors/synthetics_monitor.rb +1 -1
  147. data/lib/new_relic/agent/new_relic_service/json_marshaller.rb +2 -2
  148. data/lib/new_relic/agent/new_relic_service.rb +8 -2
  149. data/lib/new_relic/agent/opentelemetry/context/propagation/trace_propagator.rb +66 -0
  150. data/lib/new_relic/agent/opentelemetry/context/propagation.rb +15 -0
  151. data/lib/{tasks/instrumentation_generator/templates/Envfile.tt → new_relic/agent/opentelemetry/context.rb} +9 -5
  152. data/lib/new_relic/agent/opentelemetry/trace/span.rb +31 -0
  153. data/lib/new_relic/agent/opentelemetry/trace/tracer.rb +129 -0
  154. data/lib/new_relic/agent/opentelemetry/trace/tracer_provider.rb +18 -0
  155. data/lib/new_relic/agent/opentelemetry/trace.rb +15 -0
  156. data/lib/new_relic/agent/opentelemetry/transaction_patch.rb +69 -0
  157. data/lib/new_relic/agent/opentelemetry_bridge.rb +32 -0
  158. data/lib/new_relic/agent/parameter_filtering.rb +1 -1
  159. data/lib/new_relic/agent/samplers/cpu_sampler.rb +1 -1
  160. data/lib/new_relic/agent/samplers/memory_sampler.rb +1 -1
  161. data/lib/new_relic/agent/serverless_handler.rb +247 -12
  162. data/lib/new_relic/agent/serverless_handler_event_sources.json +155 -0
  163. data/lib/new_relic/agent/serverless_handler_event_sources.rb +49 -0
  164. data/lib/new_relic/agent/span_event_primitive.rb +16 -11
  165. data/lib/new_relic/agent/system_info.rb +14 -0
  166. data/lib/new_relic/agent/threading/backtrace_node.rb +10 -1
  167. data/lib/new_relic/agent/tracer.rb +1 -1
  168. data/lib/new_relic/agent/transaction/abstract_segment.rb +2 -1
  169. data/lib/new_relic/agent/transaction/datastore_segment.rb +1 -1
  170. data/lib/new_relic/agent/transaction/distributed_tracer.rb +3 -3
  171. data/lib/new_relic/agent/transaction/external_request_segment.rb +0 -10
  172. data/lib/new_relic/agent/transaction/message_broker_segment.rb +4 -1
  173. data/lib/new_relic/agent/transaction/request_attributes.rb +14 -7
  174. data/lib/new_relic/agent/transaction/trace_context.rb +34 -5
  175. data/lib/new_relic/agent/transaction/tracing.rb +3 -3
  176. data/lib/new_relic/agent/transaction.rb +4 -7
  177. data/lib/new_relic/agent/transaction_time_aggregator.rb +1 -1
  178. data/lib/new_relic/agent/utilization/ecs.rb +22 -0
  179. data/lib/new_relic/agent/utilization/ecs_v4.rb +22 -0
  180. data/lib/new_relic/agent/utilization_data.rb +40 -5
  181. data/lib/new_relic/agent/vm/c_ruby_vm.rb +3 -3
  182. data/lib/new_relic/agent.rb +124 -2
  183. data/lib/new_relic/constants.rb +1 -0
  184. data/lib/new_relic/control/frameworks/grape.rb +14 -0
  185. data/lib/new_relic/control/frameworks/padrino.rb +14 -0
  186. data/lib/new_relic/control/frameworks/rails4.rb +1 -3
  187. data/lib/new_relic/control/instance_methods.rb +6 -0
  188. data/lib/new_relic/control/instrumentation.rb +1 -1
  189. data/lib/new_relic/control/private_instance_methods.rb +4 -0
  190. data/lib/new_relic/control/security_interface.rb +57 -0
  191. data/lib/new_relic/control.rb +1 -1
  192. data/lib/new_relic/dependency_detection.rb +11 -14
  193. data/lib/new_relic/environment_report.rb +2 -2
  194. data/lib/new_relic/helper.rb +22 -0
  195. data/lib/new_relic/language_support.rb +3 -1
  196. data/lib/new_relic/local_environment.rb +1 -4
  197. data/lib/new_relic/rack/browser_monitoring.rb +20 -8
  198. data/lib/new_relic/version.rb +1 -1
  199. data/lib/sequel/extensions/new_relic_instrumentation.rb +3 -2
  200. data/lib/tasks/config.rake +7 -3
  201. data/lib/tasks/gha.rake +31 -0
  202. data/lib/tasks/helpers/config.html.erb +3 -2
  203. data/lib/tasks/helpers/format.rb +1 -1
  204. data/lib/tasks/helpers/newrelicyml.rb +80 -13
  205. data/newrelic.yml +425 -162
  206. data/newrelic_rpm.gemspec +3 -1
  207. data/test/agent_helper.rb +24 -2
  208. metadata +91 -22
  209. data/lib/tasks/instrumentation_generator/README.md +0 -63
  210. data/lib/tasks/instrumentation_generator/TODO.md +0 -33
  211. data/lib/tasks/instrumentation_generator/instrumentation.thor +0 -121
  212. data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -21
  213. data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -7
  214. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +0 -29
  215. data/lib/tasks/instrumentation_generator/templates/instrumentation_method.tt +0 -3
  216. data/lib/tasks/instrumentation_generator/templates/newrelic.yml.tt +0 -19
  217. data/lib/tasks/instrumentation_generator/templates/prepend.tt +0 -13
  218. data/lib/tasks/instrumentation_generator/templates/prepend_method.tt +0 -3
  219. data/lib/tasks/instrumentation_generator/templates/test.tt +0 -15
@@ -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,19 @@ 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
42
+
43
+ txn_name = function_name
44
+ if ENV['NEW_RELIC_APM_LAMBDA_MODE'] == 'true'
45
+ source = event_source_event_info['name'] if event_source_event_info
46
+ txn_name = "#{source.upcase} #{txn_name}" if source
47
+ end
39
48
 
40
- NewRelic::Agent::Tracer.in_transaction(category: :other, name: function_name) do
41
- add_agent_attributes
49
+ NewRelic::Agent::Tracer.in_transaction(category: category, name: txn_name) do
50
+ prep_transaction
42
51
 
43
- NewRelic::LanguageSupport.constantize(namespace).send(method_name, event: event, context: context)
52
+ process_response(NewRelic::LanguageSupport.constantize(namespace)
53
+ .send(method_name, event: event, context: context))
44
54
  end
45
55
  ensure
46
56
  harvest!
@@ -86,6 +96,12 @@ module NewRelic
86
96
 
87
97
  private
88
98
 
99
+ def prep_transaction
100
+ process_api_gateway_info
101
+ process_headers
102
+ add_agent_attributes
103
+ end
104
+
89
105
  def harvest!
90
106
  NewRelic::Agent.instance.harvest_and_send_analytic_event_data
91
107
  NewRelic::Agent.instance.harvest_and_send_custom_event_data
@@ -109,6 +125,11 @@ module NewRelic
109
125
  ENV.fetch(LAMBDA_ENVIRONMENT_VARIABLE, FUNCTION_NAME)
110
126
  end
111
127
 
128
+ def category
129
+ @category ||=
130
+ @event&.dig('requestContext', 'http', 'method') || @event&.fetch('httpMethod', nil) ? :web : :other
131
+ end
132
+
112
133
  def write_output
113
134
  string = PAYLOAD_VERSION == 1 ? payload_v1 : payload_v2
114
135
 
@@ -120,7 +141,7 @@ module NewRelic
120
141
  "BEGIN PAYLOAD>>>\n#{string}\n<<<END PAYLOAD"
121
142
  end
122
143
 
123
- def payload_v1
144
+ def payload_v1 # New Relic serverless payload v1
124
145
  payload_hash = {'metadata' => metadata, 'data' => @payloads}
125
146
  json = NewRelic::Agent.agent.service.marshaller.dump(payload_hash)
126
147
  gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
@@ -129,7 +150,7 @@ module NewRelic
129
150
  ::JSON.dump(array)
130
151
  end
131
152
 
132
- def payload_v2
153
+ def payload_v2 # New Relic serverless payload v2
133
154
  json = NewRelic::Agent.agent.service.marshaller.dump(@payloads)
134
155
  gzipped = NewRelic::Agent::NewRelicService::Encoders::Compressed::Gzip.encode(json)
135
156
  base64_encoded = NewRelic::Base64.strict_encode64(gzipped)
@@ -137,6 +158,88 @@ module NewRelic
137
158
  ::JSON.dump(array)
138
159
  end
139
160
 
161
+ def determine_api_gateway_version
162
+ return unless @event
163
+
164
+ version = @event.fetch('version', '')
165
+ if version.start_with?('2.')
166
+ return 2
167
+ elsif version.start_with?('1.')
168
+ return 1
169
+ end
170
+
171
+ headers = headers_from_event
172
+ return unless headers
173
+
174
+ if @event.dig('requestContext', 'http', 'path') && @event.dig('requestContext', 'http', 'method')
175
+ 2
176
+ elsif @event.fetch('path', nil) && @event.fetch('httpMethod', nil)
177
+ 1
178
+ end
179
+ end
180
+
181
+ def process_api_gateway_info
182
+ api_v = determine_api_gateway_version
183
+ return unless api_v
184
+
185
+ info = api_v == 2 ? info_for_api_gateway_v2 : info_for_api_gateway_v1
186
+ info[:query_parameters] = @event.fetch('queryStringParameters', nil)
187
+
188
+ @http_method = info[:method]
189
+ @http_uri = http_uri(info)
190
+ end
191
+
192
+ def http_uri(info)
193
+ return unless info[:host] && info[:path]
194
+
195
+ url_str = "https://#{info[:host]}"
196
+ url_str += ":#{info[:port]}" unless info[:host].match?(':')
197
+ url_str += "#{info[:path]}"
198
+
199
+ if info[:query_parameters]
200
+ qp = info[:query_parameters].map { |k, v| "#{k}=#{v}" }.join('&')
201
+ url_str += "?#{qp}"
202
+ end
203
+
204
+ URI.parse(url_str)
205
+ rescue StandardError => e
206
+ NewRelic::Agent.logger.error "ServerlessHandler failed to parse the source HTTP URI: #{e}"
207
+ end
208
+
209
+ def info_for_api_gateway_v2
210
+ ctx = @event.fetch('requestContext', nil)
211
+ return {} unless ctx
212
+
213
+ {method: ctx.dig('http', 'method'),
214
+ path: ctx.dig('http', 'path'),
215
+ host: ctx.fetch('domainName', @event.dig('headers', 'Host')),
216
+ port: @event.dig('headers', 'X-Forwarded-Port') || 443}
217
+ end
218
+
219
+ def info_for_api_gateway_v1
220
+ headers = headers_from_event
221
+ {method: @event.fetch('httpMethod', nil),
222
+ path: @event.fetch('path', nil),
223
+ host: headers.fetch('Host', nil),
224
+ port: headers.fetch('X-Forwarded-Port', 443)}
225
+ end
226
+
227
+ def process_headers
228
+ return unless ::NewRelic::Agent.config[:'distributed_tracing.enabled']
229
+
230
+ headers = headers_from_event
231
+ return unless headers && !headers.empty?
232
+
233
+ dt_headers = headers.fetch(NewRelic::NEWRELIC_KEY, nil)
234
+ return unless dt_headers
235
+
236
+ ::NewRelic::Agent::DistributedTracing::accept_distributed_trace_headers(dt_headers, 'Other')
237
+ end
238
+
239
+ def headers_from_event
240
+ @headers ||= @event&.dig('requestContext', 'http') || @event&.dig('headers')
241
+ end
242
+
140
243
  def use_named_pipe?
141
244
  return @use_named_pipe if defined?(@use_named_pipe)
142
245
 
@@ -146,15 +249,142 @@ module NewRelic
146
249
  def add_agent_attributes
147
250
  return unless NewRelic::Agent::Tracer.current_transaction
148
251
 
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)
252
+ add_agent_attribute('aws.lambda.coldStart', true) if cold?
253
+ add_agent_attribute('aws.lambda.arn', @context.invoked_function_arn)
254
+ add_agent_attribute('aws.requestId', @context.aws_request_id)
255
+
256
+ add_event_source_attributes
257
+ add_http_attributes if api_gateway_event?
258
+ end
259
+
260
+ def add_http_attributes
261
+ return unless category == :web
262
+
263
+ if @http_uri
264
+ add_agent_attribute('uri.host', @http_uri.host)
265
+ add_agent_attribute('uri.port', @http_uri.port)
266
+ if NewRelic::Agent.instance.attribute_filter.allows_key?('http.url', AttributeFilter::DST_SPAN_EVENTS)
267
+ add_agent_attribute('http.url', @http_uri.to_s)
268
+ end
269
+ end
270
+
271
+ if @http_method
272
+ add_agent_attribute('http.method', @http_method)
273
+ add_agent_attribute('http.request.method', @http_method)
274
+ end
275
+ end
276
+
277
+ def api_gateway_event?
278
+ return false unless @event
279
+
280
+ # '1.0' for API Gateway V1, '2.0' for API Gateway V2
281
+ return true if @event.fetch('version', '').start_with?(DIGIT)
282
+
283
+ return false unless headers_from_event
284
+
285
+ # API Gateway V1 - look for toplevel 'path' and 'httpMethod' keys if a version is unset
286
+ return true if @event.fetch('path', nil) && @event.fetch('httpMethod', nil)
287
+
288
+ # API Gateway V2 - look for 'requestContext/http' inner nested 'path' and 'method' keys if a version is unset
289
+ return true if @event.dig('requestContext', 'http', 'path') && @event.dig('requestContext', 'http', 'method')
290
+
291
+ false
292
+ end
293
+
294
+ def add_event_source_attributes
295
+ arn = event_source_arn
296
+ add_agent_attribute('aws.lambda.eventSource.arn', arn) if arn
297
+
298
+ info = event_source_event_info
299
+ return unless info
300
+
301
+ add_agent_attribute('aws.lambda.eventSource.eventType', info['name'])
302
+
303
+ info['attributes'].each do |name, elements|
304
+ next if elements.empty?
305
+
306
+ size = false
307
+ if elements.last.eql?('#size')
308
+ elements = elements.dup
309
+ elements.pop
310
+ size = true
311
+ end
312
+ value = @event.dig(*elements)
313
+ value = value.size if size
314
+ next unless value
315
+
316
+ add_agent_attribute(name, value)
317
+ end
318
+ end
319
+
320
+ def event_source_arn
321
+ return unless @event
322
+
323
+ # SQS/Kinesis Stream/DynamoDB/CodeCommit/S3/SNS
324
+ return event_source_arn_for_records if @event.fetch('Records', nil)
325
+
326
+ # Kinesis Firehose
327
+ ds_arn = @event.fetch('deliveryStreamArn', nil) if @event.fetch('records', nil)
328
+ return ds_arn if ds_arn
329
+
330
+ # ELB
331
+ elb_arn = @event.dig('requestContext', 'elb', 'targetGroupArn')
332
+ return elb_arn if elb_arn
333
+
334
+ # (other)
335
+ es_arn = @event.dig('resources', 0)
336
+ return es_arn if es_arn
337
+
338
+ NewRelic::Agent.logger.debug 'Unable to determine an event source arn'
339
+
340
+ nil
341
+ end
342
+
343
+ def event_source_event_info
344
+ return unless @event
345
+
346
+ # if every required key for a source is found, consider that source
347
+ # to be a match
348
+ EVENT_SOURCES.each_value do |info|
349
+ return info unless info['required_keys'].detect { |r| @event.dig(*r).nil? }
350
+ end
351
+
352
+ nil
353
+ end
354
+
355
+ def event_source_arn_for_records
356
+ record = @event['Records'].first
357
+ unless record
358
+ NewRelic::Agent.logger.debug "Unable to find any records in the event's 'Records' array"
359
+ return
360
+ end
361
+
362
+ arn = record.fetch('eventSourceARN', nil) || # SQS/Kinesis Stream/DynamoDB/CodeCommit
363
+ record.dig('s3', 'bucket', 'arn') || # S3
364
+ record.fetch('EventSubscriptionArn', nil) # SNS
365
+
366
+ unless arn
367
+ NewRelic::Agent.logger.debug "Unable to determine an event source arn from the event's 'Records' array"
368
+ end
369
+
370
+ arn
152
371
  end
153
372
 
154
373
  def add_agent_attribute(attribute, value)
155
374
  NewRelic::Agent::Tracer.current_transaction.add_agent_attribute(attribute, value, AGENT_ATTRIBUTE_DESTINATIONS)
156
375
  end
157
376
 
377
+ def process_response(response)
378
+ return response unless category == :web && response.respond_to?(:fetch)
379
+
380
+ http_status = response.fetch(:statusCode, response.fetch('statusCode', nil))
381
+ return unless http_status
382
+
383
+ add_agent_attribute('http.statusCode', http_status)
384
+
385
+ response
386
+ end
387
+
158
388
  def cold?
159
389
  return @cold if defined?(@cold)
160
390
 
@@ -163,7 +393,12 @@ module NewRelic
163
393
  end
164
394
 
165
395
  def reset!
396
+ @event = nil
397
+ @category = nil
166
398
  @context = nil
399
+ @headers = nil
400
+ @http_method = nil
401
+ @http_uri = nil
167
402
  @payloads.replace({})
168
403
  end
169
404
  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
@@ -40,10 +40,12 @@ module NewRelic
40
40
  SERVER_ADDRESS_KEY = 'server.address'
41
41
  SERVER_PORT_KEY = 'server.port'
42
42
  SPAN_KIND_KEY = 'span.kind'
43
+ STACKTRACE_KEY = 'code.stacktrace'
43
44
  ENTRY_POINT_KEY = 'nr.entryPoint'
44
45
  TRUSTED_PARENT_KEY = 'trustedParentId'
45
46
  TRACING_VENDORS_KEY = 'tracingVendors'
46
47
  TRANSACTION_NAME_KEY = 'transaction.name'
48
+ THREAD_ID_KEY = 'thread.id'
47
49
 
48
50
  # Strings for static values of the event structure
49
51
  EVENT_TYPE = 'Span'
@@ -52,6 +54,8 @@ module NewRelic
52
54
  DATASTORE_CATEGORY = 'datastore'
53
55
  CLIENT = 'client'
54
56
 
57
+ DB_STATEMENT_MAX_BYTES = 4096
58
+
55
59
  # Builds a Hash of error attributes as well as the Span ID when
56
60
  # an error is present. Otherwise, returns nil when no error present.
57
61
  def error_attributes(segment)
@@ -79,17 +83,13 @@ module NewRelic
79
83
  intrinsics[SPAN_KIND_KEY] = CLIENT
80
84
  intrinsics[SERVER_ADDRESS_KEY] = segment.uri.host
81
85
  intrinsics[SERVER_PORT_KEY] = segment.uri.port
82
- agent_attributes = error_attributes(segment) || {}
86
+ agent_attributes = {}
83
87
 
84
88
  if allowed?(HTTP_URL_KEY)
85
89
  agent_attributes[HTTP_URL_KEY] = truncate(segment.uri)
86
90
  end
87
91
 
88
- if segment.respond_to?(:record_agent_attributes?) && segment.record_agent_attributes?
89
- agent_attributes.merge!(agent_attributes(segment))
90
- end
91
-
92
- [intrinsics, custom_attributes(segment), agent_attributes]
92
+ [intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
93
93
  end
94
94
 
95
95
  def for_datastore_segment(segment) # rubocop:disable Metrics/AbcSize
@@ -99,7 +99,7 @@ module NewRelic
99
99
  intrinsics[SPAN_KIND_KEY] = CLIENT
100
100
  intrinsics[CATEGORY_KEY] = DATASTORE_CATEGORY
101
101
 
102
- agent_attributes = error_attributes(segment) || {}
102
+ agent_attributes = {}
103
103
 
104
104
  if segment.database_name && allowed?(DB_INSTANCE_KEY)
105
105
  agent_attributes[DB_INSTANCE_KEY] = truncate(segment.database_name)
@@ -118,12 +118,16 @@ module NewRelic
118
118
  agent_attributes[DB_SYSTEM_KEY] = segment.product if allowed?(DB_SYSTEM_KEY)
119
119
 
120
120
  if segment.sql_statement && allowed?(DB_STATEMENT_KEY)
121
- agent_attributes[DB_STATEMENT_KEY] = truncate(segment.sql_statement.safe_sql, 2000)
121
+ agent_attributes[DB_STATEMENT_KEY] = truncate(segment.sql_statement.safe_sql, DB_STATEMENT_MAX_BYTES)
122
122
  elsif segment.nosql_statement && allowed?(DB_STATEMENT_KEY)
123
- agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, 2000)
123
+ agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, DB_STATEMENT_MAX_BYTES)
124
+ end
125
+
126
+ if segment.params[:backtrace]
127
+ agent_attributes[STACKTRACE_KEY] = segment.params[:backtrace]
124
128
  end
125
129
 
126
- [intrinsics, custom_attributes(segment), agent_attributes]
130
+ [intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
127
131
  end
128
132
 
129
133
  private
@@ -137,7 +141,8 @@ module NewRelic
137
141
  PRIORITY_KEY => segment.transaction.priority,
138
142
  TIMESTAMP_KEY => milliseconds_since_epoch(segment),
139
143
  DURATION_KEY => segment.duration,
140
- NAME_KEY => segment.name
144
+ NAME_KEY => segment.name,
145
+ THREAD_ID_KEY => segment.thread_id
141
146
  }
142
147
 
143
148
  # with infinite-tracing, transactions may or may not be sampled!
@@ -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
@@ -125,7 +125,16 @@ module NewRelic
125
125
 
126
126
  # Returns [filename, method, line number]
127
127
  def parse_backtrace_frame(frame)
128
- frame =~ /([^:]*)(\:(\d+))?\:in `(.*)'/
128
+ # TODO: OLD RUBIES - Ruby 3.3
129
+ # The (?:`|') non-capturing group can be removed when the agent
130
+ # drops support for Ruby 3.3
131
+ # This group is used to capture the pre-Ruby 3.4.0 backtrace syntax.
132
+ # Example frame:
133
+ # Ruby 3.3.0 and below
134
+ # "irb.rb:69:in `catch'"
135
+ # Ruby 3.4.0+
136
+ # "irb.rb:69:in 'Kernel#catch'"
137
+ frame =~ /([^:]*)(\:(\d+))?\:in (?:`|')(.*)'/
129
138
  [$1, $4, $3] # sic
130
139
  end
131
140
  end
@@ -247,7 +247,7 @@ module NewRelic
247
247
  log_error('start_segment', exception)
248
248
  end
249
249
 
250
- UNKNOWN = 'Unknown'.freeze
250
+ UNKNOWN = NewRelic::UNKNOWN
251
251
  OTHER = 'other'.freeze
252
252
 
253
253
  # Creates and starts a datastore segment used to time