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