solarwinds_apm 6.1.2 → 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 +4 -2
- data/lib/solarwinds_apm/api/current_trace_info.rb +10 -6
- 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/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/{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 +99 -40
- 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 -11045
- 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/transaction_cache.rb +0 -57
- data/lib/solarwinds_apm/support/x_trace_options.rb +0 -138
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolarWindsAPM
|
4
|
+
module MetricsExporter
|
5
|
+
module Patch
|
6
|
+
# do not send metrics if no data_points present
|
7
|
+
def export(metrics, timeout: nil)
|
8
|
+
metrics.reject! { |m| m.data_points.empty? }
|
9
|
+
return ::OpenTelemetry::SDK::Metrics::Export::SUCCESS unless metrics.any? { |m| m.data_points.any? }
|
10
|
+
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module SolarWindsAPM
|
18
|
+
module Span
|
19
|
+
module Patch
|
20
|
+
def finish(end_timestamp: nil)
|
21
|
+
@mutex.synchronize do
|
22
|
+
if @ended
|
23
|
+
::OpenTelemetry.logger.warn('Calling finish on an ended Span.')
|
24
|
+
return self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
@span_processors.each do |processor|
|
29
|
+
processor.on_finishing(self) if processor.respond_to?(:on_finishing)
|
30
|
+
end
|
31
|
+
|
32
|
+
@mutex.synchronize do
|
33
|
+
@end_timestamp = relative_timestamp(end_timestamp)
|
34
|
+
@attributes = validated_attributes(@attributes).freeze
|
35
|
+
@events.freeze
|
36
|
+
@links.freeze
|
37
|
+
@ended = true
|
38
|
+
end
|
39
|
+
@span_processors.each { |processor| processor.on_finish(self) }
|
40
|
+
self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.prepend(SolarWindsAPM::MetricsExporter::Patch)
|
47
|
+
|
48
|
+
# issue: https://github.com/open-telemetry/opentelemetry-ruby/issues/1824
|
49
|
+
OpenTelemetry::SDK::Trace::Span.prepend(SolarWindsAPM::Span::Patch)
|
@@ -0,0 +1 @@
|
|
1
|
+
{"value"=>1000000, "flags"=>"SAMPLE_START,SAMPLE_THROUGH_ALWAYS,SAMPLE_BUCKET_ENABLED,TRIGGER_TRACE", "timestamp"=>1738523568, "ttl"=>120, "arguments"=>{"BucketCapacity"=>2, "BucketRate"=>1, "TriggerRelaxedBucketCapacity"=>20, "TriggerRelaxedBucketRate"=>1, "TriggerStrictBucketCapacity"=>6, "TriggerStrictBucketRate"=>0.1, "SignatureKey"=>"0sftj1EYX7JJp01DblJwkccYCIZ91fbU"}}
|
@@ -6,34 +6,23 @@
|
|
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
|
-
# end
|
9
|
+
module SolarWindsAPM
|
10
|
+
module SamplingSettings
|
11
|
+
def self.merge(remote, local)
|
12
|
+
flags = local[:tracing_mode] || remote[:flags]
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# toString would return the current trace context as string
|
21
|
-
#
|
22
|
-
def self.toString
|
23
|
-
'99-00000000000000000000000000000000-0000000000000000-00'
|
24
|
-
end
|
14
|
+
if local[:trigger_mode] == :enabled
|
15
|
+
flags |= SolarWindsAPM::Flags::TRIGGERED_TRACE
|
16
|
+
elsif local[:trigger_mode] == :disabled
|
17
|
+
flags &= ~ SolarWindsAPM::Flags::TRIGGERED_TRACE
|
18
|
+
end
|
25
19
|
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
if remote[:flags].anybits?(SolarWindsAPM::Flags::OVERRIDE)
|
21
|
+
flags &= remote[:flags]
|
22
|
+
flags |= SolarWindsAPM::Flags::OVERRIDE
|
23
|
+
end
|
29
24
|
|
30
|
-
|
31
|
-
[-1, -1, -1, 0, 0.0, 0.0, -1, -1, '', '', 4]
|
25
|
+
remote.dup.tap { |merged| merged[:flags] = flags }
|
32
26
|
end
|
33
|
-
|
34
|
-
##
|
35
|
-
# noop version of :clear
|
36
|
-
#
|
37
|
-
def self.clear; end
|
38
27
|
end
|
39
28
|
end
|
@@ -0,0 +1,126 @@
|
|
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
|
+
# Bucket is used to consume that determine if capacity is enough
|
10
|
+
# capacity is updated through update_settings
|
11
|
+
module SolarWindsAPM
|
12
|
+
class TokenBucket
|
13
|
+
# Maximum value of a signed 32-bit integer
|
14
|
+
MAX_INTERVAL = (2**31) - 1
|
15
|
+
|
16
|
+
attr_reader :capacity, :rate, :interval, :tokens
|
17
|
+
|
18
|
+
def initialize(token_bucket_settings)
|
19
|
+
self.capacity = token_bucket_settings.capacity || 0
|
20
|
+
self.rate = token_bucket_settings.rate || 0
|
21
|
+
self.interval = token_bucket_settings.interval || MAX_INTERVAL
|
22
|
+
self.tokens = @capacity
|
23
|
+
@timer = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# used call from update_settings e.g. bucket.update(bucket_settings)
|
27
|
+
def update(settings)
|
28
|
+
settings.instance_of?(Hash) ? update_from_hash(settings) : update_from_token_bucket_settings(settings)
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_from_hash(settings)
|
32
|
+
if settings[:capacity]
|
33
|
+
difference = settings[:capacity] - @capacity
|
34
|
+
self.capacity = settings[:capacity]
|
35
|
+
self.tokens = @tokens + difference
|
36
|
+
end
|
37
|
+
|
38
|
+
self.rate = settings[:rate] if settings[:rate]
|
39
|
+
|
40
|
+
return unless settings[:interval]
|
41
|
+
|
42
|
+
self.interval = settings[:interval]
|
43
|
+
return unless running
|
44
|
+
|
45
|
+
stop
|
46
|
+
start
|
47
|
+
end
|
48
|
+
|
49
|
+
def update_from_token_bucket_settings(settings)
|
50
|
+
if settings.capacity
|
51
|
+
difference = settings.capacity - @capacity
|
52
|
+
self.capacity = settings.capacity
|
53
|
+
self.tokens = @tokens + difference
|
54
|
+
end
|
55
|
+
|
56
|
+
self.rate = settings.rate if settings.rate
|
57
|
+
|
58
|
+
return unless settings.interval
|
59
|
+
|
60
|
+
self.interval = settings.interval
|
61
|
+
return unless running
|
62
|
+
|
63
|
+
stop
|
64
|
+
start
|
65
|
+
end
|
66
|
+
|
67
|
+
def capacity=(capacity)
|
68
|
+
@capacity = [0, capacity].max
|
69
|
+
end
|
70
|
+
|
71
|
+
def rate=(rate)
|
72
|
+
@rate = [0, rate].max
|
73
|
+
end
|
74
|
+
|
75
|
+
def interval=(interval)
|
76
|
+
@interval = interval.clamp(0, MAX_INTERVAL)
|
77
|
+
end
|
78
|
+
|
79
|
+
def tokens=(tokens)
|
80
|
+
@tokens = tokens.clamp(0, @capacity)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Attempts to consume tokens from the bucket
|
84
|
+
# @param n [Integer] Number of tokens to consume
|
85
|
+
# @return [Boolean] Whether there were enough tokens
|
86
|
+
def consume(token = 1)
|
87
|
+
if @tokens >= token
|
88
|
+
self.tokens = @tokens - token
|
89
|
+
true
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Starts replenishing the bucket
|
96
|
+
def start
|
97
|
+
return if running
|
98
|
+
|
99
|
+
@timer = Thread.new do
|
100
|
+
loop do
|
101
|
+
task
|
102
|
+
sleep(@interval / 1000.0)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Stops replenishing the bucket
|
108
|
+
def stop
|
109
|
+
return unless running
|
110
|
+
|
111
|
+
@timer.kill
|
112
|
+
@timer = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# Whether the bucket is actively being replenished
|
116
|
+
def running
|
117
|
+
!@timer.nil?
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def task
|
123
|
+
self.tokens = tokens + @rate
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,100 @@
|
|
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
|
+
module SolarWindsAPM
|
10
|
+
class TraceOptions
|
11
|
+
TRIGGER_TRACE_KEY = 'trigger-trace'
|
12
|
+
TIMESTAMP_KEY = 'ts'
|
13
|
+
SW_KEYS_KEY = 'sw-keys'
|
14
|
+
|
15
|
+
CUSTOM_KEY_REGEX = /^custom-[^\s]*$/
|
16
|
+
|
17
|
+
def self.parse_trace_options(header, logger)
|
18
|
+
trace_options = TriggerTraceOptions.new(nil, nil, nil, {}, [], TraceOptionsResponse.new(nil, nil, []))
|
19
|
+
|
20
|
+
kvs = header.split(';').map do |kv|
|
21
|
+
key, *values = kv.split('=').map(&:strip)
|
22
|
+
value = values.any? ? values.join('=') : nil
|
23
|
+
[key, value]
|
24
|
+
end
|
25
|
+
|
26
|
+
kvs.reject! { |key, _| key.nil? || key.empty? }
|
27
|
+
|
28
|
+
kvs.each do |k, v|
|
29
|
+
case k
|
30
|
+
when TRIGGER_TRACE_KEY
|
31
|
+
if v || trace_options.trigger_trace
|
32
|
+
logger.debug { 'invalid trace option for trigger trace' }
|
33
|
+
trace_options.ignored << [k, v]
|
34
|
+
next
|
35
|
+
end
|
36
|
+
trace_options.trigger_trace = true
|
37
|
+
when TIMESTAMP_KEY
|
38
|
+
if v.nil? || trace_options.timestamp
|
39
|
+
logger.debug { 'invalid trace option for timestamp' }
|
40
|
+
trace_options.ignored << [k, v]
|
41
|
+
next
|
42
|
+
end
|
43
|
+
|
44
|
+
unless numeric_integer?(v)
|
45
|
+
logger.debug { 'invalid trace option for timestamp, should be an integer' }
|
46
|
+
trace_options.ignored << [k, v]
|
47
|
+
next
|
48
|
+
end
|
49
|
+
trace_options.timestamp = v.to_i
|
50
|
+
when SW_KEYS_KEY
|
51
|
+
if v.nil? || trace_options.sw_keys
|
52
|
+
logger.debug { 'invalid trace option for sw keys' }
|
53
|
+
trace_options.ignored << [k, v]
|
54
|
+
next
|
55
|
+
end
|
56
|
+
trace_options.sw_keys = v
|
57
|
+
when CUSTOM_KEY_REGEX
|
58
|
+
if v.nil? || trace_options.custom[k]
|
59
|
+
logger.debug { "invalid trace option for custom key #{k}" }
|
60
|
+
trace_options.ignored << [k, v]
|
61
|
+
next
|
62
|
+
end
|
63
|
+
trace_options.custom[k] = v
|
64
|
+
else
|
65
|
+
trace_options.ignored << [k, v]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
trace_options
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.numeric_integer?(str)
|
73
|
+
true if Integer(str)
|
74
|
+
rescue StandardError
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
# combine the array to string separate by ;
|
79
|
+
# tracestate doesn't accept value with k=v, here we use k:v
|
80
|
+
# but it will be replaced with = when inject in respond header
|
81
|
+
def self.stringify_trace_options_response(trace_options_response)
|
82
|
+
return if trace_options_response.nil?
|
83
|
+
|
84
|
+
kvs = {
|
85
|
+
auth: trace_options_response.auth,
|
86
|
+
'trigger-trace': trace_options_response.trigger_trace,
|
87
|
+
ignored: trace_options_response.ignored.empty? ? nil : trace_options_response.ignored.join(',')
|
88
|
+
}
|
89
|
+
kvs.compact.map { |k, v| "#{k}:#{v}" }.join(';')
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.validate_signature(header, signature, key, timestamp)
|
93
|
+
return Auth::NO_SIGNATURE_KEY unless key
|
94
|
+
return Auth::BAD_TIMESTAMP unless timestamp && (Time.now.to_i - timestamp).abs <= 5 * 60
|
95
|
+
|
96
|
+
digest = OpenSSL::HMAC.hexdigest('SHA1', key, header)
|
97
|
+
signature == digest ? Auth::OK : Auth::BAD_SIGNATURE
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -6,8 +6,24 @@
|
|
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
|
-
|
9
|
+
require 'json'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'tempfile'
|
12
|
+
require 'uri'
|
13
|
+
require 'opentelemetry-sdk'
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
require_relative 'sampling/sampling_constants'
|
16
|
+
require_relative 'sampling/dice'
|
17
|
+
require_relative 'sampling/settings'
|
18
|
+
require_relative 'sampling/token_bucket'
|
19
|
+
require_relative 'sampling/trace_options'
|
20
|
+
require_relative 'sampling/metrics'
|
21
|
+
|
22
|
+
# HttpSampler/JsonSampler < Sampler < OboeSampler
|
23
|
+
require_relative 'sampling/oboe_sampler'
|
24
|
+
require_relative 'sampling/sampler'
|
25
|
+
require_relative 'sampling/http_sampler'
|
26
|
+
require_relative 'sampling/json_sampler'
|
27
|
+
|
28
|
+
# Patching
|
29
|
+
require_relative 'sampling/sampling_patch'
|
@@ -1,25 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# ©
|
3
|
+
# © 2025 SolarWinds Worldwide, LLC. All rights reserved.
|
4
4
|
#
|
5
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
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
|
-
# Span
|
14
|
-
class Span
|
15
|
-
##
|
16
|
-
# noop version of :createHttpSpan
|
17
|
-
#
|
18
|
-
def self.createHttpSpan(*); end
|
19
|
-
|
20
|
-
##
|
21
|
-
# noop version of :createSpan
|
22
|
-
#
|
23
|
-
def self.createSpan(*); end
|
24
|
-
end
|
25
|
-
end
|
9
|
+
require_relative 'resource_detector/aws/ecs'
|
10
|
+
require_relative 'resource_detector/aws/eks'
|
11
|
+
require_relative 'resource_detector/aws/lambda'
|
12
|
+
require_relative 'resource_detector/aws/beanstalk'
|
@@ -47,4 +47,4 @@ end
|
|
47
47
|
|
48
48
|
# To use the trace context in log, ::Logger::Formatter.new must be defined
|
49
49
|
# e.g. config.log_formatter = ::Logger::Formatter.new
|
50
|
-
Logger::Formatter.prepend(SolarWindsAPM::Logger::Formatter)
|
50
|
+
Logger::Formatter.prepend(SolarWindsAPM::Logger::Formatter)
|
@@ -0,0 +1,99 @@
|
|
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 'service_key_checker'
|
10
|
+
|
11
|
+
module SolarWindsAPM
|
12
|
+
# OTLPEndPoint
|
13
|
+
class OTLPEndPoint
|
14
|
+
attr_reader :token, :service_name
|
15
|
+
|
16
|
+
SWO_APM_ENDPOINT_REGEX = /^apm\.collector\.([a-z]{2}-\d{2})\.([^.]+)\.solarwinds\.com(?::\d+)?$/
|
17
|
+
SWO_APM_ENDPOINT_DEFAULT = 'apm.collector.na-01.cloud.solarwinds.com:443'
|
18
|
+
|
19
|
+
SWO_OTLP_GENERAL_ENDPOINT_REGEX = %r{^https://otel\.collector\.[a-z0-9-]+\.[a-z0-9-]+\.solarwinds\.com(?::\d+)?$}
|
20
|
+
SWO_OTLP_SIGNAL_ENDPOINT_REGEX = %r{^https://otel\.collector\.[a-z0-9-]+\.[a-z0-9-]+\.solarwinds\.com(?::\d+)?/v1/(?:logs|metrics|traces)$}
|
21
|
+
SWO_OTLP_ENDPOINT_DEFAULT = 'https://otel.collector.na-01.cloud.solarwinds.com:443'
|
22
|
+
|
23
|
+
OTEL_SIGNAL_TYPE = %w[TRACES METRICS LOGS].freeze
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@token = nil
|
27
|
+
@service_name = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def config_otlp_token_and_endpoint
|
31
|
+
matches = ENV['SW_APM_COLLECTOR'].to_s.match(SWO_APM_ENDPOINT_REGEX)
|
32
|
+
|
33
|
+
resolve_get_setting_endpoint(matches)
|
34
|
+
|
35
|
+
service_key_checker = SolarWindsAPM::ServiceKeyChecker.new('ssl', SolarWindsAPM::Utils.determine_lambda)
|
36
|
+
@token = service_key_checker.token
|
37
|
+
@service_name = service_key_checker.service_name
|
38
|
+
|
39
|
+
OTEL_SIGNAL_TYPE.each do |data_type|
|
40
|
+
config_token(data_type)
|
41
|
+
configure_otlp_endpoint(data_type, matches)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# APM Libraries should only set the bearer token header as a convenience if:
|
46
|
+
# The OTEL config for exporter OTLP headers is not already set, i.e. explicitly configured by the end user, AND
|
47
|
+
# The OTLP export endpoint is SWO, i.e. host is otel.collector.*.*.solarwinds.com
|
48
|
+
def config_token(data_type)
|
49
|
+
data_type_upper = data_type.upcase
|
50
|
+
|
51
|
+
return unless @token
|
52
|
+
|
53
|
+
if ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_HEADERS"].to_s.empty? && ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"].to_s.match?(SWO_OTLP_SIGNAL_ENDPOINT_REGEX)
|
54
|
+
ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_HEADERS"] = "authorization=Bearer #{@token}"
|
55
|
+
elsif ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_ENDPOINT'].to_s.match?(SWO_OTLP_GENERAL_ENDPOINT_REGEX)
|
56
|
+
ENV['OTEL_EXPORTER_OTLP_HEADERS'] = "authorization=Bearer #{@token}"
|
57
|
+
end
|
58
|
+
|
59
|
+
ENV['OTEL_EXPORTER_OTLP_HEADERS'] = "authorization=Bearer #{@token}" if ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? && ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_HEADERS"].to_s.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def configure_otlp_endpoint(data_type, matches)
|
63
|
+
data_type_upper = data_type.upcase
|
64
|
+
data_type_lower = data_type.downcase
|
65
|
+
otlp_endpoint_source = if ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"]
|
66
|
+
"#{data_type_lower}_endpoint"
|
67
|
+
elsif ENV['OTEL_EXPORTER_OTLP_ENDPOINT']
|
68
|
+
'general_endpoint'
|
69
|
+
else
|
70
|
+
'no_endpoint'
|
71
|
+
end
|
72
|
+
|
73
|
+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] otlp_endpoint_source: #{otlp_endpoint_source}" }
|
74
|
+
|
75
|
+
return unless otlp_endpoint_source == 'no_endpoint'
|
76
|
+
|
77
|
+
if matches&.size == 3
|
78
|
+
region = matches[1]
|
79
|
+
env = matches[2]
|
80
|
+
ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"] = "https://otel.collector.#{region}.#{env}.solarwinds.com:443/v1/#{data_type_lower}"
|
81
|
+
else
|
82
|
+
ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"] = "https://otel.collector.na-01.cloud.solarwinds.com:443/v1/#{data_type_lower}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# only valid value is apm.collector.*.*.solarwinds.com
|
87
|
+
# If SW APM config for collector is not set, the fallback: apm.collector.na-01.cloud.solarwinds.com
|
88
|
+
def resolve_get_setting_endpoint(matches)
|
89
|
+
return if matches&.size == 3
|
90
|
+
|
91
|
+
unless ENV['SW_APM_COLLECTOR'].to_s.empty?
|
92
|
+
SolarWindsAPM.logger.warn do
|
93
|
+
"[#{self.class}/#{__method__}] SW_APM_COLLECTOR format invalid: #{ENV.fetch('SW_APM_COLLECTOR', nil)}. Valid format: apm.collector.*.*.solarwinds.com"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
ENV['SW_APM_COLLECTOR'] = SWO_APM_ENDPOINT_DEFAULT
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,51 @@
|
|
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 'net/http'
|
10
|
+
require 'uri'
|
11
|
+
require 'json'
|
12
|
+
require 'socket'
|
13
|
+
|
14
|
+
module SolarWindsAPM
|
15
|
+
module ResourceDetector
|
16
|
+
# Beanstalk
|
17
|
+
module Beanstalk
|
18
|
+
module_function
|
19
|
+
|
20
|
+
DEFAULT_BEANSTALK_CONF_PATH = '/var/elasticbeanstalk/xray/environment.conf'
|
21
|
+
WIN_OS_BEANSTALK_CONF_PATH = 'C:\\Program Files\\Amazon\\XRay\\environment.conf'
|
22
|
+
|
23
|
+
def detect
|
24
|
+
beanstalk_config_path = if RUBY_PLATFORM.include?('mingw32') || RUBY_PLATFORM.include?('mswin')
|
25
|
+
WIN_OS_BEANSTALK_CONF_PATH
|
26
|
+
else
|
27
|
+
DEFAULT_BEANSTALK_CONF_PATH
|
28
|
+
end
|
29
|
+
|
30
|
+
attribute = gather_data(beanstalk_config_path)
|
31
|
+
::OpenTelemetry::SDK::Resources::Resource.create(attribute)
|
32
|
+
end
|
33
|
+
|
34
|
+
def gather_data(config_path)
|
35
|
+
raw_data = File.read(config_path, encoding: 'utf-8')
|
36
|
+
parsed_data = JSON.parse(raw_data)
|
37
|
+
{
|
38
|
+
::OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER => 'aws',
|
39
|
+
::OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM => 'aws_elastic_beanstalk',
|
40
|
+
::OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => 'aws_elastic_beanstalk',
|
41
|
+
::OpenTelemetry::SemanticConventions::Resource::SERVICE_NAMESPACE => parsed_data['environment_name'],
|
42
|
+
::OpenTelemetry::SemanticConventions::Resource::SERVICE_VERSION => parsed_data['version_label'],
|
43
|
+
::OpenTelemetry::SemanticConventions::Resource::SERVICE_INSTANCE_ID => parsed_data['deployment_id']
|
44
|
+
}
|
45
|
+
rescue StandardError => e
|
46
|
+
SolarWindsAPM.logger.debug { "Gather data for AWS Elastic Beanstalk resource detector failed: #{e.message}" }
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|