appoptics_apm 4.1.2 → 4.2.0
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/.travis.yml +8 -6
- data/Dockerfile_test +4 -2
- data/Rakefile +15 -4
- data/appoptics_apm.gemspec +3 -1
- data/examples/DNT.md +35 -0
- data/examples/carrying_context.rb +220 -0
- data/examples/instrumenting_metal_controller.rb +8 -0
- data/examples/puma_on_heroku_config.rb +17 -0
- data/examples/tracing_async_threads.rb +124 -0
- data/examples/tracing_background_jobs.rb +53 -0
- data/examples/tracing_forked_processes.rb +99 -0
- data/examples/unicorn_on_heroku_config.rb +28 -0
- data/ext/oboe_metal/src/VERSION +1 -1
- data/lib/appoptics_apm.rb +2 -0
- data/lib/appoptics_apm/api/logging.rb +13 -12
- data/lib/appoptics_apm/api/tracing.rb +28 -1
- data/lib/appoptics_apm/base.rb +7 -1
- data/lib/appoptics_apm/config.rb +15 -13
- data/lib/appoptics_apm/frameworks/grape.rb +7 -6
- data/lib/appoptics_apm/inst/rack.rb +102 -69
- data/lib/appoptics_apm/noop/README.md +9 -0
- data/lib/appoptics_apm/noop/context.rb +19 -0
- data/lib/appoptics_apm/support.rb +17 -18
- data/lib/appoptics_apm/version.rb +2 -2
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +8 -0
- data/run_tests_docker.rb +0 -6
- metadata +12 -2
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler.require
|
5
|
+
|
6
|
+
# Make sure oboe is at the bottom of your Gemfile.
|
7
|
+
# This is likely redundant but just in case.
|
8
|
+
require 'oboe'
|
9
|
+
|
10
|
+
# Tracing mode can be 'never' or 'always'
|
11
|
+
AppOpticsAPM::Config[:tracing_mode] = 'always'
|
12
|
+
|
13
|
+
#
|
14
|
+
# Update April 9, 2015 - this is done automagically now
|
15
|
+
# and doesn't have to be called manually
|
16
|
+
#
|
17
|
+
# Load library instrumentation to auto-capture stuff we know about...
|
18
|
+
# e.g. ActiveRecord, Cassandra, Dalli, Redis, Memcache, Mongo
|
19
|
+
# AppOpticsAPM::Ruby.load
|
20
|
+
|
21
|
+
# Some KVs to report to the dashboard
|
22
|
+
report_kvs = {}
|
23
|
+
report_kvs[:command_line_params] = ARGV.to_s
|
24
|
+
report_kvs[:user_id] = `whoami`
|
25
|
+
|
26
|
+
AppOpticsAPM::API.start_trace('my_background_job', nil, report_kvs) do
|
27
|
+
#
|
28
|
+
# Initialization code
|
29
|
+
#
|
30
|
+
|
31
|
+
tasks = get_all_tasks
|
32
|
+
|
33
|
+
tasks.each do |t|
|
34
|
+
# Optional: Here we embed another 'trace' to separate actual
|
35
|
+
# work for each task. In the APPOPTICS dashboard, this will show
|
36
|
+
# up as a large 'my_background_job' parent layer with many
|
37
|
+
# child 'task" layers.
|
38
|
+
AppOpticsAPM::API.trace('task', :task_id => t.id) do
|
39
|
+
t.perform
|
40
|
+
end
|
41
|
+
end
|
42
|
+
#
|
43
|
+
# cleanup code
|
44
|
+
#
|
45
|
+
end
|
46
|
+
|
47
|
+
# Note that we use 'start_trace' in the outer block and 'trace' for
|
48
|
+
# any sub-blocks of code we wish to instrument. The arguments for
|
49
|
+
# both methods vary slightly.
|
50
|
+
#
|
51
|
+
# TODO update location of the following doc
|
52
|
+
# Details in RubyDoc:
|
53
|
+
# https://www.omniref.com/ruby/gems/oboe/2.7.10.1/symbols/AppOpticsAPM::API::Tracing#tab=Methods
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#
|
2
|
+
# This sample demonstrates how to instrument a main loop that
|
3
|
+
# retrieves work and calls fork to do the actual work
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'math'
|
7
|
+
require 'oboe'
|
8
|
+
|
9
|
+
AppOpticsAPM::Config[:tracing_mode] = :always
|
10
|
+
AppOpticsAPM::Config[:verbose] = true
|
11
|
+
|
12
|
+
# The parent process/loop which collects data
|
13
|
+
Kernel.loop do
|
14
|
+
# For each loop, we instrument the work retrieval. These traces
|
15
|
+
# will show up as layer 'get_the_work'.
|
16
|
+
AppOpticsAPM::API.start_trace('get_the_work') do
|
17
|
+
work = get_the_work
|
18
|
+
|
19
|
+
# Loop through work and pass to `do_the_work` method
|
20
|
+
# that spawns a thread each time
|
21
|
+
work.each do |job|
|
22
|
+
fork do
|
23
|
+
# Since the context is copied from the parent process, we clear it
|
24
|
+
# and start a new trace via `AppOpticsAPM::API.start_trace`.
|
25
|
+
AppOpticsAPM::Context.clear
|
26
|
+
result = nil
|
27
|
+
|
28
|
+
AppOpticsAPM::API.start_trace('do_the_work', nil, :job_id => job.id) do
|
29
|
+
result = do_the_work(job)
|
30
|
+
end
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# get_the_work
|
40
|
+
#
|
41
|
+
# Method to retrieve work to do
|
42
|
+
#
|
43
|
+
def get_the_work
|
44
|
+
# We'll just return random integers as a
|
45
|
+
# fake work load
|
46
|
+
w = []
|
47
|
+
w << rand(25)
|
48
|
+
w << rand(25)
|
49
|
+
w << rand(25)
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# do_the_work
|
54
|
+
#
|
55
|
+
# The work-horse method
|
56
|
+
#
|
57
|
+
def do_the_work(job_to_do)
|
58
|
+
i = job_to_do
|
59
|
+
i * Math::PI
|
60
|
+
end
|
61
|
+
|
62
|
+
#########################################################################
|
63
|
+
# Notes
|
64
|
+
#########################################################################
|
65
|
+
|
66
|
+
# If your parent process only forks a small number of processes per loop (< 5..10),
|
67
|
+
# you may want to mark the child traces as asynchronous and have them directly
|
68
|
+
# linked to the parent tracing context.
|
69
|
+
#
|
70
|
+
# The benefit of this is that instead of having two independent traces (parent
|
71
|
+
# and child), you will have a single view of the parent trace showing the
|
72
|
+
# spawned child process and it's performance in the AppOptics dashboard.
|
73
|
+
#
|
74
|
+
# To do this:
|
75
|
+
# 1. Don't clear the context in the child process
|
76
|
+
# 2. Use `AppOpticsAPM::API.trace` instead
|
77
|
+
# 3. Pass the `Async` flag to mark this child as asynchronous
|
78
|
+
#
|
79
|
+
Kernel.loop do
|
80
|
+
AppOpticsAPM::API.start_trace('get_the_work') do
|
81
|
+
|
82
|
+
work = get_the_work
|
83
|
+
|
84
|
+
work.each do |job|
|
85
|
+
fork do
|
86
|
+
result = nil
|
87
|
+
# 1 Don't clear context
|
88
|
+
# 2 Use `AppOpticsAPM::API.trace` instead
|
89
|
+
# 3 Pass the Async flag
|
90
|
+
AppOpticsAPM::API.trace('do_the_work', {:job_id => job.id, :Async => 1 }) do
|
91
|
+
result = do_the_work(job)
|
92
|
+
end
|
93
|
+
|
94
|
+
result
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
sleep 5
|
99
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
|
2
|
+
timeout 15
|
3
|
+
preload_app true
|
4
|
+
|
5
|
+
before_fork do |server, worker|
|
6
|
+
Signal.trap 'TERM' do
|
7
|
+
puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
|
8
|
+
Process.kill 'QUIT', Process.pid
|
9
|
+
end
|
10
|
+
|
11
|
+
defined?(ActiveRecord::Base) and
|
12
|
+
ActiveRecord::Base.connection.disconnect!
|
13
|
+
|
14
|
+
defined?(::AppOpticsAPM) and
|
15
|
+
::AppOpticsAPM.disconnect!
|
16
|
+
end
|
17
|
+
|
18
|
+
after_fork do |server, worker|
|
19
|
+
Signal.trap 'TERM' do
|
20
|
+
puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
|
21
|
+
end
|
22
|
+
|
23
|
+
defined?(ActiveRecord::Base) and
|
24
|
+
ActiveRecord::Base.establish_connection
|
25
|
+
|
26
|
+
defined?(::AppOpticsAPM) and
|
27
|
+
::AppOpticsAPM.reconnect!
|
28
|
+
end
|
data/ext/oboe_metal/src/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.0
|
data/lib/appoptics_apm.rb
CHANGED
@@ -60,6 +60,8 @@ begin
|
|
60
60
|
require 'appoptics_apm/frameworks/sinatra'
|
61
61
|
require 'appoptics_apm/frameworks/padrino'
|
62
62
|
require 'appoptics_apm/frameworks/grape'
|
63
|
+
else
|
64
|
+
require 'appoptics_apm/noop/context'
|
63
65
|
end
|
64
66
|
|
65
67
|
# Load Ruby module last. If there is no framework detected,
|
@@ -40,7 +40,7 @@ module AppOpticsAPM
|
|
40
40
|
#
|
41
41
|
# Returns nothing.
|
42
42
|
def log(layer, label, opts = {}, event=nil)
|
43
|
-
return
|
43
|
+
return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
|
44
44
|
|
45
45
|
event ||= AppOpticsAPM::Context.createEvent
|
46
46
|
log_event(layer, label, event, opts)
|
@@ -66,11 +66,11 @@ module AppOpticsAPM
|
|
66
66
|
#
|
67
67
|
# Returns nothing.
|
68
68
|
def log_exception(layer, exn, opts = {})
|
69
|
-
return if !AppOpticsAPM.tracing? || exn.instance_variable_get(:@oboe_logged)
|
69
|
+
return AppOpticsAPM::Context.toString if !AppOpticsAPM.tracing? || exn.instance_variable_get(:@oboe_logged)
|
70
70
|
|
71
71
|
unless exn
|
72
72
|
AppOpticsAPM.logger.debug '[appoptics_apm/debug] log_exception called with nil exception'
|
73
|
-
return
|
73
|
+
return AppOpticsAPM::Context.toString
|
74
74
|
end
|
75
75
|
|
76
76
|
opts.merge!(:ErrorClass => exn.class.name,
|
@@ -180,12 +180,11 @@ module AppOpticsAPM
|
|
180
180
|
#
|
181
181
|
# Returns an xtrace metadata string if we are tracing
|
182
182
|
def log_end(layer, opts = {})
|
183
|
-
return unless AppOpticsAPM.tracing?
|
183
|
+
return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
|
184
184
|
|
185
|
-
log_event(layer, :exit, AppOpticsAPM::Context.createEvent, opts)
|
186
|
-
AppOpticsAPM::Context.toString
|
185
|
+
log_event(layer, :exit, AppOpticsAPM::Context.createEvent, opts)
|
187
186
|
ensure
|
188
|
-
# FIXME has_incoming_context commented out, it has importance for JRuby only
|
187
|
+
# FIXME has_incoming_context commented out, it has importance for JRuby only but breaks Ruby tests
|
189
188
|
AppOpticsAPM::Context.clear # unless AppOpticsAPM.has_incoming_context?
|
190
189
|
end
|
191
190
|
|
@@ -206,7 +205,7 @@ module AppOpticsAPM
|
|
206
205
|
#
|
207
206
|
# Returns an xtrace metadata string if we are tracing
|
208
207
|
def log_entry(layer, opts = {}, op = nil)
|
209
|
-
return unless AppOpticsAPM.tracing?
|
208
|
+
return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
|
210
209
|
|
211
210
|
AppOpticsAPM.layer_op = op.to_sym if op
|
212
211
|
log_event(layer, :entry, AppOpticsAPM::Context.createEvent, opts)
|
@@ -228,7 +227,7 @@ module AppOpticsAPM
|
|
228
227
|
#
|
229
228
|
# Returns an xtrace metadata string if we are tracing
|
230
229
|
def log_info(layer, opts = {})
|
231
|
-
return unless AppOpticsAPM.tracing?
|
230
|
+
return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
|
232
231
|
|
233
232
|
log_event(layer, :info, AppOpticsAPM::Context.createEvent, opts)
|
234
233
|
end
|
@@ -251,7 +250,7 @@ module AppOpticsAPM
|
|
251
250
|
#
|
252
251
|
# Returns an xtrace metadata string if we are tracing
|
253
252
|
def log_exit(layer, opts = {}, op = nil)
|
254
|
-
return unless AppOpticsAPM.tracing?
|
253
|
+
return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
|
255
254
|
|
256
255
|
AppOpticsAPM.layer_op = nil if op
|
257
256
|
log_event(layer, :exit, AppOpticsAPM::Context.createEvent, opts)
|
@@ -270,7 +269,7 @@ module AppOpticsAPM
|
|
270
269
|
# * +traces+ - An array with X-Trace strings returned from the requests
|
271
270
|
#
|
272
271
|
def log_multi_exit(layer, traces)
|
273
|
-
return unless AppOpticsAPM.tracing?
|
272
|
+
return AppOpticsAPM::Context.toString unless AppOpticsAPM.tracing?
|
274
273
|
task_id = AppOpticsAPM::XTrace.task_id(AppOpticsAPM::Context.toString)
|
275
274
|
event = AppOpticsAPM::Context.createEvent
|
276
275
|
traces.each do |trace|
|
@@ -289,7 +288,7 @@ module AppOpticsAPM
|
|
289
288
|
# * +opts+ - A hash containing key/value pairs that will be reported along with this event
|
290
289
|
def log_init(layer = :rack, opts = {})
|
291
290
|
context = AppOpticsAPM::Metadata.makeRandom
|
292
|
-
return unless context.isValid
|
291
|
+
return AppOpticsAPM::Context.toString unless context.isValid
|
293
292
|
|
294
293
|
event = context.createEvent
|
295
294
|
event.addInfo(APPOPTICS_STR_LAYER, layer.to_s)
|
@@ -299,6 +298,7 @@ module AppOpticsAPM
|
|
299
298
|
end
|
300
299
|
|
301
300
|
AppOpticsAPM::Reporter.sendStatus(event, context)
|
301
|
+
AppOpticsAPM::Context.toString
|
302
302
|
end
|
303
303
|
|
304
304
|
private
|
@@ -353,6 +353,7 @@ module AppOpticsAPM
|
|
353
353
|
end if !opts.nil? && opts.any?
|
354
354
|
|
355
355
|
AppOpticsAPM::Reporter.sendReport(event)
|
356
|
+
AppOpticsAPM::Context.toString
|
356
357
|
end
|
357
358
|
|
358
359
|
end
|
@@ -51,7 +51,8 @@ module AppOpticsAPM
|
|
51
51
|
#
|
52
52
|
# Returns the result of the block.
|
53
53
|
def trace(layer, opts = {}, protect_op = nil)
|
54
|
-
return if protect_op && AppOpticsAPM.layer_op == protect_op.to_sym
|
54
|
+
return if !AppOpticsAPM.loaded || (protect_op && AppOpticsAPM.layer_op == protect_op.to_sym)
|
55
|
+
|
55
56
|
log_entry(layer, opts, protect_op)
|
56
57
|
begin
|
57
58
|
yield
|
@@ -158,6 +159,32 @@ module AppOpticsAPM
|
|
158
159
|
AppOpticsAPM::Context.clear
|
159
160
|
end
|
160
161
|
end
|
162
|
+
|
163
|
+
# Public: Set a ThreadLocal custom transaction name to be used when sending a trace or metrics for the
|
164
|
+
# current transaction
|
165
|
+
#
|
166
|
+
# In addition to setting a transaction name here there is also a configuration
|
167
|
+
# AppOpticsAPM::Config['transaction_name']['prepend_domain'] which allows to have the domain prepended
|
168
|
+
# to the transaction name
|
169
|
+
#
|
170
|
+
# ===== Arguments
|
171
|
+
# * +name+ - A non-empty string with the custom transaction name
|
172
|
+
#
|
173
|
+
def set_transaction_name(name)
|
174
|
+
if name.is_a?(String) && name.strip != ''
|
175
|
+
AppOpticsAPM.transaction_name = name
|
176
|
+
else
|
177
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/api] Could not set transaction name, provided name is empty or not a String."
|
178
|
+
end
|
179
|
+
AppOpticsAPM.transaction_name
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
# this is provided for testing
|
184
|
+
# returns the current transaction name
|
185
|
+
def get_transaction_name
|
186
|
+
AppOpticsAPM.transaction_name
|
187
|
+
end
|
161
188
|
end
|
162
189
|
end
|
163
190
|
end
|
data/lib/appoptics_apm/base.rb
CHANGED
@@ -42,6 +42,12 @@ module AppOpticsAPMBase
|
|
42
42
|
thread_local :sample_rate
|
43
43
|
thread_local :layer
|
44
44
|
thread_local :layer_op
|
45
|
+
|
46
|
+
# transaction_name is used for custom transaction naming
|
47
|
+
# It needs to be globally accessible, but is only set by the request processors of the different frameworks
|
48
|
+
# and read by rack
|
49
|
+
thread_local :transaction_name
|
50
|
+
|
45
51
|
# Semaphore used during the test suite to test
|
46
52
|
# global config options.
|
47
53
|
thread_local :config_lock
|
@@ -54,7 +60,7 @@ module AppOpticsAPMBase
|
|
54
60
|
# X-Trace request header, tracing may have already been started
|
55
61
|
# by Joboe. Such a scenario occurs when the application is being
|
56
62
|
# hosted by a Java container (such as Tomcat or Glassfish) and
|
57
|
-
#
|
63
|
+
# AppOpticsAPM has already initiated tracing. In this case, we shouldn't
|
58
64
|
# pickup the X-Trace context in the X-Trace header and we shouldn't
|
59
65
|
# set the outgoing response X-Trace header or clear context.
|
60
66
|
# Yeah I know. Yuck.
|
data/lib/appoptics_apm/config.rb
CHANGED
@@ -73,31 +73,32 @@ module AppOpticsAPM
|
|
73
73
|
# to create an output similar to the content of the config file
|
74
74
|
#
|
75
75
|
def self.print_config
|
76
|
-
|
76
|
+
AppOpticsAPM.logger.warn "# General configurations"
|
77
77
|
non_instrumentation = @@config.keys - @@instrumentation
|
78
78
|
non_instrumentation.each do |config|
|
79
|
-
|
79
|
+
AppOpticsAPM.logger.warn "AppOpticsAPM::Config[:#{config}] = #{@@config[config]}"
|
80
80
|
end
|
81
81
|
|
82
|
-
|
83
|
-
|
82
|
+
AppOpticsAPM.logger.warn "\n# Instrumentation specific configurations"
|
83
|
+
AppOpticsAPM.logger.warn "# Enabled/Disabled Instrumentation"
|
84
84
|
@@instrumentation.each do |config|
|
85
|
-
|
85
|
+
AppOpticsAPM.logger.warn "AppOpticsAPM::Config[:#{config}][:enabled] = #{@@config[config][:enabled]}"
|
86
86
|
end
|
87
87
|
|
88
|
-
|
88
|
+
AppOpticsAPM.logger.warn "\n# Enabled/Disabled Backtrace Collection"
|
89
89
|
@@instrumentation.each do |config|
|
90
|
-
|
90
|
+
AppOpticsAPM.logger.warn "AppOpticsAPM::Config[:#{config}][:collect_backtraces] = #{@@config[config][:collect_backtraces]}"
|
91
|
+
AppOpticsAPM.logger.warn
|
91
92
|
end
|
92
93
|
|
93
|
-
|
94
|
+
AppOpticsAPM.logger.warn "\n# Logging of outgoing HTTP query args"
|
94
95
|
@@instrumentation.each do |config|
|
95
|
-
|
96
|
+
AppOpticsAPM.logger.warn "AppOpticsAPM::Config[:#{config}][:log_args] = #{@@config[config][:log_args]}"
|
96
97
|
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
99
|
+
AppOpticsAPM.logger.warn "\n# Bunny Controller and Action"
|
100
|
+
AppOpticsAPM.logger.warn "AppOpticsAPM::Config[:bunnyconsumer][:controller] = #{@@config[:bunnyconsumer][:controller].inspect}"
|
101
|
+
AppOpticsAPM.logger.warn "AppOpticsAPM::Config[:bunnyconsumer][:action] = #{@@config[:bunnyconsumer][:action].inspect}"
|
101
102
|
nil
|
102
103
|
end
|
103
104
|
|
@@ -112,7 +113,8 @@ module AppOpticsAPM
|
|
112
113
|
@@instrumentation.each do |k|
|
113
114
|
@@config[k] = {}
|
114
115
|
end
|
115
|
-
|
116
|
+
@@config[:transaction_name] = {}
|
117
|
+
load(File.join(File.dirname(File.dirname(__FILE__)),
|
116
118
|
'rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb'))
|
117
119
|
end
|
118
120
|
# rubocop:enable Metrics/AbcSize
|
@@ -51,6 +51,8 @@ module AppOpticsAPM
|
|
51
51
|
def error_response_with_appoptics(error = {})
|
52
52
|
status, headers, body = error_response_without_appoptics(error)
|
53
53
|
|
54
|
+
xtrace = AppOpticsAPM::Context.toString
|
55
|
+
|
54
56
|
if AppOpticsAPM.tracing?
|
55
57
|
# Since Grape uses throw/catch and not Exceptions, we manually log
|
56
58
|
# the error here.
|
@@ -58,18 +60,17 @@ module AppOpticsAPM
|
|
58
60
|
kvs[:ErrorClass] = 'GrapeError'
|
59
61
|
kvs[:ErrorMsg] = error[:message] ? error[:message] : "No message given."
|
60
62
|
kvs[:Backtrace] = ::AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:grape][:collect_backtraces]
|
61
|
-
|
62
|
-
::AppOpticsAPM::API.log(nil, 'error', kvs)
|
63
|
+
::AppOpticsAPM::API.log('rack', 'error', kvs)
|
63
64
|
|
64
65
|
# Since calls to error() are handled similar to abort in Grape. We
|
65
66
|
# manually log the rack exit here since the original code won't
|
66
67
|
# be returned to
|
67
68
|
xtrace = AppOpticsAPM::API.log_end('rack', :Status => status)
|
69
|
+
end
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
71
|
+
if headers && AppOpticsAPM::XTrace.valid?(xtrace)
|
72
|
+
unless defined?(JRUBY_VERSION) && AppOpticsAPM.is_continued_trace?
|
73
|
+
headers['X-Trace'] = xtrace if headers.is_a?(Hash)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|