skylight 4.3.2 → 5.0.1
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/CHANGELOG.md +35 -3
- data/CONTRIBUTING.md +2 -8
- data/ext/extconf.rb +6 -5
- data/ext/libskylight.yml +7 -6
- data/ext/skylight_native.c +22 -99
- data/lib/skylight.rb +211 -14
- data/lib/skylight/api.rb +10 -3
- data/lib/skylight/cli.rb +4 -3
- data/lib/skylight/cli/doctor.rb +13 -14
- data/lib/skylight/cli/merger.rb +6 -4
- data/lib/skylight/config.rb +597 -127
- data/lib/skylight/deprecation.rb +17 -0
- data/lib/skylight/errors.rb +21 -6
- data/lib/skylight/extensions.rb +107 -0
- data/lib/skylight/extensions/source_location.rb +291 -0
- data/lib/skylight/formatters/http.rb +20 -0
- data/lib/skylight/gc.rb +109 -0
- data/lib/skylight/helpers.rb +69 -26
- data/lib/skylight/instrumenter.rb +326 -15
- data/lib/skylight/middleware.rb +138 -1
- data/lib/skylight/native.rb +52 -2
- data/lib/skylight/native_ext_fetcher.rb +4 -3
- data/lib/skylight/normalizers.rb +153 -0
- data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
- data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
- data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
- data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
- data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
- data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
- data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
- data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
- data/lib/skylight/normalizers/active_job/perform.rb +86 -0
- data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
- data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
- data/lib/skylight/normalizers/active_record/sql.rb +12 -0
- data/lib/skylight/normalizers/active_storage.rb +30 -0
- data/lib/skylight/normalizers/active_support/cache.rb +22 -0
- data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
- data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
- data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
- data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
- data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
- data/lib/skylight/normalizers/default.rb +32 -0
- data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
- data/lib/skylight/normalizers/faraday/request.rb +40 -0
- data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
- data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
- data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
- data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
- data/lib/skylight/normalizers/grape/format_response.rb +20 -0
- data/lib/skylight/normalizers/graphiti/render.rb +22 -0
- data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
- data/lib/skylight/normalizers/graphql/base.rb +132 -0
- data/lib/skylight/normalizers/render.rb +81 -0
- data/lib/skylight/normalizers/sequel/sql.rb +12 -0
- data/lib/skylight/normalizers/shrine.rb +34 -0
- data/lib/skylight/normalizers/sql.rb +45 -0
- data/lib/skylight/probes.rb +181 -0
- data/lib/skylight/probes/action_controller.rb +48 -0
- data/lib/skylight/probes/action_dispatch.rb +2 -0
- data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
- data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
- data/lib/skylight/probes/action_view.rb +43 -0
- data/lib/skylight/probes/active_job.rb +27 -0
- data/lib/skylight/probes/active_job_enqueue.rb +41 -0
- data/lib/skylight/probes/active_model_serializers.rb +50 -0
- data/lib/skylight/probes/delayed_job.rb +149 -0
- data/lib/skylight/probes/elasticsearch.rb +38 -0
- data/lib/skylight/probes/excon.rb +25 -0
- data/lib/skylight/probes/excon/middleware.rb +66 -0
- data/lib/skylight/probes/faraday.rb +23 -0
- data/lib/skylight/probes/graphql.rb +43 -0
- data/lib/skylight/probes/httpclient.rb +44 -0
- data/lib/skylight/probes/middleware.rb +126 -0
- data/lib/skylight/probes/mongo.rb +164 -0
- data/lib/skylight/probes/mongoid.rb +13 -0
- data/lib/skylight/probes/net_http.rb +54 -0
- data/lib/skylight/probes/redis.rb +63 -0
- data/lib/skylight/probes/sequel.rb +33 -0
- data/lib/skylight/probes/sinatra.rb +63 -0
- data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
- data/lib/skylight/probes/tilt.rb +27 -0
- data/lib/skylight/railtie.rb +162 -18
- data/lib/skylight/sidekiq.rb +48 -0
- data/lib/skylight/subscriber.rb +110 -0
- data/lib/skylight/test.rb +146 -0
- data/lib/skylight/trace.rb +307 -10
- data/lib/skylight/user_config.rb +61 -0
- data/lib/skylight/util.rb +12 -0
- data/lib/skylight/util/allocation_free.rb +26 -0
- data/lib/skylight/util/clock.rb +56 -0
- data/lib/skylight/util/component.rb +5 -2
- data/lib/skylight/util/deploy.rb +7 -10
- data/lib/skylight/util/gzip.rb +20 -0
- data/lib/skylight/util/http.rb +4 -10
- data/lib/skylight/util/instrumenter_method.rb +26 -0
- data/lib/skylight/util/logging.rb +138 -0
- data/lib/skylight/util/lru_cache.rb +40 -0
- data/lib/skylight/util/platform.rb +1 -1
- data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
- data/lib/skylight/version.rb +5 -1
- data/lib/skylight/vm/gc.rb +68 -0
- metadata +126 -13
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Sidekiq
|
|
3
|
+
def self.add_middleware
|
|
4
|
+
unless defined?(::Sidekiq)
|
|
5
|
+
Skylight.warn "Skylight for Sidekiq is active, but Sidekiq is not defined."
|
|
6
|
+
return
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
::Sidekiq.configure_server do |sidekiq_config|
|
|
10
|
+
Skylight.debug "Adding Sidekiq Middleware"
|
|
11
|
+
|
|
12
|
+
sidekiq_config.server_middleware do |chain|
|
|
13
|
+
# Put it at the front
|
|
14
|
+
chain.prepend ServerMiddleware
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ServerMiddleware
|
|
20
|
+
include Util::Logging
|
|
21
|
+
|
|
22
|
+
def call(worker, job, queue)
|
|
23
|
+
t { "Sidekiq middleware beginning trace" }
|
|
24
|
+
title = job["wrapped"] || job["class"]
|
|
25
|
+
|
|
26
|
+
# TODO: Using hints here would be ideal but requires further refactoring
|
|
27
|
+
meta =
|
|
28
|
+
if (source_location = worker.method(:perform).source_location)
|
|
29
|
+
{ source_file: source_location[0], source_line: source_location[1] }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Skylight.trace(title, "app.sidekiq.worker", title, meta: meta, segment: queue, component: :worker) do |trace|
|
|
33
|
+
yield
|
|
34
|
+
rescue Exception # includes Sidekiq::Shutdown
|
|
35
|
+
trace.segment = "error" if trace
|
|
36
|
+
raise
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ActiveSupport::Notifications.subscribe("started_instrumenter.skylight") \
|
|
42
|
+
do |_name, _started, _finished, _unique_id, payload|
|
|
43
|
+
if payload[:instrumenter].config.enable_sidekiq?
|
|
44
|
+
add_middleware
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
# @api private
|
|
3
|
+
class Subscriber
|
|
4
|
+
include Util::Logging
|
|
5
|
+
|
|
6
|
+
attr_reader :config, :normalizers
|
|
7
|
+
|
|
8
|
+
def initialize(config, instrumenter)
|
|
9
|
+
@config = config
|
|
10
|
+
@normalizers = Normalizers.build(config)
|
|
11
|
+
@instrumenter = instrumenter
|
|
12
|
+
@subscribers = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register!
|
|
16
|
+
unregister!
|
|
17
|
+
@normalizers.keys.each do |key| # rubocop:disable Style/HashEachMethods
|
|
18
|
+
@subscribers << ActiveSupport::Notifications.subscribe(key, self)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def unregister!
|
|
23
|
+
ActiveSupport::Notifications.unsubscribe @subscribers.shift until @subscribers.empty?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
#
|
|
28
|
+
# ===== ActiveSupport::Notifications API
|
|
29
|
+
#
|
|
30
|
+
#
|
|
31
|
+
|
|
32
|
+
class Notification
|
|
33
|
+
attr_reader :name, :span
|
|
34
|
+
|
|
35
|
+
def initialize(name, span)
|
|
36
|
+
@name = name
|
|
37
|
+
@span = span
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def start(name, _id, payload)
|
|
42
|
+
return if @instrumenter.disabled?
|
|
43
|
+
return unless (trace = @instrumenter.current_trace)
|
|
44
|
+
|
|
45
|
+
_start(trace, name, payload)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def finish(name, _id, payload)
|
|
49
|
+
return if @instrumenter.disabled?
|
|
50
|
+
return unless (trace = @instrumenter.current_trace)
|
|
51
|
+
|
|
52
|
+
while (curr = trace.notifications.pop)
|
|
53
|
+
next unless curr.name == name
|
|
54
|
+
|
|
55
|
+
meta = {}
|
|
56
|
+
meta[:exception] = payload[:exception] if payload[:exception]
|
|
57
|
+
meta[:exception_object] = payload[:exception_object] if payload[:exception_object]
|
|
58
|
+
trace.done(curr.span, meta) if curr.span
|
|
59
|
+
normalize_after(trace, curr.span, name, payload)
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
rescue Exception => e
|
|
63
|
+
error "Subscriber#finish error; msg=%s", e.message
|
|
64
|
+
debug "trace=%s", trace.inspect
|
|
65
|
+
debug "in: name=%s", name.inspect
|
|
66
|
+
debug "in: payload=%s", payload.inspect
|
|
67
|
+
t { e.backtrace.join("\n") }
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def publish(name, *args)
|
|
72
|
+
# Ignored for now because nothing in rails uses it
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def normalize(*args)
|
|
78
|
+
@normalizers.normalize(*args)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def normalize_after(*args)
|
|
82
|
+
@normalizers.normalize_after(*args)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def _start(trace, name, payload)
|
|
86
|
+
result = normalize(trace, name, payload)
|
|
87
|
+
|
|
88
|
+
unless result == :skip
|
|
89
|
+
case result.size
|
|
90
|
+
when 3, 4
|
|
91
|
+
cat, title, desc, meta = result
|
|
92
|
+
else
|
|
93
|
+
raise "Invalid normalizer result: #{result.inspect}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
span = trace.instrument(cat, title, desc, meta)
|
|
97
|
+
end
|
|
98
|
+
rescue Exception => e
|
|
99
|
+
error "Subscriber#start error; msg=%s", e.message
|
|
100
|
+
debug "trace=%s", trace.inspect
|
|
101
|
+
debug "in: name=%s", name.inspect
|
|
102
|
+
debug "in: payload=%s", payload.inspect
|
|
103
|
+
debug "out: cat=%s, title=%s, desc=%s", cat.inspect, name.inspect, desc.inspect
|
|
104
|
+
t { e.backtrace.join("\n") }
|
|
105
|
+
nil
|
|
106
|
+
ensure
|
|
107
|
+
trace.notifications << Notification.new(name, span)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Test
|
|
3
|
+
module Mocking
|
|
4
|
+
def mock!(config_opts = {}, &callback)
|
|
5
|
+
config_opts[:mock_submission] ||= callback || proc {}
|
|
6
|
+
|
|
7
|
+
config_class = Class.new(Config) do
|
|
8
|
+
def validate_with_server
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
config = config_class.load(config_opts)
|
|
14
|
+
config[:authentication] ||= "zomg"
|
|
15
|
+
|
|
16
|
+
class_eval do
|
|
17
|
+
unless const_defined?(:OriginalInstrumenter)
|
|
18
|
+
const_set :OriginalInstrumenter, Instrumenter
|
|
19
|
+
remove_const :Instrumenter
|
|
20
|
+
const_set(:Instrumenter, Class.new(OriginalInstrumenter) do
|
|
21
|
+
def self.name
|
|
22
|
+
"Mocked Instrumenter"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.native_new(*)
|
|
26
|
+
allocate
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def native_start
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def native_submit_trace(trace)
|
|
34
|
+
config[:mock_submission].call(trace)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def native_stop; end
|
|
38
|
+
end)
|
|
39
|
+
|
|
40
|
+
const_set :OriginalTrace, Trace
|
|
41
|
+
remove_const :Trace
|
|
42
|
+
const_set(:Trace, Class.new(OriginalTrace) do
|
|
43
|
+
def self.native_new(start, _uuid, endpoint, meta)
|
|
44
|
+
inst = allocate
|
|
45
|
+
inst.instance_variable_set(:@start, start)
|
|
46
|
+
inst.instance_variable_set(:@endpoint, endpoint)
|
|
47
|
+
inst.instance_variable_set(:@starting_endpoint, endpoint)
|
|
48
|
+
inst.instance_variable_set(:@meta, meta)
|
|
49
|
+
inst
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
attr_reader :endpoint, :starting_endpoint, :meta
|
|
53
|
+
|
|
54
|
+
def mock_spans
|
|
55
|
+
@mock_spans ||= []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def filter_spans
|
|
59
|
+
if block_given?
|
|
60
|
+
mock_spans.select { |span| yield span }
|
|
61
|
+
else
|
|
62
|
+
mock_spans.reject { |span| span[:cat] == "noise.gc" }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def native_get_uuid
|
|
67
|
+
@uuid
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def uuid=(value)
|
|
71
|
+
@uuid = value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def native_get_started_at
|
|
75
|
+
@start
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def native_set_endpoint(endpoint)
|
|
79
|
+
@endpoint = endpoint
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def native_set_component(component)
|
|
83
|
+
@component = component
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def native_start_span(time, cat)
|
|
87
|
+
span = {
|
|
88
|
+
start: time,
|
|
89
|
+
cat: cat
|
|
90
|
+
}
|
|
91
|
+
mock_spans << span
|
|
92
|
+
# Return integer like the native method does
|
|
93
|
+
mock_spans.index(span)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def native_span_set_title(span, title)
|
|
97
|
+
mock_spans[span][:title] = title
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def native_span_set_description(span, desc)
|
|
101
|
+
mock_spans[span][:desc] = desc
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def native_span_set_meta(span, meta)
|
|
105
|
+
mock_spans[span][:meta] = meta
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def native_span_started(span); end
|
|
109
|
+
|
|
110
|
+
def native_span_set_exception(span, exception_object, exception)
|
|
111
|
+
mock_spans[span][:exception_object] = exception_object
|
|
112
|
+
mock_spans[span][:exception] = exception
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def native_stop_span(span, time)
|
|
116
|
+
span = mock_spans[span]
|
|
117
|
+
span[:duration] = time - span[:start]
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def native_use_pruning
|
|
122
|
+
@using_native_pruning = true
|
|
123
|
+
end
|
|
124
|
+
end)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
start!(config)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def unmock!
|
|
132
|
+
if const_defined?(:OriginalInstrumenter)
|
|
133
|
+
class_eval do
|
|
134
|
+
remove_const :Instrumenter
|
|
135
|
+
const_set :Instrumenter, OriginalInstrumenter
|
|
136
|
+
remove_const :OriginalInstrumenter
|
|
137
|
+
|
|
138
|
+
remove_const :Trace
|
|
139
|
+
const_set :Trace, OriginalTrace
|
|
140
|
+
remove_const :OriginalTrace
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/lib/skylight/trace.rb
CHANGED
|
@@ -1,9 +1,55 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
require "skylight/util/logging"
|
|
3
|
+
|
|
1
4
|
module Skylight
|
|
2
|
-
class Trace
|
|
3
|
-
|
|
5
|
+
class Trace
|
|
6
|
+
GC_CAT = "noise.gc".freeze
|
|
7
|
+
SYNTHETIC = "<synthetic>".freeze
|
|
8
|
+
|
|
9
|
+
META_KEYS = %i[mute_children database].freeze
|
|
10
|
+
|
|
11
|
+
include Util::Logging
|
|
12
|
+
|
|
13
|
+
attr_reader :instrumenter, :endpoint, :segment, :notifications, :meta, :component
|
|
14
|
+
|
|
15
|
+
def self.new(instrumenter, endpoint, start, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
|
|
16
|
+
uuid = SecureRandom.uuid
|
|
17
|
+
inst = native_new(normalize_time(start), uuid, endpoint, meta)
|
|
18
|
+
inst.uuid = uuid
|
|
19
|
+
inst.send(:initialize, instrumenter, cat, title, desc, meta, component: component)
|
|
20
|
+
inst.endpoint = endpoint
|
|
21
|
+
inst.segment = segment
|
|
22
|
+
inst
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# TODO: Move this into native
|
|
26
|
+
def self.normalize_time(time)
|
|
27
|
+
# At least one customer has extensions that cause integer division to produce rationals.
|
|
28
|
+
# Since the native code expects an integer, we force it again.
|
|
29
|
+
(time.to_i / 100_000).to_i
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(instrumenter, cat, title, desc, meta, component: nil)
|
|
33
|
+
raise ArgumentError, "instrumenter is required" unless instrumenter
|
|
34
|
+
|
|
35
|
+
@instrumenter = instrumenter
|
|
36
|
+
@submitted = false
|
|
37
|
+
@broken = false
|
|
38
|
+
|
|
39
|
+
@notifications = []
|
|
40
|
+
|
|
41
|
+
@spans = []
|
|
42
|
+
|
|
43
|
+
preprocess_meta(meta) if meta
|
|
44
|
+
|
|
45
|
+
# create the root node
|
|
46
|
+
@root = start(native_get_started_at, cat, title, desc, meta, normalize: false)
|
|
47
|
+
|
|
48
|
+
# Also store meta for later access
|
|
49
|
+
@meta = meta
|
|
50
|
+
|
|
51
|
+
@gc = config.gc.track unless ENV.key?("SKYLIGHT_DISABLE_GC_TRACKING")
|
|
4
52
|
|
|
5
|
-
def initialize(*, component: nil)
|
|
6
|
-
super
|
|
7
53
|
self.component = component if component
|
|
8
54
|
@too_many_spans = false
|
|
9
55
|
native_use_pruning if use_pruning?
|
|
@@ -26,29 +72,248 @@ module Skylight
|
|
|
26
72
|
!!@too_many_spans
|
|
27
73
|
end
|
|
28
74
|
|
|
29
|
-
def
|
|
30
|
-
|
|
75
|
+
def log_context
|
|
76
|
+
@log_context ||= { trace: uuid }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def endpoint=(value)
|
|
80
|
+
if muted?
|
|
81
|
+
maybe_warn(:endpoint_set_muted, "tried to set endpoint name while muted")
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
@endpoint = value
|
|
85
|
+
native_set_endpoint(value)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def segment=(value)
|
|
89
|
+
if muted?
|
|
90
|
+
maybe_warn(:segment_set_muted, "tried to set segment name while muted")
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
@segment = value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
attr_accessor :compound_response_error_status
|
|
97
|
+
|
|
98
|
+
def config
|
|
99
|
+
@instrumenter.config
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def muted?
|
|
103
|
+
!!@child_instrumentation_muted_by || @instrumenter.muted?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def broken?
|
|
107
|
+
!!@broken
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def maybe_broken(err)
|
|
111
|
+
if err.is_a?(Skylight::MaximumTraceSpansError) && config.get(:report_max_spans_exceeded)
|
|
31
112
|
too_many_spans!
|
|
32
113
|
else
|
|
33
|
-
|
|
114
|
+
error "failed to instrument span; msg=%s; endpoint=%s", err.message, endpoint
|
|
115
|
+
broken!
|
|
34
116
|
end
|
|
35
117
|
end
|
|
36
118
|
|
|
119
|
+
def instrument(cat, title = nil, desc = nil, meta = nil)
|
|
120
|
+
return if muted?
|
|
121
|
+
return if broken?
|
|
122
|
+
|
|
123
|
+
t { "instrument: #{cat}, #{title}" }
|
|
124
|
+
|
|
125
|
+
title.freeze if title.is_a?(String)
|
|
126
|
+
desc.freeze if desc.is_a?(String)
|
|
127
|
+
|
|
128
|
+
now = Skylight::Util::Clock.nanos
|
|
129
|
+
|
|
130
|
+
preprocess_meta(meta) if meta
|
|
131
|
+
|
|
132
|
+
start(now - gc_time, cat, title, desc, meta)
|
|
133
|
+
rescue => e
|
|
134
|
+
maybe_broken(e)
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def done(span, meta = nil)
|
|
139
|
+
# `span` will be `nil` if we failed to start instrumenting, such as in
|
|
140
|
+
# the case of too many spans in a request.
|
|
141
|
+
return unless span
|
|
142
|
+
return if broken?
|
|
143
|
+
|
|
144
|
+
if meta&.[](:defer)
|
|
145
|
+
deferred_spans[span] ||= (Skylight::Util::Clock.nanos - gc_time)
|
|
146
|
+
return
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
if meta && (meta[:exception_object] || meta[:exception])
|
|
150
|
+
native_span_set_exception(span, meta[:exception_object], meta[:exception])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
stop(span, Skylight::Util::Clock.nanos - gc_time)
|
|
154
|
+
rescue => e
|
|
155
|
+
error "failed to close span; msg=%s; endpoint=%s", e.message, endpoint
|
|
156
|
+
log_trace "Original Backtrace:\n#{e.backtrace.join("\n")}"
|
|
157
|
+
broken!
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def inspect
|
|
162
|
+
to_s
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def release
|
|
166
|
+
t { "release; is_current=#{@instrumenter.current_trace == self}" }
|
|
167
|
+
return unless @instrumenter.current_trace == self
|
|
168
|
+
|
|
169
|
+
@instrumenter.current_trace = nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def broken!
|
|
173
|
+
debug "trace is broken"
|
|
174
|
+
@broken = true
|
|
175
|
+
end
|
|
176
|
+
|
|
37
177
|
def traced
|
|
38
178
|
if too_many_spans?
|
|
39
179
|
error("[E%04d] The request exceeded the maximum number of spans allowed. It will still " \
|
|
40
180
|
"be tracked but with reduced information. endpoint=%s", Skylight::MaximumTraceSpansError.code, endpoint)
|
|
41
181
|
end
|
|
42
182
|
|
|
43
|
-
|
|
183
|
+
gc = gc_time
|
|
184
|
+
now = Skylight::Util::Clock.nanos
|
|
185
|
+
track_gc(gc, now)
|
|
186
|
+
stop(@root, now)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def submit
|
|
190
|
+
t { "submitting trace" }
|
|
191
|
+
|
|
192
|
+
# This must always be called to clean up properly
|
|
193
|
+
release
|
|
194
|
+
|
|
195
|
+
if broken?
|
|
196
|
+
t { "broken, not submitting" }
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
if @submitted
|
|
201
|
+
t { "already submitted" }
|
|
202
|
+
return
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
@submitted = true
|
|
206
|
+
|
|
207
|
+
traced
|
|
208
|
+
|
|
209
|
+
@instrumenter.process(self)
|
|
210
|
+
rescue Exception => e
|
|
211
|
+
error e.message
|
|
212
|
+
t { e.backtrace.join("\n") }
|
|
44
213
|
end
|
|
45
214
|
|
|
46
215
|
private
|
|
47
216
|
|
|
48
|
-
def track_gc(
|
|
217
|
+
def track_gc(time, now)
|
|
49
218
|
# This attempts to log another span which will fail if we have too many
|
|
50
219
|
return if too_many_spans?
|
|
51
|
-
|
|
220
|
+
|
|
221
|
+
if time > 0
|
|
222
|
+
t { fmt "tracking GC time; duration=%d", time }
|
|
223
|
+
meta = { source_location: SYNTHETIC }
|
|
224
|
+
stop(start(now - time, GC_CAT, nil, nil, meta), now)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def start(time, cat, title, desc, meta, opts = {})
|
|
229
|
+
time = self.class.normalize_time(time) unless opts[:normalize] == false
|
|
230
|
+
|
|
231
|
+
mute_children = meta&.delete(:mute_children)
|
|
232
|
+
|
|
233
|
+
sp = native_start_span(time, cat.to_s)
|
|
234
|
+
native_span_set_title(sp, title.to_s) if title
|
|
235
|
+
native_span_set_description(sp, desc.to_s) if desc
|
|
236
|
+
native_span_set_meta(sp, meta) if meta
|
|
237
|
+
native_span_started(sp)
|
|
238
|
+
|
|
239
|
+
@spans << sp
|
|
240
|
+
t { "started span: #{sp} - #{cat}, #{title}" }
|
|
241
|
+
|
|
242
|
+
if mute_children
|
|
243
|
+
t { "muting child instrumentation for span=#{sp}" }
|
|
244
|
+
mute_child_instrumentation(sp)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
sp
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def mute_child_instrumentation(span)
|
|
251
|
+
@child_instrumentation_muted_by = span
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Middleware spans that were interrupted by a throw/catch should be cached here.
|
|
255
|
+
# keys: span ids
|
|
256
|
+
# values: nsec timestamp at which the span was cached here.
|
|
257
|
+
def deferred_spans
|
|
258
|
+
@deferred_spans ||= {}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def stop(span, time)
|
|
262
|
+
t { "stopping span: #{span}" }
|
|
263
|
+
|
|
264
|
+
# If `stop` is called for a span that is not the last item in the stack,
|
|
265
|
+
# check to see if the last item has been marked as deferred. If so, close
|
|
266
|
+
# that span first, then try to close the original.
|
|
267
|
+
while deferred_spans[expected = @spans.pop]
|
|
268
|
+
normalized_stop(expected, deferred_spans.delete(expected))
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
handle_unexpected_stop(expected, span) unless span == expected
|
|
272
|
+
|
|
273
|
+
normalized_stop(span, time)
|
|
274
|
+
nil
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def normalized_stop(span, time)
|
|
278
|
+
time = self.class.normalize_time(time)
|
|
279
|
+
native_stop_span(span, time)
|
|
280
|
+
|
|
281
|
+
if @child_instrumentation_muted_by == span
|
|
282
|
+
@child_instrumentation_muted_by = nil # restart instrumenting
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Originally extracted from `stop`.
|
|
287
|
+
# If we attempt to close spans out of order, and it appears to be a middleware issue,
|
|
288
|
+
# disable the middleware probe and mark trace as broken.
|
|
289
|
+
def handle_unexpected_stop(expected, span)
|
|
290
|
+
message = "[E0001] Spans were closed out of order. Expected to see '#{native_span_get_title(expected)}', " \
|
|
291
|
+
"but got '#{native_span_get_title(span)}' instead."
|
|
292
|
+
|
|
293
|
+
if native_span_get_category(span) == "rack.middleware" && Skylight::Probes.installed.key?(:middleware)
|
|
294
|
+
if Skylight::Probes::Middleware::Probe.disabled?
|
|
295
|
+
message << "\nWe disabled the Middleware probe but unfortunately, this didn't solve the issue."
|
|
296
|
+
else
|
|
297
|
+
Skylight::Probes::Middleware::Probe.disable!
|
|
298
|
+
message << "\n#{native_span_get_title(span)} may be a Middleware that doesn't fully conform " \
|
|
299
|
+
"to the Rack SPEC. We've disabled the Middleware probe to see if that resolves the issue."
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
message << "\nThis request will not be tracked. Please contact support@skylight.io for more information."
|
|
304
|
+
|
|
305
|
+
error message
|
|
306
|
+
|
|
307
|
+
t { "expected=#{expected}, actual=#{span}" }
|
|
308
|
+
|
|
309
|
+
broken!
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def gc_time
|
|
313
|
+
return 0 unless @gc
|
|
314
|
+
|
|
315
|
+
@gc.update
|
|
316
|
+
@gc.time
|
|
52
317
|
end
|
|
53
318
|
|
|
54
319
|
def use_pruning?
|
|
@@ -61,9 +326,41 @@ module Skylight
|
|
|
61
326
|
|
|
62
327
|
def component=(component)
|
|
63
328
|
resolve_component(component).tap do |c|
|
|
329
|
+
# Would it be better for the component getter to get from native?
|
|
64
330
|
@component = c
|
|
65
331
|
native_set_component(c)
|
|
66
332
|
end
|
|
67
333
|
end
|
|
334
|
+
|
|
335
|
+
def preprocess_meta(meta)
|
|
336
|
+
validate_meta(meta)
|
|
337
|
+
instrumenter.extensions.trace_preprocess_meta(meta)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def validate_meta(meta)
|
|
341
|
+
unknown_keys = meta.keys - allowed_meta_keys
|
|
342
|
+
if unknown_keys.any?
|
|
343
|
+
unknown_keys.each do |key|
|
|
344
|
+
maybe_warn("unknown_meta:#{key}", "Unknown meta key will be ignored; key=#{key.inspect}")
|
|
345
|
+
meta.delete(key)
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def allowed_meta_keys
|
|
351
|
+
META_KEYS | instrumenter.extensions.allowed_meta_keys
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def maybe_warn(context, msg)
|
|
355
|
+
return if warnings_silenced?(context)
|
|
356
|
+
|
|
357
|
+
instrumenter.silence_warnings(context)
|
|
358
|
+
|
|
359
|
+
warn(msg)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def warnings_silenced?(context)
|
|
363
|
+
instrumenter.warnings_silenced?(context)
|
|
364
|
+
end
|
|
68
365
|
end
|
|
69
366
|
end
|