solarwinds_apm 6.1.1 → 7.0.0.prev1
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/README.md +53 -4
- data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +0 -4
- data/lib/solarwinds_apm/api/current_trace_info.rb +10 -6
- data/lib/solarwinds_apm/api/custom_instrumentation.rb +80 -0
- data/lib/solarwinds_apm/api/custom_metrics.rb +8 -25
- data/lib/solarwinds_apm/api/tracing.rb +12 -27
- data/lib/solarwinds_apm/api/transaction_name.rb +6 -10
- data/lib/solarwinds_apm/api.rb +2 -0
- data/lib/solarwinds_apm/config.rb +1 -1
- data/lib/solarwinds_apm/constants.rb +1 -0
- data/lib/solarwinds_apm/noop/api.rb +5 -2
- data/lib/solarwinds_apm/noop.rb +0 -24
- data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +90 -69
- data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +0 -2
- data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +5 -4
- data/lib/solarwinds_apm/opentelemetry.rb +5 -7
- data/lib/solarwinds_apm/otel_native_config.rb +177 -0
- data/lib/solarwinds_apm/patch/README.md +15 -0
- data/lib/solarwinds_apm/patch/tag_sql/sw_dbo_utils.rb +35 -0
- data/lib/solarwinds_apm/patch/tag_sql/sw_mysql2_patch.rb +1 -12
- data/lib/solarwinds_apm/patch/tag_sql/sw_pg_patch.rb +39 -0
- data/lib/solarwinds_apm/patch/tag_sql_patch.rb +2 -0
- data/lib/solarwinds_apm/{noop/metadata.rb → sampling/dice.rb} +19 -17
- data/lib/solarwinds_apm/sampling/http_sampler.rb +87 -0
- data/lib/solarwinds_apm/sampling/json_sampler.rb +52 -0
- data/lib/solarwinds_apm/sampling/metrics.rb +38 -0
- data/lib/solarwinds_apm/sampling/oboe_sampler.rb +348 -0
- data/lib/solarwinds_apm/sampling/sampler.rb +197 -0
- data/lib/solarwinds_apm/sampling/sampling_constants.rb +127 -0
- data/lib/solarwinds_apm/sampling/sampling_patch.rb +49 -0
- data/lib/solarwinds_apm/sampling/setting_example.txt +1 -0
- data/lib/solarwinds_apm/{noop/context.rb → sampling/settings.rb} +14 -25
- data/lib/solarwinds_apm/sampling/token_bucket.rb +126 -0
- data/lib/solarwinds_apm/sampling/trace_options.rb +100 -0
- data/lib/solarwinds_apm/{patch.rb → sampling.rb} +20 -4
- data/lib/solarwinds_apm/{noop/span.rb → support/aws_resource_detector.rb} +5 -18
- data/lib/solarwinds_apm/support/logger_formatter.rb +1 -1
- data/lib/solarwinds_apm/support/logging_log_event.rb +1 -1
- data/lib/solarwinds_apm/support/lumberjack_formatter.rb +1 -1
- data/lib/solarwinds_apm/support/otlp_endpoint.rb +99 -0
- data/lib/solarwinds_apm/support/resource_detector/aws/beanstalk.rb +51 -0
- data/lib/solarwinds_apm/support/resource_detector/aws/ec2.rb +145 -0
- data/lib/solarwinds_apm/support/resource_detector/aws/ecs.rb +173 -0
- data/lib/solarwinds_apm/support/resource_detector/aws/eks.rb +174 -0
- data/lib/solarwinds_apm/support/resource_detector/aws/lambda.rb +66 -0
- data/lib/solarwinds_apm/support/resource_detector.rb +192 -0
- data/lib/solarwinds_apm/support/service_key_checker.rb +12 -6
- data/lib/solarwinds_apm/support/transaction_settings.rb +6 -0
- data/lib/solarwinds_apm/support/txn_name_manager.rb +54 -9
- data/lib/solarwinds_apm/support/utils.rb +9 -0
- data/lib/solarwinds_apm/support.rb +3 -4
- data/lib/solarwinds_apm/version.rb +4 -4
- data/lib/solarwinds_apm.rb +27 -73
- metadata +105 -50
- data/ext/oboe_metal/extconf.rb +0 -168
- data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +0 -1
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +0 -1
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +0 -1
- data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +0 -1
- data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +0 -1
- data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +0 -1
- data/ext/oboe_metal/src/VERSION +0 -1
- data/ext/oboe_metal/src/bson/bson.h +0 -220
- data/ext/oboe_metal/src/bson/platform_hacks.h +0 -91
- data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -18
- data/ext/oboe_metal/src/oboe.h +0 -930
- data/ext/oboe_metal/src/oboe_api.cpp +0 -793
- data/ext/oboe_metal/src/oboe_api.h +0 -621
- data/ext/oboe_metal/src/oboe_debug.h +0 -17
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +0 -10954
- data/lib/oboe_metal.rb +0 -187
- data/lib/solarwinds_apm/cert/star.appoptics.com.issuer.crt +0 -24
- data/lib/solarwinds_apm/oboe_init_options.rb +0 -222
- data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +0 -239
- data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +0 -174
- data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +0 -333
- data/lib/solarwinds_apm/otel_config.rb +0 -174
- data/lib/solarwinds_apm/otel_lambda_config.rb +0 -56
- data/lib/solarwinds_apm/patch/dummy_patch.rb +0 -12
- data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +0 -33
- data/lib/solarwinds_apm/support/support_report.rb +0 -99
- data/lib/solarwinds_apm/support/swomarginalia/LICENSE +0 -20
- data/lib/solarwinds_apm/support/swomarginalia/README.md +0 -46
- data/lib/solarwinds_apm/support/swomarginalia/comment.rb +0 -206
- data/lib/solarwinds_apm/support/swomarginalia/formatter.rb +0 -20
- data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +0 -55
- data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +0 -24
- data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +0 -89
- data/lib/solarwinds_apm/support/transaction_cache.rb +0 -57
- data/lib/solarwinds_apm/support/x_trace_options.rb +0 -138
@@ -8,41 +8,60 @@
|
|
8
8
|
|
9
9
|
module SolarWindsAPM
|
10
10
|
module OpenTelemetry
|
11
|
-
# reference: OpenTelemetry::SDK::Trace::SpanProcessor
|
12
|
-
class OTLPProcessor
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
# reference: OpenTelemetry::SDK::Trace::SpanProcessor
|
12
|
+
class OTLPProcessor
|
13
|
+
attr_reader :txn_manager
|
14
|
+
|
15
|
+
SW_TRANSACTION_NAME = 'sw.transaction'
|
16
|
+
SW_IS_ENTRY_SPAN = 'sw.is_entry_span'
|
17
|
+
SW_IS_ERROR = 'sw.is_error'
|
18
|
+
|
19
|
+
HTTP_METHOD = 'http.method'
|
20
|
+
HTTP_ROUTE = 'http.route'
|
21
|
+
HTTP_STATUS_CODE = 'http.status_code'
|
22
|
+
HTTP_URL = 'http.url'
|
23
|
+
|
24
|
+
INVALID_HTTP_STATUS_CODE = 0
|
25
|
+
|
26
|
+
def initialize(txn_manager)
|
27
|
+
@txn_manager = txn_manager
|
28
|
+
@meters = { 'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics') }
|
29
|
+
@metrics = { response_time: @meters['sw.apm.request.metrics'].create_histogram('trace.service.response_time', unit: 'ms', description: 'Duration of each entry span for the service, typically meaning the time taken to process an inbound request.') }
|
30
|
+
@transaction_name = nil
|
19
31
|
end
|
20
32
|
|
21
|
-
# @param [Span] span the {Span} that just started.
|
22
|
-
# @param [Context] parent_context the
|
23
|
-
# started span.
|
33
|
+
# @param [Span] span the (mutable) {Span} that just started.
|
34
|
+
# @param [Context] parent_context of the started span.
|
24
35
|
def on_start(span, parent_context)
|
25
36
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_start span: #{span.to_span_data.inspect}" }
|
26
37
|
|
27
38
|
return if non_entry_span(parent_context: parent_context)
|
28
39
|
|
29
|
-
span.
|
30
|
-
span.
|
40
|
+
trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
|
41
|
+
@txn_manager&.set_root_context_h(span.context.hex_trace_id, "#{span.context.hex_span_id}-#{trace_flags}")
|
42
|
+
span.add_attributes({ SW_IS_ENTRY_SPAN => true })
|
31
43
|
rescue StandardError => e
|
32
44
|
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
|
33
45
|
end
|
34
46
|
|
35
|
-
|
47
|
+
def on_finishing(span)
|
48
|
+
@transaction_name = calculate_transaction_names(span)
|
49
|
+
span.set_attribute(SW_TRANSACTION_NAME, @transaction_name)
|
50
|
+
@txn_manager.delete_root_context_h(span.context.hex_trace_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [Span] span the (immutable) {Span} that just ended.
|
36
54
|
def on_finish(span)
|
37
55
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.to_span_data.inspect}" }
|
38
|
-
|
39
|
-
# return if span is non-entry span
|
40
56
|
return if non_entry_span(span: span)
|
41
57
|
|
42
58
|
record_request_metrics(span)
|
43
|
-
record_sampling_metrics
|
44
59
|
|
45
|
-
|
60
|
+
# pull should work on any instrument from oboe_sampler
|
61
|
+
::OpenTelemetry.meter_provider.metric_readers.each do |reader|
|
62
|
+
reader.pull if reader.respond_to? :pull
|
63
|
+
end
|
64
|
+
|
46
65
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
|
47
66
|
rescue StandardError => e
|
48
67
|
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }
|
@@ -50,24 +69,10 @@ module SolarWindsAPM
|
|
50
69
|
|
51
70
|
private
|
52
71
|
|
53
|
-
# Create two meters for sampling and request count
|
54
|
-
def init_meters
|
55
|
-
{
|
56
|
-
'sw.apm.sampling.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.sampling.metrics'),
|
57
|
-
'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics')
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
|
-
def span_attributes(span)
|
62
|
-
span_attrs = { 'sw.transaction' => calculate_lambda_transaction_name(span) }
|
63
|
-
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_attrs: #{span_attrs.inspect}" }
|
64
|
-
span_attrs
|
65
|
-
end
|
66
|
-
|
67
72
|
def meter_attributes(span)
|
68
73
|
meter_attrs = {
|
69
|
-
|
70
|
-
|
74
|
+
SW_IS_ERROR => error?(span) == 1,
|
75
|
+
SW_TRANSACTION_NAME => @transaction_name
|
71
76
|
}
|
72
77
|
|
73
78
|
if span_http?(span)
|
@@ -76,26 +81,33 @@ module SolarWindsAPM
|
|
76
81
|
meter_attrs['http.method'] = span.attributes[HTTP_METHOD] if span.attributes[HTTP_METHOD]
|
77
82
|
end
|
78
83
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] meter_attrs: #{meter_attrs.inspect}" }
|
84
|
+
meter_attrs.compact!
|
79
85
|
meter_attrs
|
80
86
|
end
|
81
87
|
|
82
|
-
def calculate_lambda_transaction_name(
|
83
|
-
(ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] ||
|
88
|
+
def calculate_lambda_transaction_name(span_name)
|
89
|
+
(ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span_name || 'unknown').slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
|
84
90
|
end
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
92
|
+
# Get trans_name and url_tran of this span instance.
|
93
|
+
# Predecessor order: custom SDK > env var SW_APM_TRANSACTION_NAME > automatic naming
|
94
|
+
def calculate_transaction_names(span)
|
95
|
+
return calculate_lambda_transaction_name(span.name) if SolarWindsAPM::Utils.determine_lambda
|
96
|
+
|
97
|
+
trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
|
98
|
+
trans_name = @txn_manager.get(trace_span_id)
|
99
|
+
if trans_name
|
100
|
+
SolarWindsAPM.logger.debug do
|
101
|
+
"[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"
|
102
|
+
end
|
103
|
+
@txn_manager.del(trace_span_id)
|
104
|
+
elsif !ENV['SW_APM_TRANSACTION_NAME'].to_s.empty?
|
105
|
+
trans_name = ENV.fetch('SW_APM_TRANSACTION_NAME', nil)
|
106
|
+
else
|
107
|
+
trans_name = span.attributes[HTTP_ROUTE] || nil
|
108
|
+
trans_name = span.name if trans_name.to_s.empty? && span.name
|
109
|
+
end
|
110
|
+
trans_name.to_s.slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
|
99
111
|
end
|
100
112
|
|
101
113
|
def record_request_metrics(span)
|
@@ -106,31 +118,40 @@ module SolarWindsAPM
|
|
106
118
|
@metrics[:response_time].record(span_time, attributes: meter_attrs)
|
107
119
|
end
|
108
120
|
|
109
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
@metrics[:tracecount].add(trace_count)
|
121
|
+
# Calculate span time in microseconds (us) using start and end time
|
122
|
+
# in nanoseconds (ns). OTel span start/end_time are optional.
|
123
|
+
def calculate_span_time(start_time: nil, end_time: nil)
|
124
|
+
return 0 if start_time.nil? || end_time.nil?
|
114
125
|
|
115
|
-
|
116
|
-
|
117
|
-
@metrics[:samplecount].add(sample_count)
|
126
|
+
((end_time.to_i - start_time.to_i) / 1e3).round
|
127
|
+
end
|
118
128
|
|
119
|
-
|
120
|
-
|
121
|
-
|
129
|
+
# Calculate if this span instance has_error
|
130
|
+
# return [Integer]
|
131
|
+
def error?(span)
|
132
|
+
span.status.code == ::OpenTelemetry::Trace::Status::ERROR ? 1 : 0
|
133
|
+
end
|
122
134
|
|
123
|
-
|
124
|
-
|
125
|
-
|
135
|
+
# This span from inbound HTTP request if from a SERVER by some http.method
|
136
|
+
def span_http?(span)
|
137
|
+
span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER && !span.attributes[HTTP_METHOD].nil?
|
138
|
+
end
|
126
139
|
|
127
|
-
|
128
|
-
|
129
|
-
|
140
|
+
# Calculate HTTP status_code from span or default to UNAVAILABLE
|
141
|
+
# Something went wrong in OTel or instrumented service crashed early
|
142
|
+
# if no status_code in attributes of HTTP span
|
143
|
+
def get_http_status_code(span)
|
144
|
+
span.attributes[HTTP_STATUS_CODE] || INVALID_HTTP_STATUS_CODE
|
145
|
+
end
|
130
146
|
|
131
|
-
|
132
|
-
|
133
|
-
|
147
|
+
# check if it's entry span based on no parent or parent is remote
|
148
|
+
def non_entry_span(span: nil, parent_context: nil)
|
149
|
+
if parent_context
|
150
|
+
parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
|
151
|
+
parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
|
152
|
+
elsif span
|
153
|
+
span.attributes['sw.is_entry_span'] != true
|
154
|
+
end
|
134
155
|
end
|
135
156
|
end
|
136
157
|
end
|
@@ -51,8 +51,6 @@ module SolarWindsAPM
|
|
51
51
|
# text map setter will be used.
|
52
52
|
def inject(carrier, context: ::OpenTelemetry::Context.current,
|
53
53
|
setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
|
54
|
-
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] inject context: #{context.inspect}" }
|
55
|
-
|
56
54
|
span_context = ::OpenTelemetry::Trace.current_span(context)&.context
|
57
55
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context #{span_context.inspect}" }
|
58
56
|
return unless span_context&.valid?
|
@@ -16,6 +16,7 @@ module SolarWindsAPM
|
|
16
16
|
XTRACE_HEADER_NAME = 'x-trace'
|
17
17
|
XTRACEOPTIONS_RESPONSE_HEADER_NAME = 'x-trace-options-response'
|
18
18
|
INTL_SWO_EQUALS = '='
|
19
|
+
SW_XTRACEOPTIONS_RESPONSE_KEY = 'xtrace_options_response'
|
19
20
|
|
20
21
|
private_constant \
|
21
22
|
:HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, :XTRACE_HEADER_NAME,
|
@@ -36,14 +37,13 @@ module SolarWindsAPM
|
|
36
37
|
setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
|
37
38
|
span_context = ::OpenTelemetry::Trace.current_span(context).context
|
38
39
|
|
39
|
-
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] context current_span: #{context.instance_variable_get(:@entries)&.values&.first.inspect}" }
|
40
40
|
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context: #{span_context.inspect}" }
|
41
41
|
|
42
42
|
return unless span_context&.valid?
|
43
43
|
|
44
44
|
x_trace = Utils.traceparent_from_context(span_context)
|
45
45
|
exposed_headers = [XTRACE_HEADER_NAME]
|
46
|
-
xtraceoptions_response = recover_response_from_tracestate(span_context
|
46
|
+
xtraceoptions_response = recover_response_from_tracestate(span_context)
|
47
47
|
|
48
48
|
SolarWindsAPM.logger.debug do
|
49
49
|
"[#{self.class}/#{__method__}] x-trace: #{x_trace}; exposed headers: #{exposed_headers.inspect}; x-trace-options-response: #{xtraceoptions_response}"
|
@@ -69,11 +69,12 @@ module SolarWindsAPM
|
|
69
69
|
private
|
70
70
|
|
71
71
|
# sw_xtraceoptions_response_key -> xtrace_options_response
|
72
|
-
def recover_response_from_tracestate(
|
73
|
-
sanitized = tracestate.value(
|
72
|
+
def recover_response_from_tracestate(span_context)
|
73
|
+
sanitized = span_context.tracestate.value(SW_XTRACEOPTIONS_RESPONSE_KEY)
|
74
74
|
sanitized = '' if sanitized.nil?
|
75
75
|
sanitized = sanitized.gsub(SolarWindsAPM::Constants::INTL_SWO_EQUALS_W3C_SANITIZED,
|
76
76
|
SolarWindsAPM::Constants::INTL_SWO_EQUALS)
|
77
|
+
sanitized = sanitized.gsub(':', SolarWindsAPM::Constants::INTL_SWO_EQUALS)
|
77
78
|
sanitized = sanitized.gsub(SolarWindsAPM::Constants::INTL_SWO_COMMA_W3C_SANITIZED,
|
78
79
|
SolarWindsAPM::Constants::INTL_SWO_COMMA)
|
79
80
|
SolarWindsAPM.logger.debug do
|
@@ -6,14 +6,12 @@
|
|
6
6
|
#
|
7
7
|
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
8
8
|
|
9
|
-
require 'opentelemetry
|
10
|
-
require 'opentelemetry
|
11
|
-
|
12
|
-
|
9
|
+
require 'opentelemetry-sdk'
|
10
|
+
require 'opentelemetry-metrics-sdk'
|
11
|
+
require 'opentelemetry-exporter-otlp'
|
12
|
+
require 'opentelemetry-exporter-otlp-metrics'
|
13
|
+
require 'opentelemetry-instrumentation-all'
|
13
14
|
|
14
15
|
require_relative 'opentelemetry/solarwinds_propagator'
|
15
|
-
require_relative 'opentelemetry/solarwinds_processor'
|
16
|
-
require_relative 'opentelemetry/solarwinds_sampler'
|
17
|
-
require_relative 'opentelemetry/solarwinds_exporter'
|
18
16
|
require_relative 'opentelemetry/solarwinds_response_propagator'
|
19
17
|
require_relative 'opentelemetry/otlp_processor'
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# © 2023 SolarWinds Worldwide, LLC. All rights reserved.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
8
|
+
|
9
|
+
require_relative 'api'
|
10
|
+
require_relative 'support'
|
11
|
+
require_relative 'opentelemetry'
|
12
|
+
require_relative 'sampling'
|
13
|
+
|
14
|
+
module SolarWindsAPM
|
15
|
+
# OTelNativeConfig module
|
16
|
+
module OTelNativeConfig
|
17
|
+
@@config = {}
|
18
|
+
@@config_map = {}
|
19
|
+
@@agent_enabled = false
|
20
|
+
|
21
|
+
RESOURCE_ATTRIBUTES = 'RESOURCE_ATTRIBUTES'
|
22
|
+
|
23
|
+
def self.initialize
|
24
|
+
return unless defined?(::OpenTelemetry::SDK::Configurator)
|
25
|
+
|
26
|
+
is_lambda = SolarWindsAPM::Utils.determine_lambda
|
27
|
+
|
28
|
+
ENV['OTEL_TRACES_EXPORTER'] = ENV['OTEL_TRACES_EXPORTER'].to_s.split(',').tap { |e| e << 'otlp' unless e.include?('otlp') }.join(',')
|
29
|
+
|
30
|
+
# add response propagator to rack instrumentation
|
31
|
+
resolve_response_propagator
|
32
|
+
|
33
|
+
# dbo: traceparent injection as sql comments
|
34
|
+
require_relative 'patch/tag_sql_patch' if SolarWindsAPM::Config[:tag_sql]
|
35
|
+
|
36
|
+
# endpoint and service_key for non-lambda
|
37
|
+
otlp_endpoint = nil
|
38
|
+
unless is_lambda
|
39
|
+
otlp_endpoint = SolarWindsAPM::OTLPEndPoint.new
|
40
|
+
otlp_endpoint.config_otlp_token_and_endpoint
|
41
|
+
return if otlp_endpoint.token.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.apm.version=#{SolarWindsAPM::Version::STRING},sw.data.module=apm,service.name=#{ENV.fetch('OTEL_SERVICE_NAME', nil)}," + ENV['OTEL_RESOURCE_ATTRIBUTES'].to_s
|
45
|
+
|
46
|
+
# resource attributes
|
47
|
+
mandatory_resource = SolarWindsAPM::ResourceDetector.detect
|
48
|
+
additional_attributes = @@config_map[RESOURCE_ATTRIBUTES]
|
49
|
+
if additional_attributes
|
50
|
+
if additional_attributes.instance_of?(::OpenTelemetry::SDK::Resources::Resource)
|
51
|
+
final_attributes = mandatory_resource.merge(additional_attributes)
|
52
|
+
elsif additional_attributes.instance_of?(Hash)
|
53
|
+
final_attributes = mandatory_resource.merge(::OpenTelemetry::SDK::Resources::Resource.create(additional_attributes))
|
54
|
+
end
|
55
|
+
@@config_map.delete(RESOURCE_ATTRIBUTES)
|
56
|
+
else
|
57
|
+
final_attributes = mandatory_resource.merge({})
|
58
|
+
end
|
59
|
+
|
60
|
+
# set gzip compression
|
61
|
+
%w[TRACES METRICS LOGS].each do |signal|
|
62
|
+
ENV["OTEL_EXPORTER_OTLP_#{signal}_COMPRESSION"] = 'gzip' if ENV["OTEL_EXPORTER_OTLP_#{signal}_COMPRESSION"].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_COMPRESSION'].to_s.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# set delta temporality
|
66
|
+
ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'] = 'delta' if ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'].to_s.empty?
|
67
|
+
|
68
|
+
# log level
|
69
|
+
if ENV['OTEL_LOG_LEVEL'].to_s.empty?
|
70
|
+
log_level = (ENV['SW_APM_DEBUG_LEVEL'] || SolarWindsAPM::Config[:debug_level] || 3).to_i
|
71
|
+
ENV['OTEL_LOG_LEVEL'] = SolarWindsAPM::Config::SW_LOG_LEVEL_MAPPING.dig(log_level, :otel)
|
72
|
+
end
|
73
|
+
|
74
|
+
::OpenTelemetry::SDK.configure do |c|
|
75
|
+
c.resource = final_attributes
|
76
|
+
c.use_all(@@config_map)
|
77
|
+
end
|
78
|
+
|
79
|
+
# append our propagators
|
80
|
+
::OpenTelemetry.propagation.instance_variable_get(:@propagators).append(SolarWindsAPM::OpenTelemetry::SolarWindsPropagator::TextMapPropagator.new)
|
81
|
+
|
82
|
+
# add sw metrics processors (only record respone_time)
|
83
|
+
txn_manager = TxnNameManager.new
|
84
|
+
otlp_processor = SolarWindsAPM::OpenTelemetry::OTLPProcessor.new(txn_manager)
|
85
|
+
|
86
|
+
@@config[:metrics_processor] = otlp_processor
|
87
|
+
::OpenTelemetry.tracer_provider.add_span_processor(otlp_processor)
|
88
|
+
|
89
|
+
# collector, service and headers are used for http sampler get settings
|
90
|
+
sampler_config = {
|
91
|
+
tracing_mode: SolarWindsAPM::Config[:tracing_mode],
|
92
|
+
trigger_trace_enabled: SolarWindsAPM::Config[:trigger_tracing_mode],
|
93
|
+
transaction_settings: SolarWindsAPM::Config[:transaction_settings]
|
94
|
+
}
|
95
|
+
|
96
|
+
unless otlp_endpoint.nil?
|
97
|
+
sampler_config.merge!({
|
98
|
+
collector: "https://#{ENV.fetch('SW_APM_COLLECTOR', 'apm.collector.cloud.solarwinds.com:443')}",
|
99
|
+
service: otlp_endpoint.service_name,
|
100
|
+
headers: "Bearer #{otlp_endpoint.token}"
|
101
|
+
})
|
102
|
+
end
|
103
|
+
|
104
|
+
sampler = is_lambda ? JsonSampler.new(sampler_config) : HttpSampler.new(sampler_config)
|
105
|
+
|
106
|
+
::OpenTelemetry.tracer_provider.sampler = ::OpenTelemetry::SDK::Trace::Samplers.parent_based(
|
107
|
+
root: sampler,
|
108
|
+
remote_parent_sampled: sampler,
|
109
|
+
remote_parent_not_sampled: sampler
|
110
|
+
)
|
111
|
+
|
112
|
+
@@agent_enabled = true
|
113
|
+
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.[](key)
|
118
|
+
@@config[key.to_sym]
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.agent_enabled
|
122
|
+
@@agent_enabled
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.resolve_response_propagator
|
126
|
+
response_propagator = SolarWindsAPM::OpenTelemetry::SolarWindsResponsePropagator::TextMapPropagator.new
|
127
|
+
rack_setting = @@config_map['OpenTelemetry::Instrumentation::Rack']
|
128
|
+
|
129
|
+
if rack_setting
|
130
|
+
if rack_setting[:response_propagators].instance_of?(Array)
|
131
|
+
rack_setting[:response_propagators].append(response_propagator)
|
132
|
+
elsif rack_setting[:response_propagators].nil?
|
133
|
+
rack_setting[:response_propagators] = [response_propagator]
|
134
|
+
else
|
135
|
+
SolarWindsAPM.logger.warn do
|
136
|
+
"[#{name}/#{__method__}] Rack response propagator resolve failed. Provided type #{rack_setting[:response_propagators].class}, please provide Array e.g. [#{rack_setting[:response_propagators]}]"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
else
|
140
|
+
@@config_map['OpenTelemetry::Instrumentation::Rack'] = { response_propagators: [response_propagator] }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Allow initialize after set new value to SolarWindsAPM::Config[:key]=value
|
146
|
+
#
|
147
|
+
# Usage:
|
148
|
+
#
|
149
|
+
# Default using the use_all to load all instrumentation
|
150
|
+
# But with specific instrumentation disabled, use {:enabled: false} in config
|
151
|
+
# SolarWindsAPM::OTelNativeConfig.initialize_with_config do |config|
|
152
|
+
# config["OpenTelemetry::Instrumentation::Rack"] = {"a" => "b"}
|
153
|
+
# config["OpenTelemetry::Instrumentation::Dalli"] = {:enabled: false}
|
154
|
+
# config["RESOURCE_ATTRIBUTES"] = ::OpenTelemetry::Resource::Detector::GoogleCloudPlatform.detect
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
def self.initialize_with_config
|
158
|
+
unless block_given?
|
159
|
+
SolarWindsAPM.logger.warn do
|
160
|
+
"[#{name}/#{__method__}] Block not given while doing in-code configuration. Agent disabled."
|
161
|
+
end
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
165
|
+
yield @@config_map
|
166
|
+
|
167
|
+
if @@config_map.empty?
|
168
|
+
SolarWindsAPM.logger.warn do
|
169
|
+
"[#{name}/#{__method__}] No configuration given for in-code configuration. Agent disabled."
|
170
|
+
end
|
171
|
+
return
|
172
|
+
end
|
173
|
+
|
174
|
+
initialize
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Patch
|
2
|
+
|
3
|
+
## Patch upstream code
|
4
|
+
|
5
|
+
This folder is for storing patch files that apply to upstream code.
|
6
|
+
|
7
|
+
For example, to patch otel ruby sdk `Registry`, and assume you have dummy_patch file that have module `SolarWindsAPM::Patch::DummyPatch`,
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require_relative './patch/dummy_patch'
|
11
|
+
|
12
|
+
if defined? OpenTelemetry::Instrumentation::Registry && OpenTelemetry::Instrumentation::Registry::VERSION <= '0.3.0'
|
13
|
+
OpenTelemetry::Instrumentation::Registry.prepend(SolarWindsAPM::Patch::DummyPatch)
|
14
|
+
end
|
15
|
+
```
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module SolarWindsAPM
|
8
|
+
module Patch
|
9
|
+
module TagSql
|
10
|
+
module SWODboUtils
|
11
|
+
def self.annotate_span_and_sql(sql)
|
12
|
+
return sql if sql.to_s.empty?
|
13
|
+
|
14
|
+
current_span = ::OpenTelemetry::Trace.current_span
|
15
|
+
|
16
|
+
annotated_sql = ''
|
17
|
+
if current_span.context.trace_flags.sampled?
|
18
|
+
traceparent = SolarWindsAPM::Utils.traceparent_from_context(current_span.context)
|
19
|
+
annotated_traceparent = "/*traceparent='#{traceparent}'*/"
|
20
|
+
current_span.add_attributes({ 'sw.query_tag' => annotated_traceparent })
|
21
|
+
annotated_sql = "#{sql} #{annotated_traceparent}"
|
22
|
+
else
|
23
|
+
annotated_sql = sql
|
24
|
+
end
|
25
|
+
|
26
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] annotated_sql: #{annotated_sql}" }
|
27
|
+
annotated_sql
|
28
|
+
rescue StandardError => e
|
29
|
+
SolarWindsAPM.logger.error { "[#{self.class}/#{__method__}] Failed to annotated sql. Error: #{e.message}" }
|
30
|
+
sql
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -11,18 +11,7 @@ module SolarWindsAPM
|
|
11
11
|
module TagSql
|
12
12
|
module SWOMysql2Patch
|
13
13
|
def query(sql, options = {})
|
14
|
-
|
15
|
-
|
16
|
-
annotated_sql = ''
|
17
|
-
if current_span.context.trace_flags.sampled?
|
18
|
-
traceparent = SolarWindsAPM::Utils.traceparent_from_context(current_span.context)
|
19
|
-
annotated_traceparent = "/*traceparent='#{traceparent}'*/"
|
20
|
-
current_span.add_attributes({ 'sw.query_tag' => annotated_traceparent })
|
21
|
-
annotated_sql = "#{sql} #{annotated_traceparent}"
|
22
|
-
else
|
23
|
-
annotated_sql = sql
|
24
|
-
end
|
25
|
-
|
14
|
+
annotated_sql = ::SolarWindsAPM::Patch::TagSql::SWODboUtils.annotate_span_and_sql(sql)
|
26
15
|
super(annotated_sql, options)
|
27
16
|
end
|
28
17
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module SolarWindsAPM
|
8
|
+
module Patch
|
9
|
+
module TagSql
|
10
|
+
module SWOPgPatch
|
11
|
+
# We target operations covered by the upstream pg instrumentation.
|
12
|
+
# These are all alike in that they will have a SQL
|
13
|
+
# statement as the first parameter, and they are all
|
14
|
+
# non-prepared statement execute.
|
15
|
+
EXEC_ISH_METHODS = %i[
|
16
|
+
exec
|
17
|
+
query
|
18
|
+
sync_exec
|
19
|
+
async_exec
|
20
|
+
exec_params
|
21
|
+
async_exec_params
|
22
|
+
sync_exec_params
|
23
|
+
].freeze
|
24
|
+
|
25
|
+
EXEC_ISH_METHODS.each do |method|
|
26
|
+
define_method method do |*args|
|
27
|
+
annotated_sql = ::SolarWindsAPM::Patch::TagSql::SWODboUtils.annotate_span_and_sql(args[0])
|
28
|
+
args[0] = annotated_sql
|
29
|
+
super(*args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# need to prepend before pg instrumentation patch itself
|
38
|
+
# upstream instrumentation -> our patch -> original function
|
39
|
+
PG::Connection.prepend(SolarWindsAPM::Patch::TagSql::SWOPgPatch) if defined?(PG::Connection)
|
@@ -6,4 +6,6 @@
|
|
6
6
|
#
|
7
7
|
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
8
8
|
|
9
|
+
require_relative 'tag_sql/sw_dbo_utils'
|
9
10
|
require_relative 'tag_sql/sw_mysql2_patch'
|
11
|
+
require_relative 'tag_sql/sw_pg_patch'
|
@@ -6,27 +6,29 @@
|
|
6
6
|
#
|
7
7
|
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
9
|
+
# Dice is used in oboe_sampler diceRollAlgo
|
10
|
+
module SolarWindsAPM
|
11
|
+
class Dice
|
12
|
+
attr_reader :rate, :scale
|
13
|
+
|
14
|
+
def initialize(settings)
|
15
|
+
@scale = settings[:scale]
|
16
|
+
@rate = settings[:rate] || 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(settings)
|
20
|
+
@scale = settings[:scale] if settings[:scale]
|
21
|
+
@rate = settings[:rate] if settings[:rate]
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
# return Boolean
|
25
|
+
def roll
|
26
|
+
(rand * @scale) < @rate
|
26
27
|
end
|
27
28
|
|
28
|
-
def
|
29
|
-
|
29
|
+
def rate=(rate)
|
30
|
+
# Math.max(0, Math.min(this.#scale, n))
|
31
|
+
@rate = rate.clamp(0, @scale)
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|