skylight 4.3.1 → 5.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -3
  3. data/CONTRIBUTING.md +1 -7
  4. data/ext/extconf.rb +4 -3
  5. data/ext/libskylight.yml +5 -6
  6. data/ext/skylight_native.c +22 -99
  7. data/lib/skylight.rb +204 -14
  8. data/lib/skylight/api.rb +7 -3
  9. data/lib/skylight/cli.rb +4 -3
  10. data/lib/skylight/cli/doctor.rb +3 -2
  11. data/lib/skylight/cli/merger.rb +6 -4
  12. data/lib/skylight/config.rb +603 -126
  13. data/lib/skylight/deprecation.rb +15 -0
  14. data/lib/skylight/errors.rb +17 -2
  15. data/lib/skylight/extensions.rb +99 -0
  16. data/lib/skylight/extensions/source_location.rb +249 -0
  17. data/lib/skylight/fanout.rb +0 -0
  18. data/lib/skylight/formatters/http.rb +19 -0
  19. data/lib/skylight/gc.rb +109 -0
  20. data/lib/skylight/helpers.rb +18 -2
  21. data/lib/skylight/instrumenter.rb +325 -15
  22. data/lib/skylight/middleware.rb +138 -1
  23. data/lib/skylight/native.rb +51 -1
  24. data/lib/skylight/native_ext_fetcher.rb +2 -1
  25. data/lib/skylight/normalizers.rb +151 -0
  26. data/lib/skylight/normalizers/action_controller/process_action.rb +69 -0
  27. data/lib/skylight/normalizers/action_controller/send_file.rb +50 -0
  28. data/lib/skylight/normalizers/action_dispatch/process_middleware.rb +22 -0
  29. data/lib/skylight/normalizers/action_dispatch/route_set.rb +27 -0
  30. data/lib/skylight/normalizers/action_view/render_collection.rb +24 -0
  31. data/lib/skylight/normalizers/action_view/render_layout.rb +25 -0
  32. data/lib/skylight/normalizers/action_view/render_partial.rb +23 -0
  33. data/lib/skylight/normalizers/action_view/render_template.rb +23 -0
  34. data/lib/skylight/normalizers/active_job/perform.rb +81 -0
  35. data/lib/skylight/normalizers/active_model_serializers/render.rb +28 -0
  36. data/lib/skylight/normalizers/active_record/instantiation.rb +16 -0
  37. data/lib/skylight/normalizers/active_record/sql.rb +12 -0
  38. data/lib/skylight/normalizers/active_storage.rb +30 -0
  39. data/lib/skylight/normalizers/active_support/cache.rb +22 -0
  40. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  44. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  45. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  46. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  47. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  48. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  49. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  50. data/lib/skylight/normalizers/coach/handler_finish.rb +46 -0
  51. data/lib/skylight/normalizers/coach/middleware_finish.rb +33 -0
  52. data/lib/skylight/normalizers/couch_potato/query.rb +20 -0
  53. data/lib/skylight/normalizers/data_mapper/sql.rb +12 -0
  54. data/lib/skylight/normalizers/default.rb +32 -0
  55. data/lib/skylight/normalizers/elasticsearch/request.rb +20 -0
  56. data/lib/skylight/normalizers/faraday/request.rb +40 -0
  57. data/lib/skylight/normalizers/grape/endpoint.rb +34 -0
  58. data/lib/skylight/normalizers/grape/endpoint_render.rb +25 -0
  59. data/lib/skylight/normalizers/grape/endpoint_run.rb +41 -0
  60. data/lib/skylight/normalizers/grape/endpoint_run_filters.rb +22 -0
  61. data/lib/skylight/normalizers/grape/format_response.rb +20 -0
  62. data/lib/skylight/normalizers/graphiti/render.rb +22 -0
  63. data/lib/skylight/normalizers/graphiti/resolve.rb +31 -0
  64. data/lib/skylight/normalizers/graphql/base.rb +131 -0
  65. data/lib/skylight/normalizers/render.rb +81 -0
  66. data/lib/skylight/normalizers/sequel/sql.rb +12 -0
  67. data/lib/skylight/normalizers/sql.rb +44 -0
  68. data/lib/skylight/probes.rb +153 -0
  69. data/lib/skylight/probes/action_controller.rb +48 -0
  70. data/lib/skylight/probes/action_dispatch.rb +2 -0
  71. data/lib/skylight/probes/action_dispatch/request_id.rb +29 -0
  72. data/lib/skylight/probes/action_dispatch/routing/route_set.rb +28 -0
  73. data/lib/skylight/probes/action_view.rb +43 -0
  74. data/lib/skylight/probes/active_job.rb +29 -0
  75. data/lib/skylight/probes/active_job_enqueue.rb +37 -0
  76. data/lib/skylight/probes/active_model_serializers.rb +54 -0
  77. data/lib/skylight/probes/delayed_job.rb +62 -0
  78. data/lib/skylight/probes/elasticsearch.rb +38 -0
  79. data/lib/skylight/probes/excon.rb +25 -0
  80. data/lib/skylight/probes/excon/middleware.rb +66 -0
  81. data/lib/skylight/probes/faraday.rb +23 -0
  82. data/lib/skylight/probes/graphql.rb +43 -0
  83. data/lib/skylight/probes/httpclient.rb +44 -0
  84. data/lib/skylight/probes/middleware.rb +125 -0
  85. data/lib/skylight/probes/mongo.rb +163 -0
  86. data/lib/skylight/probes/mongoid.rb +13 -0
  87. data/lib/skylight/probes/net_http.rb +55 -0
  88. data/lib/skylight/probes/redis.rb +60 -0
  89. data/lib/skylight/probes/sequel.rb +33 -0
  90. data/lib/skylight/probes/sinatra.rb +63 -0
  91. data/lib/skylight/probes/sinatra_add_middleware.rb +10 -10
  92. data/lib/skylight/probes/tilt.rb +27 -0
  93. data/lib/skylight/railtie.rb +162 -18
  94. data/lib/skylight/sidekiq.rb +43 -0
  95. data/lib/skylight/subscriber.rb +110 -0
  96. data/lib/skylight/test.rb +146 -0
  97. data/lib/skylight/trace.rb +301 -10
  98. data/lib/skylight/user_config.rb +61 -0
  99. data/lib/skylight/util.rb +12 -0
  100. data/lib/skylight/util/allocation_free.rb +26 -0
  101. data/lib/skylight/util/clock.rb +56 -0
  102. data/lib/skylight/util/component.rb +5 -2
  103. data/lib/skylight/util/deploy.rb +4 -4
  104. data/lib/skylight/util/gzip.rb +20 -0
  105. data/lib/skylight/util/http.rb +4 -10
  106. data/lib/skylight/util/instrumenter_method.rb +26 -0
  107. data/lib/skylight/util/logging.rb +138 -0
  108. data/lib/skylight/util/lru_cache.rb +42 -0
  109. data/lib/skylight/vendor/cli/thor/rake_compat.rb +1 -1
  110. data/lib/skylight/version.rb +5 -1
  111. data/lib/skylight/vm/gc.rb +68 -0
  112. metadata +113 -14
@@ -135,14 +135,24 @@ module Skylight
135
135
  title = (opts[:title] || title).to_s
136
136
  desc = opts[:description].to_s if opts[:description]
137
137
 
138
+ # NOTE: The source location logic happens before we have have a config so we can'
139
+ # check if source locations are enabled. However, it only happens once so the potential impact
140
+ # should be minimal. This would more appropriately belong to Extensions::SourceLocation,
141
+ # but as that is a runtime concern, and this happens at compile time, there isn't currently
142
+ # a clean way to turn this on and off. The absence of the extension will cause the
143
+ # source_file and source_line to be removed from the trace span before it is submitted.
144
+ source_file, source_line = klass.instance_method(name).source_location
145
+
138
146
  klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
139
147
  alias_method :"before_instrument_#{name}", :"#{name}"
140
148
 
141
149
  def #{name}(*args, &blk)
142
150
  span = Skylight.instrument(
143
- category: :"#{category}",
151
+ category: :"#{category}",
144
152
  title: #{title.inspect},
145
- description: #{desc.inspect})
153
+ description: #{desc.inspect},
154
+ source_file: #{source_file.inspect},
155
+ source_line: #{source_line.inspect})
146
156
 
147
157
  meta = {}
148
158
  begin
@@ -154,6 +164,12 @@ module Skylight
154
164
  Skylight.done(span, meta) if span
155
165
  end
156
166
  end
167
+
168
+ if protected_method_defined?(:"before_instrument_#{name}")
169
+ protected :"#{name}"
170
+ elsif private_method_defined?(:"before_instrument_#{name}")
171
+ private :"#{name}"
172
+ end
157
173
  RUBY
158
174
  end
159
175
 
@@ -1,10 +1,111 @@
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
+
64
+ enable_extension!(:source_location) if @config.enable_source_locations?
65
+ end
66
+
67
+ def enable_extension!(name)
68
+ @mutex.synchronize { extensions.enable!(name) }
69
+ end
70
+
71
+ def disable_extension!(name)
72
+ @mutex.synchronize { extensions.disable!(name) }
73
+ end
74
+
75
+ def extension_enabled?(name)
76
+ extensions.enabled?(name)
77
+ end
78
+
79
+ def log_context
80
+ @log_context ||= { inst: uuid }
81
+ end
82
+
83
+ def native_start
84
+ raise "not implemented"
5
85
  end
6
86
 
7
- def check_install!
87
+ def native_stop
88
+ raise "not implemented"
89
+ end
90
+
91
+ def native_track_desc(_endpoint, _description)
92
+ raise "not implemented"
93
+ end
94
+
95
+ def native_submit_trace(_trace)
96
+ raise "not implemented"
97
+ end
98
+
99
+ def current_trace
100
+ @trace_info.current
101
+ end
102
+
103
+ def current_trace=(trace)
104
+ t { "setting current_trace=#{trace ? trace.uuid : 'nil'}; thread=#{Thread.current.object_id}" }
105
+ @trace_info.current = trace
106
+ end
107
+
108
+ def validate_installation
8
109
  # Warn if there was an error installing Skylight.
9
110
 
10
111
  if defined?(Skylight.check_install_errors)
@@ -13,24 +114,233 @@ module Skylight
13
114
 
14
115
  if !Skylight.native? && defined?(Skylight.warn_skylight_native_missing)
15
116
  Skylight.warn_skylight_native_missing(config)
16
- return
117
+ return false
118
+ end
119
+
120
+ true
121
+ end
122
+
123
+ def muted=(val)
124
+ @trace_info.muted = val
125
+ end
126
+
127
+ def muted?
128
+ @trace_info.muted?
129
+ end
130
+
131
+ def mute
132
+ old_muted = muted?
133
+ self.muted = true
134
+ yield if block_given?
135
+ ensure
136
+ self.muted = old_muted
137
+ end
138
+
139
+ def unmute
140
+ old_muted = muted?
141
+ self.muted = false
142
+ yield if block_given?
143
+ ensure
144
+ self.muted = old_muted
145
+ end
146
+
147
+ def silence_warnings(context)
148
+ @warnings_silenced || @mutex.synchronize do
149
+ @warnings_silenced ||= {}
17
150
  end
151
+
152
+ @warnings_silenced[context] = true
18
153
  end
19
154
 
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}"
155
+ def warnings_silenced?(context)
156
+ @warnings_silenced && @warnings_silenced[context]
157
+ end
158
+
159
+ alias disable mute
160
+ alias disabled? muted?
161
+
162
+ def start!
163
+ # We do this here since we can't report these issues via Gem install without stopping install entirely.
164
+ return unless validate_installation
165
+
166
+ t { "starting instrumenter" }
167
+
168
+ unless config.validate_with_server
169
+ log_error "invalid config"
170
+ return
27
171
  end
172
+
173
+ t { "starting native instrumenter" }
174
+ unless native_start
175
+ warn "failed to start instrumenter"
176
+ return
177
+ end
178
+
179
+ config.gc.enable
180
+ @subscriber.register!
181
+
182
+ ActiveSupport::Notifications.instrument("started_instrumenter.skylight", instrumenter: self)
183
+
184
+ self
185
+ rescue Exception => e
186
+ log_error "failed to start instrumenter; msg=%s; config=%s", e.message, config.inspect
187
+ t { e.backtrace.join("\n") }
28
188
  nil
29
189
  end
30
190
 
31
- def handle_instrumenter_error(trace, e)
32
- poison! if e.is_a?(Skylight::InstrumenterUnrecoverableError)
33
- super
191
+ def shutdown
192
+ @subscriber.unregister!
193
+ native_stop
194
+ end
195
+
196
+ def trace(endpoint, cat, title = nil, desc = nil, meta: nil, segment: nil, component: nil)
197
+ # If a trace is already in progress, continue with that one
198
+ if (trace = @trace_info.current)
199
+ return yield(trace) if block_given?
200
+
201
+ return trace
202
+ end
203
+
204
+ begin
205
+ trace = Trace.new(self, endpoint, Skylight::Util::Clock.nanos, cat, title, desc,
206
+ meta: meta, segment: segment, component: component)
207
+ rescue Exception => e
208
+ log_error e.message
209
+ t { e.backtrace.join("\n") }
210
+ return
211
+ end
212
+
213
+ @trace_info.current = trace
214
+ return trace unless block_given?
215
+
216
+ begin
217
+ yield trace
218
+ ensure
219
+ @trace_info.current = nil
220
+ t { "instrumenter submitting trace; trace=#{trace.uuid}" }
221
+ trace.submit
222
+ end
223
+ end
224
+
225
+ def instrument(cat, title = nil, desc = nil, meta = nil)
226
+ raise ArgumentError, "cat is required" unless cat
227
+
228
+ if muted?
229
+ return yield if block_given?
230
+
231
+ return
232
+ end
233
+
234
+ unless (trace = @trace_info.current)
235
+ return yield if block_given?
236
+
237
+ return
238
+ end
239
+
240
+ cat = cat.to_s
241
+
242
+ unless Skylight::CATEGORY_REGEX.match?(cat)
243
+ warn "invalid skylight instrumentation category; trace=%s; value=%s", trace.uuid, cat
244
+ return yield if block_given?
245
+
246
+ return
247
+ end
248
+
249
+ cat = "other.#{cat}" unless Skylight::TIER_REGEX.match?(cat)
250
+
251
+ unless (sp = trace.instrument(cat, title, desc, meta))
252
+ return yield if block_given?
253
+
254
+ return
255
+ end
256
+
257
+ return sp unless block_given?
258
+
259
+ begin
260
+ yield sp
261
+ rescue Exception => e
262
+ meta ||= {}
263
+ meta[:exception] = [e.class.name, e.message]
264
+ meta[:exception_object] = e
265
+ raise e
266
+ ensure
267
+ trace.done(sp, meta)
268
+ end
269
+ end
270
+
271
+ def broken!
272
+ return unless (trace = @trace_info.current)
273
+
274
+ trace.broken!
275
+ end
276
+
277
+ def poison!
278
+ @poisoned = true
279
+ end
280
+
281
+ def poisoned?
282
+ @poisoned
283
+ end
284
+
285
+ def done(span, meta = nil)
286
+ return unless (trace = @trace_info.current)
287
+
288
+ trace.done(span, meta)
289
+ end
290
+
291
+ def process(trace)
292
+ t { fmt "processing trace=#{trace.uuid}" }
293
+
294
+ if ignore?(trace)
295
+ t { fmt "ignoring trace=#{trace.uuid}" }
296
+ return false
297
+ end
298
+
299
+ begin
300
+ finalize_endpoint_segment(trace)
301
+ native_submit_trace(trace)
302
+ true
303
+ rescue => e
304
+ handle_instrumenter_error(trace, e)
305
+ end
306
+ end
307
+
308
+ def handle_instrumenter_error(trace, err)
309
+ poison! if err.is_a?(Skylight::InstrumenterUnrecoverableError)
310
+
311
+ warn "failed to submit trace to worker; trace=%s, err=%s", trace.uuid, err
312
+ t { "BACKTRACE:\n#{err.backtrace.join("\n")}" }
313
+
314
+ false
315
+ end
316
+
317
+ def ignore?(trace)
318
+ config.ignored_endpoints.include?(trace.endpoint)
319
+ end
320
+
321
+ # Because GraphQL can return multiple results, each of which
322
+ # may have their own success/error states, we need to set the
323
+ # skylight segment as follows:
324
+ #
325
+ # - when all queries have errors: "error"
326
+ # - when some queries have errors: "<rendered format>+error"
327
+ # - when no queries have errors: "<rendered format>"
328
+ #
329
+ # <rendered format> will be determined by the Rails controller as usual.
330
+ # See Instrumenter#finalize_endpoint_segment for the actual segment/error assignment.
331
+ def finalize_endpoint_segment(trace)
332
+ return unless (segment = trace.segment)
333
+
334
+ segment = case trace.compound_response_error_status
335
+ when :all
336
+ "error"
337
+ when :partial
338
+ "#{segment}+error"
339
+ else
340
+ segment
341
+ end
342
+
343
+ trace.endpoint += "<sk-segment>#{segment}</sk-segment>"
34
344
  end
35
345
  end
36
346
  end
@@ -1,4 +1,141 @@
1
+ require "securerandom"
2
+
1
3
  module Skylight
2
- class Middleware < Core::Middleware
4
+ # @api private
5
+ class Middleware
6
+ class BodyProxy
7
+ def initialize(body, &block)
8
+ @body = body
9
+ @block = block
10
+ @closed = false
11
+ end
12
+
13
+ def respond_to_missing?(*args)
14
+ return false if args.first.to_s !~ /^to_ary$/
15
+
16
+ @body.respond_to?(*args)
17
+ end
18
+
19
+ def close
20
+ return if @closed
21
+
22
+ @closed = true
23
+ begin
24
+ @body.close if @body.respond_to? :close
25
+ ensure
26
+ @block.call
27
+ end
28
+ end
29
+
30
+ def closed?
31
+ @closed
32
+ end
33
+
34
+ # N.B. This method is a special case to address the bug described by
35
+ # https://github.com/rack/rack/issues/434.
36
+ # We are applying this special case for #each only. Future bugs of this
37
+ # class will be handled by requesting users to patch their ruby
38
+ # implementation, to save adding too many methods in this class.
39
+ def each(*args, &block)
40
+ @body.each(*args, &block)
41
+ end
42
+
43
+ def method_missing(*args, &block)
44
+ super if args.first.to_s =~ /^to_ary$/
45
+ @body.__send__(*args, &block)
46
+ end
47
+ end
48
+
49
+ def self.with_after_close(resp, debug_identifier: "unknown", &block)
50
+ unless resp.respond_to?(:to_ary)
51
+ if resp.respond_to?(:to_a)
52
+ Skylight.warn("Rack response from \"#{debug_identifier}\" cannot be implicitly converted to an array. " \
53
+ "This is in violation of the Rack SPEC and will raise an error in future versions.")
54
+ resp = resp.to_a
55
+ else
56
+ Skylight.error("Rack response from \"#{debug_identifier}\" cannot be converted to an array. This is in " \
57
+ "violation of the Rack SPEC and may cause problems with Skylight operation.")
58
+ return resp
59
+ end
60
+ end
61
+
62
+ status, headers, body = resp
63
+ [status, headers, BodyProxy.new(body, &block)]
64
+ end
65
+
66
+ include Skylight::Util::Logging
67
+
68
+ # For Util::Logging
69
+ attr_reader :config
70
+
71
+ def initialize(app, opts = {})
72
+ @app = app
73
+ @config = opts[:config]
74
+ end
75
+
76
+ def call(env)
77
+ set_request_id(env)
78
+
79
+ if Skylight.tracing?
80
+ error "Already instrumenting. Make sure the Skylight Rack Middleware hasn't been added more than once."
81
+ end
82
+
83
+ if env["REQUEST_METHOD"] == "HEAD"
84
+ t { "middleware skipping HEAD" }
85
+ @app.call(env)
86
+ else
87
+ begin
88
+ t { "middleware beginning trace" }
89
+ trace = Skylight.trace(endpoint_name(env), "app.rack.request", nil, meta: endpoint_meta(env), component: :web)
90
+ t { "middleware began trace=#{trace ? trace.uuid : nil}" }
91
+
92
+ resp = @app.call(env)
93
+
94
+ if trace
95
+ Middleware.with_after_close(resp, debug_identifier: "Rack App: #{@app.class}") { trace.submit }
96
+ else
97
+ resp
98
+ end
99
+ rescue Exception => e
100
+ t { "middleware exception: #{e}\n#{e.backtrace.join("\n")}" }
101
+ trace&.submit
102
+ raise
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def log_context
110
+ # Don't cache this, it will change
111
+ { request_id: @current_request_id, inst: Skylight.instrumenter&.uuid }
112
+ end
113
+
114
+ # Allow for overwriting
115
+ def endpoint_name(_env)
116
+ "Rack"
117
+ end
118
+
119
+ def endpoint_meta(_env)
120
+ nil
121
+ end
122
+
123
+ # Request ID code based on ActionDispatch::RequestId
124
+ def set_request_id(env)
125
+ existing_request_id = env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"]
126
+ @current_request_id = env["skylight.request_id"] = make_request_id(existing_request_id)
127
+ end
128
+
129
+ def make_request_id(request_id)
130
+ if request_id && !request_id.empty?
131
+ request_id.gsub(/[^\w\-]/, "".freeze)[0...255]
132
+ else
133
+ internal_request_id
134
+ end
135
+ end
136
+
137
+ def internal_request_id
138
+ SecureRandom.uuid
139
+ end
3
140
  end
4
141
  end