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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +420 -331
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +2 -8
  5. data/ERRORS.md +3 -0
  6. data/LICENSE.md +7 -17
  7. data/README.md +1 -1
  8. data/ext/extconf.rb +61 -56
  9. data/ext/libskylight.yml +8 -6
  10. data/ext/skylight_native.c +26 -100
  11. data/lib/skylight/api.rb +32 -21
  12. data/lib/skylight/cli/doctor.rb +64 -65
  13. data/lib/skylight/cli/helpers.rb +19 -19
  14. data/lib/skylight/cli/merger.rb +142 -138
  15. data/lib/skylight/cli.rb +48 -46
  16. data/lib/skylight/config.rb +640 -201
  17. data/lib/skylight/data/cacert.pem +730 -1023
  18. data/lib/skylight/deprecation.rb +17 -0
  19. data/lib/skylight/errors.rb +26 -9
  20. data/lib/skylight/extensions/source_location.rb +291 -0
  21. data/lib/skylight/extensions.rb +95 -0
  22. data/lib/skylight/formatters/http.rb +18 -0
  23. data/lib/skylight/gc.rb +99 -0
  24. data/lib/skylight/helpers.rb +81 -36
  25. data/lib/skylight/instrumenter.rb +336 -18
  26. data/lib/skylight/middleware.rb +147 -1
  27. data/lib/skylight/native.rb +60 -12
  28. data/lib/skylight/native_ext_fetcher.rb +13 -14
  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 +87 -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 +20 -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 +41 -0
  72. data/lib/skylight/normalizers.rb +157 -0
  73. data/lib/skylight/probes/action_controller.rb +52 -0
  74. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  75. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  76. data/lib/skylight/probes/action_dispatch.rb +2 -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/active_record_async.rb +96 -0
  82. data/lib/skylight/probes/delayed_job.rb +144 -0
  83. data/lib/skylight/probes/elasticsearch.rb +36 -0
  84. data/lib/skylight/probes/excon/middleware.rb +65 -0
  85. data/lib/skylight/probes/excon.rb +25 -0
  86. data/lib/skylight/probes/faraday.rb +23 -0
  87. data/lib/skylight/probes/graphql.rb +38 -0
  88. data/lib/skylight/probes/httpclient.rb +44 -0
  89. data/lib/skylight/probes/middleware.rb +135 -0
  90. data/lib/skylight/probes/mongo.rb +156 -0
  91. data/lib/skylight/probes/mongoid.rb +13 -0
  92. data/lib/skylight/probes/net_http.rb +54 -0
  93. data/lib/skylight/probes/rack_builder.rb +37 -0
  94. data/lib/skylight/probes/redis.rb +51 -0
  95. data/lib/skylight/probes/sequel.rb +29 -0
  96. data/lib/skylight/probes/sinatra.rb +66 -0
  97. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  98. data/lib/skylight/probes/tilt.rb +25 -0
  99. data/lib/skylight/probes.rb +173 -0
  100. data/lib/skylight/railtie.rb +166 -28
  101. data/lib/skylight/sidekiq.rb +47 -0
  102. data/lib/skylight/sinatra.rb +1 -1
  103. data/lib/skylight/subscriber.rb +130 -0
  104. data/lib/skylight/test.rb +147 -0
  105. data/lib/skylight/trace.rb +325 -22
  106. data/lib/skylight/user_config.rb +58 -0
  107. data/lib/skylight/util/allocation_free.rb +26 -0
  108. data/lib/skylight/util/clock.rb +57 -0
  109. data/lib/skylight/util/component.rb +22 -22
  110. data/lib/skylight/util/deploy.rb +19 -24
  111. data/lib/skylight/util/gzip.rb +20 -0
  112. data/lib/skylight/util/http.rb +106 -113
  113. data/lib/skylight/util/instrumenter_method.rb +26 -0
  114. data/lib/skylight/util/logging.rb +136 -0
  115. data/lib/skylight/util/lru_cache.rb +36 -0
  116. data/lib/skylight/util/platform.rb +3 -7
  117. data/lib/skylight/util/ssl.rb +1 -25
  118. data/lib/skylight/util.rb +12 -0
  119. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  120. data/lib/skylight/version.rb +5 -1
  121. data/lib/skylight/vm/gc.rb +60 -0
  122. data/lib/skylight.rb +201 -14
  123. metadata +134 -18
@@ -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
@@ -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 = resolve_name(name, force_worker)
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
- def program_name
50
- $PROGRAM_NAME
51
- end
48
+ def program_name
49
+ $PROGRAM_NAME
50
+ end
52
51
 
53
- def argv
54
- ARGV
55
- end
52
+ def argv
53
+ ARGV
54
+ end
56
55
 
57
- def resolve_name(given_name, force_worker)
58
- # don't allow workers to be called 'web'
59
- return WORKER_NAME if force_worker && (given_name.nil? || given_name == DEFAULT_NAME)
60
- return DEFAULT_NAME if given_name.nil?
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
- given_name
63
- end
61
+ given_name
62
+ end
63
+
64
+ def validate_string!(string, kind)
65
+ return true if string =~ NAME_FORMAT
64
66
 
65
- def validate_string!(string, kind)
66
- return true if string =~ NAME_FORMAT
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
@@ -1,5 +1,5 @@
1
1
  require "json"
2
- require "skylight/core/util/logging"
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: timestamp,
39
- deploy_id: id.to_s[0..100] # Keep this sane
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 Core::Util::Logging
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(:'deploy.id') || git_sha
55
+ config.get(:"deploy.id") || git_sha
59
56
  end
60
57
 
61
58
  def git_sha
62
- config.get(:'deploy.git_sha')
59
+ config.get(:"deploy.git_sha")
63
60
  end
64
61
 
65
62
  def description
66
- config.get(:'deploy.description')
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
- def get_info
91
- info_path = config[:'heroku.dyno_info_path']
87
+ def get_info
88
+ info_path = config[:"heroku.dyno_info_path"]
92
89
 
93
- if File.exist?(info_path)
94
- if (info = JSON.parse(File.read(info_path)))
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
- def get_info
112
- Dir.chdir(config.root) do
113
- info = `git log -1 --pretty="%H %s" 2>&1`
114
- info.split(" ", 2).map(&:strip) if $CHILD_STATUS.success?
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