skylight-core 4.1.2 → 4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31230fe4577c70e86430a7577519b96ad6b9ea1f15c3f5338f68f3dce99eea4e
4
- data.tar.gz: 333e4a3a2482b6904e7c03a4f51155f220ebce8f13e602ceded65345275927af
3
+ metadata.gz: b967f11a876f41f4337a88fce73301993cf3160f862186b64a7d03a931b5ccb6
4
+ data.tar.gz: b33c7c1ecbfab7545ea7efdbb691371bc35a0f90ef786c8fb8a60e3db9f7a8a8
5
5
  SHA512:
6
- metadata.gz: ae7bf4433a8e87b1cfe4ffbebe3830078c187aa0708d381e62e790cbb086509f2b022e849294d85219958370a77e4e7d025298946f97a809ca017be1dda05d14
7
- data.tar.gz: 895244e11a0e17bacb76cd85e3c7d6b7d4226cc52f57b1ace4ba6ba440ad49d5573d5ec64afa2c61bfcdfcece813d3bc8eb0c74e07553b430e8fbfddbf17c32d
6
+ metadata.gz: '0285603cf5bdd82f8cfa8c9e0f3c54569b8e46051ccd16926e14498e024e5b92c9432b150422285625eee6b359aa46c036e483bda9a0cd7cf1dfd94edcc0d530'
7
+ data.tar.gz: 27fdc7636b44ddaece0b205e425a6546b5ef39ad0671a1719983b8133825940bded7c711abf5631eab83326c79fbd20adb0925c41aa934155670487cc5c4bb7a
@@ -36,6 +36,7 @@ module Skylight::Core
36
36
  # == Instrumenter ==
37
37
  "ENABLE_SEGMENTS" => :enable_segments,
38
38
  "ENABLE_SIDEKIQ" => :enable_sidekiq,
39
+ "SINATRA_ROUTE_PREFIXES" => :sinatra_route_prefixes,
39
40
 
40
41
  # == User config settings ==
41
42
  "USER_CONFIG_PATH" => :user_config_path,
@@ -54,6 +55,7 @@ module Skylight::Core
54
55
  log_sql_parse_errors: true,
55
56
  enable_segments: true,
56
57
  enable_sidekiq: false,
58
+ sinatra_route_prefixes: false,
57
59
  'heroku.dyno_info_path': "/etc/heroku/dyno"
58
60
  }
59
61
  end
@@ -411,6 +413,10 @@ module Skylight::Core
411
413
  !!get(:enable_sidekiq)
412
414
  end
413
415
 
416
+ def sinatra_route_prefixes?
417
+ !!get(:sinatra_route_prefixes)
418
+ end
419
+
414
420
  def user_config
415
421
  @user_config ||= UserConfig.new(self)
416
422
  end
@@ -92,18 +92,24 @@ 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
99
99
  end
100
100
 
101
+ if instrumenter.poisoned?
102
+ spawn_shutdown_thread!
103
+ return yield if block_given?
104
+ return
105
+ end
106
+
101
107
  cat ||= DEFAULT_CATEGORY
102
108
 
103
109
  if block_given?
104
- instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment) { |tr| yield tr }
110
+ instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment, component: component) { |tr| yield tr }
105
111
  else
106
- instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment)
112
+ instrumenter.trace(endpoint, cat, title, nil, meta: meta, segment: segment, component: component)
107
113
  end
108
114
  end
109
115
 
@@ -132,6 +138,32 @@ module Skylight
132
138
  instrumenter.instrument(category, title, desc, meta, &block)
133
139
  end
134
140
 
141
+ def mute
142
+ unless instrumenter
143
+ return yield if block_given?
144
+ return
145
+ end
146
+
147
+ instrumenter.mute do
148
+ yield if block_given?
149
+ end
150
+ end
151
+
152
+ def unmute
153
+ unless instrumenter
154
+ return yield if block_given?
155
+ return
156
+ end
157
+
158
+ instrumenter.unmute do
159
+ yield if block_given?
160
+ end
161
+ end
162
+
163
+ def muted?
164
+ instrumenter&.muted?
165
+ end
166
+
135
167
  def span_correlation_header(span)
136
168
  return unless instrumenter
137
169
  instrumenter.span_correlation_header(span)
@@ -162,6 +194,14 @@ module Skylight
162
194
  return unless instrumenter
163
195
  instrumenter.config
164
196
  end
197
+
198
+ # Runs the shutdown procedure in the background.
199
+ # This should do little more than unsubscribe from all ActiveSupport::Notifications
200
+ def spawn_shutdown_thread!
201
+ @shutdown_thread || const_get(:LOCK).synchronize do
202
+ @shutdown_thread ||= Thread.new { @instrumenter&.shutdown }
203
+ end
204
+ end
165
205
  end
166
206
  end
167
207
  end
@@ -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
@@ -214,6 +260,14 @@ module Skylight::Core
214
260
  trace.broken!
215
261
  end
216
262
 
263
+ def poison!
264
+ @poisoned = true
265
+ end
266
+
267
+ def poisoned?
268
+ @poisoned
269
+ end
270
+
217
271
  def done(span, meta = nil)
218
272
  return unless (trace = @trace_info.current)
219
273
  trace.done(span, meta)
@@ -244,12 +298,16 @@ module Skylight::Core
244
298
  native_submit_trace(trace)
245
299
  true
246
300
  rescue => e
247
- warn "failed to submit trace to worker; trace=%s, err=%s", trace.uuid, e
248
- t { "BACKTRACE:\n#{e.backtrace.join("\n")}" }
249
- false
301
+ handle_instrumenter_error(trace, e)
250
302
  end
251
303
  end
252
304
 
305
+ def handle_instrumenter_error(trace, e)
306
+ warn "failed to submit trace to worker; trace=%s, err=%s", trace.uuid, e
307
+ t { "BACKTRACE:\n#{e.backtrace.join("\n")}" }
308
+ false
309
+ end
310
+
253
311
  def ignore?(trace)
254
312
  config.ignored_endpoints.include?(trace.endpoint)
255
313
  end
@@ -259,9 +317,29 @@ module Skylight::Core
259
317
  [nil, sql]
260
318
  end
261
319
 
320
+ # Because GraphQL can return multiple results, each of which
321
+ # may have their own success/error states, we need to set the
322
+ # skylight segment as follows:
323
+ #
324
+ # - when all queries have errors: "error"
325
+ # - when some queries have errors: "<rendered format>+error"
326
+ # - when no queries have errors: "<rendered format>"
327
+ #
328
+ # <rendered format> will be determined by the Rails controller as usual.
329
+ # See Instrumenter#finalize_endpoint_segment for the actual segment/error assignment.
262
330
  def finalize_endpoint_segment(trace)
263
- return unless trace.segment
264
- trace.endpoint += "<sk-segment>#{trace.segment}</sk-segment>"
331
+ return unless (segment = trace.segment)
332
+
333
+ segment = case trace.compound_response_error_status
334
+ when :all
335
+ "error"
336
+ when :partial
337
+ "#{segment}+error"
338
+ else
339
+ segment
340
+ end
341
+
342
+ trace.endpoint += "<sk-segment>#{segment}</sk-segment>"
265
343
  end
266
344
  end
267
345
  end
@@ -44,17 +44,31 @@ module Skylight::Core
44
44
  end
45
45
  end
46
46
 
47
- def self.with_after_close(resp, &block)
47
+ def self.with_after_close(resp, debug_identifier: "unknown", &block)
48
48
  # Responses should be arrays but in some situations they aren't
49
49
  # e.g. https://github.com/ruby-grape/grape/issues/1041
50
- # The safest approach seems to be to rely on implicit destructuring
51
- # since that is currently what Rack::Lint does.
52
50
  # See also https://github.com/rack/rack/issues/1239
53
- status, headers, body = resp
54
51
 
52
+ unless resp.respond_to?(:to_ary)
53
+ if resp.respond_to?(:to_a)
54
+ log_target.warn("Rack response from \"#{debug_identifier}\" cannot be implicitly converted to an array. This is in violation of "\
55
+ "the Rack SPEC and will raise an error in future versions.")
56
+ resp = resp.to_a
57
+ else
58
+ log_target.error("Rack response from \"#{debug_identifier}\" cannot be converted to an array. This is in violation of the Rack SPEC "\
59
+ "and may cause problems with Skylight operation.")
60
+ return resp
61
+ end
62
+ end
63
+
64
+ status, headers, body = resp
55
65
  [status, headers, BodyProxy.new(body, &block)]
56
66
  end
57
67
 
68
+ def self.log_target
69
+ @log_target ||= (Skylight::Core::Fanout.registered.first&.config || Logger.new(STDERR))
70
+ end
71
+
58
72
  include Util::Logging
59
73
 
60
74
  # For Util::Logging
@@ -78,13 +92,13 @@ module Skylight::Core
78
92
  else
79
93
  begin
80
94
  t { "middleware beginning trace" }
81
- trace = instrumentable.trace(endpoint_name(env), "app.rack.request", nil, meta: endpoint_meta(env))
95
+ trace = instrumentable.trace(endpoint_name(env), "app.rack.request", nil, meta: endpoint_meta(env), component: :web)
82
96
  t { "middleware began trace=#{trace ? trace.uuid : nil}" }
83
97
 
84
98
  resp = @app.call(env)
85
99
 
86
100
  if trace
87
- Middleware.with_after_close(resp) { trace.submit }
101
+ Middleware.with_after_close(resp, debug_identifier: "Rack App: #{@app.class}") { trace.submit }
88
102
  else
89
103
  resp
90
104
  end
@@ -4,12 +4,12 @@ module Skylight::Core
4
4
  class Perform < Normalizer
5
5
  register "perform.active_job"
6
6
 
7
- DELIVERY_JOB = "ActionMailer::DeliveryJob".freeze
7
+ DELIVERY_JOB = /\AActionMailer::(Mail)?DeliveryJob\Z/.freeze
8
8
  DELAYED_JOB_WRAPPER = "ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper".freeze
9
9
 
10
10
  def self.normalize_title(job_instance)
11
11
  job_instance.class.name.to_s.tap do |str|
12
- if str == DELIVERY_JOB
12
+ if str.match(DELIVERY_JOB)
13
13
  mailer_class, mailer_method, * = job_instance.arguments
14
14
  return ["#{mailer_class}##{mailer_method}", str]
15
15
  end
@@ -25,7 +25,8 @@ module Skylight::Core
25
25
  end
26
26
 
27
27
  def get_namespace(endpoint)
28
- ::Grape::Namespace.joined_space(endpoint.namespace_stackable(:namespace))
28
+ # slice off preceding slash for data continuity
29
+ ::Grape::Namespace.joined_space_path(endpoint.namespace_stackable(:namespace)).to_s[1..-1]
29
30
  end
30
31
  end
31
32
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/inflector"
4
+
5
+ module Skylight::Core::Normalizers::GraphQL
6
+ # Some AS::N events in GraphQL are not super useful.
7
+ # We are purposefully ignoring the following keys (and you probably shouldn't add them):
8
+ # - "graphql.analyze_multiplex"
9
+ # - "graphql.execute_field" (very frequently called)
10
+ # - "graphql.execute_field_lazy"
11
+
12
+ class Base < Skylight::Core::Normalizers::Normalizer
13
+ ANONYMOUS = "[anonymous]".freeze
14
+ CAT = "app.graphql".freeze
15
+
16
+ if defined?(::GraphQL::VERSION) && Gem::Version.new(::GraphQL::VERSION) >= Gem::Version.new("1.10")
17
+ def self.register_graphql
18
+ register("#{key}.graphql")
19
+ end
20
+ else
21
+ def self.register_graphql
22
+ register("graphql.#{key}")
23
+ end
24
+ end
25
+
26
+ def self.inherited(klass)
27
+ klass.const_set(
28
+ :KEY,
29
+ ActiveSupport::Inflector.underscore(
30
+ ActiveSupport::Inflector.demodulize(klass.name)
31
+ ).freeze
32
+ )
33
+ end
34
+
35
+ def self.key
36
+ self::KEY
37
+ end
38
+
39
+ def normalize(_trace, _name, _payload)
40
+ [CAT, "graphql.#{key}", nil]
41
+ end
42
+
43
+ private
44
+
45
+ def key
46
+ self.class.key
47
+ end
48
+
49
+ def extract_query_name(query)
50
+ query&.context&.[](:skylight_endpoint) ||
51
+ query&.operation_name ||
52
+ ANONYMOUS
53
+ end
54
+ end
55
+
56
+ class Lex < Base
57
+ register_graphql
58
+ end
59
+
60
+ class Parse < Base
61
+ register_graphql
62
+ end
63
+
64
+ class Validate < Base
65
+ register_graphql
66
+ end
67
+
68
+ class ExecuteMultiplex < Base
69
+ register_graphql
70
+
71
+ def normalize_after(trace, _span, _name, payload)
72
+ # This is in normalize_after because the queries may not have
73
+ # an assigned operation name before they are executed.
74
+ # For example, if you send a single query with a defined operation name, e.g.:
75
+ # ```graphql
76
+ # query MyNamedQuery { user(id: 1) { name } }
77
+ # ```
78
+ # ... but do _not_ send the operationName request param, the GraphQL docs[1]
79
+ # specify that the executor should use the operation name from the definition.
80
+ #
81
+ # In graphql-ruby's case, the calculation of the operation name is lazy, and
82
+ # has not been done yet at the point where execute_multiplex starts.
83
+ # [1] https://graphql.org/learn/serving-over-http/#post-request
84
+ queries, has_errors = payload[:multiplex].queries.each_with_object([Set.new, Set.new]) do |query, (names, errors)|
85
+ names << extract_query_name(query)
86
+ errors << query.static_errors.any?
87
+ end
88
+
89
+ trace.endpoint = "graphql:#{queries.sort.join('+')}"
90
+ trace.compound_response_error_status = if has_errors.all?
91
+ :all
92
+ elsif !has_errors.none?
93
+ :partial
94
+ end
95
+ end
96
+ end
97
+
98
+ class AnalyzeQuery < Base
99
+ register_graphql
100
+ end
101
+
102
+ class ExecuteQuery < Base
103
+ register_graphql
104
+
105
+ def normalize(trace, _name, payload)
106
+ query_name = extract_query_name(payload[:query])
107
+
108
+ if query_name == ANONYMOUS
109
+ meta = { mute_children: true }
110
+ end
111
+
112
+ # This is probably always overriden by execute_multiplex#normalize_after,
113
+ # but in the case of a single query, it will be the same value anyway.
114
+ trace.endpoint = "graphql:#{query_name}"
115
+
116
+ [CAT, "graphql.#{key}: #{query_name}", nil, meta]
117
+ end
118
+ end
119
+
120
+ class ExecuteQueryLazy < ExecuteQuery
121
+ register_graphql
122
+
123
+ def normalize(trace, _name, payload)
124
+ if payload[:query]
125
+ super
126
+ elsif payload[:multiplex]
127
+ [CAT, "graphql.#{key}.multiplex", nil]
128
+ end
129
+ end
130
+ end
131
+ end
@@ -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}"
@@ -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
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Skylight::Core
4
+ module Probes
5
+ module GraphQL
6
+ module Instrumentation
7
+ def initialize(*, **)
8
+ super
9
+
10
+ return unless defined?(@tracers)
11
+
12
+ unless @tracers.include?(::GraphQL::Tracing::ActiveSupportNotificationsTracing)
13
+ @tracers << ::GraphQL::Tracing::ActiveSupportNotificationsTracing
14
+ end
15
+ end
16
+ end
17
+
18
+ class Probe
19
+ def install
20
+ tracing_klass_name = "::GraphQL::Tracing::ActiveSupportNotificationsTracing"
21
+ klasses_to_probe = %w(
22
+ ::GraphQL::Execution::Multiplex
23
+ ::GraphQL::Query
24
+ )
25
+
26
+ return unless ([tracing_klass_name] + klasses_to_probe).all?(&method(:safe_constantize))
27
+
28
+ klasses_to_probe.each do |klass_name|
29
+ safe_constantize(klass_name).prepend(Instrumentation)
30
+ end
31
+ end
32
+
33
+ def safe_constantize(klass_name)
34
+ Skylight::Core::Util::Inflector.safe_constantize(klass_name)
35
+ end
36
+ end
37
+ end
38
+
39
+ register(:graphql, "GraphQL", "graphql", GraphQL::Probe.new)
40
+ end
41
+ end
@@ -33,7 +33,7 @@ module Skylight::Core
33
33
  spans = Skylight::Core::Fanout.instrument(title: name, category: "#{category}")
34
34
  resp = call_without_sk(*args, &block)
35
35
 
36
- proxied_response = Skylight::Core::Middleware.with_after_close(resp) do
36
+ proxied_response = Skylight::Core::Middleware.with_after_close(resp, debug_identifier: "Middleware: #{name}") do
37
37
  Skylight::Core::Fanout.done(spans)
38
38
  end
39
39
  rescue Exception => err
@@ -8,10 +8,11 @@ module Skylight::Core
8
8
  DISABLED_KEY = :__skylight_net_http_disabled
9
9
 
10
10
  def self.disable
11
+ state_was = Thread.current[DISABLED_KEY]
11
12
  Thread.current[DISABLED_KEY] = true
12
13
  yield
13
14
  ensure
14
- Thread.current[DISABLED_KEY] = false
15
+ Thread.current[DISABLED_KEY] = state_was
15
16
  end
16
17
 
17
18
  def self.disabled?
@@ -36,8 +36,18 @@ module Skylight::Core
36
36
  dispatch_without_sk!(*args, &block).tap do
37
37
  Skylight::Core::Fanout.each_trace do |trace|
38
38
  # Set the endpoint name to the route name
39
- route = env["sinatra.route"]
40
- trace.endpoint = route if route
39
+ if (route = env["sinatra.route"])
40
+ # Include the app's mount point (if available)
41
+ script_name = trace.instrumenter.config.sinatra_route_prefixes? && env["SCRIPT_NAME"]
42
+
43
+ trace.endpoint =
44
+ if script_name && !script_name.empty?
45
+ verb, path = route.split(" ", 2)
46
+ "#{verb} [#{script_name}]#{path}"
47
+ else
48
+ route
49
+ end
50
+ end
41
51
  end
42
52
  end
43
53
  end
@@ -2,6 +2,11 @@ module Skylight
2
2
  module Core
3
3
  module Sidekiq
4
4
  def self.add_middleware(instrumentable)
5
+ unless defined?(::Sidekiq)
6
+ instrumentable.warn "Skylight for Sidekiq is active, but Sidekiq is not defined."
7
+ return
8
+ end
9
+
5
10
  ::Sidekiq.configure_server do |sidekiq_config|
6
11
  instrumentable.debug "Adding Sidekiq Middleware"
7
12
 
@@ -22,7 +27,7 @@ module Skylight
22
27
  def call(_worker, job, queue)
23
28
  t { "Sidekiq middleware beginning trace" }
24
29
  title = job["wrapped"] || job["class"]
25
- @instrumentable.trace(title, "app.sidekiq.worker", title, segment: queue) do |trace|
30
+ @instrumentable.trace(title, "app.sidekiq.worker", title, segment: queue, component: :worker) do |trace|
26
31
  begin
27
32
  yield
28
33
  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.3.0".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.3.0
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: 2020-04-14 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
@@ -223,6 +224,7 @@ files:
223
224
  - lib/skylight/core/probes/excon/middleware.rb
224
225
  - lib/skylight/core/probes/faraday.rb
225
226
  - lib/skylight/core/probes/grape.rb
227
+ - lib/skylight/core/probes/graphql.rb
226
228
  - lib/skylight/core/probes/httpclient.rb
227
229
  - lib/skylight/core/probes/middleware.rb
228
230
  - lib/skylight/core/probes/mongo.rb