newrelic_rpm 6.3.0.355 → 6.8.0.360

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +50 -10
  4. data/CHANGELOG.md +198 -0
  5. data/Guardfile +7 -1
  6. data/lib/new_relic/agent.rb +80 -0
  7. data/lib/new_relic/agent/agent.rb +87 -26
  8. data/lib/new_relic/agent/agent_logger.rb +4 -0
  9. data/lib/new_relic/agent/attribute_filter.rb +7 -7
  10. data/lib/new_relic/agent/attributes.rb +152 -0
  11. data/lib/new_relic/agent/autostart.rb +19 -14
  12. data/lib/new_relic/agent/commands/agent_command_router.rb +2 -21
  13. data/lib/new_relic/agent/configuration/default_source.rb +129 -39
  14. data/lib/new_relic/agent/configuration/environment_source.rb +4 -2
  15. data/lib/new_relic/agent/configuration/event_harvest_config.rb +45 -0
  16. data/lib/new_relic/agent/configuration/high_security_source.rb +1 -0
  17. data/lib/new_relic/agent/configuration/manager.rb +13 -9
  18. data/lib/new_relic/agent/configuration/server_source.rb +33 -9
  19. data/lib/new_relic/agent/configuration/yaml_source.rb +10 -5
  20. data/lib/new_relic/agent/connect/request_builder.rb +11 -13
  21. data/lib/new_relic/agent/connect/response_handler.rb +1 -1
  22. data/lib/new_relic/agent/cross_app_monitor.rb +1 -1
  23. data/lib/new_relic/agent/datastores/mongo/event_formatter.rb +2 -2
  24. data/lib/new_relic/agent/datastores/mongo/obfuscator.rb +8 -8
  25. data/lib/new_relic/agent/distributed_trace_intrinsics.rb +90 -0
  26. data/lib/new_relic/agent/distributed_trace_metrics.rb +74 -0
  27. data/lib/new_relic/agent/distributed_trace_monitor.rb +2 -12
  28. data/lib/new_relic/agent/distributed_trace_payload.rb +9 -76
  29. data/lib/new_relic/agent/distributed_trace_transport_type.rb +43 -0
  30. data/lib/new_relic/agent/error_collector.rb +2 -2
  31. data/lib/new_relic/agent/error_event_aggregator.rb +2 -1
  32. data/lib/new_relic/agent/error_trace_aggregator.rb +1 -0
  33. data/lib/new_relic/agent/event_aggregator.rb +26 -32
  34. data/lib/new_relic/agent/guid_generator.rb +28 -0
  35. data/lib/new_relic/agent/inbound_request_monitor.rb +2 -2
  36. data/lib/new_relic/agent/instrumentation/action_cable_subscriber.rb +24 -42
  37. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +45 -69
  38. data/lib/new_relic/agent/instrumentation/action_view_subscriber.rb +70 -53
  39. data/lib/new_relic/agent/instrumentation/active_record_notifications.rb +25 -18
  40. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +33 -47
  41. data/lib/new_relic/agent/instrumentation/active_storage_subscriber.rb +4 -4
  42. data/lib/new_relic/agent/instrumentation/grape.rb +2 -3
  43. data/lib/new_relic/agent/instrumentation/{evented_subscriber.rb → notifications_subscriber.rb} +7 -66
  44. data/lib/new_relic/agent/instrumentation/rails_notifications/action_cable.rb +2 -3
  45. data/lib/new_relic/agent/javascript_instrumentor.rb +1 -1
  46. data/lib/new_relic/agent/logging.rb +129 -0
  47. data/lib/new_relic/agent/new_relic_service.rb +7 -9
  48. data/lib/new_relic/agent/priority_sampled_buffer.rb +2 -0
  49. data/lib/new_relic/agent/span_event_aggregator.rb +2 -4
  50. data/lib/new_relic/agent/span_event_primitive.rb +29 -7
  51. data/lib/new_relic/agent/sql_sampler.rb +1 -1
  52. data/lib/new_relic/agent/threading/backtrace_service.rb +3 -3
  53. data/lib/new_relic/agent/threading/thread_profile.rb +9 -23
  54. data/lib/new_relic/agent/trace_context.rb +244 -0
  55. data/lib/new_relic/agent/trace_context_payload.rb +134 -0
  56. data/lib/new_relic/agent/trace_context_request_monitor.rb +42 -0
  57. data/lib/new_relic/agent/tracer.rb +32 -0
  58. data/lib/new_relic/agent/transaction.rb +26 -20
  59. data/lib/new_relic/agent/transaction/abstract_segment.rb +2 -2
  60. data/lib/new_relic/agent/transaction/distributed_tracing.rb +20 -101
  61. data/lib/new_relic/agent/transaction/external_request_segment.rb +18 -5
  62. data/lib/new_relic/agent/transaction/segment.rb +7 -1
  63. data/lib/new_relic/agent/transaction/trace.rb +3 -8
  64. data/lib/new_relic/agent/transaction/trace_builder.rb +0 -1
  65. data/lib/new_relic/agent/transaction/trace_context.rb +159 -0
  66. data/lib/new_relic/agent/transaction/trace_node.rb +8 -3
  67. data/lib/new_relic/agent/transaction_error_primitive.rb +4 -11
  68. data/lib/new_relic/agent/transaction_event_primitive.rb +3 -11
  69. data/lib/new_relic/agent/transaction_event_recorder.rb +3 -3
  70. data/lib/new_relic/agent/transaction_sampler.rb +1 -5
  71. data/lib/new_relic/cli/commands/deployments.rb +1 -1
  72. data/lib/new_relic/coerce.rb +29 -6
  73. data/lib/new_relic/control/instance_methods.rb +10 -1
  74. data/lib/new_relic/dependency_detection.rb +4 -4
  75. data/lib/new_relic/noticed_error.rb +8 -4
  76. data/lib/new_relic/rack/browser_monitoring.rb +10 -8
  77. data/lib/new_relic/version.rb +1 -1
  78. data/lib/tasks/config.rake +1 -2
  79. data/newrelic_rpm.gemspec +13 -4
  80. data/test/agent_helper.rb +95 -9
  81. data/true +0 -0
  82. metadata +58 -24
  83. data/lib/new_relic/agent/commands/xray_session.rb +0 -55
  84. data/lib/new_relic/agent/commands/xray_session_collection.rb +0 -161
  85. data/lib/new_relic/agent/configuration/event_data.rb +0 -39
  86. data/lib/new_relic/agent/transaction/attributes.rb +0 -154
  87. data/lib/new_relic/agent/transaction/xray_sample_buffer.rb +0 -64
  88. data/lib/tasks/versions.html.erb +0 -28
  89. data/lib/tasks/versions.postface.html +0 -8
  90. data/lib/tasks/versions.preface.html +0 -9
  91. data/lib/tasks/versions.rake +0 -65
  92. data/lib/tasks/versions.txt.erb +0 -14
@@ -28,9 +28,8 @@ DependencyDetection.defer do
28
28
  NewRelic::Agent::Instrumentation::ActionCableSubscriber.new)
29
29
 
30
30
  ActiveSupport.on_load(:action_cable) do
31
- ::NewRelic::Agent::PrependSupportability.record_metrics_for(
32
- ::ActionCable::Engine,
33
- ::ActionCable::RemoteConnections)
31
+ ::NewRelic::Agent::PrependSupportability.record_metrics_for(::ActionCable::Engine) if defined?(::ActionCable::Engine)
32
+ ::NewRelic::Agent::PrependSupportability.record_metrics_for(::ActionCable::RemoteConnections) if defined?(::ActionCable::RemoteConnections)
34
33
  end
35
34
  end
36
35
  end
@@ -14,7 +14,7 @@ module NewRelic
14
14
  RUM_KEY_LENGTH = 13
15
15
 
16
16
  def initialize(event_listener)
17
- event_listener.subscribe(:finished_configuring, &method(:log_configuration))
17
+ event_listener.subscribe(:initial_configuration_complete, &method(:log_configuration))
18
18
  end
19
19
 
20
20
  def log_configuration
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require 'json'
6
+ require 'new_relic/agent/hostname'
7
+
8
+ module NewRelic
9
+ module Agent
10
+ module Logging
11
+
12
+ # This class can be used as the formatter for an existing logger. It
13
+ # decorates log messages with trace and entity metadata, and formats each
14
+ # log messages as a JSON object.
15
+ #
16
+ # It can be added to a Rails application like this:
17
+ #
18
+ # require 'newrelic_rpm'
19
+ #
20
+ # Rails.application.configure do
21
+ # config.log_formatter = ::NewRelic::Agent::Logging::DecoratingFormatter.new
22
+ # end
23
+ #
24
+ # @api public
25
+ class DecoratingFormatter < ::Logger::Formatter
26
+ TIMESTAMP_KEY = 'timestamp'.freeze
27
+ MESSAGE_KEY = 'message'.freeze
28
+ LOG_LEVEL_KEY = 'log.level'.freeze
29
+ LOG_NAME_KEY = 'logger.name'.freeze
30
+ NEWLINE = "\n".freeze
31
+
32
+ QUOTE = '"'.freeze
33
+ COLON = ':'.freeze
34
+ COMMA = ','.freeze
35
+ CLOSING_BRACE = '}'.freeze
36
+
37
+ def initialize
38
+ Agent.config.register_callback :app_name do
39
+ @app_name = nil
40
+ end
41
+ end
42
+
43
+ def call severity, time, progname, msg
44
+ message = '{'
45
+ if app_name
46
+ add_key_value message, ENTITY_NAME_KEY, app_name
47
+ message << COMMA
48
+ end
49
+ add_key_value message, ENTITY_TYPE_KEY, ENTITY_TYPE
50
+ message << COMMA
51
+ add_key_value message, HOSTNAME_KEY, Hostname.get
52
+
53
+ if entity_guid = Agent.config[:entity_guid]
54
+ message << COMMA
55
+ add_key_value message, ENTITY_GUID_KEY, entity_guid
56
+ end
57
+
58
+ if trace_id = Tracer.trace_id
59
+ message << COMMA
60
+ add_key_value message, TRACE_ID_KEY, trace_id
61
+ end
62
+ if span_id = Tracer.span_id
63
+ message << COMMA
64
+ add_key_value message, SPAN_ID_KEY, span_id
65
+ end
66
+
67
+ message << COMMA
68
+ message << QUOTE << MESSAGE_KEY << QUOTE << COLON << escape(msg)
69
+ message << COMMA
70
+ add_key_value message, LOG_LEVEL_KEY, severity
71
+ if progname
72
+ message << COMMA
73
+ add_key_value message, LOG_NAME_KEY, progname
74
+ end
75
+
76
+ message << COMMA
77
+ message << QUOTE << TIMESTAMP_KEY << QUOTE << COLON << time.to_f.to_s
78
+ message << CLOSING_BRACE << NEWLINE
79
+ end
80
+
81
+ def app_name
82
+ @app_name ||= Agent.config[:app_name][0]
83
+ end
84
+
85
+ def add_key_value message, key, value
86
+ message << QUOTE << key << QUOTE << COLON << QUOTE << value << QUOTE
87
+ end
88
+
89
+ def escape message
90
+ if String === message
91
+ message.to_json
92
+ else
93
+ message.inspect.to_json
94
+ end
95
+ end
96
+
97
+ def clear_tags!
98
+ # No-op; just avoiding issues with act-fluent-logger-rails
99
+ end
100
+ end
101
+
102
+
103
+ # This logger decorates logs with trace and entity metadata, and emits log
104
+ # messages formatted as JSON objects. It extends the Logger class from
105
+ # the Ruby standard library, and accepts the same constructor parameters.
106
+ #
107
+ # It aliases the `:info` message to overwrite the `:write` method, so it
108
+ # can be used in Rack applications that expect the logger to be a file-like
109
+ # object.
110
+ #
111
+ # It can be added to an application like this:
112
+ #
113
+ # require 'newrelic_rpm'
114
+ #
115
+ # config.logger = NewRelic::Agent::Logging::DecoratingLogger.new "log/application.log"
116
+ #
117
+ # @api public
118
+ class DecoratingLogger < (defined?(::ActiveSupport) && defined?(::ActiveSupport::Logger) ? ::ActiveSupport::Logger : ::Logger)
119
+
120
+ alias :write :info
121
+
122
+ def initialize *args
123
+ super
124
+ self.formatter = DecoratingFormatter.new
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -92,14 +92,16 @@ module NewRelic
92
92
  token = Agent.config[:security_policies_token]
93
93
 
94
94
  if token && !token.empty?
95
- response = invoke_remote(:preconnect, [{'security_policies_token' => token}])
95
+ response = invoke_remote(:preconnect, [{'security_policies_token' => token, 'high_security' => false}])
96
96
 
97
97
  validator = SecurityPolicySettings::Validator.new(response)
98
98
  validator.validate_matching_agent_config!
99
99
 
100
100
  response
101
+ elsif Agent.config[:high_security]
102
+ invoke_remote(:preconnect, [{'high_security' => true}])
101
103
  else
102
- invoke_remote(:preconnect, [])
104
+ invoke_remote(:preconnect, [{'high_security' => false}])
103
105
  end
104
106
  end
105
107
 
@@ -163,10 +165,6 @@ module NewRelic
163
165
  invoke_remote(:agent_command_results, [@agent_id, results])
164
166
  end
165
167
 
166
- def get_xray_metadata(xray_ids)
167
- invoke_remote(:get_xray_metadata, [@agent_id, *xray_ids])
168
- end
169
-
170
168
  def analytic_event_data(data)
171
169
  _, items = data
172
170
  invoke_remote(:analytic_event_data, [@agent_id, *data],
@@ -200,10 +198,10 @@ module NewRelic
200
198
  encoding = 'identity'
201
199
  if data.size > 64 * 1024
202
200
  encoding = Agent.config[:compressed_content_encoding]
203
- data = if encoding == 'gzip'
204
- Encoders::Compressed::Gzip.encode(data)
205
- else
201
+ data = if encoding == 'deflate'
206
202
  Encoders::Compressed::Deflate.encode(data)
203
+ else
204
+ Encoders::Compressed::Gzip.encode(data)
207
205
  end
208
206
  end
209
207
  check_post_size(data, endpoint)
@@ -22,6 +22,8 @@ module NewRelic
22
22
  def append(priority: nil, event: nil, &blk)
23
23
  increment_seen
24
24
 
25
+ return if @capacity == 0
26
+
25
27
  if @seen == @capacity
26
28
  @items = Heap.new(@items) { |x| priority_for(x) }
27
29
  end
@@ -13,10 +13,8 @@ module NewRelic
13
13
  named :SpanEventAggregator
14
14
  capacity_key :'span_events.max_samples_stored'
15
15
 
16
- enabled_fn -> {
17
- NewRelic::Agent.config[:'span_events.enabled'] &&
18
- NewRelic::Agent.config[:'distributed_tracing.enabled']
19
- }
16
+ enabled_keys :'span_events.enabled',
17
+ :'distributed_tracing.enabled'
20
18
 
21
19
  def record priority: nil, event:nil, &blk
22
20
  unless(event || priority && blk)
@@ -36,6 +36,8 @@ module NewRelic
36
36
  PEER_HOSTNAME_KEY = 'peer.hostname'.freeze
37
37
  SPAN_KIND_KEY = 'span.kind'.freeze
38
38
  ENTRY_POINT_KEY = 'nr.entryPoint'.freeze
39
+ TRUSTED_PARENT_KEY = "trustedParentId".freeze
40
+ TRACING_VENDORS_KEY = "tracingVendors".freeze
39
41
 
40
42
  # Strings for static values of the event structure
41
43
  EVENT_TYPE = 'Span'.freeze
@@ -51,7 +53,7 @@ module NewRelic
51
53
  intrinsics = intrinsics_for(segment)
52
54
  intrinsics[CATEGORY_KEY] = GENERIC_CATEGORY
53
55
 
54
- [intrinsics, EMPTY_HASH, EMPTY_HASH]
56
+ [intrinsics, custom_attributes(segment.attributes), EMPTY_HASH]
55
57
  end
56
58
 
57
59
  def for_external_request_segment(segment)
@@ -68,7 +70,7 @@ module NewRelic
68
70
  agent_attributes[HTTP_URL_KEY] = truncate(segment.uri)
69
71
  end
70
72
 
71
- [intrinsics, EMPTY_HASH, agent_attributes]
73
+ [intrinsics, custom_attributes(segment.attributes), agent_attributes]
72
74
  end
73
75
 
74
76
  def for_datastore_segment(segment)
@@ -96,7 +98,7 @@ module NewRelic
96
98
  agent_attributes[DB_STATEMENT_KEY] = truncate(segment.nosql_statement, 2000)
97
99
  end
98
100
 
99
- [intrinsics, EMPTY_HASH, agent_attributes]
101
+ [intrinsics, custom_attributes(segment.attributes), agent_attributes]
100
102
  end
101
103
 
102
104
  private
@@ -115,19 +117,39 @@ module NewRelic
115
117
  NAME_KEY => segment.name
116
118
  }
117
119
 
120
+ if segment.parent.nil?
121
+ intrinsics[ENTRY_POINT_KEY] = true
122
+ if segment.transaction
123
+ if segment.transaction.trace_context_header_data
124
+ intrinsics[TRACING_VENDORS_KEY] = segment.transaction.trace_context_header_data.trace_state_vendors
125
+ end
126
+ if segment.transaction.trace_state_payload
127
+ intrinsics[TRUSTED_PARENT_KEY] = segment.transaction.trace_state_payload.id
128
+ end
129
+ end
130
+ end
131
+
118
132
  parent_id = parent_guid(segment)
119
133
  intrinsics[PARENT_ID_KEY] = parent_id if parent_id
120
-
121
- intrinsics[ENTRY_POINT_KEY] = true unless segment.parent
122
-
123
134
  intrinsics
124
135
  end
125
136
 
137
+ def custom_attributes attributes
138
+ if attributes
139
+ result = attributes.custom_attributes_for(NewRelic::Agent::AttributeFilter::DST_SPAN_EVENTS)
140
+ result.freeze
141
+ else
142
+ EMPTY_HASH
143
+ end
144
+ end
145
+
126
146
  def parent_guid(segment)
127
147
  if segment.parent
128
148
  segment.parent.guid
129
- elsif segment.transaction && segment.transaction.distributed_trace?
149
+ elsif segment.transaction && segment.transaction.distributed_trace_payload
130
150
  segment.transaction.distributed_trace_payload.id
151
+ elsif segment.transaction && segment.transaction.trace_context_header_data
152
+ segment.transaction.trace_context_header_data.parent_id
131
153
  end
132
154
  end
133
155
 
@@ -162,7 +162,7 @@ module NewRelic
162
162
  if transaction && transaction.distributed_trace_payload
163
163
  params = {}
164
164
  payload = transaction.distributed_trace_payload
165
- payload.assign_intrinsics transaction, params
165
+ DistributedTraceIntrinsics.copy_from_transaction transaction, payload, params
166
166
  params[PRIORITY] = transaction.priority
167
167
  end
168
168
  params
@@ -36,8 +36,8 @@ module NewRelic
36
36
  @worker_loop = NewRelic::Agent::WorkerLoop.new
37
37
 
38
38
  # Memoize overhead % to avoid getting stale OR looked up every poll
39
- @overhead_percent_threshold = NewRelic::Agent.config[:'xray_session.max_profile_overhead']
40
- NewRelic::Agent.config.register_callback(:'xray_session.max_profile_overhead') do |new_value|
39
+ @overhead_percent_threshold = NewRelic::Agent.config[:'thread_profiler.max_profile_overhead']
40
+ NewRelic::Agent.config.register_callback(:'thread_profiler.max_profile_overhead') do |new_value|
41
41
  @overhead_percent_threshold = new_value
42
42
  end
43
43
 
@@ -213,7 +213,7 @@ module NewRelic
213
213
  if @buffer[thread].length < MAX_BUFFER_LENGTH
214
214
  @buffer[thread] << [timestamp, backtrace]
215
215
  else
216
- NewRelic::Agent.increment_metric('Supportability/XraySessions/DroppedBacktraces')
216
+ NewRelic::Agent.increment_metric('Supportability/ThreadProfiler/DroppedBacktraces')
217
217
  end
218
218
  end
219
219
  end
@@ -16,7 +16,7 @@ module NewRelic
16
16
 
17
17
  attr_reader :profile_id, :traces, :sample_period,
18
18
  :duration, :poll_count, :backtrace_count, :failure_count,
19
- :created_at, :xray_id, :command_arguments, :profile_agent_code
19
+ :created_at, :command_arguments, :profile_agent_code
20
20
  attr_accessor :finished_at
21
21
 
22
22
  def initialize(command_arguments={})
@@ -25,7 +25,6 @@ module NewRelic
25
25
  @duration = command_arguments.fetch('duration', 120)
26
26
  @sample_period = command_arguments.fetch('sample_period', 0.1)
27
27
  @profile_agent_code = command_arguments.fetch('profile_agent_code', false)
28
- @xray_id = command_arguments.fetch('x_ray_id', nil)
29
28
  @finished = false
30
29
 
31
30
  @traces = {
@@ -51,14 +50,6 @@ module NewRelic
51
50
  @poll_count += 1
52
51
  end
53
52
 
54
- def sample_count
55
- xray? ? @backtrace_count : @poll_count
56
- end
57
-
58
- def xray?
59
- !!@xray_id
60
- end
61
-
62
53
  def empty?
63
54
  @backtrace_count == 0
64
55
  end
@@ -121,26 +112,21 @@ module NewRelic
121
112
  def to_collector_array(encoder)
122
113
  encoded_trace_tree = encoder.encode(generate_traces, :skip_normalization => true)
123
114
  result = [
124
- int(self.profile_id),
125
- float(self.created_at),
126
- float(self.finished_at),
127
- int(self.sample_count),
115
+ int(profile_id),
116
+ float(created_at),
117
+ float(finished_at),
118
+ int(poll_count),
128
119
  encoded_trace_tree,
129
- int(self.unique_thread_count),
120
+ int(unique_thread_count),
130
121
  0 # runnable thread count, which we don't track
131
122
  ]
132
- result << int(@xray_id) if xray?
133
123
  result
134
124
  end
135
125
 
136
126
  def to_log_description
137
- id = if xray?
138
- "@xray_id: #{xray_id}"
139
- else
140
- "@profile_id: #{profile_id}"
141
- end
142
-
143
- "#<ThreadProfile:#{object_id} #{id} @command_arguments=#{@command_arguments.inspect}>"
127
+ "#<ThreadProfile:#{object_id} "\
128
+ "@profile_id: #{profile_id} "\
129
+ "@command_arguments=#{@command_arguments.inspect}>"
144
130
  end
145
131
 
146
132
  end
@@ -0,0 +1,244 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require 'new_relic/agent/trace_context_payload'
6
+
7
+ module NewRelic
8
+ module Agent
9
+ class TraceContext
10
+ VERSION = 0x0
11
+ TRACE_PARENT = 'traceparent'.freeze
12
+ TRACE_STATE = 'tracestate'.freeze
13
+
14
+ TRACE_PARENT_RACK = 'HTTP_TRACEPARENT'.freeze
15
+ TRACE_STATE_RACK = 'HTTP_TRACESTATE'.freeze
16
+
17
+ FORMAT_HTTP = 0
18
+ FORMAT_RACK = 1
19
+
20
+ TRACE_PARENT_REGEX = /\A(?<version>[a-f\d]{2})-(?<trace_id>[a-f\d]{32})-(?<parent_id>[a-f\d]{16})-(?<trace_flags>\d{2})(?<undefined_fields>-[a-zA-Z\d-]*)?\z/.freeze
21
+
22
+ COMMA = ','.freeze
23
+ EMPTY_STRING = ''.freeze
24
+ EQUALS = '='.freeze
25
+ TRACE_ID_KEY = 'trace_id'.freeze
26
+ PARENT_ID_KEY = 'parent_id'.freeze
27
+ VERSION_KEY = 'version'.freeze
28
+ UNDEFINED_FIELDS_KEY = 'undefined_fields'.freeze
29
+ INVALID_TRACE_ID = ('0' * 32).freeze
30
+ INVALID_PARENT_ID = ('0' * 16).freeze
31
+ INVALID_VERSION = 'ff'.freeze
32
+
33
+ MAX_TRACE_STATE_SIZE = 512 # bytes
34
+ MAX_TRACE_STATE_ENTRY_SIZE = 128 # bytes
35
+
36
+ SUPPORTABILITY_TRACE_PARENT_PARSE_EXCEPTION = "Supportability/TraceContext/TraceParent/Parse/Exception".freeze
37
+ SUPPORTABILITY_TRACE_STATE_PARSE_EXCEPTION = "Supportability/TraceContext/TraceState/Parse/Exception".freeze
38
+
39
+ class << self
40
+ def insert format: FORMAT_HTTP,
41
+ carrier: nil,
42
+ parent_id: nil,
43
+ trace_id: nil,
44
+ trace_flags: nil,
45
+ trace_state: nil
46
+
47
+ trace_parent_header = trace_parent_header_for_format format
48
+ carrier[trace_parent_header] = format_trace_parent \
49
+ trace_id: trace_id,
50
+ parent_id: parent_id,
51
+ trace_flags: trace_flags
52
+
53
+ trace_state_header = trace_state_header_for_format format
54
+ carrier[trace_state_header] = trace_state if trace_state && !trace_state.empty?
55
+ end
56
+
57
+ def parse format: FORMAT_HTTP,
58
+ carrier: nil,
59
+ trace_state_entry_key: nil
60
+ trace_parent = extract_traceparent(format, carrier)
61
+ unless trace_parent_valid? trace_parent
62
+ NewRelic::Agent.increment_metric SUPPORTABILITY_TRACE_PARENT_PARSE_EXCEPTION
63
+ return
64
+ end
65
+
66
+ begin
67
+ if header_data = extract_tracestate(format, carrier, trace_state_entry_key)
68
+ header_data.trace_parent = trace_parent
69
+ header_data
70
+ end
71
+ rescue Exception
72
+ NewRelic::Agent.increment_metric SUPPORTABILITY_TRACE_STATE_PARSE_EXCEPTION
73
+ end
74
+ end
75
+
76
+ def create_trace_state_entry entry_key, payload
77
+ "#{entry_key}=#{payload}"
78
+ end
79
+
80
+ private
81
+
82
+ TRACE_PARENT_FORMAT_STRING = "%02x-%s-%s-%02x".freeze
83
+
84
+ def format_trace_parent trace_id: nil,
85
+ parent_id: nil,
86
+ trace_flags: nil
87
+ sprintf TRACE_PARENT_FORMAT_STRING,
88
+ VERSION,
89
+ trace_id,
90
+ parent_id,
91
+ trace_flags
92
+ end
93
+
94
+ def extract_traceparent format, carrier
95
+ header_name = trace_parent_header_for_format format
96
+ return unless header = carrier[header_name]
97
+ if matchdata = header.match(TRACE_PARENT_REGEX)
98
+ TRACE_PARENT_REGEX.named_captures.inject({}) do |hash, (name, (index))|
99
+ hash[name] = matchdata[index]
100
+ hash
101
+ end
102
+ end
103
+ end
104
+
105
+ def trace_parent_valid? trace_parent
106
+ return false if trace_parent.nil?
107
+ return false if trace_parent[TRACE_ID_KEY] == INVALID_TRACE_ID
108
+ return false if trace_parent[PARENT_ID_KEY] == INVALID_PARENT_ID
109
+ return false if trace_parent[VERSION_KEY] == INVALID_VERSION
110
+ return false if trace_parent[VERSION_KEY].to_i(16) == VERSION && !trace_parent[UNDEFINED_FIELDS_KEY].nil?
111
+
112
+ true
113
+ end
114
+
115
+ def trace_parent_header_for_format format
116
+ if format == FORMAT_RACK
117
+ TRACE_PARENT_RACK
118
+ else
119
+ TRACE_PARENT
120
+ end
121
+ end
122
+
123
+ def trace_state_header_for_format format
124
+ if format == FORMAT_RACK
125
+ TRACE_STATE_RACK
126
+ else
127
+ TRACE_STATE
128
+ end
129
+ end
130
+
131
+ def extract_tracestate format, carrier, trace_state_entry_key
132
+ header_name = trace_state_header_for_format format
133
+ header = carrier[header_name]
134
+ return HeaderData.create if header.nil? || header.empty?
135
+
136
+ payload = nil
137
+ trace_state_size = 0
138
+ trace_state_vendors = ''
139
+ trace_state = header.split(COMMA).map(&:strip)
140
+ trace_state.reject! do |entry|
141
+ vendor_id = entry.slice 0, entry.index(EQUALS)
142
+ if vendor_id == trace_state_entry_key
143
+ payload = entry.slice! trace_state_entry_key.size + 1, entry.size
144
+ true
145
+ else
146
+ trace_state_size += entry.size
147
+ trace_state_vendors << vendor_id << COMMA
148
+ false
149
+ end
150
+ end
151
+
152
+ trace_state_vendors.chomp! COMMA unless trace_state_vendors.empty?
153
+
154
+ HeaderData.create trace_state_payload: payload ? decode_payload(payload) : nil,
155
+ trace_state_entries: trace_state,
156
+ trace_state_size: trace_state_size,
157
+ trace_state_vendors: trace_state_vendors
158
+ end
159
+
160
+ def decode_payload payload
161
+ TraceContextPayload.from_s payload
162
+ end
163
+ end
164
+
165
+ class HeaderData
166
+ class << self
167
+ def create trace_parent: nil,
168
+ trace_state_payload: nil,
169
+ trace_state_entries: nil,
170
+ trace_state_size: 0,
171
+ trace_state_vendors: nil
172
+ new trace_parent, \
173
+ trace_state_payload, \
174
+ trace_state_entries, \
175
+ trace_state_size, \
176
+ trace_state_vendors
177
+ end
178
+ end
179
+
180
+ def initialize trace_parent, trace_state_payload, trace_state_entries, trace_state_size, trace_state_vendors
181
+ @trace_parent = trace_parent
182
+ @trace_state_entries = trace_state_entries
183
+ @trace_state_payload = trace_state_payload
184
+ @trace_state_size = trace_state_size
185
+ @trace_state_vendors = trace_state_vendors
186
+ end
187
+
188
+ attr_accessor :trace_parent, :trace_state_payload, :trace_state_vendors
189
+
190
+ def trace_state trace_state_entry
191
+ @trace_state ||= join_trace_state trace_state_entry.size
192
+ @trace_state_entries = nil
193
+
194
+ trace_state_entry << @trace_state
195
+ end
196
+
197
+ def trace_id
198
+ @trace_parent[TRACE_ID_KEY]
199
+ end
200
+
201
+ def parent_id
202
+ @trace_parent[PARENT_ID_KEY]
203
+ end
204
+
205
+ private
206
+
207
+ def join_trace_state trace_state_entry_size
208
+ return @trace_state || EMPTY_STRING if @trace_state_entries.nil? || @trace_state_entries.empty?
209
+
210
+ max_size = MAX_TRACE_STATE_SIZE - trace_state_entry_size
211
+ return @trace_state_entries.join(COMMA).prepend(COMMA) if @trace_state_size < max_size
212
+
213
+ joined_trace_state = ''
214
+
215
+ used_size = 0
216
+
217
+ @trace_state_entries.each do |entry|
218
+ entry_size = entry.size + 1
219
+ break if used_size + entry_size > max_size
220
+ next if entry_size > MAX_TRACE_STATE_ENTRY_SIZE
221
+
222
+ joined_trace_state << COMMA << entry
223
+ used_size += entry_size
224
+ end
225
+
226
+ joined_trace_state
227
+ end
228
+
229
+ end
230
+
231
+ module AccountHelpers
232
+ extend self
233
+
234
+ def trace_state_entry_key
235
+ @trace_state_entry_key ||= if Agent.config[:trusted_account_key]
236
+ "#{Agent.config[:trusted_account_key]}@nr".freeze
237
+ elsif Agent.config[:account_id]
238
+ "#{Agent.config[:account_id]}@nr".freeze
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
244
+ end