skylight-core 4.1.2 → 4.2.0.beta

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31230fe4577c70e86430a7577519b96ad6b9ea1f15c3f5338f68f3dce99eea4e
4
- data.tar.gz: 333e4a3a2482b6904e7c03a4f51155f220ebce8f13e602ceded65345275927af
3
+ metadata.gz: 7836c1098f49c2f7b0f8581656367354e1ccec3c148f593a96df4deef011e9c9
4
+ data.tar.gz: 390b30be3374c7bcb827806c230d36772a96bef0f5692c83a2730d4face3f162
5
5
  SHA512:
6
- metadata.gz: ae7bf4433a8e87b1cfe4ffbebe3830078c187aa0708d381e62e790cbb086509f2b022e849294d85219958370a77e4e7d025298946f97a809ca017be1dda05d14
7
- data.tar.gz: 895244e11a0e17bacb76cd85e3c7d6b7d4226cc52f57b1ace4ba6ba440ad49d5573d5ec64afa2c61bfcdfcece813d3bc8eb0c74e07553b430e8fbfddbf17c32d
6
+ metadata.gz: ab419f49f172e95c075796d849ad643eb84032a98fe392b38101d93a3c6a7c2477d381f224752b0ae20ad607a7f883fdcbda4485f34f03e2ffaace66a8c1e18b
7
+ data.tar.gz: f1db03e7f479a3853036f6c7d3f94b7081cd5545cf4286d83393f8b19ff4e350f29cd91a869d93f0cb505d8a22dd4439165f477c27290aa5ce14b13649dc34f0
@@ -92,7 +92,7 @@ module Skylight
92
92
  end
93
93
 
94
94
  # Start a trace
95
- def trace(endpoint = nil, cat = nil, title = nil, meta: nil, segment: nil)
95
+ def trace(endpoint = nil, cat = nil, title = nil, meta: nil, segment: nil, component: nil)
96
96
  unless instrumenter
97
97
  return yield if block_given?
98
98
  return
@@ -101,9 +101,9 @@ module Skylight
101
101
  cat ||= DEFAULT_CATEGORY
102
102
 
103
103
  if block_given?
104
- instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment) { |tr| yield tr }
104
+ instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment, component: component) { |tr| yield tr }
105
105
  else
106
- instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment)
106
+ instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment, component: component)
107
107
  end
108
108
  end
109
109
 
@@ -132,6 +132,32 @@ module Skylight
132
132
  instrumenter.instrument(category, title, desc, meta, &block)
133
133
  end
134
134
 
135
+ def mute
136
+ unless instrumenter
137
+ return yield if block_given?
138
+ return
139
+ end
140
+
141
+ instrumenter.mute do
142
+ yield if block_given?
143
+ end
144
+ end
145
+
146
+ def unmute
147
+ unless instrumenter
148
+ return yield if block_given?
149
+ return
150
+ end
151
+
152
+ instrumenter.unmute do
153
+ yield if block_given?
154
+ end
155
+ end
156
+
157
+ def muted?
158
+ instrumenter&.muted?
159
+ end
160
+
135
161
  def span_correlation_header(span)
136
162
  return unless instrumenter
137
163
  instrumenter.span_correlation_header(span)
@@ -13,6 +13,7 @@ module Skylight::Core
13
13
  class TraceInfo
14
14
  def initialize(key = KEY)
15
15
  @key = key
16
+ @muted_key = "#{key}_muted"
16
17
  end
17
18
 
18
19
  def current
@@ -22,9 +23,20 @@ module Skylight::Core
22
23
  def current=(trace)
23
24
  Thread.current[@key] = trace
24
25
  end
26
+
27
+ # NOTE: This should only be set by the instrumenter, and only
28
+ # in the context of a `mute` block. Do not try to turn this
29
+ # flag on and off directly.
30
+ def muted=(val)
31
+ Thread.current[@muted_key] = val
32
+ end
33
+
34
+ def muted?
35
+ !!Thread.current[@muted_key]
36
+ end
25
37
  end
26
38
 
27
- attr_reader :uuid, :config, :gc, :trace_info
39
+ attr_reader :uuid, :config, :gc
28
40
 
29
41
  def self.trace_class
30
42
  Trace
@@ -51,6 +63,7 @@ module Skylight::Core
51
63
 
52
64
  key = "#{KEY}_#{self.class.trace_class.name}".gsub(/\W/, "_")
53
65
  @trace_info = @config[:trace_info] || TraceInfo.new(key)
66
+ @mutex = Mutex.new
54
67
  end
55
68
 
56
69
  def log_context
@@ -86,6 +99,45 @@ module Skylight::Core
86
99
  true
87
100
  end
88
101
 
102
+ def muted=(val)
103
+ @trace_info.muted = val
104
+ end
105
+
106
+ def muted?
107
+ @trace_info.muted?
108
+ end
109
+
110
+ def mute
111
+ old_muted = muted?
112
+ self.muted = true
113
+ yield if block_given?
114
+ ensure
115
+ self.muted = old_muted
116
+ end
117
+
118
+ def unmute
119
+ old_muted = muted?
120
+ self.muted = false
121
+ yield if block_given?
122
+ ensure
123
+ self.muted = old_muted
124
+ end
125
+
126
+ def silence_warnings(context)
127
+ @warnings_silenced || @mutex.synchronize do
128
+ @warnings_silenced ||= {}
129
+ end
130
+
131
+ @warnings_silenced[context] = true
132
+ end
133
+
134
+ def warnings_silenced?(context)
135
+ @warnings_silenced && @warnings_silenced[context]
136
+ end
137
+
138
+ alias_method :disable, :mute
139
+ alias_method :disabled?, :muted?
140
+
89
141
  def start!
90
142
  # We do this here since we can't report these issues via Gem install without stopping install entirely.
91
143
  check_install!
@@ -120,7 +172,7 @@ module Skylight::Core
120
172
  native_stop
121
173
  end
122
174
 
123
- def trace(endpoint, cat, title = nil, desc = nil, meta: nil, segment: nil)
175
+ def trace(endpoint, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
124
176
  # If a trace is already in progress, continue with that one
125
177
  if (trace = @trace_info.current)
126
178
  return yield(trace) if block_given?
@@ -128,7 +180,7 @@ module Skylight::Core
128
180
  end
129
181
 
130
182
  begin
131
- trace = self.class.trace_class.new(self, endpoint, Util::Clock.nanos, cat, title, desc, meta: meta, segment: segment)
183
+ trace = self.class.trace_class.new(self, endpoint, Util::Clock.nanos, cat, title, desc, meta: meta, segment: segment, component: component)
132
184
  rescue Exception => e
133
185
  log_error e.message
134
186
  t { e.backtrace.join("\n") }
@@ -147,17 +199,6 @@ module Skylight::Core
147
199
  end
148
200
  end
149
201
 
150
- def disable
151
- @disabled = true
152
- yield
153
- ensure
154
- @disabled = false
155
- end
156
-
157
- def disabled?
158
- defined?(@disabled) && @disabled
159
- end
160
-
161
202
  def self.match?(string, regex)
162
203
  @scanner ||= StringScanner.new("")
163
204
  @scanner.string = string
@@ -171,6 +212,11 @@ module Skylight::Core
171
212
  def instrument(cat, title = nil, desc = nil, meta = nil)
172
213
  raise ArgumentError, "cat is required" unless cat
173
214
 
215
+ if muted?
216
+ return yield if block_given?
217
+ return
218
+ end
219
+
174
220
  unless (trace = @trace_info.current)
175
221
  return yield if block_given?
176
222
  return
@@ -259,9 +305,29 @@ module Skylight::Core
259
305
  [nil, sql]
260
306
  end
261
307
 
308
+ # Because GraphQL can return multiple results, each of which
309
+ # may have their own success/error states, we need to set the
310
+ # skylight segment as follows:
311
+ #
312
+ # - when all queries have errors: "error"
313
+ # - when some queries have errors: "<rendered format>+error"
314
+ # - when no queries have errors: "<rendered format>"
315
+ #
316
+ # <rendered format> will be determined by the Rails controller as usual.
317
+ # See Instrumenter#finalize_endpoint_segment for the actual segment/error assignment.
262
318
  def finalize_endpoint_segment(trace)
263
- return unless trace.segment
264
- trace.endpoint += "<sk-segment>#{trace.segment}</sk-segment>"
319
+ return unless (segment = trace.segment)
320
+
321
+ segment = case trace.compound_response_error_status
322
+ when :all
323
+ "error"
324
+ when :partial
325
+ "#{segment}+error"
326
+ else
327
+ segment
328
+ end
329
+
330
+ trace.endpoint += "<sk-segment>#{segment}</sk-segment>"
265
331
  end
266
332
  end
267
333
  end
@@ -78,7 +78,7 @@ module Skylight::Core
78
78
  else
79
79
  begin
80
80
  t { "middleware beginning trace" }
81
- trace = instrumentable.trace(endpoint_name(env), "app.rack.request", nil, meta: endpoint_meta(env))
81
+ trace = instrumentable.trace(endpoint_name(env), "app.rack.request", nil, meta: endpoint_meta(env), component: :web)
82
82
  t { "middleware began trace=#{trace ? trace.uuid : nil}" }
83
83
 
84
84
  resp = @app.call(env)
@@ -113,6 +113,7 @@ module Skylight::Core
113
113
  grape/endpoint
114
114
  graphiti/resolve
115
115
  graphiti/render
116
+ graphql/base
116
117
  moped/query
117
118
  sequel/sql].each do |file|
118
119
  require "skylight/core/normalizers/#{file}"
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skylight::Core::Normalizers::GraphQL
4
+ # Some AS::N events in GraphQL are not super useful.
5
+ # We are purposefully ignoring the following keys (and you probably shouldn't add them):
6
+ # - "graphql.analyze_multiplex"
7
+ # - "graphql.execute_field" (very frequently called)
8
+ # - "graphql.execute_field_lazy"
9
+
10
+ class Base < Skylight::Core::Normalizers::Normalizer
11
+ ANONYMOUS = "[anonymous]".freeze
12
+ CAT = "app.graphql".freeze
13
+
14
+ def normalize(_trace, name, _payload)
15
+ [CAT, name, nil]
16
+ end
17
+ end
18
+
19
+ class Lex < Base
20
+ register "graphql.lex"
21
+ end
22
+
23
+ class Parse < Base
24
+ register "graphql.parse"
25
+ end
26
+
27
+ class Validate < Base
28
+ register "graphql.validate"
29
+ end
30
+
31
+ class ExecuteMultiplex < Base
32
+ register "graphql.execute_multiplex"
33
+ def normalize_after(trace, _span, _name, payload)
34
+ # This is in normalize_after because the queries may not have
35
+ # an assigned operation name before they are executed.
36
+ # For example, if you send a single query with a defined operation name, e.g.:
37
+ # ```graphql
38
+ # query MyNamedQuery { user(id: 1) { name } }
39
+ # ```
40
+ # ... but do _not_ send the operationName request param, the GraphQL docs[1]
41
+ # specify that the executor should use the operation name from the definition.
42
+ #
43
+ # In graphql-ruby's case, the calculation of the operation name is lazy, and
44
+ # has not been done yet at the point where execute_multiplex starts.
45
+ # [1] https://graphql.org/learn/serving-over-http/#post-request
46
+ queries, has_errors = payload[:multiplex].queries.each_with_object([Set.new, Set.new]) do |query, (names, errors)|
47
+ names << (query.operation_name || ANONYMOUS)
48
+ errors << query.static_errors.any?
49
+ end
50
+
51
+ trace.endpoint = "graphql:#{queries.sort.join('+')}"
52
+ trace.compound_response_error_status = if has_errors.all?
53
+ :all
54
+ elsif !has_errors.none?
55
+ :partial
56
+ end
57
+ end
58
+ end
59
+
60
+ class AnalyzeQuery < Base
61
+ register "graphql.analyze_query"
62
+ end
63
+
64
+ class ExecuteQuery < Base
65
+ register "graphql.execute_query"
66
+
67
+ def normalize(trace, name, payload)
68
+ query_name = payload[:query]&.operation_name || ANONYMOUS
69
+
70
+ if query_name == ANONYMOUS
71
+ meta = { mute_children: true }
72
+ end
73
+
74
+ # This is probably always overriden by execute_multiplex#normalize_after,
75
+ # but in the case of a single query, it will be the same value anyway.
76
+ trace.endpoint = "graphql:#{query_name}"
77
+
78
+ [CAT, "#{name}: #{query_name}", nil, meta]
79
+ end
80
+ end
81
+
82
+ class ExecuteQueryLazy < ExecuteQuery
83
+ register "graphql.execute_query_lazy"
84
+
85
+ def normalize(trace, name, payload)
86
+ if payload[:query]
87
+ super
88
+ elsif payload[:multiplex]
89
+ [CAT, "#{name}.multiplex", nil]
90
+ end
91
+ end
92
+ end
93
+ end
@@ -9,19 +9,24 @@ module Skylight::Core
9
9
  def append_info_to_payload(payload)
10
10
  append_info_to_payload_without_sk(payload)
11
11
 
12
- rendered_mime = begin
13
- if content_type.is_a?(Mime::Type)
14
- content_type
15
- elsif content_type.respond_to?(:to_s)
16
- type_str = content_type.to_s.split(';').first
17
- Mime::Type.lookup(type_str) unless type_str.blank?
18
- elsif respond_to?(:rendered_format) && rendered_format
19
- rendered_format
20
- end
12
+ payload[:sk_rendered_format] = sk_rendered_mime.try(:ref)
13
+ payload[:sk_variant] = request.respond_to?(:variant) ? request.variant : nil
14
+ end
15
+
16
+ def sk_rendered_mime
17
+ if respond_to?(:media_type)
18
+ mt = media_type
19
+ return mt && Mime::Type.lookup(mt)
21
20
  end
22
21
 
23
- payload[:sk_rendered_format] = rendered_mime.try(:ref)
24
- payload[:sk_variant] = request.respond_to?(:variant) ? request.variant : nil
22
+ if content_type.is_a?(Mime::Type)
23
+ content_type
24
+ elsif content_type.respond_to?(:to_s)
25
+ type_str = content_type.to_s.split(';').first
26
+ Mime::Type.lookup(type_str) unless type_str.blank?
27
+ elsif respond_to?(:rendered_format) && rendered_format
28
+ rendered_format
29
+ end
25
30
  end
26
31
  end
27
32
  end
@@ -9,7 +9,7 @@ module Skylight::Core
9
9
  alias execute_without_sk execute
10
10
 
11
11
  def execute(*args)
12
- Skylight.trace(TITLE, "app.job.execute") do |trace|
12
+ Skylight.trace(TITLE, "app.job.execute", component: :worker) do |trace|
13
13
  # See normalizers/active_job/perform for endpoint/segment assignment
14
14
  begin
15
15
  execute_without_sk(*args)
@@ -24,7 +24,7 @@ module Skylight::Core
24
24
  UNKNOWN
25
25
  end
26
26
 
27
- Skylight.trace(handler_name, "app.delayed_job.worker", "Delayed::Worker#run", segment: job.queue) do
27
+ Skylight.trace(handler_name, "app.delayed_job.worker", "Delayed::Worker#run", component: :worker, segment: job.queue) do
28
28
  run_without_sk(job, *args)
29
29
  end
30
30
  end
@@ -22,7 +22,7 @@ module Skylight
22
22
  def call(_worker, job, queue)
23
23
  t { "Sidekiq middleware beginning trace" }
24
24
  title = job["wrapped"] || job["class"]
25
- @instrumentable.trace(title, "app.sidekiq.worker", title, segment: queue) do |trace|
25
+ @instrumentable.trace(title, "app.sidekiq.worker", title, segment: queue, component: :worker) do |trace|
26
26
  begin
27
27
  yield
28
28
  rescue Exception # includes Sidekiq::Shutdown
@@ -50,14 +50,12 @@ module Skylight::Core
50
50
 
51
51
  while (curr = trace.notifications.pop)
52
52
  next unless curr.name == name
53
- begin
54
- normalize_after(trace, curr.span, name, payload)
55
- ensure
56
- meta = {}
57
- meta[:exception] = payload[:exception] if payload[:exception]
58
- meta[:exception_object] = payload[:exception_object] if payload[:exception_object]
59
- trace.done(curr.span, meta) if curr.span
60
- end
53
+
54
+ meta = {}
55
+ meta[:exception] = payload[:exception] if payload[:exception]
56
+ meta[:exception_object] = payload[:exception_object] if payload[:exception_object]
57
+ trace.done(curr.span, meta) if curr.span
58
+ normalize_after(trace, curr.span, name, payload)
61
59
  return
62
60
  end
63
61
  rescue Exception => e
@@ -6,14 +6,14 @@ module Skylight::Core
6
6
 
7
7
  include Util::Logging
8
8
 
9
- attr_reader :instrumenter, :endpoint, :notifications, :meta
10
- attr_accessor :uuid, :segment
9
+ attr_reader :instrumenter, :endpoint, :segment, :notifications, :meta
10
+ attr_accessor :uuid
11
11
 
12
- def self.new(instrumenter, endpoint, start, cat, title = nil, desc = nil, meta: nil, segment: nil)
12
+ def self.new(instrumenter, endpoint, start, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
13
13
  uuid = SecureRandom.uuid
14
14
  inst = native_new(normalize_time(start), uuid, endpoint, meta)
15
15
  inst.uuid = uuid
16
- inst.send(:initialize, instrumenter, cat, title, desc, meta)
16
+ inst.send(:initialize, instrumenter, cat, title, desc, meta, component: component)
17
17
  inst.endpoint = endpoint
18
18
  inst.segment = segment
19
19
  inst
@@ -26,7 +26,7 @@ module Skylight::Core
26
26
  (time.to_i / 100_000).to_i
27
27
  end
28
28
 
29
- def initialize(instrumenter, cat, title, desc, meta)
29
+ def initialize(instrumenter, cat, title, desc, meta, **)
30
30
  raise ArgumentError, "instrumenter is required" unless instrumenter
31
31
 
32
32
  @instrumenter = instrumenter
@@ -51,14 +51,32 @@ module Skylight::Core
51
51
  end
52
52
 
53
53
  def endpoint=(value)
54
+ if muted?
55
+ maybe_warn(:endpoint_set_muted, "tried to set endpoint name while muted")
56
+ return
57
+ end
54
58
  @endpoint = value
55
59
  native_set_endpoint(value)
56
60
  end
57
61
 
62
+ def segment=(value)
63
+ if muted?
64
+ maybe_warn(:segment_set_muted, "tried to set segment name while muted")
65
+ return
66
+ end
67
+ @segment = value
68
+ end
69
+
70
+ attr_accessor :compound_response_error_status
71
+
58
72
  def config
59
73
  @instrumenter.config
60
74
  end
61
75
 
76
+ def muted?
77
+ !!@child_instrumentation_muted_by || @instrumenter.muted?
78
+ end
79
+
62
80
  def broken?
63
81
  !!@broken
64
82
  end
@@ -69,7 +87,7 @@ module Skylight::Core
69
87
  end
70
88
 
71
89
  def record(cat, title = nil, desc = nil)
72
- return if broken?
90
+ return if muted? || broken?
73
91
 
74
92
  title.freeze if title.is_a?(String)
75
93
  desc.freeze if desc.is_a?(String)
@@ -87,6 +105,7 @@ module Skylight::Core
87
105
  end
88
106
 
89
107
  def instrument(cat, title = nil, desc = nil, meta = nil)
108
+ return if muted?
90
109
  return if broken?
91
110
  t { "instrument: #{cat}, #{title}" }
92
111
 
@@ -119,10 +138,9 @@ module Skylight::Core
119
138
  # `span` will be `nil` if we failed to start instrumenting, such as in
120
139
  # the case of too many spans in a request.
121
140
  return unless span
122
-
123
141
  return if broken?
124
142
 
125
- if meta && meta[:defer]
143
+ if meta&.[](:defer)
126
144
  deferred_spans[span] ||= (Util::Clock.nanos - gc_time)
127
145
  return
128
146
  end
@@ -199,6 +217,8 @@ module Skylight::Core
199
217
  def start(time, cat, title, desc, meta, opts = {})
200
218
  time = self.class.normalize_time(time) unless opts[:normalize] == false
201
219
 
220
+ mute_children = meta&.delete(:mute_children)
221
+
202
222
  sp = native_start_span(time, cat.to_s)
203
223
  native_span_set_title(sp, title.to_s) if title
204
224
  native_span_set_description(sp, desc.to_s) if desc
@@ -208,9 +228,18 @@ module Skylight::Core
208
228
  @spans << sp
209
229
  t { "started span: #{sp} - #{cat}, #{title}" }
210
230
 
231
+ if mute_children
232
+ t { "muting child instrumentation for span=#{sp}" }
233
+ mute_child_instrumentation(sp)
234
+ end
235
+
211
236
  sp
212
237
  end
213
238
 
239
+ def mute_child_instrumentation(span)
240
+ @child_instrumentation_muted_by = span
241
+ end
242
+
214
243
  # Middleware spans that were interrupted by a throw/catch should be cached here.
215
244
  # keys: span ids
216
245
  # values: nsec timestamp at which the span was cached here.
@@ -237,6 +266,10 @@ module Skylight::Core
237
266
  def normalized_stop(span, time)
238
267
  time = self.class.normalize_time(time)
239
268
  native_stop_span(span, time)
269
+
270
+ if @child_instrumentation_muted_by == span
271
+ @child_instrumentation_muted_by = nil # restart instrumenting
272
+ end
240
273
  end
241
274
 
242
275
  # Originally extracted from `stop`.
@@ -271,5 +304,16 @@ module Skylight::Core
271
304
  @gc.update
272
305
  @gc.time
273
306
  end
307
+
308
+ def maybe_warn(context, msg)
309
+ return if warnings_silenced?(context)
310
+ instrumenter.silence_warnings(context)
311
+
312
+ warn(msg)
313
+ end
314
+
315
+ def warnings_silenced?(context)
316
+ instrumenter.warnings_silenced?(context)
317
+ end
274
318
  end
275
319
  end
@@ -1,5 +1,5 @@
1
1
  module Skylight
2
2
  module Core
3
- VERSION = "4.1.2".freeze
3
+ VERSION = "4.2.0-beta".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skylight-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.2
4
+ version: 4.2.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tilde, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-27 00:00:00.000000000 Z
11
+ date: 2019-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -204,6 +204,7 @@ files:
204
204
  - lib/skylight/core/normalizers/grape/format_response.rb
205
205
  - lib/skylight/core/normalizers/graphiti/render.rb
206
206
  - lib/skylight/core/normalizers/graphiti/resolve.rb
207
+ - lib/skylight/core/normalizers/graphql/base.rb
207
208
  - lib/skylight/core/normalizers/moped/query.rb
208
209
  - lib/skylight/core/normalizers/render.rb
209
210
  - lib/skylight/core/normalizers/sequel/sql.rb
@@ -263,9 +264,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
263
264
  version: '2.3'
264
265
  required_rubygems_version: !ruby/object:Gem::Requirement
265
266
  requirements:
266
- - - ">="
267
+ - - ">"
267
268
  - !ruby/object:Gem::Version
268
- version: '0'
269
+ version: 1.3.1
269
270
  requirements: []
270
271
  rubygems_version: 3.0.3
271
272
  signing_key: