newrelic_rpm 10.5.0 → 10.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/lib/new_relic/agent/configuration/default_source.rb +4 -4
- data/lib/new_relic/agent/configuration/manager.rb +7 -0
- data/lib/new_relic/agent/database/explain_plan_helpers.rb +1 -1
- data/lib/new_relic/agent/error_collector.rb +2 -2
- data/lib/new_relic/agent/log_event_aggregator.rb +13 -5
- data/lib/new_relic/agent/opentelemetry/abstract_segment_patch.rb +47 -0
- data/lib/new_relic/agent/opentelemetry/attribute_translator.rb +10 -7
- data/lib/new_relic/agent/opentelemetry/messaging_patch.rb +39 -0
- data/lib/new_relic/agent/opentelemetry/span_event_primitive_patch.rb +115 -0
- data/lib/new_relic/agent/opentelemetry/trace/span.rb +23 -1
- data/lib/new_relic/agent/opentelemetry/trace/tracer.rb +45 -5
- data/lib/new_relic/agent/opentelemetry/transaction_patch.rb +20 -0
- data/lib/new_relic/agent/opentelemetry/translators/attribute_mappings.rb +127 -0
- data/lib/new_relic/agent/opentelemetry/translators/base_translator.rb +5 -5
- data/lib/new_relic/agent/opentelemetry/translators/datastore_translator.rb +2 -2
- data/lib/new_relic/agent/opentelemetry/translators/generic_translator.rb +1 -1
- data/lib/new_relic/agent/opentelemetry/translators/http_client_translator.rb +2 -2
- data/lib/new_relic/agent/opentelemetry/translators/http_server_translator.rb +2 -2
- data/lib/new_relic/agent/opentelemetry/translators/messaging_translator.rb +128 -0
- data/lib/new_relic/agent/opentelemetry/translators/redis_datastore_translator.rb +19 -0
- data/lib/new_relic/agent/opentelemetry/translators/rpc_translator.rb +63 -0
- data/lib/new_relic/agent/opentelemetry_bridge.rb +5 -1
- data/lib/new_relic/agent/span_event_aggregator.rb +19 -0
- data/lib/new_relic/agent/span_event_primitive.rb +2 -0
- data/lib/new_relic/agent/transaction.rb +5 -0
- data/lib/new_relic/dependency_detection.rb +2 -1
- data/lib/new_relic/version.rb +1 -1
- data/newrelic.yml +3 -2
- data/newrelic_rpm.gemspec +2 -0
- data/test/agent_helper.rb +2 -2
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04f3daab2bb3609b38de8f2a1eb9c16bbbee814b6a7f8536abc4db1f09bc8634
|
|
4
|
+
data.tar.gz: 8c32e76b543d25220865b18f2247489df611bce74c1b755a97fcd79c7ddeb9d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 369084a1fd6fc7313177f80da8701e51e431062fc5d3d6fc602491a58c4e91afe997262f74ff15a66a451812dbcb40c68634bf580cdbdc0c8053d4bf2c3a12b4
|
|
7
|
+
data.tar.gz: ef014f91db60cd980859de6eda2177dac2f42c49b16d590d69aec2aa4365a160045d2c181badf990944519e824e3fb54f0a52dd5cfef3fe8d836d132d69ce340
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# New Relic Ruby Agent Release Notes
|
|
2
2
|
|
|
3
|
+
## v10.6.0
|
|
4
|
+
|
|
5
|
+
- **Feature: SpanLink events are now supported for the Hybrid Agent**
|
|
6
|
+
|
|
7
|
+
Spans created by an OpenTelemetry API can now have [Span Links](https://opentelemetry.io/docs/concepts/signals/traces/#span-links) associated with them. Links can be added on a span's start, by passing them to the `links` argument, or by calling the `OpenTelemetry::Trace::Span#add_link` API. [PR#3586](https://github.com/newrelic/newrelic-ruby-agent/pull/3586)
|
|
8
|
+
|
|
9
|
+
- **Feature: SpanEvent events are now supported for the Hybrid Agent**
|
|
10
|
+
|
|
11
|
+
Spans created by an OpenTelemetry API can now have [SpanEvent events](https://opentelemetry.io/docs/concepts/signals/traces/#span-events) associated with them via the `OpenTelemetry::Trace::Span#add_event` API. SpanEvent events capture timestamped annotations on a span and are sent to New Relic alongside the parent span. [PR#3587](https://github.com/newrelic/newrelic-ruby-agent/pull/3587)
|
|
12
|
+
|
|
13
|
+
- **Feature: Set Span Kind on all Hybrid Agent spans**
|
|
14
|
+
|
|
15
|
+
Previously, only OpenTelemetry spans translated into external request segments or datastore segments added span kind as an attribute. Now, the agent adds span kind to all OpenTelemetry spans where the value is available. [PR#3589](https://github.com/newrelic/newrelic-ruby-agent/pull/3589)
|
|
16
|
+
|
|
17
|
+
- **Feature: Add support for OpenTelemetry::Tracer#start_root_span**
|
|
18
|
+
|
|
19
|
+
The `OpenTelemetry::Tracer#start_root_span` API can now be used to force a transaction to start for a given span, provided it has a `:server` or `:consumer` span kind. For any other span kinds, it will no-op. This method is most commonly used in background job instrumentation. [PR#3588](https://github.com/newrelic/newrelic-ruby-agent/pull/3588)
|
|
20
|
+
|
|
21
|
+
- **Bugfix: Fix `instrumentation.rails_event_logger: false` not disabling the instrumentation**
|
|
22
|
+
|
|
23
|
+
Setting `instrumentation.rails_event_logger` to `false` was not disabling the Rails.event instrumentation as expected. The instrumentation would still be installed during Rails boot. This has been fixed. [PR#3564](https://github.com/newrelic/newrelic-ruby-agent/pull/3564)
|
|
24
|
+
|
|
25
|
+
- **Bugfix: Normalize boolean-like values to `disabled` for instrumentation config keys**
|
|
26
|
+
|
|
27
|
+
Setting any `instrumentation.*` config key to `false`, `no`, or `off` instead of `disabled` will now resolve to `disabled` and prevent the instrumentation from being installed. [PR#3579](https://github.com/newrelic/newrelic-ruby-agent/pull/3579)
|
|
28
|
+
|
|
29
|
+
- **Bugfix: Per-library logging supportability metrics now reflect each library's instrumentation state**
|
|
30
|
+
|
|
31
|
+
The `Supportability/Logging/Ruby/{library}/{enabled|disabled}` metrics previously echoed `application_logging.enabled` for every library, so disabling a single logging instrumentation or running without one of its gems still reported `enabled`. Each library now reports based on its own instrumentation state. [PR#3571](https://github.com/newrelic/newrelic-ruby-agent/pull/3571)
|
|
32
|
+
|
|
3
33
|
## v10.5.0
|
|
4
34
|
|
|
5
35
|
- **Feature: Add Dalli 5.0 support and fix meta protocol instrumentation**
|
|
@@ -1792,12 +1792,12 @@ module NewRelic
|
|
|
1792
1792
|
:description => 'Controls auto-instrumentation of the LogStasher library at start-up. May be one of: `auto`, `prepend`, `chain`, `disabled`.'
|
|
1793
1793
|
},
|
|
1794
1794
|
:'instrumentation.rails_event_logger' => {
|
|
1795
|
-
:default =>
|
|
1796
|
-
:documentation_default => '
|
|
1795
|
+
:default => instrumentation_value_from_boolean(:'application_logging.enabled'),
|
|
1796
|
+
:documentation_default => 'enabled',
|
|
1797
1797
|
:public => true,
|
|
1798
|
-
:type =>
|
|
1798
|
+
:type => String,
|
|
1799
1799
|
:allowed_from_server => false,
|
|
1800
|
-
:description => 'Controls instrumentation of Rails.event as structured logs'
|
|
1800
|
+
:description => 'Controls instrumentation of Rails.event as structured logs. May be one of: `enabled`, `disabled`.'
|
|
1801
1801
|
},
|
|
1802
1802
|
:'instrumentation.rails_event_logger.event_names' => {
|
|
1803
1803
|
:default => [],
|
|
@@ -26,6 +26,7 @@ module NewRelic
|
|
|
26
26
|
}.freeze
|
|
27
27
|
|
|
28
28
|
INSTRUMENTATION_VALUES = %w[chain prepend unsatisfied]
|
|
29
|
+
INSTRUMENTATION_DISABLED_VALUES = %w[false no off].freeze
|
|
29
30
|
NUMERIC_TYPES = [Integer, Float]
|
|
30
31
|
STRINGLIKE_TYPES = [String, Symbol]
|
|
31
32
|
|
|
@@ -178,6 +179,11 @@ module NewRelic
|
|
|
178
179
|
false
|
|
179
180
|
end
|
|
180
181
|
|
|
182
|
+
def instrumentation_key?(key)
|
|
183
|
+
key.to_s.match?(/\Ainstrumentation\.[^.]+\z/) &&
|
|
184
|
+
DEFAULTS.dig(key, :type) == String
|
|
185
|
+
end
|
|
186
|
+
|
|
181
187
|
def handle_nil_type(key, value, category)
|
|
182
188
|
return value if %i[manual test].include?(category)
|
|
183
189
|
|
|
@@ -205,6 +211,7 @@ module NewRelic
|
|
|
205
211
|
|
|
206
212
|
type = DEFAULTS.dig(key, :type)
|
|
207
213
|
return handle_nil_type(key, value, category) unless type
|
|
214
|
+
return 'disabled' if instrumentation_key?(key) && INSTRUMENTATION_DISABLED_VALUES.include?(value.to_s.downcase)
|
|
208
215
|
return value if value.is_a?(type) || boolean?(type, value) || instrumentation?(type, value)
|
|
209
216
|
return numeric_conversion(value) if NUMERIC_TYPES.include?(type) && NUMERIC_TYPES.include?(value.class)
|
|
210
217
|
return string_conversion(value) if STRINGLIKE_TYPES.include?(type) && STRINGLIKE_TYPES.include?(value.class)
|
|
@@ -80,7 +80,7 @@ module NewRelic
|
|
|
80
80
|
unless NewRelic::Agent::Database.record_sql_method == :raw
|
|
81
81
|
query_plan_string = NewRelic::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string)
|
|
82
82
|
end
|
|
83
|
-
values = query_plan_string.split("\n").
|
|
83
|
+
values = query_plan_string.split("\n").zip
|
|
84
84
|
|
|
85
85
|
[[QUERY_PLAN], values]
|
|
86
86
|
end
|
|
@@ -286,13 +286,13 @@ module NewRelic
|
|
|
286
286
|
trace[-keep_frames..-1].unshift("<truncated #{truncate_frames} additional frames>")
|
|
287
287
|
when 'end'
|
|
288
288
|
# Remove the end, keep the beginning
|
|
289
|
-
trace[0...keep_frames].
|
|
289
|
+
trace[0...keep_frames].push("<truncated #{truncate_frames} additional frames>")
|
|
290
290
|
else # 'middle' or default (original behavior)
|
|
291
291
|
# Remove the middle, keep beginning and end
|
|
292
292
|
# If keep_frames is odd, we will split things up favoring the top of the trace
|
|
293
293
|
keep_top = (keep_frames / 2.0).ceil
|
|
294
294
|
keep_bottom = (keep_frames / 2.0).floor
|
|
295
|
-
trace[0...keep_top].
|
|
295
|
+
trace[0...keep_top].push("<truncated #{truncate_frames} additional frames>").concat(trace[-keep_bottom..-1])
|
|
296
296
|
end
|
|
297
297
|
end
|
|
298
298
|
|
|
@@ -24,7 +24,13 @@ module NewRelic
|
|
|
24
24
|
FORWARDING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Forwarding/Ruby/%s'.freeze
|
|
25
25
|
DECORATING_SUPPORTABILITY_FORMAT = 'Supportability/Logging/LocalDecorating/Ruby/%s'.freeze
|
|
26
26
|
LABELS_SUPPORTABILITY_FORMAT = 'Supportability/Logging/Labels/Ruby/%s'.freeze
|
|
27
|
-
SUPPORTED_LOGGING_LIBRARIES =
|
|
27
|
+
SUPPORTED_LOGGING_LIBRARIES = {
|
|
28
|
+
'Logger' => :'instrumentation.logger',
|
|
29
|
+
'LogStasher' => :'instrumentation.logstasher',
|
|
30
|
+
'Logging' => :'instrumentation.logging',
|
|
31
|
+
'SemanticLogger' => :'instrumentation.semantic_logger',
|
|
32
|
+
'RailsEventLogger' => :'instrumentation.rails_event_logger'
|
|
33
|
+
}.freeze
|
|
28
34
|
MAX_BYTES = 32768 # 32 * 1024 bytes (32 kibibytes)
|
|
29
35
|
SKIP_SEMANTIC_LOGGER_VARS = %w[@level @level_index @message @time @payload].freeze
|
|
30
36
|
SKIP_RAILS_EVENT_KEYS = %w[message level].freeze
|
|
@@ -479,8 +485,8 @@ module NewRelic
|
|
|
479
485
|
record_configuration_metric(FORWARDING_SUPPORTABILITY_FORMAT, FORWARDING_ENABLED_KEY)
|
|
480
486
|
record_configuration_metric(DECORATING_SUPPORTABILITY_FORMAT, DECORATING_ENABLED_KEY)
|
|
481
487
|
record_configuration_metric(LABELS_SUPPORTABILITY_FORMAT, LABELS_ENABLED_KEY)
|
|
482
|
-
SUPPORTED_LOGGING_LIBRARIES.each do |library|
|
|
483
|
-
record_logs_supportability_metric(library,
|
|
488
|
+
SUPPORTED_LOGGING_LIBRARIES.each do |library, instrumentation_key|
|
|
489
|
+
record_logs_supportability_metric(library, instrumentation_key)
|
|
484
490
|
end
|
|
485
491
|
|
|
486
492
|
add_custom_attributes(NewRelic::Agent.config[CUSTOM_ATTRIBUTES_KEY])
|
|
@@ -492,8 +498,10 @@ module NewRelic
|
|
|
492
498
|
NewRelic::Agent.increment_metric(format % label)
|
|
493
499
|
end
|
|
494
500
|
|
|
495
|
-
def record_logs_supportability_metric(library,
|
|
496
|
-
|
|
501
|
+
def record_logs_supportability_metric(library, instrumentation_key)
|
|
502
|
+
value = NewRelic::Agent.config[instrumentation_key]
|
|
503
|
+
installed = @enabled && value != 'disabled' && value != :unsatisfied
|
|
504
|
+
label = installed ? 'enabled' : 'disabled'
|
|
497
505
|
NewRelic::Agent.increment_metric("Supportability/Logging/Ruby/#{library}/#{label}")
|
|
498
506
|
end
|
|
499
507
|
|
|
@@ -6,6 +6,53 @@ module NewRelic
|
|
|
6
6
|
module Agent
|
|
7
7
|
module OpenTelemetry
|
|
8
8
|
module AbstractSegmentPatch
|
|
9
|
+
MAX_SPAN_LINKS = 100
|
|
10
|
+
SPAN_LINKS_DROPPED_METRIC = 'Supportability/Ruby/SpanEvent/Links/Dropped'
|
|
11
|
+
MAX_SPAN_EVENTS = 100
|
|
12
|
+
SPAN_EVENTS_DROPPED_METRIC = 'Supportability/Ruby/SpanEvent/Events/Dropped'
|
|
13
|
+
|
|
14
|
+
def add_span_link(link)
|
|
15
|
+
@span_links ||= []
|
|
16
|
+
if @span_links.size >= MAX_SPAN_LINKS
|
|
17
|
+
NewRelic::Agent.record_metric(SPAN_LINKS_DROPPED_METRIC, 1)
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
@span_links << link
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def span_links
|
|
24
|
+
instance_variable_defined?(:@span_links) ? @span_links : NewRelic::EMPTY_ARRAY
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# This method adds SpanEvent events to a Span event/Segment.
|
|
28
|
+
# A SpanEvent is used to denote a meaningful, singular point in a
|
|
29
|
+
# Span's duration.
|
|
30
|
+
def add_span_event_event(name, attributes: nil, timestamp: nil)
|
|
31
|
+
@span_events ||= []
|
|
32
|
+
|
|
33
|
+
if @span_events.size >= MAX_SPAN_EVENTS
|
|
34
|
+
NewRelic::Agent.record_metric(SPAN_EVENTS_DROPPED_METRIC, 1)
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Normalize to Float seconds at storage time.
|
|
39
|
+
# OTel may pass Integer nanoseconds; New Relic uses Float seconds.
|
|
40
|
+
ts = if timestamp.nil?
|
|
41
|
+
Process.clock_gettime(Process::CLOCK_REALTIME)
|
|
42
|
+
elsif timestamp.is_a?(Integer)
|
|
43
|
+
timestamp / 1_000_000_000.0
|
|
44
|
+
else
|
|
45
|
+
timestamp
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@span_events << {name: name, attributes: attributes, timestamp: ts}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Used to reference SpanEvent events associated with a Span/Segment
|
|
52
|
+
def span_events
|
|
53
|
+
instance_variable_defined?(:@span_events) ? @span_events : NewRelic::EMPTY_ARRAY
|
|
54
|
+
end
|
|
55
|
+
|
|
9
56
|
def force_finish
|
|
10
57
|
if instance_variable_defined?(:@otel_span)
|
|
11
58
|
otel_span = instance_variable_get(:@otel_span)
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
5
|
require_relative 'translators/datastore_translator'
|
|
6
|
+
require_relative 'translators/redis_datastore_translator'
|
|
6
7
|
require_relative 'translators/http_client_translator'
|
|
7
8
|
require_relative 'translators/http_server_translator'
|
|
8
9
|
require_relative 'translators/generic_translator'
|
|
10
|
+
require_relative 'translators/rpc_translator'
|
|
11
|
+
require_relative 'translators/messaging_translator'
|
|
9
12
|
|
|
10
13
|
module NewRelic
|
|
11
14
|
module Agent
|
|
@@ -19,19 +22,19 @@ module NewRelic
|
|
|
19
22
|
# pg instrumentation doesn't have db.system assigned when connect
|
|
20
23
|
# spans start, so they would be incorrectly assigned
|
|
21
24
|
# the HttpClientTranslator
|
|
22
|
-
'opentelemetry-instrumentation-pg' => DatastoreTranslator
|
|
23
|
-
|
|
25
|
+
'opentelemetry-instrumentation-pg' => DatastoreTranslator,
|
|
26
|
+
'opentelemetry-instrumentation-redis' => RedisDatastoreTranslator
|
|
24
27
|
},
|
|
25
28
|
discriminating_attribute: {
|
|
26
29
|
'db.system' => DatastoreTranslator,
|
|
27
|
-
'db.system.name' => DatastoreTranslator
|
|
28
|
-
|
|
30
|
+
'db.system.name' => DatastoreTranslator,
|
|
31
|
+
'rpc.system' => RpcTranslator
|
|
29
32
|
},
|
|
30
33
|
span_kind: {
|
|
31
34
|
client: HttpClientTranslator,
|
|
32
35
|
server: HttpServerTranslator,
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
consumer: MessagingTranslator,
|
|
37
|
+
producer: MessagingTranslator,
|
|
35
38
|
internal: GenericTranslator
|
|
36
39
|
}
|
|
37
40
|
}.freeze
|
|
@@ -64,7 +67,7 @@ module NewRelic
|
|
|
64
67
|
GenericTranslator
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
translator.translate(attributes: attributes, name: name, instrumentation_scope: instrumentation_scope)
|
|
70
|
+
translator.translate(attributes: attributes, name: name, instrumentation_scope: instrumentation_scope, kind: span_kind)
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
end
|
|
@@ -0,0 +1,39 @@
|
|
|
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 MessagingPatch
|
|
9
|
+
# A producer span with a remote parent MUST start a new
|
|
10
|
+
# transaction. Naming for that case is handled by MessagingPatch
|
|
11
|
+
# since NR's transaction_name only knows about :consume
|
|
12
|
+
PRODUCE = 'Produce/'
|
|
13
|
+
|
|
14
|
+
def transaction_name(library, destination_type, destination_name, action = nil)
|
|
15
|
+
return super unless action == :produce
|
|
16
|
+
|
|
17
|
+
name = Transaction::MESSAGE_PREFIX + library
|
|
18
|
+
name << NewRelic::SLASH
|
|
19
|
+
name << Transaction::MessageBrokerSegment::TYPES[destination_type]
|
|
20
|
+
name << NewRelic::SLASH
|
|
21
|
+
name << PRODUCE
|
|
22
|
+
|
|
23
|
+
case destination_type
|
|
24
|
+
when :queue, :topic, :exchange
|
|
25
|
+
name << Transaction::MessageBrokerSegment::NAMED
|
|
26
|
+
name << destination_name.to_s
|
|
27
|
+
when :temporary_queue, :temporary_topic
|
|
28
|
+
name << Transaction::MessageBrokerSegment::TEMP
|
|
29
|
+
else # handles :stream or :unknown
|
|
30
|
+
name << Transaction::MessageBrokerSegment::NAMED
|
|
31
|
+
name << destination_name.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
name
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
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_relative '../attributes'
|
|
6
|
+
|
|
7
|
+
module NewRelic
|
|
8
|
+
module Agent
|
|
9
|
+
module OpenTelemetry
|
|
10
|
+
module SpanEventPrimitivePatch
|
|
11
|
+
SPAN_LINK_TYPE = 'SpanLink'
|
|
12
|
+
SPAN_EVENT_TYPE = 'SpanEvent'
|
|
13
|
+
ID_KEY = 'id'
|
|
14
|
+
LINKED_SPAN_ID_KEY = 'linkedSpanId'
|
|
15
|
+
LINKED_TRACE_ID_KEY = 'linkedTraceId'
|
|
16
|
+
TRACE_DOT_ID_KEY = 'trace.id'
|
|
17
|
+
SPAN_DOT_ID_KEY = 'span.id'
|
|
18
|
+
|
|
19
|
+
def for_segment(segment)
|
|
20
|
+
span_data = super
|
|
21
|
+
update_spans_for_otel(span_data, segment)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def for_external_request_segment(segment)
|
|
25
|
+
span_data = super
|
|
26
|
+
update_spans_for_otel(span_data, segment)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def for_datastore_segment(segment)
|
|
30
|
+
span_data = super
|
|
31
|
+
update_spans_for_otel(span_data, segment)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def update_spans_for_otel(span_data, segment)
|
|
37
|
+
span_data = attach_span_kind(span_data, segment)
|
|
38
|
+
span_data = attach_span_links(span_data, segment)
|
|
39
|
+
attach_span_event_events(span_data, segment)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def attach_span_kind(span_data, segment)
|
|
43
|
+
if segment.instance_variable_defined?(:@otel_span)
|
|
44
|
+
otel_span = segment.instance_variable_get(:@otel_span)
|
|
45
|
+
kind = otel_span.kind&.to_s
|
|
46
|
+
span_data[0][SpanEventPrimitive::SPAN_KIND_KEY] = kind if kind
|
|
47
|
+
|
|
48
|
+
span_data
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def attach_span_links(span_data, segment)
|
|
53
|
+
links = for_span_links(segment)
|
|
54
|
+
links.empty? ? span_data : span_data << links
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def for_span_links(segment)
|
|
58
|
+
return [] if segment.span_links.empty?
|
|
59
|
+
|
|
60
|
+
segment.span_links.map do |link|
|
|
61
|
+
ctx = link.span_context
|
|
62
|
+
intrinsics = {
|
|
63
|
+
SpanEventPrimitive::TYPE_KEY => SPAN_LINK_TYPE,
|
|
64
|
+
SpanEventPrimitive::TIMESTAMP_KEY => milliseconds_since_epoch(segment),
|
|
65
|
+
ID_KEY => segment.guid,
|
|
66
|
+
TRACE_DOT_ID_KEY => segment.transaction.trace_id,
|
|
67
|
+
LINKED_SPAN_ID_KEY => ctx.hex_span_id,
|
|
68
|
+
LINKED_TRACE_ID_KEY => ctx.hex_trace_id
|
|
69
|
+
}
|
|
70
|
+
[intrinsics, sanitize_event_attributes(link.attributes), {}]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def attach_span_event_events(span_data, segment)
|
|
75
|
+
span_events = for_span_event_events(segment)
|
|
76
|
+
return span_data if span_events.empty?
|
|
77
|
+
|
|
78
|
+
if span_data.length > 3
|
|
79
|
+
span_data[3].concat(span_events)
|
|
80
|
+
else
|
|
81
|
+
span_data << span_events
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
span_data
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def for_span_event_events(segment)
|
|
88
|
+
return [] if segment.span_events.empty?
|
|
89
|
+
|
|
90
|
+
segment.span_events.map do |evt|
|
|
91
|
+
intrinsics = {
|
|
92
|
+
SpanEventPrimitive::TYPE_KEY => SPAN_EVENT_TYPE,
|
|
93
|
+
# This is the same formula as milliseconds_since_epoch, but without
|
|
94
|
+
# the segment.start_time value
|
|
95
|
+
SpanEventPrimitive::TIMESTAMP_KEY => Integer(evt[:timestamp].to_f * 1000.0),
|
|
96
|
+
SPAN_DOT_ID_KEY => segment.guid,
|
|
97
|
+
TRACE_DOT_ID_KEY => segment.transaction.trace_id,
|
|
98
|
+
SpanEventPrimitive::NAME_KEY => evt[:name]
|
|
99
|
+
}
|
|
100
|
+
[intrinsics, sanitize_event_attributes(evt[:attributes]), {}]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def sanitize_event_attributes(attrs)
|
|
105
|
+
return NewRelic::EMPTY_HASH if attrs.nil? || attrs.empty?
|
|
106
|
+
|
|
107
|
+
filter = NewRelic::Agent.instance.attribute_filter
|
|
108
|
+
attr_obj = Attributes.new(filter)
|
|
109
|
+
attr_obj.merge_custom_attributes(attrs)
|
|
110
|
+
attr_obj.custom_attributes_for(AttributeFilter::DST_SPAN_EVENTS)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -8,6 +8,7 @@ module NewRelic
|
|
|
8
8
|
module Trace
|
|
9
9
|
class Span < ::OpenTelemetry::Trace::Span
|
|
10
10
|
attr_accessor :finishable
|
|
11
|
+
attr_accessor :kind
|
|
11
12
|
attr_writer :translator
|
|
12
13
|
attr_reader :status
|
|
13
14
|
|
|
@@ -23,7 +24,7 @@ module NewRelic
|
|
|
23
24
|
return if attributes.nil? || attributes.empty?
|
|
24
25
|
|
|
25
26
|
if @translator
|
|
26
|
-
translated = @translator.translate(attributes: attributes)
|
|
27
|
+
translated = @translator.translate(attributes: attributes, kind: @kind)
|
|
27
28
|
apply_translated_attributes(translated)
|
|
28
29
|
else
|
|
29
30
|
# fallback to adding all attributes to the span as custom
|
|
@@ -31,6 +32,27 @@ module NewRelic
|
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
|
|
35
|
+
def add_link(link)
|
|
36
|
+
return self unless recording?
|
|
37
|
+
|
|
38
|
+
# add_span_link lives in OpenTelemetry::AbstractSegmentPatch
|
|
39
|
+
# and in OpenTelemetry::TransactionPatch
|
|
40
|
+
finishable&.add_span_link(link)
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_event(name, attributes: nil, timestamp: nil)
|
|
45
|
+
# This method adds SpanEvent events to a Span event.
|
|
46
|
+
# A SpanEvent is used to denote a meaningful, singular point in a
|
|
47
|
+
# Span's duration.
|
|
48
|
+
return self unless recording?
|
|
49
|
+
|
|
50
|
+
# add_span_event_event lives in OpenTelemetry::AbstractSegmentPatch
|
|
51
|
+
# and in OpenTelemetry::TransactionPatch
|
|
52
|
+
finishable&.add_span_event_event(name, attributes: attributes, timestamp: timestamp)
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
34
56
|
def apply_translated_attributes(translated)
|
|
35
57
|
translated[:instance_variable].each do |key, value|
|
|
36
58
|
sym_key = "@#{key}".to_sym
|
|
@@ -35,10 +35,12 @@ module NewRelic
|
|
|
35
35
|
|
|
36
36
|
otel_span.finishable = finishable
|
|
37
37
|
otel_span.status = ::OpenTelemetry::Trace::Status.unset
|
|
38
|
+
otel_span.kind = kind
|
|
38
39
|
otel_span.translator = translated[:translator]
|
|
39
40
|
add_remote_context_to_otel_span(otel_span, parent_otel_context)
|
|
40
41
|
otel_span.add_instrumentation_scope(@name, @version)
|
|
41
42
|
otel_span.apply_translated_attributes(translated)
|
|
43
|
+
links&.each { |link| otel_span.add_link(link) }
|
|
42
44
|
|
|
43
45
|
otel_span
|
|
44
46
|
end
|
|
@@ -56,6 +58,10 @@ module NewRelic
|
|
|
56
58
|
span&.finish
|
|
57
59
|
end
|
|
58
60
|
|
|
61
|
+
def start_root_span(name, attributes: nil, links: nil, start_timestamp: nil, kind: nil)
|
|
62
|
+
start_span(name, with_parent: ::OpenTelemetry::Context.empty, attributes: attributes, links: links, start_timestamp: start_timestamp, kind: kind)
|
|
63
|
+
end
|
|
64
|
+
|
|
59
65
|
private
|
|
60
66
|
|
|
61
67
|
def start_segment_from_otel(name:, translated: nil, start_timestamp: nil, kind: nil)
|
|
@@ -63,9 +69,9 @@ module NewRelic
|
|
|
63
69
|
|
|
64
70
|
case kind
|
|
65
71
|
when :client
|
|
66
|
-
if segment_api_params[:uri] # HTTP Client
|
|
72
|
+
if segment_api_params[:uri] # HTTP Client and gRPC Client
|
|
67
73
|
NewRelic::Agent::Tracer.start_external_request_segment(
|
|
68
|
-
library: @name,
|
|
74
|
+
library: segment_api_params[:library] || @name,
|
|
69
75
|
uri: segment_api_params[:uri],
|
|
70
76
|
procedure: segment_api_params[:procedure],
|
|
71
77
|
start_time: start_timestamp
|
|
@@ -81,12 +87,29 @@ module NewRelic
|
|
|
81
87
|
start_time: start_timestamp
|
|
82
88
|
)
|
|
83
89
|
|
|
84
|
-
|
|
90
|
+
if segment_api_params[:sql]
|
|
91
|
+
segment&._notice_sql(segment_api_params[:sql])
|
|
92
|
+
elsif segment_api_params[:nosql_statement]
|
|
93
|
+
segment&.notice_nosql_statement(segment_api_params[:nosql_statement])
|
|
94
|
+
end
|
|
85
95
|
|
|
86
96
|
segment
|
|
87
97
|
else
|
|
88
98
|
NewRelic::Agent::Tracer.start_segment(name: name)
|
|
89
99
|
end
|
|
100
|
+
when :producer, :consumer
|
|
101
|
+
if segment_api_params[:library] # Messaging
|
|
102
|
+
NewRelic::Agent::Tracer.start_message_broker_segment(
|
|
103
|
+
action: segment_api_params[:action],
|
|
104
|
+
library: segment_api_params[:library],
|
|
105
|
+
destination_type: segment_api_params[:destination_type],
|
|
106
|
+
destination_name: segment_api_params[:destination_name],
|
|
107
|
+
parameters: messaging_segment_parameters(segment_api_params),
|
|
108
|
+
start_time: start_timestamp
|
|
109
|
+
)
|
|
110
|
+
else
|
|
111
|
+
NewRelic::Agent::Tracer.start_segment(name: name)
|
|
112
|
+
end
|
|
90
113
|
else
|
|
91
114
|
NewRelic::Agent::Tracer.start_segment(name: name)
|
|
92
115
|
end
|
|
@@ -97,14 +120,16 @@ module NewRelic
|
|
|
97
120
|
segment_api_params = translated[:for_segment_api]
|
|
98
121
|
|
|
99
122
|
case kind
|
|
100
|
-
when :server
|
|
123
|
+
when :server # HTTP or gRPC server calls
|
|
101
124
|
nr_item = NewRelic::Agent::Tracer.start_transaction_or_segment(
|
|
102
125
|
name: segment_api_params[:name] || name,
|
|
103
126
|
category: :web
|
|
104
127
|
)
|
|
105
128
|
when :client
|
|
106
129
|
nr_item = NewRelic::Agent::Tracer.start_transaction_or_segment(name: name, category: :web)
|
|
107
|
-
when :consumer, :producer
|
|
130
|
+
when :consumer, :producer
|
|
131
|
+
nr_item = start_messaging_transaction(name, segment_api_params)
|
|
132
|
+
when :internal, nil
|
|
108
133
|
nr_item = NewRelic::Agent::Tracer.start_transaction_or_segment(name: name, category: :task)
|
|
109
134
|
end
|
|
110
135
|
|
|
@@ -113,6 +138,21 @@ module NewRelic
|
|
|
113
138
|
nr_item
|
|
114
139
|
end
|
|
115
140
|
|
|
141
|
+
def messaging_segment_parameters(segment_api_params)
|
|
142
|
+
return nil unless NewRelic::Agent.config[:'message_tracer.segment_parameters.enabled']
|
|
143
|
+
|
|
144
|
+
segment_api_params[:parameters]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def start_messaging_transaction(name, segment_api_params)
|
|
148
|
+
return NewRelic::Agent::Tracer.start_transaction_or_segment(name: name, category: :task) unless segment_api_params[:name]
|
|
149
|
+
|
|
150
|
+
NewRelic::Agent::Tracer.start_transaction_or_segment(
|
|
151
|
+
name: segment_api_params[:name],
|
|
152
|
+
category: :message
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
116
156
|
def get_otel_span_from_finishable(finishable)
|
|
117
157
|
case finishable
|
|
118
158
|
when NewRelic::Agent::Transaction
|
|
@@ -31,6 +31,26 @@ module NewRelic
|
|
|
31
31
|
super
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def add_span_link(link)
|
|
35
|
+
initial_segment&.add_span_link(link)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def span_links
|
|
39
|
+
initial_segment&.span_links || NewRelic::EMPTY_ARRAY
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# This method adds a SpanEvent event to the Transaction's initial segment.
|
|
43
|
+
# A SpanEvent is used to denote a meaningful, singular point in a
|
|
44
|
+
# Span's duration.
|
|
45
|
+
def add_span_event_event(name, attributes: nil, timestamp: nil)
|
|
46
|
+
initial_segment&.add_span_event_event(name, attributes: attributes, timestamp: timestamp)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Used to reference SpanEvent events associated with the initial segment.
|
|
50
|
+
def span_events
|
|
51
|
+
initial_segment&.span_events || NewRelic::EMPTY_ARRAY
|
|
52
|
+
end
|
|
53
|
+
|
|
34
54
|
private
|
|
35
55
|
|
|
36
56
|
def find_or_create_span(segment)
|
|
@@ -97,6 +97,33 @@ module NewRelic
|
|
|
97
97
|
}
|
|
98
98
|
}.freeze
|
|
99
99
|
|
|
100
|
+
REDIS_MAPPINGS = { # v1.25, v1.17
|
|
101
|
+
'product' => {
|
|
102
|
+
otel_keys: ['db.system.name', 'db.system'],
|
|
103
|
+
segment_field: :product
|
|
104
|
+
},
|
|
105
|
+
'database_name' => {
|
|
106
|
+
otel_keys: ['db.namespace', 'db.name'],
|
|
107
|
+
segment_field: :database_name
|
|
108
|
+
},
|
|
109
|
+
'host' => {
|
|
110
|
+
otel_keys: ['server.address', 'net.peer.name'],
|
|
111
|
+
segment_field: :host
|
|
112
|
+
},
|
|
113
|
+
'port_path_or_id' => {
|
|
114
|
+
otel_keys: ['server.port', 'net.peer.port'],
|
|
115
|
+
segment_field: :port_path_or_id
|
|
116
|
+
},
|
|
117
|
+
'operation' => {
|
|
118
|
+
otel_keys: ['db.operation.name', 'db.operation'],
|
|
119
|
+
segment_field: :operation
|
|
120
|
+
},
|
|
121
|
+
'nosql_statement' => {
|
|
122
|
+
otel_keys: ['db.statement', 'db.query.text'],
|
|
123
|
+
segment_field: :nosql_statement
|
|
124
|
+
}
|
|
125
|
+
}.freeze
|
|
126
|
+
|
|
100
127
|
HTTP_SERVER_MAPPINGS = { # v1.23, v1.20
|
|
101
128
|
'http_response_code' => {
|
|
102
129
|
otel_keys: ['http.response.status_code', 'http.status_code'],
|
|
@@ -123,6 +150,106 @@ module NewRelic
|
|
|
123
150
|
destinations: DEFAULT_DESTINATIONS
|
|
124
151
|
}
|
|
125
152
|
}.freeze
|
|
153
|
+
|
|
154
|
+
RPC_SERVER_MAPPINGS = { # v1.20
|
|
155
|
+
'response_status' => {
|
|
156
|
+
otel_keys: ['rpc.grpc.status_code'],
|
|
157
|
+
category: :instance_variable
|
|
158
|
+
},
|
|
159
|
+
'request.headers.host' => {
|
|
160
|
+
otel_keys: ['net.sock.peer.addr', 'server.address', 'net.peer.name'],
|
|
161
|
+
category: :agent,
|
|
162
|
+
destinations: DEFAULT_DESTINATIONS
|
|
163
|
+
},
|
|
164
|
+
'request.method' => {
|
|
165
|
+
otel_keys: ['rpc.method'],
|
|
166
|
+
category: :agent,
|
|
167
|
+
destinations: DEFAULT_DESTINATIONS
|
|
168
|
+
}
|
|
169
|
+
}.freeze
|
|
170
|
+
|
|
171
|
+
RPC_CLIENT_MAPPINGS = { # v1.17
|
|
172
|
+
'grpc_status_code' => {
|
|
173
|
+
otel_keys: ['rpc.grpc.status_code'],
|
|
174
|
+
category: :instance_variable
|
|
175
|
+
},
|
|
176
|
+
# the value closest to library in intention is rpc.system. however,
|
|
177
|
+
# to get the correct segment name, we need to set this to rpc.service
|
|
178
|
+
'library' => {
|
|
179
|
+
otel_keys: ['rpc.service'],
|
|
180
|
+
segment_field: :library
|
|
181
|
+
},
|
|
182
|
+
'procedure' => {
|
|
183
|
+
otel_keys: ['rpc.method'],
|
|
184
|
+
segment_field: :procedure
|
|
185
|
+
},
|
|
186
|
+
'host' => {
|
|
187
|
+
# traditional host keys aren't sent by otel grpc instrumentation
|
|
188
|
+
# so we use net.sock.peer.address to fill the host field
|
|
189
|
+
otel_keys: ['net.sock.peer.addr', 'server.address', 'net.peer.name'],
|
|
190
|
+
segment_field: :host
|
|
191
|
+
},
|
|
192
|
+
# not sent by gRPC OTel instrumentation, but can sometimes be gleaned
|
|
193
|
+
# by net.sock.peer.addr
|
|
194
|
+
'port' => {
|
|
195
|
+
otel_keys: ['server.port', 'net.peer.port'],
|
|
196
|
+
segment_field: :port
|
|
197
|
+
}
|
|
198
|
+
}.freeze
|
|
199
|
+
|
|
200
|
+
MESSAGING_CONSUMER_MAPPINGS = { # v1.30/v1.24, v1.17
|
|
201
|
+
'messaging.system' => {
|
|
202
|
+
otel_keys: ['messaging.system'],
|
|
203
|
+
category: :agent,
|
|
204
|
+
destinations: AttributeFilter::DST_TRANSACTION_TRACER
|
|
205
|
+
},
|
|
206
|
+
'message.queueName' => {
|
|
207
|
+
otel_keys: ['messaging.destination.name', 'messaging.destination'],
|
|
208
|
+
category: :agent,
|
|
209
|
+
destinations: DEFAULT_DESTINATIONS,
|
|
210
|
+
segment_field: :destination_name
|
|
211
|
+
},
|
|
212
|
+
'port' => {
|
|
213
|
+
otel_keys: ['server.port', 'net.peer.port'],
|
|
214
|
+
category: :agent,
|
|
215
|
+
destinations: DEFAULT_DESTINATIONS
|
|
216
|
+
},
|
|
217
|
+
'host' => {
|
|
218
|
+
otel_keys: ['server.address', 'net.peer.name'],
|
|
219
|
+
category: :agent,
|
|
220
|
+
destinations: DEFAULT_DESTINATIONS
|
|
221
|
+
},
|
|
222
|
+
'message.routingKey' => {
|
|
223
|
+
otel_keys: [
|
|
224
|
+
'messaging.kafka.message.key',
|
|
225
|
+
'messaging.rabbitmq.destination.routing_key'
|
|
226
|
+
],
|
|
227
|
+
category: :agent,
|
|
228
|
+
destinations: DEFAULT_DESTINATIONS
|
|
229
|
+
}
|
|
230
|
+
}.freeze
|
|
231
|
+
|
|
232
|
+
MESSAGING_PRODUCER_MAPPINGS = { # v1.30/v1.24, v1.17
|
|
233
|
+
'messaging.system' => {
|
|
234
|
+
otel_keys: ['messaging.system'],
|
|
235
|
+
category: :agent,
|
|
236
|
+
destinations: AttributeFilter::DST_TRANSACTION_TRACER
|
|
237
|
+
},
|
|
238
|
+
'destination_name' => {
|
|
239
|
+
otel_keys: ['messaging.destination.name', 'messaging.destination'],
|
|
240
|
+
segment_field: :destination_name
|
|
241
|
+
},
|
|
242
|
+
'port' => {
|
|
243
|
+
otel_keys: ['server.port', 'net.peer.port'],
|
|
244
|
+
category: :agent,
|
|
245
|
+
destinations: DEFAULT_DESTINATIONS
|
|
246
|
+
},
|
|
247
|
+
'host' => {
|
|
248
|
+
otel_keys: ['server.address', 'net.peer.name'],
|
|
249
|
+
category: :agent,
|
|
250
|
+
destinations: DEFAULT_DESTINATIONS
|
|
251
|
+
}
|
|
252
|
+
}.freeze
|
|
126
253
|
end
|
|
127
254
|
end
|
|
128
255
|
end
|
|
@@ -14,7 +14,7 @@ module NewRelic
|
|
|
14
14
|
# This method should be redefined in child classes.
|
|
15
15
|
# The body of the method should be the mappings constant defined in the
|
|
16
16
|
# AttributesMappings module for the attribute type being translated
|
|
17
|
-
def mappings_hash
|
|
17
|
+
def mappings_hash(kind)
|
|
18
18
|
# no-op
|
|
19
19
|
NewRelic::EMPTY_HASH
|
|
20
20
|
end
|
|
@@ -28,11 +28,11 @@ module NewRelic
|
|
|
28
28
|
#
|
|
29
29
|
# @return [Hash] A hash with attributes divided into various categories for assignment on a New Relic
|
|
30
30
|
# transaction or segment.
|
|
31
|
-
def translate(attributes: {}, name: nil, instrumentation_scope: nil)
|
|
31
|
+
def translate(attributes: {}, name: nil, instrumentation_scope: nil, kind: nil)
|
|
32
32
|
working_attrs = attributes.dup # shallow copy
|
|
33
33
|
result = {intrinsic: {}, agent: {}, custom: {}, for_segment_api: {}, instance_variable: {}, translator: self}
|
|
34
34
|
|
|
35
|
-
mappings_hash.each do |nr_key, mapping|
|
|
35
|
+
mappings_hash(kind).each do |nr_key, mapping|
|
|
36
36
|
value = extract_first_present(working_attrs, mapping[:otel_keys])
|
|
37
37
|
next unless value
|
|
38
38
|
|
|
@@ -54,7 +54,7 @@ module NewRelic
|
|
|
54
54
|
# specialized attributes.
|
|
55
55
|
# This method will augment the working result hash, so it does not
|
|
56
56
|
# need to be merged.
|
|
57
|
-
add_specialized_attributes(result: result, name: name, attributes: attributes, instrumentation_scope: instrumentation_scope)
|
|
57
|
+
add_specialized_attributes(result: result, name: name, attributes: attributes, instrumentation_scope: instrumentation_scope, kind: kind)
|
|
58
58
|
|
|
59
59
|
# Assign any remaining attributes as custom attributes
|
|
60
60
|
result[:custom] = working_attrs
|
|
@@ -71,7 +71,7 @@ module NewRelic
|
|
|
71
71
|
# @param [optional, String] instrumentation_scope The instrumentation scope provided to the translate method
|
|
72
72
|
#
|
|
73
73
|
# @return [Hash] The augmented result hash
|
|
74
|
-
def add_specialized_attributes(result: {}, name: nil, attributes: {}, instrumentation_scope: nil)
|
|
74
|
+
def add_specialized_attributes(result: {}, name: nil, attributes: {}, instrumentation_scope: nil, kind: nil)
|
|
75
75
|
# no-op
|
|
76
76
|
{}
|
|
77
77
|
end
|
|
@@ -9,11 +9,11 @@ module NewRelic
|
|
|
9
9
|
module OpenTelemetry
|
|
10
10
|
class DatastoreTranslator < BaseTranslator
|
|
11
11
|
class << self
|
|
12
|
-
def mappings_hash
|
|
12
|
+
def mappings_hash(_kind)
|
|
13
13
|
AttributeMappings::DATASTORE_MAPPINGS
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil)
|
|
16
|
+
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil, kind: nil)
|
|
17
17
|
operation = parse_operation(name, attributes)
|
|
18
18
|
result[:for_segment_api][:operation] = operation if operation
|
|
19
19
|
|
|
@@ -9,11 +9,11 @@ module NewRelic
|
|
|
9
9
|
module OpenTelemetry
|
|
10
10
|
class HttpClientTranslator < BaseTranslator
|
|
11
11
|
class << self
|
|
12
|
-
def mappings_hash
|
|
12
|
+
def mappings_hash(_kind)
|
|
13
13
|
AttributeMappings::HTTP_CLIENT_MAPPINGS
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil)
|
|
16
|
+
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil, kind: nil)
|
|
17
17
|
uri = build_uri(attributes)
|
|
18
18
|
result[:for_segment_api][:uri] = uri if uri
|
|
19
19
|
|
|
@@ -9,11 +9,11 @@ module NewRelic
|
|
|
9
9
|
module OpenTelemetry
|
|
10
10
|
class HttpServerTranslator < BaseTranslator
|
|
11
11
|
class << self
|
|
12
|
-
def mappings_hash
|
|
12
|
+
def mappings_hash(_kind)
|
|
13
13
|
AttributeMappings::HTTP_SERVER_MAPPINGS
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil)
|
|
16
|
+
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil, kind: nil)
|
|
17
17
|
result[:for_segment_api][:name] = create_server_transaction_name(name, instrumentation_scope, attributes)
|
|
18
18
|
|
|
19
19
|
result
|
|
@@ -0,0 +1,128 @@
|
|
|
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_relative 'base_translator'
|
|
6
|
+
require_relative '../../messaging'
|
|
7
|
+
|
|
8
|
+
module NewRelic
|
|
9
|
+
module Agent
|
|
10
|
+
module OpenTelemetry
|
|
11
|
+
class MessagingTranslator < BaseTranslator
|
|
12
|
+
# OTel seems to be inconsistent with dot or underscore,
|
|
13
|
+
# so we handle both just in case.
|
|
14
|
+
DESTINATION_TYPES_MAP = {
|
|
15
|
+
consumer: {
|
|
16
|
+
'kafka' => :topic,
|
|
17
|
+
'rabbitmq' => :queue,
|
|
18
|
+
'aws.sqs' => :queue,
|
|
19
|
+
'aws_sqs' => :queue,
|
|
20
|
+
'aws.sns' => :topic,
|
|
21
|
+
'aws_sns' => :topic,
|
|
22
|
+
'aws.kinesis' => :stream,
|
|
23
|
+
'aws_kinesis' => :stream
|
|
24
|
+
},
|
|
25
|
+
producer: {
|
|
26
|
+
'kafka' => :topic,
|
|
27
|
+
'rabbitmq' => :exchange,
|
|
28
|
+
'aws.sqs' => :queue,
|
|
29
|
+
'aws_sqs' => :queue,
|
|
30
|
+
'aws.sns' => :topic,
|
|
31
|
+
'aws_sns' => :topic,
|
|
32
|
+
'aws.kinesis' => :stream,
|
|
33
|
+
'aws_kinesis' => :stream
|
|
34
|
+
}
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# `messaging.destination_kind` is a v1.17 attribute that we
|
|
38
|
+
# should honor when present.
|
|
39
|
+
DESTINATION_KIND_MAP = {
|
|
40
|
+
'queue' => :queue,
|
|
41
|
+
'topic' => :topic
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
TEMP_MAP = {
|
|
45
|
+
queue: :temporary_queue,
|
|
46
|
+
topic: :temporary_topic
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
ACTION_MAP = {
|
|
50
|
+
consumer: :consume,
|
|
51
|
+
producer: :produce
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
# Capitalize the OTel `messaging.system` value to match the casing used
|
|
55
|
+
# by NR messaging instrumentation.
|
|
56
|
+
LIBRARY_MAP = {
|
|
57
|
+
'kafka' => 'Kafka',
|
|
58
|
+
'rabbitmq' => 'RabbitMQ',
|
|
59
|
+
'aws.sqs' => 'SQS',
|
|
60
|
+
'aws_sqs' => 'SQS',
|
|
61
|
+
'aws.sns' => 'SNS',
|
|
62
|
+
'aws_sns' => 'SNS',
|
|
63
|
+
'aws.kinesis' => 'Kinesis',
|
|
64
|
+
'aws_kinesis' => 'Kinesis'
|
|
65
|
+
}.freeze
|
|
66
|
+
|
|
67
|
+
ROUTING_KEY_OTEL_KEYS = [
|
|
68
|
+
'messaging.kafka.message.key',
|
|
69
|
+
'messaging.rabbitmq.destination.routing_key'
|
|
70
|
+
].freeze
|
|
71
|
+
CORRELATION_ID_OTEL_KEY = 'messaging.message.conversation_id'
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
def mappings_hash(kind)
|
|
75
|
+
kind == :consumer ? AttributeMappings::MESSAGING_CONSUMER_MAPPINGS : AttributeMappings::MESSAGING_PRODUCER_MAPPINGS
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil, kind: nil)
|
|
79
|
+
result[:for_segment_api][:action] = ACTION_MAP[kind]
|
|
80
|
+
result[:for_segment_api][:destination_type] = determine_destination_type(attributes, kind)
|
|
81
|
+
if (system = attributes['messaging.system'])
|
|
82
|
+
result[:for_segment_api][:library] = LIBRARY_MAP[system] || system
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
result[:for_segment_api][:name] = build_transaction_name(result[:for_segment_api])
|
|
86
|
+
|
|
87
|
+
add_producer_segment_params(result, attributes) if kind == :producer
|
|
88
|
+
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def build_transaction_name(segment_api_params)
|
|
93
|
+
return nil unless segment_api_params[:library] && segment_api_params[:destination_name]
|
|
94
|
+
|
|
95
|
+
NewRelic::Agent::Messaging.transaction_name(
|
|
96
|
+
segment_api_params[:library],
|
|
97
|
+
segment_api_params[:destination_type],
|
|
98
|
+
segment_api_params[:destination_name],
|
|
99
|
+
segment_api_params[:action]
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def add_producer_segment_params(result, attributes)
|
|
104
|
+
params = {
|
|
105
|
+
routing_key: ROUTING_KEY_OTEL_KEYS.map { |k| attributes[k] }.compact.first,
|
|
106
|
+
correlation_id: attributes[CORRELATION_ID_OTEL_KEY]
|
|
107
|
+
}.compact
|
|
108
|
+
|
|
109
|
+
result[:for_segment_api][:parameters] = params unless params.empty?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def determine_destination_type(attributes, kind)
|
|
113
|
+
type = explicit_destination_kind(attributes) ||
|
|
114
|
+
DESTINATION_TYPES_MAP[kind][attributes['messaging.system']] ||
|
|
115
|
+
:unknown
|
|
116
|
+
return type unless attributes['messaging.destination.temporary'] == true
|
|
117
|
+
|
|
118
|
+
TEMP_MAP[type] || type
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def explicit_destination_kind(attributes)
|
|
122
|
+
DESTINATION_KIND_MAP[attributes['messaging.destination_kind']]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
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_relative 'datastore_translator'
|
|
6
|
+
|
|
7
|
+
module NewRelic
|
|
8
|
+
module Agent
|
|
9
|
+
module OpenTelemetry
|
|
10
|
+
class RedisDatastoreTranslator < DatastoreTranslator
|
|
11
|
+
class << self
|
|
12
|
+
def mappings_hash(_kind)
|
|
13
|
+
AttributeMappings::REDIS_MAPPINGS
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
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_relative 'base_translator'
|
|
6
|
+
|
|
7
|
+
module NewRelic
|
|
8
|
+
module Agent
|
|
9
|
+
module OpenTelemetry
|
|
10
|
+
class RpcTranslator < BaseTranslator
|
|
11
|
+
class << self
|
|
12
|
+
def mappings_hash(kind)
|
|
13
|
+
kind == :client ? AttributeMappings::RPC_CLIENT_MAPPINGS : AttributeMappings::RPC_SERVER_MAPPINGS
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add_specialized_attributes(result: {}, name: nil, attributes: nil, instrumentation_scope: nil, kind: nil)
|
|
17
|
+
case kind
|
|
18
|
+
when :client
|
|
19
|
+
uri = build_uri(attributes)
|
|
20
|
+
result[:for_segment_api][:uri] = uri if uri
|
|
21
|
+
when :server
|
|
22
|
+
server_name = create_server_transaction_name(name: name, attributes: attributes, instrumentation_scope: instrumentation_scope) if name
|
|
23
|
+
result[:for_segment_api][:name] = server_name if server_name
|
|
24
|
+
|
|
25
|
+
uri = build_uri(attributes)
|
|
26
|
+
if uri
|
|
27
|
+
result[:agent]['request.uri'] = {value: uri, destinations: AttributeMappings::DEFAULT_DESTINATIONS}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_server_transaction_name(name: nil, attributes: nil, instrumentation_scope: nil)
|
|
35
|
+
txn_name = name&.delete_prefix(NewRelic::SLASH)
|
|
36
|
+
Instrumentation::ControllerInstrumentation::TransactionNamer.name_for(nil, nil, :web, {class_name: instrumentation_scope, name: txn_name})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_uri(attributes)
|
|
40
|
+
host = attributes['server.address'] || attributes['net.peer.name'] || attributes['net.sock.peer.addr']
|
|
41
|
+
service = attributes['rpc.service']
|
|
42
|
+
method = attributes['rpc.method']
|
|
43
|
+
|
|
44
|
+
# return early if this method is called with an attributes
|
|
45
|
+
# hash that doesn't have the required values
|
|
46
|
+
return unless host || service || method
|
|
47
|
+
|
|
48
|
+
# strip scheme prefix (e.g. dns:///, xds:///) used by gRPC name resolvers
|
|
49
|
+
host = host&.sub(%r{\A\w+:///}, '')
|
|
50
|
+
|
|
51
|
+
if host && service && method
|
|
52
|
+
"grpc://#{host}/#{service}/#{method}"
|
|
53
|
+
elsif host && method
|
|
54
|
+
"grpc://#{host}/#{method}"
|
|
55
|
+
elsif service && method
|
|
56
|
+
"grpc://#{service}/#{method}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -70,12 +70,16 @@ module NewRelic
|
|
|
70
70
|
require_relative 'opentelemetry/context'
|
|
71
71
|
require_relative 'opentelemetry/trace_patch'
|
|
72
72
|
require_relative 'opentelemetry/abstract_segment_patch'
|
|
73
|
+
require_relative 'opentelemetry/messaging_patch'
|
|
74
|
+
require_relative 'opentelemetry/span_event_primitive_patch'
|
|
73
75
|
|
|
74
76
|
NewRelic::Agent.logger.warn('OpenTelemetry SDK gem is installed. This may interfere with New Relic instrumentation.') if defined?(OpenTelemetry::SDK)
|
|
75
77
|
|
|
76
|
-
::OpenTelemetry::Trace.singleton_class.prepend(
|
|
78
|
+
::OpenTelemetry::Trace.singleton_class.prepend(OpenTelemetry::TracePatch)
|
|
77
79
|
Transaction.prepend(OpenTelemetry::TransactionPatch)
|
|
78
80
|
Transaction::AbstractSegment.prepend(OpenTelemetry::AbstractSegmentPatch)
|
|
81
|
+
Messaging.singleton_class.prepend(OpenTelemetry::MessagingPatch)
|
|
82
|
+
SpanEventPrimitive.prepend(OpenTelemetry::SpanEventPrimitivePatch)
|
|
79
83
|
|
|
80
84
|
::OpenTelemetry.tracer_provider = OpenTelemetry::Trace::TracerProvider.new
|
|
81
85
|
::OpenTelemetry.propagation = OpenTelemetry::Context::Propagation::TracePropagator.new
|
|
@@ -33,6 +33,25 @@ module NewRelic
|
|
|
33
33
|
SUPPORTABILITY_TOTAL_SENT = 'Supportability/SpanEvent/TotalEventsSent'.freeze
|
|
34
34
|
SUPPORTABILITY_DISCARDED = 'Supportability/SpanEvent/Discarded'.freeze
|
|
35
35
|
|
|
36
|
+
def harvest!
|
|
37
|
+
metadata, events = super
|
|
38
|
+
# extra_events is for SpanLink and SpanEvent data related to a Span
|
|
39
|
+
# SpanLink and SpanEvent events are both created by OpenTelemetry APIs
|
|
40
|
+
extra_events = []
|
|
41
|
+
events.each do |event|
|
|
42
|
+
# Spans are normally: [intrinsics, custom_attrs, agent_attrs].
|
|
43
|
+
# When a span has links, a 4th element is appended:
|
|
44
|
+
# [intrinsics, custom_attrs, agent_attrs, extra_events]
|
|
45
|
+
#
|
|
46
|
+
# SpanLinks and SpanEvents must be sent to the backend at the top
|
|
47
|
+
# level of the main payload, not nested inside an individual Span.
|
|
48
|
+
# This pulls them out to be flattened into the final array:
|
|
49
|
+
# [Span, SpanLink, Span, SpanEvent, Span]
|
|
50
|
+
extra_events.concat(event.slice!(3)) if event.length > 3
|
|
51
|
+
end
|
|
52
|
+
[metadata, events.concat(extra_events)]
|
|
53
|
+
end
|
|
54
|
+
|
|
36
55
|
def after_harvest(metadata)
|
|
37
56
|
seen = metadata[:seen]
|
|
38
57
|
sent = metadata[:captured]
|
|
@@ -27,6 +27,7 @@ module NewRelic
|
|
|
27
27
|
DURATION_KEY = 'duration'
|
|
28
28
|
NAME_KEY = 'name'
|
|
29
29
|
CATEGORY_KEY = 'category'
|
|
30
|
+
GRPC_STATUS_CODE_KEY = 'grpc.statusCode'
|
|
30
31
|
HTTP_URL_KEY = 'http.url'
|
|
31
32
|
HTTP_METHOD_KEY = 'http.method'
|
|
32
33
|
HTTP_REQUEST_METHOD_KEY = 'http.request.method'
|
|
@@ -79,6 +80,7 @@ module NewRelic
|
|
|
79
80
|
intrinsics[HTTP_METHOD_KEY] = segment.procedure
|
|
80
81
|
intrinsics[HTTP_REQUEST_METHOD_KEY] = segment.procedure
|
|
81
82
|
intrinsics[HTTP_STATUS_CODE_KEY] = segment.http_status_code if segment.http_status_code
|
|
83
|
+
intrinsics[GRPC_STATUS_CODE_KEY] = segment.instance_variable_get(:@grpc_status_code) if segment.instance_variable_defined?(:@grpc_status_code)
|
|
82
84
|
intrinsics[CATEGORY_KEY] = HTTP_CATEGORY
|
|
83
85
|
intrinsics[SPAN_KIND_KEY] = CLIENT
|
|
84
86
|
intrinsics[SERVER_ADDRESS_KEY] = segment.uri.host
|
|
@@ -68,6 +68,7 @@ module NewRelic
|
|
|
68
68
|
:jruby_cpu_start,
|
|
69
69
|
:process_cpu_start,
|
|
70
70
|
:http_response_code,
|
|
71
|
+
:response_status,
|
|
71
72
|
:response_content_length,
|
|
72
73
|
:response_content_type,
|
|
73
74
|
:parent_span_id
|
|
@@ -633,6 +634,10 @@ module NewRelic
|
|
|
633
634
|
add_agent_attribute(:'http.statusCode', http_response_code, default_destinations)
|
|
634
635
|
end
|
|
635
636
|
|
|
637
|
+
if response_status
|
|
638
|
+
add_agent_attribute(:'response.status', response_status, default_destinations)
|
|
639
|
+
end
|
|
640
|
+
|
|
636
641
|
if response_content_length
|
|
637
642
|
add_agent_attribute(:'response.headers.contentLength', response_content_length.to_i, default_destinations)
|
|
638
643
|
end
|
|
@@ -70,7 +70,8 @@ module DependencyDetection
|
|
|
70
70
|
# https://github.com/newrelic/newrelic-ruby-agent/issues/2912
|
|
71
71
|
return if name == :padrino
|
|
72
72
|
|
|
73
|
-
NewRelic::Agent.config.instance_variable_get(:@cache)
|
|
73
|
+
cache = NewRelic::Agent.config.instance_variable_get(:@cache)
|
|
74
|
+
cache[config_key] = config_key && cache[config_key] == false ? :disabled : :unsatisfied
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
def source_location_for(klass, method_name)
|
data/lib/new_relic/version.rb
CHANGED
data/newrelic.yml
CHANGED
|
@@ -657,8 +657,9 @@ common: &default_settings
|
|
|
657
657
|
# auto, prepend, chain, disabled.
|
|
658
658
|
# instrumentation.rack_urlmap: auto
|
|
659
659
|
|
|
660
|
-
# Controls instrumentation of Rails.event as structured logs
|
|
661
|
-
#
|
|
660
|
+
# Controls instrumentation of Rails.event as structured logs. May be one of:
|
|
661
|
+
# enabled, disabled.
|
|
662
|
+
# instrumentation.rails_event_logger: enabled
|
|
662
663
|
|
|
663
664
|
# An array of Rails.event names to capture as structured log events. For
|
|
664
665
|
# example,
|
data/newrelic_rpm.gemspec
CHANGED
|
@@ -54,6 +54,8 @@ Gem::Specification.new do |s|
|
|
|
54
54
|
s.add_development_dependency 'bundler'
|
|
55
55
|
s.add_development_dependency 'feedjira', '3.2.1' unless ENV['CI']
|
|
56
56
|
s.add_development_dependency 'httparty' unless ENV['CI'] # for perf tests and Gabby
|
|
57
|
+
# TODO: MAJOR VERSION - Remove support for Minitest 4.7.5 and all calls to
|
|
58
|
+
# skip_unless_minitest5_or_above when we drop support for Ruby 2.7
|
|
57
59
|
s.add_development_dependency 'minitest', "#{RUBY_VERSION >= '2.7.0' ? '5.3.3' : '4.7.5'}"
|
|
58
60
|
s.add_development_dependency 'minitest-stub-const', '0.6'
|
|
59
61
|
s.add_development_dependency 'mocha', '~> 1.16'
|
data/test/agent_helper.rb
CHANGED
|
@@ -30,8 +30,8 @@ def fake_guid(length = 16)
|
|
|
30
30
|
NewRelic::Agent::GuidGenerator.generate_guid(length)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def assert_between(floor, ceiling, value, message = "expected #{
|
|
34
|
-
assert((floor
|
|
33
|
+
def assert_between(floor, ceiling, value, message = "expected #{value} to be between #{floor} and #{ceiling}")
|
|
34
|
+
assert((value.between?(floor, ceiling)), message)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def assert_in_delta(expected, actual, delta)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: newrelic_rpm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 10.
|
|
4
|
+
version: 10.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tanna McClure
|
|
@@ -639,6 +639,8 @@ files:
|
|
|
639
639
|
- lib/new_relic/agent/opentelemetry/context.rb
|
|
640
640
|
- lib/new_relic/agent/opentelemetry/context/propagation.rb
|
|
641
641
|
- lib/new_relic/agent/opentelemetry/context/propagation/trace_propagator.rb
|
|
642
|
+
- lib/new_relic/agent/opentelemetry/messaging_patch.rb
|
|
643
|
+
- lib/new_relic/agent/opentelemetry/span_event_primitive_patch.rb
|
|
642
644
|
- lib/new_relic/agent/opentelemetry/trace.rb
|
|
643
645
|
- lib/new_relic/agent/opentelemetry/trace/span.rb
|
|
644
646
|
- lib/new_relic/agent/opentelemetry/trace/tracer.rb
|
|
@@ -652,6 +654,9 @@ files:
|
|
|
652
654
|
- lib/new_relic/agent/opentelemetry/translators/generic_translator.rb
|
|
653
655
|
- lib/new_relic/agent/opentelemetry/translators/http_client_translator.rb
|
|
654
656
|
- lib/new_relic/agent/opentelemetry/translators/http_server_translator.rb
|
|
657
|
+
- lib/new_relic/agent/opentelemetry/translators/messaging_translator.rb
|
|
658
|
+
- lib/new_relic/agent/opentelemetry/translators/redis_datastore_translator.rb
|
|
659
|
+
- lib/new_relic/agent/opentelemetry/translators/rpc_translator.rb
|
|
655
660
|
- lib/new_relic/agent/opentelemetry_bridge.rb
|
|
656
661
|
- lib/new_relic/agent/parameter_filtering.rb
|
|
657
662
|
- lib/new_relic/agent/payload_metric_mapping.rb
|