skylight-core 4.1.2 → 4.3.0

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: 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