skylight 4.2.3 → 5.3.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/CHANGELOG.md +420 -331
- data/CLA.md +1 -1
- data/CONTRIBUTING.md +2 -8
- data/ERRORS.md +3 -0
- data/LICENSE.md +7 -17
- data/README.md +1 -1
- data/ext/extconf.rb +61 -56
- data/ext/libskylight.yml +8 -6
- data/ext/skylight_native.c +26 -100
- data/lib/skylight/api.rb +32 -21
- data/lib/skylight/cli/doctor.rb +64 -65
- data/lib/skylight/cli/helpers.rb +19 -19
- data/lib/skylight/cli/merger.rb +142 -138
- data/lib/skylight/cli.rb +48 -46
- data/lib/skylight/config.rb +640 -201
- data/lib/skylight/data/cacert.pem +730 -1023
- data/lib/skylight/deprecation.rb +17 -0
- data/lib/skylight/errors.rb +26 -9
- data/lib/skylight/extensions/source_location.rb +291 -0
- data/lib/skylight/extensions.rb +95 -0
- data/lib/skylight/formatters/http.rb +18 -0
- data/lib/skylight/gc.rb +99 -0
- data/lib/skylight/helpers.rb +81 -36
- data/lib/skylight/instrumenter.rb +336 -18
- data/lib/skylight/middleware.rb +147 -1
- data/lib/skylight/native.rb +60 -12
- data/lib/skylight/native_ext_fetcher.rb +13 -14
- data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
- data/lib/skylight/normalizers/action_controller/send_file.rb +51 -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 +87 -0
- data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
- data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
- data/lib/skylight/normalizers/active_record/sql.rb +20 -0
- data/lib/skylight/normalizers/active_storage.rb +28 -0
- data/lib/skylight/normalizers/active_support/cache.rb +11 -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 +44 -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 +24 -0
- data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
- data/lib/skylight/normalizers/faraday/request.rb +38 -0
- data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
- data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
- data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
- data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -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 +127 -0
- data/lib/skylight/normalizers/render.rb +79 -0
- data/lib/skylight/normalizers/sequel/sql.rb +12 -0
- data/lib/skylight/normalizers/shrine.rb +32 -0
- data/lib/skylight/normalizers/sql.rb +41 -0
- data/lib/skylight/normalizers.rb +157 -0
- data/lib/skylight/probes/action_controller.rb +52 -0
- data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
- data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
- data/lib/skylight/probes/action_dispatch.rb +2 -0
- data/lib/skylight/probes/action_view.rb +42 -0
- data/lib/skylight/probes/active_job.rb +27 -0
- data/lib/skylight/probes/active_job_enqueue.rb +35 -0
- data/lib/skylight/probes/active_model_serializers.rb +50 -0
- data/lib/skylight/probes/active_record_async.rb +96 -0
- data/lib/skylight/probes/delayed_job.rb +144 -0
- data/lib/skylight/probes/elasticsearch.rb +36 -0
- data/lib/skylight/probes/excon/middleware.rb +65 -0
- data/lib/skylight/probes/excon.rb +25 -0
- data/lib/skylight/probes/faraday.rb +23 -0
- data/lib/skylight/probes/graphql.rb +38 -0
- data/lib/skylight/probes/httpclient.rb +44 -0
- data/lib/skylight/probes/middleware.rb +135 -0
- data/lib/skylight/probes/mongo.rb +156 -0
- data/lib/skylight/probes/mongoid.rb +13 -0
- data/lib/skylight/probes/net_http.rb +54 -0
- data/lib/skylight/probes/rack_builder.rb +37 -0
- data/lib/skylight/probes/redis.rb +51 -0
- data/lib/skylight/probes/sequel.rb +29 -0
- data/lib/skylight/probes/sinatra.rb +66 -0
- data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
- data/lib/skylight/probes/tilt.rb +25 -0
- data/lib/skylight/probes.rb +173 -0
- data/lib/skylight/railtie.rb +166 -28
- data/lib/skylight/sidekiq.rb +47 -0
- data/lib/skylight/sinatra.rb +1 -1
- data/lib/skylight/subscriber.rb +130 -0
- data/lib/skylight/test.rb +147 -0
- data/lib/skylight/trace.rb +325 -22
- data/lib/skylight/user_config.rb +58 -0
- data/lib/skylight/util/allocation_free.rb +26 -0
- data/lib/skylight/util/clock.rb +57 -0
- data/lib/skylight/util/component.rb +22 -22
- data/lib/skylight/util/deploy.rb +19 -24
- data/lib/skylight/util/gzip.rb +20 -0
- data/lib/skylight/util/http.rb +106 -113
- data/lib/skylight/util/instrumenter_method.rb +26 -0
- data/lib/skylight/util/logging.rb +136 -0
- data/lib/skylight/util/lru_cache.rb +36 -0
- data/lib/skylight/util/platform.rb +3 -7
- data/lib/skylight/util/ssl.rb +1 -25
- data/lib/skylight/util.rb +12 -0
- data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
- data/lib/skylight/version.rb +5 -1
- data/lib/skylight/vm/gc.rb +60 -0
- data/lib/skylight.rb +201 -14
- metadata +134 -18
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,44 +72,301 @@ 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!
|
|
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(
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "skylight/errors"
|
|
3
|
+
|
|
4
|
+
module Skylight
|
|
5
|
+
class UserConfig
|
|
6
|
+
attr_accessor :disable_dev_warning, :disable_env_warning
|
|
7
|
+
|
|
8
|
+
def initialize(config)
|
|
9
|
+
@config = config
|
|
10
|
+
@file_path = nil
|
|
11
|
+
reload
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def file_path
|
|
15
|
+
return @file_path if @file_path
|
|
16
|
+
|
|
17
|
+
config_path =
|
|
18
|
+
@config[:user_config_path] ||
|
|
19
|
+
begin
|
|
20
|
+
require "etc"
|
|
21
|
+
home_dir = ENV["HOME"] || Etc.getpwuid.dir || (ENV["USER"] && File.expand_path("~#{ENV["USER"]}"))
|
|
22
|
+
if home_dir
|
|
23
|
+
File.join(home_dir, ".skylight")
|
|
24
|
+
else
|
|
25
|
+
raise ConfigError,
|
|
26
|
+
"The Skylight `user_config_path` must be defined since the home directory cannot be inferred"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@file_path = File.expand_path(config_path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def disable_dev_warning?
|
|
34
|
+
disable_dev_warning || ENV["SKYLIGHT_DISABLE_DEV_WARNING"] =~ /^true$/i
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def disable_env_warning?
|
|
38
|
+
disable_env_warning
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reload
|
|
42
|
+
config = File.exist?(file_path) ? YAML.load_file(file_path) : false
|
|
43
|
+
return unless config
|
|
44
|
+
|
|
45
|
+
self.disable_dev_warning = !!config["disable_dev_warning"]
|
|
46
|
+
self.disable_env_warning = !!config["disable_env_warning"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def save
|
|
50
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
|
51
|
+
File.open(file_path, "w") { |f| f.puts YAML.dump(to_hash) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_hash
|
|
55
|
+
{ "disable_dev_warning" => disable_dev_warning, "disable_env_warning" => disable_env_warning }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Util
|
|
3
|
+
# Helpers to reduce memory allocation
|
|
4
|
+
module AllocationFree
|
|
5
|
+
# Find an item in an array without allocation.
|
|
6
|
+
#
|
|
7
|
+
# @param array [Array] the array to search
|
|
8
|
+
# @yield a block called against each item until a match is found
|
|
9
|
+
# @yieldparam item an item from the array
|
|
10
|
+
# @yieldreturn [Boolean] whether `item` matches the criteria
|
|
11
|
+
# return the found item or nil, if nothing found
|
|
12
|
+
def array_find(array)
|
|
13
|
+
i = 0
|
|
14
|
+
|
|
15
|
+
while i < array.size
|
|
16
|
+
item = array[i]
|
|
17
|
+
return item if yield item
|
|
18
|
+
|
|
19
|
+
i += 1
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Util
|
|
3
|
+
# A more precise clock
|
|
4
|
+
class Clock
|
|
5
|
+
def self.use_native!
|
|
6
|
+
class_eval do
|
|
7
|
+
def tick
|
|
8
|
+
native_hrtime
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# rubocop:disable Lint/DuplicateMethods
|
|
14
|
+
def tick
|
|
15
|
+
now = Time.now
|
|
16
|
+
(now.to_i * 1_000_000_000) + (now.usec * 1_000)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# rubocop:enable Lint/DuplicateMethods
|
|
20
|
+
|
|
21
|
+
# TODO: rename to secs
|
|
22
|
+
def absolute_secs
|
|
23
|
+
Time.now.to_i
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# TODO: remove
|
|
27
|
+
def nanos
|
|
28
|
+
tick
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# TODO: remove
|
|
32
|
+
def secs
|
|
33
|
+
nanos / 1_000_000_000
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class << self
|
|
37
|
+
def absolute_secs
|
|
38
|
+
default.absolute_secs
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def nanos
|
|
42
|
+
default.nanos
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def secs
|
|
46
|
+
default.secs
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def default
|
|
50
|
+
@default ||= Clock.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attr_writer :default
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "uri"
|
|
3
4
|
|
|
4
5
|
module Skylight
|
|
@@ -6,16 +7,17 @@ module Skylight
|
|
|
6
7
|
class Component
|
|
7
8
|
attr_accessor :environment, :name
|
|
8
9
|
|
|
9
|
-
NAME_FORMAT = /\A[a-zA-Z0-9_-]+\z
|
|
10
|
+
NAME_FORMAT = /\A[a-zA-Z0-9_-]+\z/.freeze
|
|
10
11
|
DEFAULT_NAME = "web"
|
|
11
12
|
WORKER_NAME = "worker"
|
|
12
13
|
DEFAULT_ENVIRONMENT = "production"
|
|
13
14
|
|
|
14
15
|
def initialize(environment, name, force_worker: false)
|
|
15
16
|
@environment = environment || DEFAULT_ENVIRONMENT
|
|
16
|
-
@name
|
|
17
|
+
@name = resolve_name(name, force_worker)
|
|
17
18
|
|
|
18
19
|
raise ArgumentError, "environment can't be blank" if @environment.empty?
|
|
20
|
+
|
|
19
21
|
validate_string!(@environment, "environment")
|
|
20
22
|
validate_string!(@name, "name")
|
|
21
23
|
end
|
|
@@ -38,34 +40,32 @@ module Skylight
|
|
|
38
40
|
|
|
39
41
|
# keys here should match those from the main config
|
|
40
42
|
def as_json(*)
|
|
41
|
-
{
|
|
42
|
-
component: name,
|
|
43
|
-
env: environment
|
|
44
|
-
}
|
|
43
|
+
{ component: name, env: environment }
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
private
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
def program_name
|
|
49
|
+
$PROGRAM_NAME
|
|
50
|
+
end
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
def argv
|
|
53
|
+
ARGV
|
|
54
|
+
end
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
def resolve_name(given_name, force_worker)
|
|
57
|
+
# don't allow workers to be called 'web'
|
|
58
|
+
return WORKER_NAME if force_worker && (given_name.nil? || given_name == DEFAULT_NAME)
|
|
59
|
+
return DEFAULT_NAME if given_name.nil?
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
given_name
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def validate_string!(string, kind)
|
|
65
|
+
return true if string =~ NAME_FORMAT
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
raise ArgumentError, "#{kind} can only contain lowercase letters, numbers, and dashes"
|
|
68
|
-
end
|
|
67
|
+
raise ArgumentError, "#{kind} can only contain lowercase letters, numbers, and dashes"
|
|
68
|
+
end
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
end
|
data/lib/skylight/util/deploy.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require "json"
|
|
2
|
-
require "skylight/
|
|
2
|
+
require "skylight/util/logging"
|
|
3
3
|
|
|
4
4
|
module Skylight
|
|
5
5
|
module Util
|
|
@@ -13,8 +13,7 @@ module Skylight
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
class EmptyDeploy
|
|
16
|
-
attr_reader :config
|
|
17
|
-
attr_reader :timestamp
|
|
16
|
+
attr_reader :config, :timestamp
|
|
18
17
|
|
|
19
18
|
def initialize(config)
|
|
20
19
|
@config = config
|
|
@@ -35,8 +34,8 @@ module Skylight
|
|
|
35
34
|
|
|
36
35
|
def to_query_hash
|
|
37
36
|
hash = {
|
|
38
|
-
timestamp:
|
|
39
|
-
deploy_id:
|
|
37
|
+
timestamp: timestamp,
|
|
38
|
+
deploy_id: id.to_s[0..100] # Keep this sane
|
|
40
39
|
}
|
|
41
40
|
hash[:git_sha] = git_sha.to_s[0..40] if git_sha # A valid SHA will never exceed 40
|
|
42
41
|
hash[:description] = description[0..255] if description # Avoid massive descriptions
|
|
@@ -45,25 +44,23 @@ module Skylight
|
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
class DefaultDeploy < EmptyDeploy
|
|
48
|
-
include
|
|
47
|
+
include Util::Logging
|
|
49
48
|
|
|
50
49
|
def initialize(*)
|
|
51
50
|
super
|
|
52
|
-
if description && !id
|
|
53
|
-
warn "The configured deploy will be ignored as an id or git_sha must be provided."
|
|
54
|
-
end
|
|
51
|
+
warn "The configured deploy will be ignored as an id or git_sha must be provided." if description && !id
|
|
55
52
|
end
|
|
56
53
|
|
|
57
54
|
def id
|
|
58
|
-
config.get(:
|
|
55
|
+
config.get(:"deploy.id") || git_sha
|
|
59
56
|
end
|
|
60
57
|
|
|
61
58
|
def git_sha
|
|
62
|
-
config.get(:
|
|
59
|
+
config.get(:"deploy.git_sha")
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
def description
|
|
66
|
-
config.get(:
|
|
63
|
+
config.get(:"deploy.description")
|
|
67
64
|
end
|
|
68
65
|
end
|
|
69
66
|
|
|
@@ -87,15 +84,13 @@ module Skylight
|
|
|
87
84
|
|
|
88
85
|
private
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
def get_info
|
|
88
|
+
info_path = config[:"heroku.dyno_info_path"]
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
info["release"]
|
|
96
|
-
end
|
|
97
|
-
end
|
|
90
|
+
if File.exist?(info_path) && (info = JSON.parse(File.read(info_path)))
|
|
91
|
+
info["release"]
|
|
98
92
|
end
|
|
93
|
+
end
|
|
99
94
|
end
|
|
100
95
|
|
|
101
96
|
class GitDeploy < EmptyDeploy
|
|
@@ -108,12 +103,12 @@ module Skylight
|
|
|
108
103
|
|
|
109
104
|
private
|
|
110
105
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
end
|
|
106
|
+
def get_info
|
|
107
|
+
Dir.chdir(config.root) do
|
|
108
|
+
info = `git log -1 --pretty="%H %s" 2>&1`
|
|
109
|
+
info.split(" ", 2).map(&:strip) if $CHILD_STATUS.success?
|
|
116
110
|
end
|
|
111
|
+
end
|
|
117
112
|
end
|
|
118
113
|
|
|
119
114
|
DEPLOY_TYPES = [DefaultDeploy, HerokuDeploy, GitDeploy].freeze
|