appoptics_apm 4.8.0 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/appoptics_apm.rb CHANGED
@@ -49,6 +49,7 @@ begin
49
49
  if AppOpticsAPM.loaded
50
50
  require 'appoptics_apm/instrumentation'
51
51
  require 'appoptics_apm/support/transaction_metrics'
52
+ require 'appoptics_apm/support/x_trace_options'
52
53
 
53
54
  # Frameworks
54
55
  require 'appoptics_apm/frameworks/rails'
@@ -16,13 +16,16 @@ module AppOpticsAPM
16
16
  @@instrumentation = [:action_controller, :action_controller_api, :action_view,
17
17
  :active_record, :bunnyclient, :bunnyconsumer, :cassandra, :curb,
18
18
  :dalli, :delayed_jobclient, :delayed_jobworker,
19
- :em_http_request, :excon, :faraday, :grpc_client, :grpc_server, :grape,
19
+ # :em_http_request,
20
+ :excon, :faraday, :grpc_client, :grpc_server, :grape,
20
21
  :httpclient, :nethttp, :memcached, :mongo, :moped, :padrino, :rack, :redis,
21
22
  :resqueclient, :resqueworker, :rest_client,
22
23
  :sequel, :sidekiqclient, :sidekiqworker, :sinatra, :typhoeus]
23
24
 
24
25
  # Subgrouping of instrumentation
25
- @@http_clients = [:curb, :excon, :em_http_request, :faraday, :httpclient, :nethttp, :rest_client, :typhoeus]
26
+ @@http_clients = [:curb, :excon,
27
+ # :em_http_request,
28
+ :faraday, :httpclient, :nethttp, :rest_client, :typhoeus]
26
29
 
27
30
  ##
28
31
  # load_config_file
@@ -240,11 +243,15 @@ module AppOpticsAPM
240
243
  end
241
244
 
242
245
  elsif key == :tracing_mode
243
- # CAN'T DO THIS ANYMORE, ALL TRACING COMMUNICATION TO OBOE
246
+ # CAN'T DO `set_tracing_mode` ANYMORE, ALL TRACING COMMUNICATION TO OBOE
244
247
  # IS NOW HANDLED BY TransactionSettings
245
248
  # AppOpticsAPM.set_tracing_mode(value.to_sym) if AppOpticsAPM.loaded
246
249
 
247
- # Make sure that the mode is stored as a symbol
250
+ # Make sure that the mode is stored as a symbol
251
+ @@config[key.to_sym] = value.to_sym
252
+
253
+ elsif key == :trigger_tracing_mode
254
+ # Make sure that the mode is stored as a symbol
248
255
  @@config[key.to_sym] = value.to_sym
249
256
  end
250
257
  end
@@ -84,18 +84,18 @@ module AppOpticsAPM
84
84
  end
85
85
 
86
86
  # ActionController::Base
87
- if defined?(ActionController::Base) && AppOpticsAPM::Config[:action_controller][:enabled]
87
+ if defined?(ActionController::Base) && AppOpticsAPM::Config[:action_controller][:enabled] && Rails::VERSION::MAJOR <= 6
88
88
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting actioncontroller' if AppOpticsAPM::Config[:verbose]
89
89
  require "appoptics_apm/frameworks/rails/inst/action_controller#{Rails::VERSION::MAJOR}"
90
90
  if Rails::VERSION::MAJOR >= 5
91
91
  ActionController::Base.send(:prepend, ::AppOpticsAPM::Inst::ActionController)
92
- else
92
+ elsif Rails::VERSION::MAJOR < 5
93
93
  AppOpticsAPM::Util.send_include(::ActionController::Base, AppOpticsAPM::Inst::ActionController)
94
94
  end
95
95
  end
96
96
 
97
- # ActionController::API - Rails 5+ or via the rails-api gem
98
- if defined?(ActionController::API) && AppOpticsAPM::Config[:action_controller_api][:enabled]
97
+ # ActionController::API - Rails 5 or via the rails-api gem
98
+ if defined?(ActionController::API) && AppOpticsAPM::Config[:action_controller_api][:enabled] && Rails::VERSION::MAJOR <= 6
99
99
  AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting actioncontroller api' if AppOpticsAPM::Config[:verbose]
100
100
  require "appoptics_apm/frameworks/rails/inst/action_controller_api"
101
101
  ActionController::API.send(:prepend, ::AppOpticsAPM::Inst::ActionControllerAPI)
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module Inst
6
+ #
7
+ # ActionController
8
+ #
9
+ # This modules contains the instrumentation code specific
10
+ # to Rails v6
11
+ #
12
+ module ActionController
13
+ include AppOpticsAPM::Inst::RailsBase
14
+
15
+ def process_action(method_name, *args)
16
+ kvs = {
17
+ :Controller => self.class.name,
18
+ :Action => self.action_name,
19
+ }
20
+ request.env['appoptics_apm.controller'] = kvs[:Controller]
21
+ request.env['appoptics_apm.action'] = kvs[:Action]
22
+
23
+ return super(method_name, *args) unless AppOpticsAPM.tracing?
24
+ begin
25
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:action_controller][:collect_backtraces]
26
+
27
+ AppOpticsAPM::API.log_entry('rails', kvs)
28
+ super(method_name, *args)
29
+
30
+ rescue Exception => e
31
+ AppOpticsAPM::API.log_exception('rails', e) if log_rails_error?(e)
32
+ raise
33
+ ensure
34
+ AppOpticsAPM::API.log_exit('rails')
35
+ end
36
+ end
37
+
38
+ #
39
+ # render
40
+ #
41
+ # Our render wrapper that calls 'trace', which will log if we are tracing
42
+ #
43
+ def render(*args, &blk)
44
+ trace('actionview') do
45
+ super(*args, &blk)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2016 SolarWinds, LLC.
2
2
  # All rights reserved.
3
3
 
4
- if defined?(ActionView::Base) && AppOpticsAPM::Config[:action_view][:enabled]
4
+ if defined?(ActionView::Base) && AppOpticsAPM::Config[:action_view][:enabled] && Rails::VERSION::MAJOR < 6
5
5
 
6
6
  ##
7
7
  # ActionView Instrumentation is version dependent. ActionView 2.x is separate
@@ -5,13 +5,13 @@ require 'appoptics_apm/frameworks/rails/inst/connection_adapters/mysql'
5
5
  require 'appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2'
6
6
  require 'appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql'
7
7
 
8
- if AppOpticsAPM::Config[:active_record][:enabled] && !defined?(JRUBY_VERSION)
8
+ if AppOpticsAPM::Config[:active_record][:enabled] && !defined?(JRUBY_VERSION) && Rails::VERSION::MAJOR <= 6
9
9
  begin
10
10
  adapter = ActiveRecord::Base.connection_config[:adapter]
11
11
 
12
12
  if Rails::VERSION::MAJOR < 5
13
13
  require 'appoptics_apm/frameworks/rails/inst/connection_adapters/utils'
14
- else
14
+ elsif Rails::VERSION::MAJOR >= 5
15
15
  require 'appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x'
16
16
  end
17
17
 
@@ -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
 
@@ -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,15 +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
- puts "return_code class: #{return_code.class}" unless return_code.is_a? Integer
56
- if return_code > AO_TRACING_DECISIONS_OK
57
- 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}"
58
63
  end
59
64
 
60
65
  @do_metrics = metrics > 0
@@ -65,6 +70,20 @@ module AppOpticsAPM
65
70
  "do_propagate: #{do_propagate}, do_sample: #{do_sample}, do_metrics: #{do_metrics} rate: #{rate}, source: #{source}"
66
71
  end
67
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
+
68
87
  private
69
88
 
70
89
  ##
@@ -104,6 +123,11 @@ module AppOpticsAPM
104
123
  false
105
124
  end
106
125
 
126
+ def trigger_tracing_mode_disabled?
127
+ AppOpticsAPM::Config[:trigger_tracing_mode] &&
128
+ AppOpticsAPM::Config[:trigger_tracing_mode] == :disabled
129
+ end
130
+
107
131
  ##
108
132
  # asset?
109
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