appoptics_apm 4.8.1 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.travis.yml +50 -28
  4. data/CONFIG.md +1 -1
  5. data/Gemfile +6 -6
  6. data/README.md +1 -1
  7. data/appoptics_apm.gemspec +4 -1
  8. data/examples/SDK/01_basic_tracing.rb +0 -2
  9. data/ext/oboe_metal/README.md +69 -0
  10. data/ext/oboe_metal/noop/noop.c +2 -1
  11. data/ext/oboe_metal/src/VERSION +1 -1
  12. data/ext/oboe_metal/src/bson/bson.h +105 -105
  13. data/ext/oboe_metal/src/bson/platform_hacks.h +10 -10
  14. data/ext/oboe_metal/src/oboe.h +104 -73
  15. data/ext/oboe_metal/src/oboe.hpp +103 -61
  16. data/ext/oboe_metal/src/oboe_wrap.cxx +1470 -314
  17. data/lib/appoptics_apm.rb +1 -3
  18. data/lib/appoptics_apm/api.rb +0 -1
  19. data/lib/appoptics_apm/api/tracing.rb +4 -0
  20. data/lib/appoptics_apm/config.rb +16 -5
  21. data/lib/appoptics_apm/frameworks/padrino.rb +6 -36
  22. data/lib/appoptics_apm/frameworks/rails.rb +0 -1
  23. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +5 -5
  24. data/lib/appoptics_apm/frameworks/rails/inst/action_controller6.rb +50 -0
  25. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +11 -24
  26. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +3 -3
  27. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +1 -1
  28. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +1 -1
  29. data/lib/appoptics_apm/frameworks/sinatra.rb +3 -32
  30. data/lib/appoptics_apm/inst/curb.rb +6 -6
  31. data/lib/appoptics_apm/inst/faraday.rb +16 -4
  32. data/lib/appoptics_apm/inst/logger_formatter.rb +4 -3
  33. data/lib/appoptics_apm/inst/rack.rb +11 -11
  34. data/lib/appoptics_apm/oboe_init_options.rb +17 -4
  35. data/lib/appoptics_apm/sdk/tracing.rb +118 -0
  36. data/lib/appoptics_apm/support/transaction_metrics.rb +1 -0
  37. data/lib/appoptics_apm/support/transaction_settings.rb +40 -15
  38. data/lib/appoptics_apm/support/x_trace_options.rb +110 -0
  39. data/lib/appoptics_apm/version.rb +2 -2
  40. data/lib/appoptics_apm/xtrace.rb +7 -7
  41. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +37 -12
  42. metadata +7 -15
  43. data/Rakefile +0 -229
  44. data/build_gem.sh +0 -15
  45. data/build_gem_upload_to_packagecloud.sh +0 -20
  46. data/lib/appoptics_apm/api/profiling.rb +0 -203
  47. data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +0 -55
  48. data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +0 -50
  49. data/lib/appoptics_apm/legacy_method_profiling.rb +0 -90
  50. data/lib/appoptics_apm/method_profiling.rb +0 -33
  51. data/lib/oboe/README +0 -2
  52. data/lib/oboe/backward_compatibility.rb +0 -80
  53. data/lib/oboe/inst/rack.rb +0 -11
@@ -23,13 +23,14 @@ module AppOpticsAPM
23
23
  if current_trace.log?
24
24
  case msg
25
25
  when ::String
26
- msg.strip.empty? ? msg : insert_before_empty_lines(msg, current_trace.for_log)
26
+ msg = msg.strip.empty? ? msg : insert_before_empty_lines(msg, current_trace.for_log)
27
27
  when ::Exception
28
28
  # conversion to String copied from Logger::Formatter private method #msg2str
29
- "#{msg.message} (#{msg.class}) #{current_trace.for_log}\n" <<
30
- (msg.backtrace || []).join("\n")
29
+ msg = ("#{msg.message} (#{msg.class}) #{current_trace.for_log}\n" <<
30
+ (msg.backtrace || []).join("\n"))
31
31
  end
32
32
  end
33
+ msg
33
34
  end
34
35
 
35
36
  def insert_before_empty_lines(msg, for_log)
@@ -27,30 +27,30 @@ if AppOpticsAPM.loaded
27
27
  end
28
28
 
29
29
  def call(env)
30
- incoming = AppOpticsAPM::Context.isValid
31
30
 
32
31
  # In the case of nested Ruby apps such as Grape inside of Rails
33
32
  # or Grape inside of Grape, each app has it's own instance
34
33
  # of rack middleware. We want to avoid tracing rack more than once
35
34
  return @app.call(env) if AppOpticsAPM.tracing? && AppOpticsAPM.layer == :rack
36
35
 
36
+ incoming = AppOpticsAPM::Context.isValid
37
37
  AppOpticsAPM.transaction_name = nil
38
38
 
39
39
  url = env['PATH_INFO']
40
+ options = AppOpticsAPM::XTraceOptions.new(env['HTTP_X_TRACE_OPTIONS'], env['HTTP_X_TRACE_OPTIONS_SIGNATURE'])
40
41
  xtrace = AppOpticsAPM::XTrace.valid?(env['HTTP_X_TRACE']) ? (env['HTTP_X_TRACE']) : nil
41
-
42
- settings = AppOpticsAPM::TransactionSettings.new(url, xtrace)
43
-
44
- # AppOpticsAPM.logger.warn "%%% FILTER: #{settings} %%%"
42
+ settings = AppOpticsAPM::TransactionSettings.new(url, xtrace, options)
45
43
 
46
44
  response =
47
45
  propagate_xtrace(env, settings, xtrace) do
48
- sample(env, settings) do
46
+ sample(env, settings, options) do
49
47
  AppOpticsAPM::TransactionMetrics.metrics(env, settings) do
50
48
  @app.call(env)
51
49
  end
52
50
  end
53
51
  end || [500, {}, nil]
52
+ options.add_response_header(response[1], settings)
53
+
54
54
  AppOpticsAPM::Context.clear unless incoming
55
55
  response
56
56
  rescue
@@ -66,7 +66,7 @@ if AppOpticsAPM.loaded
66
66
 
67
67
  private
68
68
 
69
- def collect(env, settings)
69
+ def collect(env)
70
70
  req = ::Rack::Request.new(env)
71
71
  report_kvs = {}
72
72
 
@@ -84,8 +84,6 @@ if AppOpticsAPM.loaded
84
84
 
85
85
  report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
86
86
  report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:rack][:collect_backtraces]
87
- report_kvs[:SampleRate] = settings.rate
88
- report_kvs[:SampleSource] = settings.source
89
87
 
90
88
  # Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
91
89
  report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
@@ -126,11 +124,13 @@ if AppOpticsAPM.loaded
126
124
  [status, headers, response]
127
125
  end
128
126
 
129
- def sample(env, settings)
127
+ def sample(env, settings, options)
130
128
  xtrace = env['HTTP_X_TRACE']
131
129
  if settings.do_sample
132
130
  begin
133
- report_kvs = collect(env, settings)
131
+ report_kvs = collect(env)
132
+ settings.add_kvs(report_kvs)
133
+ options&.add_kvs(report_kvs, settings)
134
134
 
135
135
  AppOpticsAPM::API.log_start(:rack, xtrace, report_kvs, settings)
136
136
 
@@ -8,7 +8,7 @@ module AppOpticsAPM
8
8
  class OboeInitOptions
9
9
  include Singleton
10
10
 
11
- attr_reader :reporter, :host, :service_name # exposing these mainly for testing
11
+ attr_reader :reporter, :host, :service_name, :ec2_md_timeout # exposing these mainly for testing
12
12
 
13
13
  # TODO decide if these globals are useful when testing
14
14
  # OBOE_HOSTNAME_ALIAS = 0
@@ -69,6 +69,8 @@ module AppOpticsAPM
69
69
  @token_bucket_rate = (ENV['APPOPTICS_TOKEN_BUCKET_RATE'] || -1).to_i
70
70
  # use single files in file reporter for each event
71
71
  @file_single = (ENV['APPOPTICS_REPORTER_FILE_SINGLE'].to_s.downcase == 'true') ? 1 : 0
72
+ # timeout for ec2 metadata
73
+ @ec2_md_timeout = read_and_validate_ec2_md_timeout
72
74
  end
73
75
 
74
76
  def re_init # for testing with changed ENV vars
@@ -94,7 +96,8 @@ module AppOpticsAPM
94
96
  @histogram_precision,
95
97
  @token_bucket_capacity,
96
98
  @token_bucket_rate,
97
- @file_single
99
+ @file_single,
100
+ @ec2_md_timeout
98
101
  ]
99
102
  end
100
103
 
@@ -108,7 +111,8 @@ module AppOpticsAPM
108
111
 
109
112
  reporter = ENV['APPOPTICS_REPORTER'] || 'ssl'
110
113
  # override with 'file', e.g. when running tests
111
- reporter = 'file' if ENV.key?('APPOPTICS_GEM_TEST')
114
+ # changed my mind => set the right reporter in the env when running tests !!!
115
+ # reporter = 'file' if ENV.key?('APPOPTICS_GEM_TEST')
112
116
 
113
117
  host = ''
114
118
  case reporter
@@ -121,6 +125,7 @@ module AppOpticsAPM
121
125
  # ____ AppOpticsAPM::Config[:reporter_host] and
122
126
  # ____ AppOpticsAPM::Config[:reporter_port] were moved here from
123
127
  # ____ oboe_metal.rb and are not documented anywhere
128
+ # ____ udp is for internal use only
124
129
  when 'null'
125
130
  host = ''
126
131
  end
@@ -148,7 +153,7 @@ module AppOpticsAPM
148
153
  end
149
154
 
150
155
  def validate_token(token)
151
- if (token !~ /^[0-9a-fA-F]{64}|[0-9a-zA-Z_\-]{71}$/)
156
+ if (token !~ /^[0-9a-fA-F]{64}|[0-9a-zA-Z_-]{71}$/) && ENV['APPOPTICS_COLLECTOR'] != "sslcollector:12222"
152
157
  masked = "#{token[0..3]}...#{token[-4..-1]}"
153
158
  AppOpticsAPM.logger.error "[appoptics_apm/oboe_options] APPOPTICS_SERVICE_KEY problem. API Token in wrong format. Masked token: #{masked}"
154
159
  return false
@@ -158,6 +163,7 @@ module AppOpticsAPM
158
163
  end
159
164
 
160
165
  def validate_transform_service_name(service_name)
166
+ service_name = 'test_ssl_collector' if ENV['APPOPTICS_COLLECTOR'] == "sslcollector:12222"
161
167
  if service_name.empty?
162
168
  AppOpticsAPM.logger.error "[appoptics_apm/oboe_options] APPOPTICS_SERVICE_KEY problem. Service Name is missing"
163
169
  return false
@@ -175,6 +181,13 @@ module AppOpticsAPM
175
181
  @service_name = service_name # instance variable used in testing
176
182
  true
177
183
  end
184
+
185
+ def read_and_validate_ec2_md_timeout
186
+ timeout = (ENV['APPOPTICS_EC2_METADATA_TIMEOUT'] || AppOpticsAPM::Config[:ec2_metadata_timeout])
187
+ return 1000 unless timeout.is_a?(Integer) || timeout =~ /^\d+$/
188
+ timeout = timeout.to_i
189
+ return timeout.between?(0, 3000) ? timeout : 1000
190
+ end
178
191
  end
179
192
  end
180
193
 
@@ -32,6 +32,7 @@ module AppOpticsAPM
32
32
  # * +AppOpticsAPM::SDK.start_trace+
33
33
  # * +AppOpticsAPM::SDK.start_trace_with_target+
34
34
  # * +AppOpticsAPM::SDK.trace+
35
+ # * +AppOpticsAPM::SDK.trace_method+
35
36
  # * +AppOpticsAPM::SDK.tracing?+
36
37
  #
37
38
  # === Example:
@@ -207,6 +208,123 @@ module AppOpticsAPM
207
208
  result
208
209
  end
209
210
 
211
+ ##
212
+ # Add tracing to a given method
213
+ #
214
+ # This instruments the given method so that every time it is called it
215
+ # will create a span depending on the current context.
216
+ #
217
+ # The method can be of any (accessible) type (instance, singleton,
218
+ # private, protected etc.).
219
+ #
220
+ # The motivating use case for this is MetalController methods in Rails,
221
+ # which can't be auto-instrumented.
222
+ #
223
+ # === Arguments:
224
+ # * +klass+ - The module/class the method belongs to.
225
+ # * +method+ - The method name as symbol
226
+ # * +config+ - (optional) possible keys are:
227
+ # :name the name of the span (default: the method name)
228
+ # :backtrace true/false (default: false) if true the backtrace will be added to the space
229
+ # * +opts+ - (optional) hash containing key/value pairs that will be reported with this span.
230
+ #
231
+ # === Example:
232
+ #
233
+ # module ExampleModule
234
+ # def do_sum(a, b)
235
+ # a + b
236
+ # end
237
+ # end
238
+ #
239
+ # AppOpticsAPM::SDK.trace_method(ExampleModule, :do_sum, {name: 'computation', backtrace: true}, { CustomKey: "some_info"})
240
+ #
241
+ def trace_method(klass, method, config = {}, opts = {})
242
+ # If we're on an unsupported platform (ahem Mac), just act
243
+ # like we did something to nicely play the no-op part.
244
+ return true unless AppOpticsAPM.loaded
245
+
246
+ if !klass.is_a?(Module)
247
+ AppOpticsAPM.logger.warn "[appoptics_apm/error] trace_method: Not sure what to do with #{klass}. Send a class or module."
248
+ return false
249
+ end
250
+
251
+ if method.is_a?(String)
252
+ method = method.to_sym
253
+ elsif !method.is_a?(Symbol)
254
+ AppOpticsAPM.logger.warn "[appoptics_apm/error] trace_method: Not sure what to do with #{method}. Send a string or symbol for method."
255
+ return false
256
+ end
257
+
258
+ instance_method = klass.instance_methods.include?(method) || klass.private_instance_methods.include?(method)
259
+ class_method = klass.singleton_methods.include?(method)
260
+
261
+ # Make sure the request klass::method exists
262
+ if !instance_method && !class_method
263
+ AppOpticsAPM.logger.warn "[appoptics_apm/error] trace_method: Can't instrument #{klass}.#{method} as it doesn't seem to exist."
264
+ AppOpticsAPM.logger.warn "[appoptics_apm/error] #{__FILE__}:#{__LINE__}"
265
+ return false
266
+ end
267
+
268
+ # Strip '!' or '?' from method if present
269
+ safe_method_name = method.to_s.chop if method.to_s =~ /\?$|\!$/
270
+ safe_method_name ||= method
271
+
272
+ without_appoptics = "#{safe_method_name}_without_appoptics"
273
+ with_appoptics = "#{safe_method_name}_with_appoptics"
274
+
275
+ # Check if already profiled
276
+ if instance_method && klass.instance_methods.include?(with_appoptics.to_sym) ||
277
+ class_method && klass.singleton_methods.include?(with_appoptics.to_sym)
278
+ AppOpticsAPM.logger.warn "[appoptics_apm/error] trace_method: #{klass}::#{method} already instrumented.\n#{__FILE__}:#{__LINE__}"
279
+ return false
280
+ end
281
+
282
+ report_kvs = opts.dup
283
+ if defined?(::AbstractController::Base) && klass.ancestors.include?(::AbstractController::Base)
284
+ report_kvs[:Controller] = klass.to_s
285
+ report_kvs[:Action] = method.to_s
286
+ else
287
+ klass.is_a?(Class) ? report_kvs[:Class] = klass.to_s : report_kvs[:Module] = klass.to_s
288
+ report_kvs[:MethodName] = safe_method_name
289
+ end
290
+ backtrace = config[:backtrace]
291
+
292
+ span = config[:name] || method
293
+ if instance_method
294
+ klass.class_eval do
295
+ define_method(with_appoptics) do |*args, &block|
296
+ # if this is a rails controller we want to set the transaction for the outbound metrics
297
+ if report_kvs[:Controller] && defined?(request) && defined?(request.env)
298
+ request.env['appoptics_apm.controller'] = report_kvs[:Controller]
299
+ request.env['appoptics_apm.action'] = report_kvs[:Action]
300
+ end
301
+
302
+ AppOpticsAPM::SDK.trace(span, report_kvs) do
303
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace(2) if backtrace
304
+ send(without_appoptics, *args, &block)
305
+ end
306
+ end
307
+
308
+ alias_method without_appoptics, method.to_s
309
+ alias_method method.to_s, with_appoptics
310
+ end
311
+ elsif class_method
312
+ klass.define_singleton_method(with_appoptics) do |*args, &block|
313
+ AppOpticsAPM::SDK.trace(span, report_kvs) do
314
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace(2) if backtrace
315
+ send(without_appoptics, *args, &block)
316
+ end
317
+ end
318
+
319
+ klass.singleton_class.class_eval do
320
+ alias_method without_appoptics, method.to_s
321
+ alias_method method.to_s, with_appoptics
322
+ end
323
+ end
324
+ true
325
+ end
326
+
327
+ ##
210
328
  # Provide a custom transaction name
211
329
  #
212
330
  # The AppOpticsAPM gem tries to create meaningful transaction names from controller+action
@@ -15,6 +15,7 @@ module AppOpticsAPM
15
15
  def metrics(env, settings)
16
16
  if settings.do_metrics
17
17
  req = ::Rack::Request.new(env)
18
+ # TODO rails 3x is not supported anymore ...
18
19
  url = req.url # saving it here because rails3.2 overrides it when there is a 500 error
19
20
  start = Time.now
20
21
 
@@ -6,14 +6,9 @@ AO_TRACING_ENABLED = 1
6
6
  AO_TRACING_DISABLED = 0
7
7
  AO_TRACING_UNSET = -1
8
8
 
9
- AO_TRACING_DECISIONS_TRACING_DISABLED = -2
10
- AO_TRACING_DECISIONS_XTRACE_NOT_SAMPLED = -1
11
9
  AO_TRACING_DECISIONS_OK = 0
12
- AO_TRACING_DECISIONS_NULL_OUT = 1
13
- AO_TRACING_DECISIONS_NO_CONFIG = 2
14
- AO_TRACING_DECISIONS_REPORTER_NOT_READY = 3
15
- AO_TRACING_DECISIONS_NO_VALID_SETTINGS = 4
16
- AO_TRACING_DECISIONS_QUEUE_FULL = 5
10
+
11
+ OBOE_SETTINGS_UNSET = -1
17
12
 
18
13
  module AppOpticsAPM
19
14
  ##
@@ -21,13 +16,15 @@ module AppOpticsAPM
21
16
  #
22
17
  class TransactionSettings
23
18
 
24
- attr_accessor :do_metrics, :do_sample
25
- attr_reader :do_propagate, :rate, :source
19
+ attr_accessor :do_sample, :do_metrics
20
+ attr_reader :auth_msg, :do_propagate, :status_msg, :type, :source, :rate, :xtrace
21
+ #, :status
26
22
 
27
- def initialize(url = nil, xtrace = '')
23
+ def initialize(url = '', xtrace = '', options = nil)
28
24
  @do_metrics = false
29
25
  @do_sample = false
30
26
  @do_propagate = true
27
+ @xtrace = xtrace || ''
31
28
  tracing_mode = AO_TRACING_ENABLED
32
29
 
33
30
  if AppOpticsAPM::Context.isValid
@@ -46,14 +43,23 @@ module AppOpticsAPM
46
43
  tracing_mode = AO_TRACING_DISABLED
47
44
  end
48
45
 
49
- args = [xtrace || '']
46
+ args = [@xtrace]
50
47
  args << tracing_mode
51
- args << AppOpticsAPM::Config[:sample_rate] if AppOpticsAPM::Config[:sample_rate]&. >= 0
48
+ args << (AppOpticsAPM::Config[:sample_rate] || OBOE_SETTINGS_UNSET)
49
+
50
+ if options && (options.options || options.signature)
51
+ args << (options.trigger_trace ? 1 : 0)
52
+ args << (trigger_tracing_mode_disabled? ? 0 : 1)
53
+ args << options.options
54
+ args << options.signature
55
+ args << options.timestamp
56
+ end
52
57
 
53
- metrics, sample, @rate, @source, return_code = AppOpticsAPM::Context.getDecisions(*args)
58
+ metrics, sample, @rate, @source, @type, @auth, @status_msg, @auth_msg, @status =
59
+ AppOpticsAPM::Context.getDecisions(*args)
54
60
 
55
- if return_code > AO_TRACING_DECISIONS_OK
56
- AppOpticsAPM.logger.warn "[appoptics-apm/sample] Problem getting the sampling decisions, code: #{return_code}"
61
+ if @status > AO_TRACING_DECISIONS_OK
62
+ AppOpticsAPM.logger.warn "[appoptics-apm/sample] Problem getting the sampling decisions: #{@status_msg} code: #{@status}"
57
63
  end
58
64
 
59
65
  @do_metrics = metrics > 0
@@ -64,6 +70,20 @@ module AppOpticsAPM
64
70
  "do_propagate: #{do_propagate}, do_sample: #{do_sample}, do_metrics: #{do_metrics} rate: #{rate}, source: #{source}"
65
71
  end
66
72
 
73
+ def add_kvs(kvs)
74
+ kvs[:SampleRate] = @rate
75
+ kvs[:SampleSource] = @source
76
+ end
77
+
78
+ def triggered_trace?
79
+ @type == 1
80
+ end
81
+
82
+ def auth_ok?
83
+ # @auth is undefined if initialize is called with an existing context
84
+ !@auth || @auth < 1
85
+ end
86
+
67
87
  private
68
88
 
69
89
  ##
@@ -103,6 +123,11 @@ module AppOpticsAPM
103
123
  false
104
124
  end
105
125
 
126
+ def trigger_tracing_mode_disabled?
127
+ AppOpticsAPM::Config[:trigger_tracing_mode] &&
128
+ AppOpticsAPM::Config[:trigger_tracing_mode] == :disabled
129
+ end
130
+
106
131
  ##
107
132
  # asset?
108
133
  #
@@ -0,0 +1,110 @@
1
+ # Copyright (c) 2019 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+
5
+ module AppOpticsAPM
6
+ class XTraceOptions
7
+
8
+ attr_reader :options, :signature, :trigger_trace, :timestamp
9
+ attr_reader :pd_keys, :custom_kvs, :ignored # used in tests
10
+ ##
11
+ # Params:
12
+ # +options+ : An X-Trace-Options @options string
13
+ # +signature+ : hmac signature to pass on for verification
14
+ #
15
+ # populates:
16
+ # - @force_trace (true|false)
17
+ # - @app_id (as given by Pingdom)
18
+ # - @probe_id (as given by Pingdom)
19
+ # - @loc (2 characters given by Pingdom)
20
+ # - @custom_kvs (hash)
21
+ # - @ignored (array)
22
+ #
23
+ # split it up by ';' separator
24
+ # kv assignment by '='
25
+ # currently valid keys:
26
+ # - force_trace (valid: 0,1) unless we use just a kv
27
+ # - application_id (format defined by pingdom (no validation))
28
+ # - probe_id
29
+ # - custom_* (';=' not allowed in key), value (validate max. length)
30
+ # - ts (unix timestamp)
31
+ # - other keys will be reported in the response options as ignored
32
+
33
+ def initialize(options, signature = nil)
34
+ @options = options.dup
35
+ @signature = signature.dup
36
+ @trigger_trace = false
37
+ @custom_kvs = {}
38
+ @pd_keys = nil
39
+ @ignored = []
40
+ @timestamp = 0
41
+
42
+ options&.split(/;+/)&.each do |val|
43
+ k = val.split('=', 2)
44
+
45
+ next unless k[0] # it can be nil, eg when the header starts with ';'
46
+
47
+ k[0]&.strip!
48
+ case k[0]
49
+ when 'trigger-trace'
50
+ if k[1]
51
+ @ignored << 'trigger-trace'
52
+ else
53
+ @trigger_trace = true
54
+ end
55
+ when 'pd-keys'
56
+ if @pd_keys
57
+ AppOpticsAPM.logger.info "[appoptics_apm/x-trace-options] Duplicate key: #{k[0]}"
58
+ else
59
+ @pd_keys = k[1].strip
60
+ end
61
+ when /^custom-[^\s]*$/
62
+ if @custom_kvs[k[0]]
63
+ AppOpticsAPM.logger.info "[appoptics_apm/x-trace-options] Duplicate key: #{k[0]}"
64
+ else
65
+ @custom_kvs[k[0]] = k[1].strip
66
+ end
67
+ when 'ts'
68
+ if @timestamp > 0
69
+ AppOpticsAPM.logger.info "[appoptics_apm/x-trace-options] Duplicate key: #{k[0]}"
70
+ else
71
+ @timestamp = k[1].to_i
72
+ end
73
+ else
74
+ @ignored << k[0]
75
+ end
76
+ end
77
+ unless @ignored.empty?
78
+ msg = "[appoptics_apm/x-trace-options] Some keys were ignored: #{@ignored.join(',' )}"
79
+ AppOpticsAPM.logger.info(msg)
80
+ end
81
+ end
82
+
83
+ def add_kvs(kvs, settings)
84
+ return unless settings.auth_ok?
85
+
86
+ @custom_kvs.each { |k,v| kvs[k] = v } unless @custom_kvs.empty?
87
+ kvs['PDKeys'] = @pd_keys if @pd_keys
88
+ kvs['TriggeredTrace'] = true if settings.triggered_trace?
89
+ end
90
+
91
+ def add_response_header(headers, settings)
92
+ return unless options
93
+
94
+ response = []
95
+ response << "auth=#{settings.auth_msg}" if @signature
96
+ if settings.auth_ok?
97
+ if @trigger_trace
98
+ trigger_msg = !settings.xtrace.empty? && settings.type == 0 ? 'ignored' : settings.status_msg
99
+ else
100
+ trigger_msg = 'not-requested'
101
+ end
102
+ response << "trigger-trace=#{trigger_msg}"
103
+ response << "ignored=#{@ignored.join(',')}" unless @ignored.empty?
104
+ end
105
+
106
+ headers['X-Trace-Options-Response'] = response.join(';')
107
+ end
108
+
109
+ end
110
+ end