Skip to content

Commit b353978

Browse files
committed
NH-105128: init add endpoint logic
1 parent 82ee0e2 commit b353978

File tree

2 files changed

+478
-0
lines changed

2 files changed

+478
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
# OTLPEndPoint
11+
class OTLPEndPoint
12+
13+
SW_ENDPOINT_REGEX = /^apm\.collector\.([a-z0-9-]+)\.cloud\.solarwinds\.com$/
14+
OTEL_ENDPOINT_REGEX = %r{^https://otel\.collector\.([a-z0-9-]+)\.cloud\.solarwinds\.com:443(?:/.*)?$}
15+
OTEL_ENDPOINT_LOCAL_REGEX = %r{\Ahttp://0\.0\.0\.0:(4317|4318)\z}
16+
OTEL_ENDPOINT_LOCAL_REGEX2 = %r{\Ahttp://0\.0\.0\.0:(4317|4318)/v1/(metrics|traces|logs)\z}
17+
DEFAULT_OTLP_ENDPOINT = 'https://otel.collector.na-01.cloud.solarwinds.com:443'
18+
DEFAULT_APMPROTO_ENDPOINT = 'apm.collector.na-01.cloud.solarwinds.com'
19+
20+
def initialize
21+
@token = nil
22+
@service_name = nil
23+
@lambda_env = determine_lambda_env
24+
@agent_enable = true
25+
@localhost = false
26+
determine_if_localhost
27+
end
28+
29+
def determine_if_localhost
30+
@localhost = true if ENV['OTEL_EXPORTER_OTLP_ENDPOINT'].to_s.match?(OTEL_ENDPOINT_LOCAL_REGEX)
31+
@localhost = true if ENV['OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'].to_s.match?(OTEL_ENDPOINT_LOCAL_REGEX2)
32+
@localhost = true if ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'].to_s.match?(OTEL_ENDPOINT_LOCAL_REGEX2)
33+
@localhost = true if ENV['OTEL_EXPORTER_OTLP_LOGS_ENDPOINT'].to_s.match?(OTEL_ENDPOINT_LOCAL_REGEX2)
34+
end
35+
36+
def config_otlp_endpoint
37+
config_service_name
38+
config_token
39+
['TRACES','METRICS','LOGS'].each { |data_type| configure_otlp_endpoint(data_type) }
40+
end
41+
42+
def config_service_name
43+
resource_attributes = ENV['OTEL_RESOURCE_ATTRIBUTES'].to_s.split(',').each_with_object({}) do |resource, hash|
44+
key, value = resource.split('=')
45+
hash[key] = value
46+
end
47+
48+
unless @lambda_env
49+
@service_name = ENV['OTEL_SERVICE_NAME'] || resource_attributes['service.name'] || @service_name || 'None'
50+
else
51+
@service_name = ENV['OTEL_SERVICE_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || resource_attributes['service.name']
52+
end
53+
54+
ENV['OTEL_SERVICE_NAME'] = @service_name
55+
end
56+
57+
def mask_token(token)
58+
token = token.to_s
59+
return '*' * token.length if token.length <= 4
60+
61+
"#{token[0, 2]}#{'*' * (token.length - 4)}#{token[-2, 2]}"
62+
end
63+
64+
def config_token
65+
agent_enable = true
66+
return agent_enable if @localhost
67+
68+
if @lambda_env
69+
# for case 10 and 11, lambda only care about SW_APM_API_TOKEN, not SW_APM_SERVICE_KEY
70+
agent_enable = ENV['SW_APM_API_TOKEN'].nil? ? false : true
71+
else
72+
73+
if ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS']
74+
token_type = 'metrics_token'
75+
elsif ENV['OTEL_EXPORTER_OTLP_HEADERS']
76+
token_type = 'general_token'
77+
elsif ENV['SW_APM_SERVICE_KEY']
78+
token_type = 'service_key'
79+
else
80+
token_type = 'invalid'
81+
end
82+
83+
case token_type
84+
when 'metrics_token' || 'general_token'
85+
# exporter header is ok, but still need extract it for sampler http get setting
86+
headers = token_type == 'general_token' ? ENV['OTEL_EXPORTER_OTLP_HEADERS'] : ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS']
87+
@token = headers.gsub("authorization=Bearer ", "")
88+
when 'service_key'
89+
if valid?(ENV['SW_APM_SERVICE_KEY'])
90+
@token, @service_name = ENV['SW_APM_SERVICE_KEY'].to_s.split(':')
91+
else
92+
SolarWindsAPM.logger.warn { "SW_APM_SERVICE_KEY is invalid: #{mask_token(ENV['SW_APM_SERVICE_KEY'])}" }
93+
end
94+
95+
ENV['OTEL_EXPORTER_OTLP_HEADERS'] = "authorization=Bearer #{@token}"
96+
end
97+
98+
agent_enable = token_type == 'invalid' ? false : true
99+
end
100+
101+
@agent_enable = agent_enable
102+
agent_enable
103+
end
104+
105+
def valid?(service_key)
106+
# servicekey checker also works on service name, may need to remove that part
107+
service_key_checker = SolarWindsAPM::ServiceKeyChecker.new('ssl', false)
108+
service_key_name = service_key_checker.read_and_validate_service_key
109+
service_key_name == '' ? false : true
110+
end
111+
112+
def determine_lambda_env
113+
if ENV['LAMBDA_TASK_ROOT'].to_s.empty? && ENV['AWS_LAMBDA_FUNCTION_NAME'].to_s.empty?
114+
false
115+
else
116+
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] lambda environment - LAMBDA_TASK_ROOT: #{ENV.fetch('LAMBDA_TASK_ROOT', nil)}; AWS_LAMBDA_FUNCTION_NAME: #{ENV.fetch('AWS_LAMBDA_FUNCTION_NAME', nil)}" }
117+
true
118+
end
119+
end
120+
121+
# token and endpoint also need to be considered for get settings
122+
# endpoint is for get settings, populate to OTEL_EXPORTER_OTLP_METRICS_ENDPOINT at the end for otlp related exporter
123+
# three sources: otlp env variable, sw env variable, sw config file
124+
125+
# sampler_config = {
126+
# collector: "https://#{ENV.fetch('SW_APM_COLLECTOR', 'apm.collector.na-01.cloud.solarwinds.com')}:443",
127+
# service: service_key_name[1],
128+
# headers: "Bearer #{service_key_name[0]}",
129+
# tracing_mode: SolarWindsAPM::Config[:tracing_mode],
130+
# trigger_trace_enabled: SolarWindsAPM::Config[:trigger_tracing_mode],
131+
# transaction_settings: SolarWindsAPM::Config[:transaction_settings]
132+
# }
133+
def configure_otlp_endpoint(data_type)
134+
# for staging, our purpose, just use OTEL_EXPORTER_OTLP_METRICS_ENDPOINT directly
135+
# https://otel.collector.cloud.solarwinds.com:443/v1/traces
136+
# SW_ENDPOINT_REGEX = /^apm\.collector(?:\.[a-z0-9-]+)?\.cloud\.solarwinds\.com$/
137+
138+
return unless ['TRACES','METRICS','LOGS'].include?(data_type)
139+
140+
data_type_upper = data_type.upcase
141+
data_type = data_type.downcase
142+
143+
endpoint_type = nil
144+
if ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"]
145+
endpoint_type = "#{data_type}_endpoint"
146+
elsif ENV['OTEL_EXPORTER_OTLP_ENDPOINT']
147+
endpoint_type = 'general_endpoint'
148+
elsif ENV['SW_APM_COLLECTOR'].nil? && !@lambda_env
149+
endpoint_type = 'default_nil'
150+
elsif ENV['SW_APM_COLLECTOR'].to_s.match?(SW_ENDPOINT_REGEX)
151+
endpoint_type = 'apm_proto'
152+
else
153+
endpoint_type = 'invalid'
154+
end
155+
156+
# endpoint = nil
157+
sampler_collector_endpoint = nil
158+
case endpoint_type
159+
when "#{data_type}_endpoint" || 'general_endpoint'
160+
# no need to worry about metrics endpoint, just need to make sure the collector endpoint is set for getsetting
161+
endpoint = endpoint_type == 'general_endpoint' ? ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] : ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"]
162+
163+
if endpoint.to_s.match?(OTEL_ENDPOINT_REGEX)
164+
matches = endpoint.to_s.match(OTEL_ENDPOINT_REGEX)
165+
region = matches[1]
166+
sampler_collector_endpoint = DEFAULT_APMPROTO_ENDPOINT.gsub('na-01', region)
167+
ENV['SW_APM_COLLECTOR'] = sampler_collector_endpoint
168+
else
169+
# not the standard otel endpoint, use it directly
170+
# what to do with collector ?
171+
end
172+
173+
when 'default_nil'
174+
# default_nil => no otlp endpoint or no SW_APM_COLLECTOR, use the default apm proto endpoint
175+
ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"] = "#{DEFAULT_OTLP_ENDPOINT}/v1/#{data_type}"
176+
ENV['SW_APM_COLLECTOR'] = DEFAULT_APMPROTO_ENDPOINT
177+
178+
when 'apm_proto'
179+
# default => no otlp endpoint but have SW_APM_COLLECTOR, use the endpoint from SW_APM_COLLECTOR
180+
# when in testing/staging, we need to set both otlp endpoint and SW_APM_COLLECTOR
181+
matches = ENV['SW_APM_COLLECTOR'].to_s.match(SW_ENDPOINT_REGEX)
182+
region = matches[1]
183+
apmproto_endpoint = DEFAULT_APMPROTO_ENDPOINT.gsub("na-01", region)
184+
apmproto_endpoint = apmproto_endpoint.gsub("apm", "otel")
185+
ENV["OTEL_EXPORTER_OTLP_#{data_type_upper}_ENDPOINT"] = "https://#{apmproto_endpoint}:443/v1/#{data_type}"
186+
end
187+
188+
# true means setup ok, false meaning setup failed
189+
# lambda use collector extension to export, so no need have valid endpoint_type
190+
endpoint_type == 'invalid' && !@lambda_env ? false : true
191+
end
192+
end
193+
end

0 commit comments

Comments
 (0)