newrelic_rpm 9.17.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/.build_ignore +1 -0
  3. data/CHANGELOG.md +105 -1
  4. data/lib/new_relic/agent/agent.rb +2 -0
  5. data/lib/new_relic/agent/agent_helpers/connect.rb +3 -3
  6. data/lib/new_relic/agent/agent_helpers/harvest.rb +3 -3
  7. data/lib/new_relic/agent/agent_helpers/shutdown.rb +1 -1
  8. data/lib/new_relic/agent/agent_helpers/start_worker_thread.rb +1 -1
  9. data/lib/new_relic/agent/agent_helpers/startup.rb +4 -4
  10. data/lib/new_relic/agent/configuration/default_source.rb +145 -120
  11. data/lib/new_relic/agent/configuration/manager.rb +5 -2
  12. data/lib/new_relic/agent/configuration/yaml_source.rb +4 -4
  13. data/lib/new_relic/agent/database.rb +1 -1
  14. data/lib/new_relic/agent/database_adapter.rb +1 -1
  15. data/lib/new_relic/agent/datastores/redis.rb +1 -1
  16. data/lib/new_relic/agent/distributed_tracing/cross_app_tracing.rb +1 -1
  17. data/lib/new_relic/agent/distributed_tracing.rb +2 -0
  18. data/lib/new_relic/agent/external.rb +2 -0
  19. data/lib/new_relic/agent/http_clients/uri_util.rb +1 -1
  20. data/lib/new_relic/agent/instrumentation/action_dispatch.rb +1 -1
  21. data/lib/new_relic/agent/instrumentation/action_dispatch_subscriber.rb +1 -1
  22. data/lib/new_relic/agent/instrumentation/action_mailbox.rb +1 -1
  23. data/lib/new_relic/agent/instrumentation/action_mailer.rb +1 -1
  24. data/lib/new_relic/agent/instrumentation/active_job.rb +1 -1
  25. data/lib/new_relic/agent/instrumentation/active_job_subscriber.rb +6 -2
  26. data/lib/new_relic/agent/instrumentation/active_record.rb +6 -4
  27. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +2 -2
  28. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +11 -9
  29. data/lib/new_relic/agent/instrumentation/active_record_prepend.rb +2 -2
  30. data/lib/new_relic/agent/instrumentation/async_http.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/aws_sdk_kinesis/instrumentation.rb +1 -1
  32. data/lib/new_relic/agent/instrumentation/concurrent_ruby.rb +1 -1
  33. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +4 -0
  34. data/lib/new_relic/agent/instrumentation/curb.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/elasticsearch/chain.rb +1 -2
  36. data/lib/new_relic/agent/instrumentation/elasticsearch/instrumentation.rb +1 -0
  37. data/lib/new_relic/agent/instrumentation/elasticsearch.rb +1 -1
  38. data/lib/new_relic/agent/instrumentation/ethon.rb +1 -1
  39. data/lib/new_relic/agent/instrumentation/excon.rb +1 -1
  40. data/lib/new_relic/agent/instrumentation/fiber/chain.rb +1 -1
  41. data/lib/new_relic/agent/instrumentation/fiber/prepend.rb +1 -1
  42. data/lib/new_relic/agent/instrumentation/httpclient.rb +1 -4
  43. data/lib/new_relic/agent/instrumentation/httpx/instrumentation.rb +1 -1
  44. data/lib/new_relic/agent/instrumentation/httpx.rb +1 -1
  45. data/lib/new_relic/agent/instrumentation/logstasher.rb +1 -1
  46. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +1 -1
  47. data/lib/new_relic/agent/instrumentation/memcache/helper.rb +2 -2
  48. data/lib/new_relic/agent/instrumentation/memcache/instrumentation.rb +1 -1
  49. data/lib/new_relic/agent/instrumentation/memcache/prepend.rb +1 -1
  50. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +1 -1
  51. data/lib/new_relic/agent/instrumentation/net_http/instrumentation.rb +3 -3
  52. data/lib/new_relic/agent/instrumentation/net_http.rb +2 -1
  53. data/lib/new_relic/agent/instrumentation/notifications_subscriber.rb +0 -2
  54. data/lib/new_relic/agent/instrumentation/rake.rb +1 -1
  55. data/lib/new_relic/agent/instrumentation/rdkafka/chain.rb +2 -2
  56. data/lib/new_relic/agent/instrumentation/rdkafka/prepend.rb +2 -2
  57. data/lib/new_relic/agent/instrumentation/redis/constants.rb +2 -2
  58. data/lib/new_relic/agent/instrumentation/resque.rb +2 -2
  59. data/lib/new_relic/agent/instrumentation/roda.rb +1 -1
  60. data/lib/new_relic/agent/instrumentation/ruby_kafka/prepend.rb +1 -1
  61. data/lib/new_relic/agent/instrumentation/ruby_openai.rb +2 -2
  62. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delay_extensions.rb +24 -0
  63. data/lib/new_relic/agent/instrumentation/sidekiq/extensions/delayed_class.rb +1 -1
  64. data/lib/new_relic/agent/instrumentation/sidekiq.rb +9 -1
  65. data/lib/new_relic/agent/instrumentation/stripe.rb +1 -1
  66. data/lib/new_relic/agent/instrumentation/typhoeus/instrumentation.rb +2 -2
  67. data/lib/new_relic/agent/llm/chat_completion_summary.rb +1 -1
  68. data/lib/new_relic/agent/llm/embedding.rb +1 -1
  69. data/lib/new_relic/agent/local_log_decorator.rb +1 -1
  70. data/lib/new_relic/agent/logging.rb +1 -1
  71. data/lib/new_relic/agent/messaging.rb +5 -0
  72. data/lib/new_relic/agent/method_tracer.rb +3 -0
  73. data/lib/new_relic/agent/monitors/inbound_request_monitor.rb +1 -1
  74. data/lib/new_relic/agent/monitors/synthetics_monitor.rb +1 -1
  75. data/lib/new_relic/agent/new_relic_service/json_marshaller.rb +2 -2
  76. data/lib/new_relic/agent/new_relic_service.rb +2 -2
  77. data/lib/new_relic/agent/opentelemetry/context/propagation/trace_propagator.rb +66 -0
  78. data/lib/new_relic/agent/opentelemetry/context/propagation.rb +15 -0
  79. data/lib/{tasks/instrumentation_generator/templates/Envfile.tt → new_relic/agent/opentelemetry/context.rb} +9 -5
  80. data/lib/new_relic/agent/opentelemetry/trace/span.rb +31 -0
  81. data/lib/new_relic/agent/opentelemetry/trace/tracer.rb +129 -0
  82. data/lib/new_relic/agent/opentelemetry/trace/tracer_provider.rb +18 -0
  83. data/lib/new_relic/agent/opentelemetry/trace.rb +15 -0
  84. data/lib/new_relic/agent/opentelemetry/transaction_patch.rb +69 -0
  85. data/lib/new_relic/agent/opentelemetry_bridge.rb +32 -0
  86. data/lib/new_relic/agent/parameter_filtering.rb +1 -1
  87. data/lib/new_relic/agent/samplers/cpu_sampler.rb +1 -1
  88. data/lib/new_relic/agent/samplers/memory_sampler.rb +1 -1
  89. data/lib/new_relic/agent/serverless_handler.rb +7 -1
  90. data/lib/new_relic/agent/span_event_primitive.rb +8 -1
  91. data/lib/new_relic/agent/tracer.rb +1 -1
  92. data/lib/new_relic/agent/transaction/abstract_segment.rb +2 -1
  93. data/lib/new_relic/agent/transaction/datastore_segment.rb +1 -1
  94. data/lib/new_relic/agent/transaction/distributed_tracer.rb +3 -3
  95. data/lib/new_relic/agent/transaction/message_broker_segment.rb +1 -1
  96. data/lib/new_relic/agent/transaction/request_attributes.rb +1 -6
  97. data/lib/new_relic/agent/transaction/trace_context.rb +33 -4
  98. data/lib/new_relic/agent/transaction/tracing.rb +3 -3
  99. data/lib/new_relic/agent/transaction.rb +2 -1
  100. data/lib/new_relic/agent/transaction_time_aggregator.rb +1 -1
  101. data/lib/new_relic/agent/utilization/ecs.rb +22 -0
  102. data/lib/new_relic/agent/utilization/ecs_v4.rb +22 -0
  103. data/lib/new_relic/agent/utilization_data.rb +40 -5
  104. data/lib/new_relic/agent/vm/c_ruby_vm.rb +3 -3
  105. data/lib/new_relic/agent.rb +28 -0
  106. data/lib/new_relic/constants.rb +1 -0
  107. data/lib/new_relic/control/instance_methods.rb +5 -0
  108. data/lib/new_relic/control/instrumentation.rb +1 -1
  109. data/lib/new_relic/dependency_detection.rb +0 -1
  110. data/lib/new_relic/helper.rb +7 -0
  111. data/lib/new_relic/version.rb +1 -1
  112. data/lib/sequel/extensions/new_relic_instrumentation.rb +1 -1
  113. data/lib/tasks/helpers/newrelicyml.rb +6 -2
  114. data/newrelic.yml +80 -43
  115. data/newrelic_rpm.gemspec +1 -1
  116. metadata +17 -19
  117. data/lib/tasks/instrumentation_generator/README.md +0 -63
  118. data/lib/tasks/instrumentation_generator/TODO.md +0 -33
  119. data/lib/tasks/instrumentation_generator/instrumentation.thor +0 -130
  120. data/lib/tasks/instrumentation_generator/templates/chain.tt +0 -21
  121. data/lib/tasks/instrumentation_generator/templates/chain_method.tt +0 -7
  122. data/lib/tasks/instrumentation_generator/templates/dependency_detection.tt +0 -32
  123. data/lib/tasks/instrumentation_generator/templates/instrumentation.tt +0 -13
  124. data/lib/tasks/instrumentation_generator/templates/instrumentation_method.tt +0 -3
  125. data/lib/tasks/instrumentation_generator/templates/newrelic.yml.tt +0 -19
  126. data/lib/tasks/instrumentation_generator/templates/prepend.tt +0 -13
  127. data/lib/tasks/instrumentation_generator/templates/prepend_method.tt +0 -3
  128. data/lib/tasks/instrumentation_generator/templates/test.tt +0 -15
@@ -8,11 +8,11 @@ module NewRelic
8
8
  module Typhoeus
9
9
  HYDRA_SEGMENT_NAME = 'External/Multiple/Typhoeus::Hydra/run'
10
10
  NOTICEABLE_ERROR_CLASS = 'Typhoeus::Errors::TyphoeusError'
11
- EARLIEST_VERSION = Gem::Version.new('0.5.3')
11
+ EARLIEST_VERSION = '0.5.3'
12
12
  INSTRUMENTATION_NAME = NewRelic::Agent.base_name(name)
13
13
 
14
14
  def self.is_supported_version?
15
- Gem::Version.new(::Typhoeus::VERSION) >= EARLIEST_VERSION
15
+ NewRelic::Helper.version_satisfied?(::Typhoeus::VERSION, '>=', EARLIEST_VERSION)
16
16
  end
17
17
 
18
18
  def self.request_is_hydra_enabled?(request)
@@ -32,7 +32,7 @@ module NewRelic
32
32
  # TODO: OLD RUBIES < 2.6
33
33
  # Hash#merge accepts multiple arguments in 2.6
34
34
  # Remove condition once support for Ruby <2.6 is dropped
35
- if RUBY_VERSION >= '2.6.0'
35
+ if NewRelic::Helper.version_satisfied?(RUBY_VERSION, '>=', '2.6.0')
36
36
  LlmEvent::ATTRIBUTE_NAME_EXCEPTIONS.merge(ResponseHeaders::ATTRIBUTE_NAME_EXCEPTIONS, ATTRIBUTE_NAME_EXCEPTIONS)
37
37
  else
38
38
  LlmEvent::ATTRIBUTE_NAME_EXCEPTIONS.merge(ResponseHeaders::ATTRIBUTE_NAME_EXCEPTIONS).merge(ATTRIBUTE_NAME_EXCEPTIONS)
@@ -25,7 +25,7 @@ module NewRelic
25
25
  # TODO: OLD RUBIES < 2.6
26
26
  # Hash#merge accepts multiple arguments in 2.6
27
27
  # Remove condition once support for Ruby <2.6 is dropped
28
- if RUBY_VERSION >= '2.6.0'
28
+ if NewRelic::Helper.version_satisfied?(RUBY_VERSION, '>=', '2.6.0')
29
29
  LlmEvent::ATTRIBUTE_NAME_EXCEPTIONS.merge(ResponseHeaders::ATTRIBUTE_NAME_EXCEPTIONS, ATTRIBUTE_NAME_EXCEPTIONS)
30
30
  else
31
31
  LlmEvent::ATTRIBUTE_NAME_EXCEPTIONS.merge(ResponseHeaders::ATTRIBUTE_NAME_EXCEPTIONS).merge(ATTRIBUTE_NAME_EXCEPTIONS)
@@ -43,7 +43,7 @@ module NewRelic
43
43
  # URI version 1.0+ will ship with Ruby 3.4
44
44
  # Once we drop support for Rubies below 3.4, we can use the
45
45
  # URI::RFC2396 parser exclusively.
46
- if Gem::Version.new(URI::VERSION) >= Gem::Version.new('1.0')
46
+ if NewRelic::Helper.version_satisfied?(URI::VERSION, '>=', '1.0')
47
47
  URI::RFC2396_PARSER.escape(entity_name)
48
48
  else
49
49
  URI::DEFAULT_PARSER.escape(entity_name)
@@ -165,7 +165,7 @@ module NewRelic
165
165
  # Positional and Keyword arguments are separated beginning with Ruby 2.7
166
166
  # Signature of ::Logger constructor changes in Ruby 2.4 to have both positional and keyword args
167
167
  # We pivot on Ruby 2.7 for widest supportability with least amount of hassle.
168
- if RUBY_VERSION < '2.7.0'
168
+ if NewRelic::Helper.version_satisfied?(RUBY_VERSION, '<', '2.7.0')
169
169
  def initialize(*args)
170
170
  super(*args)
171
171
  self.formatter = DecoratingFormatter.new
@@ -49,6 +49,7 @@ module NewRelic
49
49
  #
50
50
  # @return [NewRelic::Agent::Transaction::MessageBrokerSegment]
51
51
  #
52
+ # @!scope class
52
53
  # @api public
53
54
  #
54
55
  def start_message_broker_segment(action: nil,
@@ -107,6 +108,7 @@ module NewRelic
107
108
  # @return return value of given block, which will be the same as the
108
109
  # return value of an un-instrumented subscribed callback
109
110
  #
111
+ # @!scope class
110
112
  # @api public
111
113
  #
112
114
  def wrap_message_broker_consume_transaction(library:,
@@ -180,6 +182,7 @@ module NewRelic
180
182
  #
181
183
  # @return [NewRelic::Agent::Transaction::MessageBrokerSegment]
182
184
  #
185
+ # @!scope class
183
186
  # @api public
184
187
  #
185
188
  def start_amqp_publish_segment(library:,
@@ -240,6 +243,7 @@ module NewRelic
240
243
  #
241
244
  # @return [NewRelic::Agent::Transaction::MessageBrokerSegment]
242
245
  #
246
+ # @!scope class
243
247
  # @api public
244
248
  #
245
249
  def start_amqp_consume_segment(library:,
@@ -301,6 +305,7 @@ module NewRelic
301
305
  # @return return value of given block, which will be the same as the
302
306
  # return value of an un-instrumented subscribed callback
303
307
  #
308
+ # @!scope class
304
309
  # @api public
305
310
  #
306
311
  def wrap_amqp_consume_transaction(library: nil,
@@ -65,6 +65,7 @@ module NewRelic
65
65
  # categories, but generally this *should never ever be done*. Most of the time you can aggregate
66
66
  # on the server.
67
67
  #
68
+ # @!scope class
68
69
  # @api public
69
70
  #
70
71
  def trace_execution_scoped(metric_names, options = NewRelic::EMPTY_HASH) # THREAD_LOCAL_ACCESS
@@ -81,6 +82,7 @@ module NewRelic
81
82
  #
82
83
  # * <tt>metric_names</tt> is a single name or an array of names of metrics
83
84
  #
85
+ # @!scope class
84
86
  # @api public
85
87
  #
86
88
  def trace_execution_unscoped(metric_names, options = NewRelic::EMPTY_HASH) # THREAD_LOCAL_ACCESS
@@ -241,6 +243,7 @@ module NewRelic
241
243
  # # Instrument foo in transaction traces only
242
244
  # add_method_tracer :foo, 'Custom/foo', :metric => false
243
245
  #
246
+ # @!scope class
244
247
  # @api public
245
248
  #
246
249
  def add_method_tracer(method_name, metric_name = nil, options = {})
@@ -32,7 +32,7 @@ module NewRelic
32
32
 
33
33
  def deserialize_header(encoded_header, key)
34
34
  decoded_header = obfuscator.deobfuscate(encoded_header)
35
- ::JSON.load(decoded_header)
35
+ ::JSON.parse(decoded_header)
36
36
  rescue => err
37
37
  # If we have a failure of any type here, just return nil and carry on
38
38
  NewRelic::Agent.logger.debug("Failure deserializing encoded header '#{key}' in #{self.class}, #{err.class}, #{err.message}")
@@ -35,7 +35,7 @@ module NewRelic
35
35
  end
36
36
 
37
37
  def load_json(header, key)
38
- ::JSON.load(header)
38
+ ::JSON.parse(header)
39
39
  rescue => err
40
40
  NewRelic::Agent.logger.debug("Failure loading json header '#{key}' in #{self.class}, #{err.class}, #{err.message}")
41
41
  nil
@@ -19,7 +19,7 @@ module NewRelic
19
19
  def warn_for_yajl
20
20
  if defined?(::Yajl)
21
21
  require 'yajl/version'
22
- if Gem::Version.new(::Yajl::VERSION) < OK_YAJL_VERSION
22
+ if NewRelic::Helper.version_satisfied?(::Yajl::VERSION, '<', OK_YAJL_VERSION)
23
23
  ::NewRelic::Agent.logger.warn("Detected yajl-ruby version #{::Yajl::VERSION} which can cause segfaults with newrelic_rpm's thread profiling features. We strongly recommend you upgrade to the latest yajl-ruby version available.")
24
24
  end
25
25
  end
@@ -42,7 +42,7 @@ module NewRelic
42
42
  return nil
43
43
  end
44
44
 
45
- return_value(::JSON.load(data))
45
+ return_value(::JSON.parse(data))
46
46
  rescue => e
47
47
  ::NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} encountered loading collector response: #{data}")
48
48
  raise
@@ -455,7 +455,7 @@ module NewRelic
455
455
  end
456
456
 
457
457
  def handle_error_response(response, endpoint)
458
- NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::HTTP_ERROR, [response.code, endpoint])
458
+ NewRelic::Agent.agent&.health_check&.update_status(NewRelic::Agent::HealthCheck::HTTP_ERROR, [response.code, endpoint])
459
459
 
460
460
  case response
461
461
  when Net::HTTPRequestTimeOut,
@@ -641,7 +641,7 @@ module NewRelic
641
641
  response = relay_request(request, opts)
642
642
 
643
643
  if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPAccepted)
644
- NewRelic::Agent.agent.health_check.update_status(NewRelic::Agent::HealthCheck::HEALTHY)
644
+ NewRelic::Agent.agent&.health_check&.update_status(NewRelic::Agent::HealthCheck::HEALTHY)
645
645
  response
646
646
  else
647
647
  handle_error_response(response, opts[:endpoint])
@@ -0,0 +1,66 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Context
9
+ module Propagation
10
+ class TracePropagator
11
+ # The carrier is the object carrying the headers
12
+ # The context argument is a no-op, as the OpenTelemetry context is not used
13
+ # The setter argument is a no-op, added for consistency with the OpenTelemetry API
14
+ def inject(carrier, context: ::OpenTelemetry::Context.current, setter: nil)
15
+ # TODO: determine if we need to update this method to take Context into account
16
+ NewRelic::Agent::DistributedTracing.insert_distributed_trace_headers(carrier)
17
+ end
18
+
19
+ # The return value for this method should be an instance of the
20
+ # OpenTelemetry Context class. The return value of
21
+ # #accept_distributed_trace_headers is a transaction, so we cannot
22
+ # use it to extract the context.
23
+ def extract(carrier, context: ::OpenTelemetry::Context.current, getter: ::OpenTelemetry::Context::Propagation.text_map_getter)
24
+ carrier_format = determine_format(getter)
25
+ trace_context = NewRelic::Agent::DistributedTracing::TraceContext.parse(
26
+ carrier: carrier,
27
+ format: carrier_format,
28
+ trace_state_entry_key: Transaction::TraceContext::AccountHelpers.trace_state_entry_key
29
+ )
30
+ tp = trace_context.trace_parent
31
+ span_context = ::OpenTelemetry::Trace::SpanContext.new(
32
+ trace_id: tp['trace_id'],
33
+ span_id: tp['parent_id'],
34
+ trace_flags: tp['trace_flags'],
35
+ tracestate: trace_context.trace_state_payload,
36
+ remote: true
37
+ )
38
+ span = ::OpenTelemetry::Trace.non_recording_span(span_context)
39
+
40
+ ::OpenTelemetry::Trace.context_with_span(span, parent_context: context)
41
+ rescue StandardError => e
42
+ NewRelic::Agent.logger.error("Unable to extract context: #{e.message}")
43
+ context
44
+ end
45
+
46
+ private
47
+
48
+ # The getter is the way OpenTelemetry handles Rack vs. non-Rack
49
+ # formats. Rather than using their parser, get the class info we
50
+ # need to do things the New Relic way
51
+ def determine_format(getter)
52
+ case getter
53
+ when ::OpenTelemetry::Context::Propagation::RackEnvGetter
54
+ FORMAT_RACK
55
+ when defined?(::OpenTelemetry::Common) && ::OpenTelemetry::Common::Propagation::RackEnvGetter
56
+ FORMAT_RACK
57
+ else
58
+ FORMAT_NON_RACK
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,15 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Context
9
+ module Propagation
10
+ require_relative 'propagation/trace_propagator'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,8 +2,12 @@
2
2
  # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
3
3
  # frozen_string_literal: true
4
4
 
5
- instrumentation_methods :chain, :prepend
6
-
7
- gemfile <<~RB
8
- gem '<%= @name.downcase %>'
9
- RB
5
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Context
9
+ require_relative 'context/propagation'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Trace
9
+ class Span < ::OpenTelemetry::Trace::Span
10
+ attr_accessor :finishable
11
+
12
+ def finish(end_timestamp: nil)
13
+ finishable&.finish
14
+ end
15
+
16
+ def set_attribute(key, value)
17
+ NewRelic::Agent.add_custom_span_attributes(key => value)
18
+ end
19
+
20
+ def add_attributes(attributes)
21
+ NewRelic::Agent.add_custom_span_attributes(attributes)
22
+ end
23
+
24
+ def record_exception(exception, attributes: nil)
25
+ NewRelic::Agent.notice_error(exception, attributes: attributes)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,129 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Trace
9
+ class Tracer < ::OpenTelemetry::Trace::Tracer
10
+ def initialize(name = nil, version = nil)
11
+ @name = name || ''
12
+ @version = version || ''
13
+ end
14
+
15
+ def start_span(name, with_parent: nil, attributes: nil, links: nil, start_timestamp: nil, kind: nil)
16
+ parent_otel_context = ::OpenTelemetry::Trace.current_span(with_parent).context
17
+
18
+ finishable = if can_start_transaction?(parent_otel_context)
19
+ return if internal_span_kind_with_invalid_parent?(kind, parent_otel_context)
20
+
21
+ nr_item = NewRelic::Agent::Tracer.start_transaction_or_segment(name: name, category: :otel)
22
+ add_remote_context_to_txn(nr_item, parent_otel_context)
23
+ nr_item
24
+ else
25
+ NewRelic::Agent::Tracer.start_segment(name: name)
26
+ end
27
+
28
+ otel_span = get_otel_span_from_finishable(finishable)
29
+ otel_span.finishable = finishable
30
+ add_remote_context_to_otel_span(otel_span, parent_otel_context)
31
+ otel_span.add_attributes(attributes) if attributes
32
+ otel_span
33
+ end
34
+
35
+ def in_span(name, attributes: nil, links: nil, start_timestamp: nil, kind: nil)
36
+ span = start_span(name, attributes: attributes, links: links, start_timestamp: start_timestamp, kind: kind)
37
+ begin
38
+ yield
39
+ rescue => e
40
+ # TODO: Update for segment errors if finishable is a segment
41
+ NewRelic::Agent.notice_error(e)
42
+ raise
43
+ end
44
+ ensure
45
+ span&.finish
46
+ end
47
+
48
+ private
49
+
50
+ def get_otel_span_from_finishable(finishable)
51
+ case finishable
52
+ when NewRelic::Agent::Transaction
53
+ finishable.segments.first.instance_variable_get(:@otel_span)
54
+ when NewRelic::Agent::Transaction::Segment
55
+ finishable.instance_variable_get(:@otel_span)
56
+ else
57
+ NewRelic::Agent.logger.warn('Tracer#get_otel_span_from_finishable failed to get span from finishable - finishable is not a transaction or segment')
58
+ nil
59
+ end
60
+ end
61
+
62
+ def can_start_transaction?(parent_otel_context)
63
+ parent_otel_context.remote? || !parent_otel_context.valid?
64
+ end
65
+
66
+ def internal_span_kind_with_invalid_parent?(kind, parent_otel_context)
67
+ !parent_otel_context.valid? && kind == :internal
68
+ end
69
+
70
+ def transaction_and_remote_parent?(txn, parent_otel_context)
71
+ txn.is_a?(NewRelic::Agent::Transaction) && parent_otel_context.remote?
72
+ end
73
+
74
+ def add_remote_context_to_txn(txn, parent_otel_context)
75
+ return unless transaction_and_remote_parent?(txn, parent_otel_context)
76
+
77
+ txn.trace_id = parent_otel_context.trace_id
78
+ txn.parent_span_id = parent_otel_context.span_id
79
+
80
+ set_tracestate(txn.distributed_tracer, parent_otel_context)
81
+ end
82
+
83
+ def set_tracestate(distributed_tracer, otel_context)
84
+ case otel_context.tracestate
85
+ when ::OpenTelemetry::Trace::Tracestate
86
+ set_otel_trace_state(distributed_tracer, otel_context)
87
+ when NewRelic::Agent::TraceContextPayload
88
+ set_nr_trace_state(distributed_tracer, otel_context)
89
+ end
90
+ end
91
+
92
+ def set_nr_trace_state(distributed_tracer, otel_context)
93
+ distributed_tracer.instance_variable_set(:@trace_state_payload, otel_context.tracestate)
94
+ distributed_tracer.parent_transaction_id = distributed_tracer.trace_state_payload.transaction_id
95
+ distributed_tracer.determine_sampling_decision(otel_context.tracestate, otel_context.trace_flags)
96
+ end
97
+
98
+ def set_otel_trace_state(distributed_tracer, otel_context)
99
+ nr_entry = otel_context.tracestate.value(Transaction::TraceContext::AccountHelpers.trace_state_entry_key)
100
+ return unless nr_entry
101
+
102
+ nr_payload = NewRelic::Agent::TraceContextPayload.from_s(nr_entry)
103
+ distributed_tracer.instance_variable_set(:@trace_state_payload, nr_payload)
104
+ distributed_tracer.parent_transaction_id = distributed_tracer.trace_state_payload.transaction_id
105
+ trace_flags = parse_trace_flags(otel_context.trace_flags)
106
+ distributed_tracer.determine_sampling_decision(nr_payload, trace_flags)
107
+ end
108
+
109
+ def parse_trace_flags(trace_flags)
110
+ case trace_flags
111
+ when String
112
+ trace_flags
113
+ when Integer
114
+ trace_flags.to_s
115
+ when ::OpenTelemetry::Trace::TraceFlags
116
+ trace_flags.sampled? ? '01' : '00'
117
+ end
118
+ end
119
+
120
+ def add_remote_context_to_otel_span(otel_span, parent_otel_context)
121
+ return unless transaction_and_remote_parent?(otel_span.finishable, parent_otel_context)
122
+
123
+ otel_span.context.instance_variable_set(:@trace_id, otel_span.finishable.trace_id)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,18 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Trace
9
+ class TracerProvider < ::OpenTelemetry::Trace::TracerProvider
10
+ # TODO: Add a registration mechanism for tracers like exists in the SDK
11
+ def tracer(name = nil, version = nil)
12
+ @tracer ||= Tracer.new(name, version)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module Trace
9
+ require_relative 'trace/tracer_provider'
10
+ require_relative 'trace/tracer'
11
+ require_relative 'trace/span'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,69 @@
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
+ module NewRelic
6
+ module Agent
7
+ module OpenTelemetry
8
+ module TransactionPatch
9
+ attr_accessor :opentelemetry_context
10
+
11
+ def initialize(_category, _options)
12
+ @opentelemetry_context = {}
13
+ super
14
+ end
15
+
16
+ def set_current_segment(new_segment)
17
+ @current_segment_lock.synchronize do
18
+ unless opentelemetry_context.empty?
19
+ ::OpenTelemetry::Context.detach(opentelemetry_context[otel_current_span_key])
20
+ end
21
+
22
+ span = find_or_create_span(new_segment)
23
+ ctx = ::OpenTelemetry::Context.current.set_value(otel_current_span_key, span)
24
+ token = ::OpenTelemetry::Context.attach(ctx)
25
+
26
+ opentelemetry_context[otel_current_span_key] = token
27
+ end
28
+
29
+ super
30
+ end
31
+
32
+ def remove_current_segment_by_thread_id(id)
33
+ # make sure the context is fully detached when the transaction ends
34
+ @current_segment_lock.synchronize do
35
+ ::OpenTelemetry::Context.detach(opentelemetry_context[otel_current_span_key])
36
+ opentelemetry_context.delete(id)
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ private
43
+
44
+ def find_or_create_span(segment)
45
+ if segment.instance_variable_defined?(:@otel_span)
46
+ segment.instance_variable_get(:@otel_span)
47
+ else
48
+ span = Trace::Span.new(span_context: span_context_from_segment(segment))
49
+ segment.instance_variable_set(:@otel_span, span)
50
+ span
51
+ end
52
+ end
53
+
54
+ def span_context_from_segment(segment)
55
+ ::OpenTelemetry::Trace::SpanContext.new(
56
+ trace_id: segment.transaction.trace_id,
57
+ span_id: segment.guid,
58
+ remote: false
59
+ )
60
+ end
61
+
62
+ def otel_current_span_key
63
+ # CURRENT_SPAN_KEY is a private constant
64
+ ::OpenTelemetry::Trace.const_get(:CURRENT_SPAN_KEY)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
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
+ module NewRelic
6
+ module Agent
7
+ class OpenTelemetryBridge
8
+ def initialize
9
+ # no-op without OpenTelemetry API & config
10
+ return unless defined?(OpenTelemetry) &&
11
+ NewRelic::Agent.config[:'opentelemetry_bridge.enabled']
12
+
13
+ OpenTelemetryBridge.install
14
+ end
15
+
16
+ private
17
+
18
+ def self.install
19
+ require 'opentelemetry' # requires the opentelemetry-api gem
20
+ require_relative 'opentelemetry/trace'
21
+ require_relative 'opentelemetry/transaction_patch'
22
+ require_relative 'opentelemetry/context'
23
+
24
+ # TODO: Add a warning if SDK gem is installed
25
+
26
+ ::OpenTelemetry.tracer_provider = OpenTelemetry::Trace::TracerProvider.new
27
+ Transaction.prepend(OpenTelemetry::TransactionPatch)
28
+ ::OpenTelemetry.propagation = OpenTelemetry::Context::Propagation::TracePropagator.new
29
+ end
30
+ end
31
+ end
32
+ end
@@ -9,7 +9,7 @@ module NewRelic
9
9
 
10
10
  ACTION_DISPATCH_PARAMETER_FILTER ||= 'action_dispatch.parameter_filter'.freeze
11
11
 
12
- if defined?(Rails) && Gem::Version.new(::Rails::VERSION::STRING) >= Gem::Version.new('5.0.0')
12
+ if defined?(Rails) && NewRelic::Helper.version_satisfied?(::Rails::VERSION::STRING, '>=', '5.0.0')
13
13
  Rails.application.config.to_prepare do
14
14
  RAILS_FILTER_CLASS ||= if defined?(ActiveSupport::ParameterFilter)
15
15
  ActiveSupport::ParameterFilter
@@ -42,7 +42,7 @@ module NewRelic
42
42
  # Process.times on JRuby < 1.7.0 reports wall clock elapsed time,
43
43
  # not actual cpu time used, so this sampler can only be used on JRuby >= 1.7.0.
44
44
  if defined?(JRuby)
45
- return JRUBY_VERSION >= '1.7.0'
45
+ return NewRelic::Helper.version_satisfied?(JRUBY_VERSION, '>=', '1.7.0')
46
46
  end
47
47
 
48
48
  true
@@ -50,7 +50,7 @@ module NewRelic
50
50
  begin
51
51
  NewRelic::Helper.run_command('uname -s').downcase
52
52
  rescue NewRelic::CommandRunFailedError, NewRelic::CommandExecutableNotFoundError
53
- 'unknown'
53
+ NewRelic::UNKNOWN_LOWER
54
54
  end
55
55
  else
56
56
  RUBY_PLATFORM.downcase
@@ -40,7 +40,13 @@ module NewRelic
40
40
 
41
41
  @event, @context = event, context
42
42
 
43
- NewRelic::Agent::Tracer.in_transaction(category: category, name: function_name) do
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
48
+
49
+ NewRelic::Agent::Tracer.in_transaction(category: category, name: txn_name) do
44
50
  prep_transaction
45
51
 
46
52
  process_response(NewRelic::LanguageSupport.constantize(namespace)
@@ -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'
@@ -121,6 +123,10 @@ module NewRelic
121
123
  agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, DB_STATEMENT_MAX_BYTES)
122
124
  end
123
125
 
126
+ if segment.params[:backtrace]
127
+ agent_attributes[STACKTRACE_KEY] = segment.params[:backtrace]
128
+ end
129
+
124
130
  [intrinsics, custom_attributes(segment), agent_attributes.merge(agent_attributes(segment))]
125
131
  end
126
132
 
@@ -135,7 +141,8 @@ module NewRelic
135
141
  PRIORITY_KEY => segment.transaction.priority,
136
142
  TIMESTAMP_KEY => milliseconds_since_epoch(segment),
137
143
  DURATION_KEY => segment.duration,
138
- NAME_KEY => segment.name
144
+ NAME_KEY => segment.name,
145
+ THREAD_ID_KEY => segment.thread_id
139
146
  }
140
147
 
141
148
  # with infinite-tracing, transactions may or may not be sampled!