skylight-core 4.1.2 → 4.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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: