skylight 4.3.2 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +399 -336
  3. data/CLA.md +1 -1
  4. data/CONTRIBUTING.md +2 -8
  5. data/LICENSE.md +7 -17
  6. data/README.md +1 -1
  7. data/ext/extconf.rb +45 -56
  8. data/ext/libskylight.yml +10 -6
  9. data/ext/skylight_native.c +22 -99
  10. data/lib/skylight.rb +201 -14
  11. data/lib/skylight/api.rb +32 -21
  12. data/lib/skylight/cli.rb +48 -46
  13. data/lib/skylight/cli/doctor.rb +62 -63
  14. data/lib/skylight/cli/helpers.rb +19 -19
  15. data/lib/skylight/cli/merger.rb +142 -138
  16. data/lib/skylight/config.rb +634 -199
  17. data/lib/skylight/deprecation.rb +17 -0
  18. data/lib/skylight/errors.rb +23 -9
  19. data/lib/skylight/extensions.rb +95 -0
  20. data/lib/skylight/extensions/source_location.rb +291 -0
  21. data/lib/skylight/formatters/http.rb +18 -0
  22. data/lib/skylight/gc.rb +99 -0
  23. data/lib/skylight/helpers.rb +81 -36
  24. data/lib/skylight/instrumenter.rb +336 -18
  25. data/lib/skylight/middleware.rb +134 -1
  26. data/lib/skylight/native.rb +60 -12
  27. data/lib/skylight/native_ext_fetcher.rb +13 -14
  28. data/lib/skylight/normalizers.rb +157 -0
  29. data/lib/skylight/normalizers/action_controller/process_action.rb +68 -0
  30. data/lib/skylight/normalizers/action_controller/send_file.rb +51 -0
  31. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  32. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  33. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  34. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  35. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  36. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  37. data/lib/skylight/normalizers/active_job/perform.rb +90 -0
  38. data/lib/skylight/normalizers/active_model_serializers/render.rb +32 -0
  39. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  40. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  41. data/lib/skylight/normalizers/active_storage.rb +28 -0
  42. data/lib/skylight/normalizers/active_support/cache.rb +11 -0
  43. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  50. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  51. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  52. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  53. data/lib/skylight/normalizers/coach/handler_finish.rb +44 -0
  54. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  55. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  56. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  57. data/lib/skylight/normalizers/default.rb +24 -0
  58. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  59. data/lib/skylight/normalizers/faraday/request.rb +38 -0
  60. data/lib/skylight/normalizers/grape/endpoint.rb +28 -0
  61. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  62. data/lib/skylight/normalizers/grape/endpoint_run.rb +39 -0
  63. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +20 -0
  64. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  65. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  66. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  67. data/lib/skylight/normalizers/graphql/base.rb +127 -0
  68. data/lib/skylight/normalizers/render.rb +79 -0
  69. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  70. data/lib/skylight/normalizers/shrine.rb +32 -0
  71. data/lib/skylight/normalizers/sql.rb +45 -0
  72. data/lib/skylight/probes.rb +173 -0
  73. data/lib/skylight/probes/action_controller.rb +52 -0
  74. data/lib/skylight/probes/action_dispatch.rb +2 -0
  75. data/lib/skylight/probes/action_dispatch/request_id.rb +33 -0
  76. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +30 -0
  77. data/lib/skylight/probes/action_view.rb +42 -0
  78. data/lib/skylight/probes/active_job.rb +27 -0
  79. data/lib/skylight/probes/active_job_enqueue.rb +35 -0
  80. data/lib/skylight/probes/active_model_serializers.rb +50 -0
  81. data/lib/skylight/probes/delayed_job.rb +144 -0
  82. data/lib/skylight/probes/elasticsearch.rb +36 -0
  83. data/lib/skylight/probes/excon.rb +25 -0
  84. data/lib/skylight/probes/excon/middleware.rb +65 -0
  85. data/lib/skylight/probes/faraday.rb +23 -0
  86. data/lib/skylight/probes/graphql.rb +38 -0
  87. data/lib/skylight/probes/httpclient.rb +44 -0
  88. data/lib/skylight/probes/middleware.rb +135 -0
  89. data/lib/skylight/probes/mongo.rb +156 -0
  90. data/lib/skylight/probes/mongoid.rb +13 -0
  91. data/lib/skylight/probes/net_http.rb +54 -0
  92. data/lib/skylight/probes/redis.rb +51 -0
  93. data/lib/skylight/probes/sequel.rb +29 -0
  94. data/lib/skylight/probes/sinatra.rb +66 -0
  95. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  96. data/lib/skylight/probes/tilt.rb +25 -0
  97. data/lib/skylight/railtie.rb +157 -27
  98. data/lib/skylight/sidekiq.rb +47 -0
  99. data/lib/skylight/subscriber.rb +108 -0
  100. data/lib/skylight/test.rb +151 -0
  101. data/lib/skylight/trace.rb +325 -22
  102. data/lib/skylight/user_config.rb +58 -0
  103. data/lib/skylight/util.rb +12 -0
  104. data/lib/skylight/util/allocation_free.rb +26 -0
  105. data/lib/skylight/util/clock.rb +57 -0
  106. data/lib/skylight/util/component.rb +22 -22
  107. data/lib/skylight/util/deploy.rb +16 -21
  108. data/lib/skylight/util/gzip.rb +20 -0
  109. data/lib/skylight/util/http.rb +106 -113
  110. data/lib/skylight/util/instrumenter_method.rb +26 -0
  111. data/lib/skylight/util/logging.rb +136 -0
  112. data/lib/skylight/util/lru_cache.rb +36 -0
  113. data/lib/skylight/util/platform.rb +1 -5
  114. data/lib/skylight/util/ssl.rb +1 -25
  115. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  116. data/lib/skylight/version.rb +5 -1
  117. data/lib/skylight/vm/gc.rb +60 -0
  118. metadata +126 -13
@@ -0,0 +1,18 @@
1
+ module Skylight
2
+ module Formatters
3
+ module HTTP
4
+ # Build instrumentation options for HTTP queries
5
+ #
6
+ # @param [String] method HTTP method, e.g. get, post
7
+ # @param [String] scheme HTTP scheme, e.g. http, https
8
+ # @param [String] host Request host, e.g. example.com
9
+ # @param [String, Integer] port Request port
10
+ # @param [String] path Request path
11
+ # @param [String] query Request query string
12
+ # @return [Hash] a hash containing `:category`, `:title`, and `:annotations`
13
+ def self.build_opts(method, _scheme, host, _port, _path, _query)
14
+ { category: "api.http.#{method.downcase}", title: "#{method.upcase} #{host}", internal: true }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,99 @@
1
+ require "skylight/util/logging"
2
+
3
+ module Skylight
4
+ # @api private
5
+ class GC
6
+ METHODS = %i[enable total_time].freeze
7
+ TH_KEY = :SK_GC_CURR_WINDOW
8
+ MAX_COUNT = 1000
9
+ MAX_TIME = 30_000_000
10
+
11
+ include Util::Logging
12
+
13
+ attr_reader :config
14
+
15
+ def initialize(config, profiler)
16
+ @listeners = []
17
+ @config = config
18
+ @lock = Mutex.new
19
+ @time = 0
20
+
21
+ if METHODS.all? { |m| profiler.respond_to?(m) }
22
+ @profiler = profiler
23
+ @time = @profiler.total_time
24
+ else
25
+ debug "disabling GC profiling"
26
+ end
27
+ end
28
+
29
+ def enable
30
+ @profiler&.enable
31
+ end
32
+
33
+ # Total time in microseconds for GC over entire process lifetime
34
+ def total_time
35
+ @profiler ? @profiler.total_time : nil
36
+ end
37
+
38
+ def track
39
+ if @profiler
40
+ win = Window.new(self)
41
+
42
+ @lock.synchronize do
43
+ __update
44
+ @listeners << win
45
+
46
+ # Cleanup any listeners that might have leaked
47
+ @listeners.shift until @listeners[0].time < MAX_TIME
48
+
49
+ @listeners.shift if @listeners.length > MAX_COUNT
50
+ end
51
+
52
+ win
53
+ else
54
+ Window.new(nil)
55
+ end
56
+ end
57
+
58
+ def release(win)
59
+ @lock.synchronize { @listeners.delete(win) }
60
+ end
61
+
62
+ def update
63
+ @lock.synchronize { __update }
64
+
65
+ nil
66
+ end
67
+
68
+ private
69
+
70
+ def __update
71
+ time = @profiler.total_time
72
+ diff = time - @time
73
+ @time = time
74
+
75
+ @listeners.each { |l| l.add(diff) } if diff > 0
76
+ end
77
+
78
+ class Window
79
+ attr_reader :time
80
+
81
+ def initialize(global)
82
+ @global = global
83
+ @time = 0
84
+ end
85
+
86
+ def update
87
+ @global&.update
88
+ end
89
+
90
+ def add(time)
91
+ @time += time
92
+ end
93
+
94
+ def release
95
+ @global&.release(self)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -14,7 +14,7 @@ module Skylight
14
14
 
15
15
  if (opts = @__sk_instrument_next_method)
16
16
  @__sk_instrument_next_method = nil
17
- instrument_method(name, opts)
17
+ instrument_method(name, **opts)
18
18
  end
19
19
  end
20
20
 
@@ -24,7 +24,7 @@ module Skylight
24
24
 
25
25
  if (opts = @__sk_instrument_next_method)
26
26
  @__sk_instrument_next_method = nil
27
- instrument_class_method(name, opts)
27
+ instrument_class_method(name, **opts)
28
28
  end
29
29
  end
30
30
 
@@ -77,14 +77,12 @@ module Skylight
77
77
  # do_expensive_stuff
78
78
  # end
79
79
  # end
80
- def instrument_method(*args)
81
- opts = args.pop if args.last.is_a?(Hash)
82
-
80
+ def instrument_method(*args, **opts)
83
81
  if (name = args.pop)
84
82
  title = "#{self}##{name}"
85
- __sk_instrument_method_on(self, name, title, opts || {})
83
+ __sk_instrument_method_on(self, name, title, **opts)
86
84
  else
87
- @__sk_instrument_next_method = opts || {}
85
+ @__sk_instrument_next_method = opts
88
86
  end
89
87
  end
90
88
 
@@ -123,47 +121,94 @@ module Skylight
123
121
  #
124
122
  # instrument_class_method :my_method, title: 'Expensive work'
125
123
  # end
126
- def instrument_class_method(name, opts = {})
124
+ def instrument_class_method(name, **opts)
125
+ # NOTE: If the class is defined anonymously and then assigned to a variable this code
126
+ # will not be aware of the updated name.
127
127
  title = "#{self}.#{name}"
128
- __sk_instrument_method_on(__sk_singleton_class, name, title, opts || {})
128
+ __sk_instrument_method_on(__sk_singleton_class, name, title, **opts)
129
129
  end
130
130
 
131
131
  private
132
132
 
133
- def __sk_instrument_method_on(klass, name, title, opts)
134
- category = (opts[:category] || "app.method").to_s
135
- title = (opts[:title] || title).to_s
136
- desc = opts[:description].to_s if opts[:description]
133
+ HAS_ARGUMENT_FORWARDING = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
134
+
135
+ def __sk_instrument_method_on(klass, name, title, **opts)
136
+ category = (opts[:category] || "app.method").to_s
137
+ title = (opts[:title] || title).to_s
138
+ desc = opts[:description].to_s if opts[:description]
139
+
140
+ # NOTE: The source location logic happens before we have have a config so we can'
141
+ # check if source locations are enabled. However, it only happens once so the potential impact
142
+ # should be minimal. This would more appropriately belong to Extensions::SourceLocation,
143
+ # but as that is a runtime concern, and this happens at compile time, there isn't currently
144
+ # a clean way to turn this on and off. The absence of the extension will cause the
145
+ # source_file and source_line to be removed from the trace span before it is submitted.
146
+ source_file, source_line = klass.instance_method(name).source_location
137
147
 
138
- klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
139
- alias_method :"before_instrument_#{name}", :"#{name}"
148
+ # We should strongly prefer using the new argument-forwarding syntax (...) where available.
149
+ # In Ruby 2.7, the following are known to be syntax errors:
150
+ #
151
+ # - mixing positional arguments with argument forwarding (e.g., send(:method_name, ...))
152
+ # - calling a setter method with multiple arguments, unless dispatched via send or public_send.
153
+ #
154
+ # So it is possible, though not recommended, to define setter methods that take multiple arguments,
155
+ # keywords, and/or blocks. Unfortunately, this means that for setters, we still need to explicitly
156
+ # forward the different argument types.
157
+ is_setter_method = name.to_s.end_with?("=")
158
+
159
+ arg_string =
160
+ if HAS_ARGUMENT_FORWARDING
161
+ is_setter_method ? "*args, **kwargs, &blk" : "..."
162
+ else
163
+ "*args, &blk"
164
+ end
140
165
 
141
- def #{name}(*args, &blk)
142
- span = Skylight.instrument(
143
- category: :"#{category}",
144
- title: #{title.inspect},
145
- description: #{desc.inspect})
166
+ original_method_dispatch =
167
+ if is_setter_method
168
+ "self.send(:before_instrument_#{name}, #{arg_string})"
169
+ else
170
+ "before_instrument_#{name}(#{arg_string})"
171
+ end
146
172
 
147
- meta = {}
148
- begin
149
- send(:before_instrument_#{name}, *args, &blk)
150
- rescue Exception => e
151
- meta[:exception_object] = e
152
- raise e
153
- ensure
154
- Skylight.done(span, meta) if span
155
- end
156
- end
173
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
174
+ alias_method :"before_instrument_#{name}", :"#{name}" # alias_method :"before_instrument_process", :"process"
175
+ def #{name}(#{arg_string}) # def process(*args, **kwargs, &blk)
176
+ span = Skylight.instrument( # span = Skylight.instrument(
177
+ category: :"#{category}", # category: :"app.method",
178
+ title: #{title.inspect}, # title: "process",
179
+ description: #{desc.inspect}, # description: "Process data",
180
+ source_file: #{source_file.inspect}, # source_file: "myapp/lib/processor.rb",
181
+ source_line: #{source_line.inspect}) # source_line: 123)
182
+ #
183
+ meta = {} # meta = {}
184
+ #
185
+ begin # begin
186
+ #{original_method_dispatch} # self.before_instrument_process(...)
187
+ rescue Exception => e # rescue Exception => e
188
+ meta[:exception_object] = e # meta[:exception_object] = e
189
+ raise e # raise e
190
+ ensure # ensure
191
+ Skylight.done(span, meta) if span # Skylight.done(span, meta) if span
192
+ end # end
193
+ end # end
194
+ #
195
+ if protected_method_defined?(:"before_instrument_#{name}") # if protected_method_defined?(:"before_instrument_process")
196
+ protected :"#{name}" # protected :"process"
197
+ elsif private_method_defined?(:"before_instrument_#{name}") # elsif private_method_defined?(:"before_instrument_process")
198
+ private :"#{name}" # private :"process"
199
+ end # end
157
200
  RUBY
158
- end
201
+ end
159
202
 
160
- if respond_to?(:singleton_class)
161
- alias __sk_singleton_class singleton_class
162
- else
163
- def __sk_singleton_class
164
- class << self; self; end
203
+ if respond_to?(:singleton_class)
204
+ alias __sk_singleton_class singleton_class
205
+ else
206
+ def __sk_singleton_class
207
+ class << self
208
+ self
165
209
  end
166
210
  end
211
+ end
167
212
  end
168
213
 
169
214
  # @api private
@@ -1,36 +1,354 @@
1
+ require "strscan"
2
+ require "securerandom"
3
+ require "skylight/util/logging"
4
+ require "skylight/extensions"
5
+
1
6
  module Skylight
2
- class Instrumenter < Core::Instrumenter
3
- def self.trace_class
4
- Trace
7
+ # @api private
8
+ class Instrumenter
9
+ KEY = :__skylight_current_trace
10
+
11
+ include Util::Logging
12
+
13
+ class TraceInfo
14
+ def initialize(key = KEY)
15
+ @key = key
16
+ @muted_key = "#{key}_muted"
17
+ end
18
+
19
+ def current
20
+ Thread.current[@key]
21
+ end
22
+
23
+ def current=(trace)
24
+ Thread.current[@key] = trace
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
37
+ end
38
+
39
+ attr_reader :uuid, :config, :gc, :extensions
40
+
41
+ def self.native_new(_uuid, _config_env)
42
+ raise "not implemented"
43
+ end
44
+
45
+ def self.new(config)
46
+ config.validate!
47
+
48
+ uuid = SecureRandom.uuid
49
+ inst = native_new(uuid, config.to_native_env)
50
+ inst.send(:initialize, uuid, config)
51
+ inst
52
+ end
53
+
54
+ def initialize(uuid, config)
55
+ @uuid = uuid
56
+ @gc = config.gc
57
+ @config = config
58
+ @subscriber = Skylight::Subscriber.new(config, self)
59
+
60
+ @trace_info = @config[:trace_info] || TraceInfo.new(KEY)
61
+ @mutex = Mutex.new
62
+ @extensions = Skylight::Extensions::Collection.new(@config)
63
+ end
64
+
65
+ def enable_extension!(name)
66
+ @mutex.synchronize { extensions.enable!(name) }
67
+ end
68
+
69
+ def disable_extension!(name)
70
+ @mutex.synchronize { extensions.disable!(name) }
71
+ end
72
+
73
+ def extension_enabled?(name)
74
+ extensions.enabled?(name)
75
+ end
76
+
77
+ def log_context
78
+ @log_context ||= { inst: uuid }
79
+ end
80
+
81
+ def native_start
82
+ raise "not implemented"
5
83
  end
6
84
 
7
- def check_install!
85
+ def native_stop
86
+ raise "not implemented"
87
+ end
88
+
89
+ def native_track_desc(_endpoint, _description)
90
+ raise "not implemented"
91
+ end
92
+
93
+ def native_submit_trace(_trace)
94
+ raise "not implemented"
95
+ end
96
+
97
+ def current_trace
98
+ @trace_info.current
99
+ end
100
+
101
+ def current_trace=(trace)
102
+ t { "setting current_trace=#{trace ? trace.uuid : "nil"}; thread=#{Thread.current.object_id}" }
103
+ @trace_info.current = trace
104
+ end
105
+
106
+ def validate_installation
8
107
  # Warn if there was an error installing Skylight.
9
108
 
10
- if defined?(Skylight.check_install_errors)
11
- Skylight.check_install_errors(config)
12
- end
109
+ Skylight.check_install_errors(config) if defined?(Skylight.check_install_errors)
13
110
 
14
111
  if !Skylight.native? && defined?(Skylight.warn_skylight_native_missing)
15
112
  Skylight.warn_skylight_native_missing(config)
16
- return
113
+ return false
17
114
  end
115
+
116
+ true
117
+ end
118
+
119
+ def muted=(val)
120
+ @trace_info.muted = val
121
+ end
122
+
123
+ def muted?
124
+ @trace_info.muted?
18
125
  end
19
126
 
20
- def process_sql(sql)
21
- Skylight.lex_sql(sql)
22
- rescue SqlLexError => e
23
- if config[:log_sql_parse_errors]
24
- config.logger.error "[#{e.formatted_code}] Failed to extract binds from SQL query. " \
25
- "It's likely that this query uses more advanced syntax than we currently support. " \
26
- "sql=#{sql.inspect}"
127
+ def mute
128
+ old_muted = muted?
129
+ self.muted = true
130
+ yield if block_given?
131
+ ensure
132
+ self.muted = old_muted
133
+ end
134
+
135
+ def unmute
136
+ old_muted = muted?
137
+ self.muted = false
138
+ yield if block_given?
139
+ ensure
140
+ self.muted = old_muted
141
+ end
142
+
143
+ def silence_warnings(context)
144
+ @warnings_silenced || @mutex.synchronize { @warnings_silenced ||= {} }
145
+
146
+ @warnings_silenced[context] = true
147
+ end
148
+
149
+ def warnings_silenced?(context)
150
+ @warnings_silenced && @warnings_silenced[context]
151
+ end
152
+
153
+ alias disable mute
154
+ alias disabled? muted?
155
+
156
+ def start!
157
+ # We do this here since we can't report these issues via Gem install without stopping install entirely.
158
+ return unless validate_installation
159
+
160
+ t { "starting instrumenter" }
161
+
162
+ unless config.validate_with_server
163
+ log_error "invalid config"
164
+ return
165
+ end
166
+
167
+ t { "starting native instrumenter" }
168
+ unless native_start
169
+ warn "failed to start instrumenter"
170
+ return
27
171
  end
172
+
173
+ enable_extension!(:source_location) if @config.enable_source_locations?
174
+ config.gc.enable
175
+ @subscriber.register!
176
+
177
+ ActiveSupport::Notifications.instrument("started_instrumenter.skylight", instrumenter: self)
178
+
179
+ self
180
+ rescue Exception => e
181
+ log_error "failed to start instrumenter; msg=%s; config=%s", e.message, config.inspect
182
+ t { e.backtrace.join("\n") }
28
183
  nil
29
184
  end
30
185
 
31
- def handle_instrumenter_error(trace, e)
32
- poison! if e.is_a?(Skylight::InstrumenterUnrecoverableError)
33
- super
186
+ def shutdown
187
+ @subscriber.unregister!
188
+ native_stop
189
+ end
190
+
191
+ def trace(endpoint, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
192
+ # If a trace is already in progress, continue with that one
193
+ if (trace = @trace_info.current)
194
+ return yield(trace) if block_given?
195
+
196
+ return trace
197
+ end
198
+
199
+ begin
200
+ meta ||= {}
201
+ extensions.process_trace_meta(meta)
202
+ trace =
203
+ Trace.new(
204
+ self,
205
+ endpoint,
206
+ Skylight::Util::Clock.nanos,
207
+ cat,
208
+ title,
209
+ desc,
210
+ meta: meta,
211
+ segment: segment,
212
+ component: component
213
+ )
214
+ rescue Exception => e
215
+ log_error e.message
216
+ t { e.backtrace.join("\n") }
217
+ return
218
+ end
219
+
220
+ @trace_info.current = trace
221
+ return trace unless block_given?
222
+
223
+ begin
224
+ yield trace
225
+ ensure
226
+ @trace_info.current = nil
227
+ t { "instrumenter submitting trace; trace=#{trace.uuid}" }
228
+ trace.submit
229
+ end
230
+ end
231
+
232
+ def instrument(cat, title = nil, desc = nil, meta = nil)
233
+ raise ArgumentError, "cat is required" unless cat
234
+
235
+ if muted?
236
+ return yield if block_given?
237
+
238
+ return
239
+ end
240
+
241
+ unless (trace = @trace_info.current)
242
+ return yield if block_given?
243
+
244
+ return
245
+ end
246
+
247
+ cat = cat.to_s
248
+
249
+ unless Skylight::CATEGORY_REGEX.match?(cat)
250
+ warn "invalid skylight instrumentation category; trace=%s; value=%s", trace.uuid, cat
251
+ return yield if block_given?
252
+
253
+ return
254
+ end
255
+
256
+ cat = "other.#{cat}" unless Skylight::TIER_REGEX.match?(cat)
257
+
258
+ unless (sp = trace.instrument(cat, title, desc, meta))
259
+ return yield if block_given?
260
+
261
+ return
262
+ end
263
+
264
+ return sp unless block_given?
265
+
266
+ begin
267
+ yield sp
268
+ rescue Exception => e
269
+ meta ||= {}
270
+ meta[:exception] = [e.class.name, e.message]
271
+ meta[:exception_object] = e
272
+ raise e
273
+ ensure
274
+ trace.done(sp, meta)
275
+ end
276
+ end
277
+
278
+ def broken!
279
+ return unless (trace = @trace_info.current)
280
+
281
+ trace.broken!
282
+ end
283
+
284
+ def poison!
285
+ @poisoned = true
286
+ end
287
+
288
+ def poisoned?
289
+ @poisoned
290
+ end
291
+
292
+ def done(span, meta = nil)
293
+ return unless (trace = @trace_info.current)
294
+
295
+ trace.done(span, meta)
296
+ end
297
+
298
+ def process(trace)
299
+ t { fmt "processing trace=#{trace.uuid}" }
300
+
301
+ if ignore?(trace)
302
+ t { fmt "ignoring trace=#{trace.uuid}" }
303
+ return false
304
+ end
305
+
306
+ begin
307
+ finalize_endpoint_segment(trace)
308
+ native_submit_trace(trace)
309
+ true
310
+ rescue StandardError => e
311
+ handle_instrumenter_error(trace, e)
312
+ end
313
+ end
314
+
315
+ def handle_instrumenter_error(trace, err)
316
+ poison! if err.is_a?(Skylight::InstrumenterUnrecoverableError)
317
+
318
+ warn "failed to submit trace to worker; trace=%s, err=%s", trace.uuid, err
319
+ t { "BACKTRACE:\n#{err.backtrace.join("\n")}" }
320
+
321
+ false
322
+ end
323
+
324
+ def ignore?(trace)
325
+ config.ignored_endpoints.include?(trace.endpoint)
326
+ end
327
+
328
+ # Because GraphQL can return multiple results, each of which
329
+ # may have their own success/error states, we need to set the
330
+ # skylight segment as follows:
331
+ #
332
+ # - when all queries have errors: "error"
333
+ # - when some queries have errors: "<rendered format>+error"
334
+ # - when no queries have errors: "<rendered format>"
335
+ #
336
+ # <rendered format> will be determined by the Rails controller as usual.
337
+ # See Instrumenter#finalize_endpoint_segment for the actual segment/error assignment.
338
+ def finalize_endpoint_segment(trace)
339
+ return unless (segment = trace.segment)
340
+
341
+ segment =
342
+ case trace.compound_response_error_status
343
+ when :all
344
+ "error"
345
+ when :partial
346
+ "#{segment}+error"
347
+ else
348
+ segment
349
+ end
350
+
351
+ trace.endpoint += "<sk-segment>#{segment}</sk-segment>"
34
352
  end
35
353
  end
36
354
  end