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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -3
  3. data/CONTRIBUTING.md +2 -8
  4. data/ext/extconf.rb +6 -5
  5. data/ext/libskylight.yml +7 -6
  6. data/ext/skylight_native.c +22 -99
  7. data/lib/skylight.rb +211 -14
  8. data/lib/skylight/api.rb +10 -3
  9. data/lib/skylight/cli.rb +4 -3
  10. data/lib/skylight/cli/doctor.rb +13 -14
  11. data/lib/skylight/cli/merger.rb +6 -4
  12. data/lib/skylight/config.rb +597 -127
  13. data/lib/skylight/deprecation.rb +17 -0
  14. data/lib/skylight/errors.rb +21 -6
  15. data/lib/skylight/extensions.rb +107 -0
  16. data/lib/skylight/extensions/source_location.rb +291 -0
  17. data/lib/skylight/formatters/http.rb +20 -0
  18. data/lib/skylight/gc.rb +109 -0
  19. data/lib/skylight/helpers.rb +69 -26
  20. data/lib/skylight/instrumenter.rb +326 -15
  21. data/lib/skylight/middleware.rb +138 -1
  22. data/lib/skylight/native.rb +52 -2
  23. data/lib/skylight/native_ext_fetcher.rb +4 -3
  24. data/lib/skylight/normalizers.rb +153 -0
  25. data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
  26. data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
  27. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  28. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  29. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  30. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  31. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  32. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  33. data/lib/skylight/normalizers/active_job/perform.rb +86 -0
  34. data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
  35. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  36. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  37. data/lib/skylight/normalizers/active_storage.rb +30 -0
  38. data/lib/skylight/normalizers/active_support/cache.rb +22 -0
  39. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  40. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  49. data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
  50. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  51. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  52. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  53. data/lib/skylight/normalizers/default.rb +32 -0
  54. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  55. data/lib/skylight/normalizers/faraday/request.rb +40 -0
  56. data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
  57. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  58. data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
  59. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
  60. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  61. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  62. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  63. data/lib/skylight/normalizers/graphql/base.rb +132 -0
  64. data/lib/skylight/normalizers/render.rb +81 -0
  65. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  66. data/lib/skylight/normalizers/shrine.rb +34 -0
  67. data/lib/skylight/normalizers/sql.rb +45 -0
  68. data/lib/skylight/probes.rb +181 -0
  69. data/lib/skylight/probes/action_controller.rb +48 -0
  70. data/lib/skylight/probes/action_dispatch.rb +2 -0
  71. data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
  72. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
  73. data/lib/skylight/probes/action_view.rb +43 -0
  74. data/lib/skylight/probes/active_job.rb +27 -0
  75. data/lib/skylight/probes/active_job_enqueue.rb +41 -0
  76. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  77. data/lib/skylight/probes/delayed_job.rb +149 -0
  78. data/lib/skylight/probes/elasticsearch.rb +38 -0
  79. data/lib/skylight/probes/excon.rb +25 -0
  80. data/lib/skylight/probes/excon/middleware.rb +66 -0
  81. data/lib/skylight/probes/faraday.rb +23 -0
  82. data/lib/skylight/probes/graphql.rb +43 -0
  83. data/lib/skylight/probes/httpclient.rb +44 -0
  84. data/lib/skylight/probes/middleware.rb +126 -0
  85. data/lib/skylight/probes/mongo.rb +164 -0
  86. data/lib/skylight/probes/mongoid.rb +13 -0
  87. data/lib/skylight/probes/net_http.rb +54 -0
  88. data/lib/skylight/probes/redis.rb +63 -0
  89. data/lib/skylight/probes/sequel.rb +33 -0
  90. data/lib/skylight/probes/sinatra.rb +63 -0
  91. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  92. data/lib/skylight/probes/tilt.rb +27 -0
  93. data/lib/skylight/railtie.rb +162 -18
  94. data/lib/skylight/sidekiq.rb +48 -0
  95. data/lib/skylight/subscriber.rb +110 -0
  96. data/lib/skylight/test.rb +146 -0
  97. data/lib/skylight/trace.rb +307 -10
  98. data/lib/skylight/user_config.rb +61 -0
  99. data/lib/skylight/util.rb +12 -0
  100. data/lib/skylight/util/allocation_free.rb +26 -0
  101. data/lib/skylight/util/clock.rb +56 -0
  102. data/lib/skylight/util/component.rb +5 -2
  103. data/lib/skylight/util/deploy.rb +7 -10
  104. data/lib/skylight/util/gzip.rb +20 -0
  105. data/lib/skylight/util/http.rb +4 -10
  106. data/lib/skylight/util/instrumenter_method.rb +26 -0
  107. data/lib/skylight/util/logging.rb +138 -0
  108. data/lib/skylight/util/lru_cache.rb +40 -0
  109. data/lib/skylight/util/platform.rb +1 -1
  110. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  111. data/lib/skylight/version.rb +5 -1
  112. data/lib/skylight/vm/gc.rb +68 -0
  113. 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
@@ -1,9 +1,55 @@
1
+ require "securerandom"
2
+ require "skylight/util/logging"
3
+
1
4
  module Skylight
2
- class Trace < Core::Trace
3
- attr_reader :component
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 maybe_broken(error)
30
- if error.is_a?(Skylight::MaximumTraceSpansError) && config.get(:report_max_spans_exceeded)
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
- super
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
- super
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
- super
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