appsignal 3.9.3 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -19
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +180 -0
  5. data/Gemfile +1 -0
  6. data/README.md +0 -1
  7. data/Rakefile +1 -1
  8. data/benchmark.rake +99 -42
  9. data/build_matrix.yml +10 -12
  10. data/gemfiles/webmachine1.gemfile +5 -4
  11. data/lib/appsignal/cli/demo.rb +0 -1
  12. data/lib/appsignal/config.rb +57 -97
  13. data/lib/appsignal/demo.rb +15 -20
  14. data/lib/appsignal/environment.rb +6 -1
  15. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
  16. data/lib/appsignal/event_formatter.rb +3 -2
  17. data/lib/appsignal/helpers/instrumentation.rb +490 -16
  18. data/lib/appsignal/hooks/action_cable.rb +21 -16
  19. data/lib/appsignal/hooks/active_job.rb +15 -14
  20. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  21. data/lib/appsignal/hooks/shoryuken.rb +3 -63
  22. data/lib/appsignal/integrations/action_cable.rb +5 -7
  23. data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
  24. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
  25. data/lib/appsignal/integrations/data_mapper.rb +1 -0
  26. data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
  27. data/lib/appsignal/integrations/dry_monitor.rb +1 -0
  28. data/lib/appsignal/integrations/excon.rb +1 -0
  29. data/lib/appsignal/integrations/http.rb +1 -0
  30. data/lib/appsignal/integrations/net_http.rb +1 -0
  31. data/lib/appsignal/integrations/object.rb +6 -0
  32. data/lib/appsignal/integrations/padrino.rb +21 -25
  33. data/lib/appsignal/integrations/que.rb +13 -20
  34. data/lib/appsignal/integrations/railtie.rb +1 -1
  35. data/lib/appsignal/integrations/rake.rb +45 -15
  36. data/lib/appsignal/integrations/redis.rb +1 -0
  37. data/lib/appsignal/integrations/redis_client.rb +1 -0
  38. data/lib/appsignal/integrations/resque.rb +2 -5
  39. data/lib/appsignal/integrations/shoryuken.rb +75 -0
  40. data/lib/appsignal/integrations/sidekiq.rb +7 -25
  41. data/lib/appsignal/integrations/unicorn.rb +1 -0
  42. data/lib/appsignal/integrations/webmachine.rb +12 -9
  43. data/lib/appsignal/logger.rb +7 -3
  44. data/lib/appsignal/probes/helpers.rb +1 -0
  45. data/lib/appsignal/probes/mri.rb +1 -0
  46. data/lib/appsignal/probes/sidekiq.rb +1 -0
  47. data/lib/appsignal/probes.rb +3 -0
  48. data/lib/appsignal/rack/abstract_middleware.rb +67 -24
  49. data/lib/appsignal/rack/body_wrapper.rb +143 -0
  50. data/lib/appsignal/rack/event_handler.rb +39 -8
  51. data/lib/appsignal/rack/generic_instrumentation.rb +6 -4
  52. data/lib/appsignal/rack/grape_middleware.rb +3 -2
  53. data/lib/appsignal/rack/hanami_middleware.rb +1 -1
  54. data/lib/appsignal/rack/instrumentation_middleware.rb +62 -0
  55. data/lib/appsignal/rack/rails_instrumentation.rb +1 -3
  56. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -3
  57. data/lib/appsignal/rack/streaming_listener.rb +14 -59
  58. data/lib/appsignal/rack.rb +60 -0
  59. data/lib/appsignal/span.rb +1 -0
  60. data/lib/appsignal/transaction.rb +353 -104
  61. data/lib/appsignal/utils/data.rb +0 -1
  62. data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
  63. data/lib/appsignal/utils/integration_logger.rb +0 -13
  64. data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
  65. data/lib/appsignal/utils/json.rb +0 -1
  66. data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
  67. data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
  68. data/lib/appsignal/utils.rb +6 -0
  69. data/lib/appsignal/version.rb +1 -1
  70. data/lib/appsignal.rb +9 -6
  71. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  72. data/spec/lib/appsignal/config_spec.rb +139 -43
  73. data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
  74. data/spec/lib/appsignal/hooks/activejob_spec.rb +9 -0
  75. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
  76. data/spec/lib/appsignal/hooks/rake_spec.rb +100 -17
  77. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
  78. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
  79. data/spec/lib/appsignal/integrations/padrino_spec.rb +181 -131
  80. data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
  81. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
  82. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -4
  83. data/spec/lib/appsignal/integrations/sinatra_spec.rb +10 -2
  84. data/spec/lib/appsignal/integrations/webmachine_spec.rb +77 -17
  85. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +144 -11
  86. data/spec/lib/appsignal/rack/body_wrapper_spec.rb +263 -0
  87. data/spec/lib/appsignal/rack/event_handler_spec.rb +81 -10
  88. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +70 -17
  89. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +1 -1
  90. data/spec/lib/appsignal/rack/instrumentation_middleware_spec.rb +38 -0
  91. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
  92. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +43 -120
  93. data/spec/lib/appsignal/rack_spec.rb +63 -0
  94. data/spec/lib/appsignal/transaction_spec.rb +1675 -953
  95. data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
  96. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
  97. data/spec/lib/appsignal_spec.rb +517 -13
  98. data/spec/support/helpers/transaction_helpers.rb +44 -20
  99. data/spec/support/matchers/transaction.rb +15 -1
  100. data/spec/support/mocks/dummy_app.rb +1 -1
  101. data/spec/support/testing.rb +1 -1
  102. metadata +12 -4
  103. data/support/check_versions +0 -22
@@ -6,28 +6,80 @@ module Appsignal
6
6
  class Transaction
7
7
  HTTP_REQUEST = "http_request"
8
8
  BACKGROUND_JOB = "background_job"
9
+ # @api private
9
10
  ACTION_CABLE = "action_cable"
11
+ # @api private
10
12
  FRONTEND = "frontend"
13
+ # @api private
11
14
  BLANK = ""
15
+ # @api private
12
16
  ALLOWED_TAG_KEY_TYPES = [Symbol, String].freeze
13
- ALLOWED_TAG_VALUE_TYPES = [Symbol, String, Integer].freeze
17
+ # @api private
18
+ ALLOWED_TAG_VALUE_TYPES = [Symbol, String, Integer, TrueClass, FalseClass].freeze
19
+ # @api private
14
20
  BREADCRUMB_LIMIT = 20
21
+ # @api private
15
22
  ERROR_CAUSES_LIMIT = 10
16
23
 
17
24
  class << self
18
- def create(id, namespace, request, options = {})
25
+ # Create a new transaction and set it as the currently active
26
+ # transaction.
27
+ #
28
+ # @param id_or_namespace [String] Namespace of the to be created transaction.
29
+ # @return [Transaction]
30
+ def create(id_or_namespace, arg_namespace = nil, request = nil, options = {})
31
+ if id_or_namespace && arg_namespace
32
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
33
+ "Appsignal::Transaction.create: " \
34
+ "A new Transaction is created using the transaction ID argument. " \
35
+ "This argument is deprecated without replacement."
36
+ )
37
+ end
38
+ if arg_namespace
39
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
40
+ "Appsignal::Transaction.create: " \
41
+ "A Transaction is created using the namespace argument. " \
42
+ "Specify the namespace as the first argument to the 'create' " \
43
+ "method without the ID argument."
44
+ )
45
+ end
46
+ if request
47
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
48
+ "Appsignal::Transaction.create: " \
49
+ "A Transaction is created using the request argument. " \
50
+ "This argument is deprecated. Please use the `Appsignal.set_*` helpers instead."
51
+ )
52
+ end
19
53
  # Allow middleware to force a new transaction
20
- Thread.current[:appsignal_transaction] = nil if options.include?(:force) && options[:force]
54
+ if options[:force]
55
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
56
+ "Appsignal::Transaction.create: " \
57
+ "A Transaction is created using the `:force => true` option argument. " \
58
+ "The options argument is deprecated without replacement."
59
+ )
60
+ Thread.current[:appsignal_transaction] = nil
61
+ end
62
+ if arg_namespace
63
+ id = id_or_namespace
64
+ namespace = arg_namespace
65
+ else
66
+ id = SecureRandom.uuid
67
+ namespace = id_or_namespace
68
+ end
21
69
 
22
70
  # Check if we already have a running transaction
23
71
  if Thread.current[:appsignal_transaction].nil?
24
72
  # If not, start a new transaction
25
73
  Thread.current[:appsignal_transaction] =
26
- Appsignal::Transaction.new(id, namespace, request, options)
74
+ Appsignal::Transaction.new(
75
+ id,
76
+ namespace,
77
+ request,
78
+ options
79
+ )
27
80
  else
28
81
  # Otherwise, log the issue about trying to start another transaction
29
- Appsignal.internal_logger.warn_once_then_debug(
30
- :transaction_id,
82
+ Appsignal.internal_logger.warn(
31
83
  "Trying to start new transaction with id " \
32
84
  "'#{id}', but a transaction with id '#{current.transaction_id}' " \
33
85
  "is already running. Using transaction '#{current.transaction_id}'."
@@ -56,6 +108,8 @@ module Appsignal
56
108
  current && !current.nil_transaction?
57
109
  end
58
110
 
111
+ # Complete the currently active transaction and unset it as the active
112
+ # transaction.
59
113
  def complete_current!
60
114
  current.complete
61
115
  rescue => e
@@ -73,21 +127,32 @@ module Appsignal
73
127
  end
74
128
  end
75
129
 
130
+ # @api private
76
131
  attr_reader :ext, :transaction_id, :action, :namespace, :request, :paused, :tags, :options,
77
- :discarded, :breadcrumbs
132
+ :breadcrumbs, :custom_data
78
133
 
79
- def initialize(transaction_id, namespace, request, options = {})
134
+ # Use {.create} to create new transactions.
135
+ #
136
+ # @param transaction_id [String] ID of the to be created transaction.
137
+ # @param namespace [String] Namespace of the to be created transaction.
138
+ # @see create
139
+ # @api private
140
+ def initialize(transaction_id, namespace, request = nil, options = {})
80
141
  @transaction_id = transaction_id
81
142
  @action = nil
82
143
  @namespace = namespace
83
- @request = request
144
+ @request = request || InternalGenericRequest.new({})
84
145
  @paused = false
85
146
  @discarded = false
86
147
  @tags = {}
148
+ @custom_data = nil
87
149
  @breadcrumbs = []
88
150
  @store = Hash.new({})
89
151
  @options = options
90
152
  @options[:params_method] ||= :params
153
+ @params = nil
154
+ @session_data = nil
155
+ @headers = nil
91
156
 
92
157
  @ext = Appsignal::Extension.start_transaction(
93
158
  @transaction_id,
@@ -106,42 +171,57 @@ module Appsignal
106
171
  "because it was manually discarded."
107
172
  return
108
173
  end
109
- sample_data if @ext.finish(0)
174
+ _sample_data if @ext.finish(0)
110
175
  @ext.complete
111
176
  end
112
177
 
178
+ # @api private
113
179
  def pause!
114
180
  @paused = true
115
181
  end
116
182
 
183
+ # @api private
117
184
  def resume!
118
185
  @paused = false
119
186
  end
120
187
 
188
+ # @api private
121
189
  def paused?
122
190
  @paused == true
123
191
  end
124
192
 
193
+ # @api private
125
194
  def discard!
126
195
  @discarded = true
127
196
  end
128
197
 
198
+ # @api private
129
199
  def restore!
130
200
  @discarded = false
131
201
  end
132
202
 
203
+ # @api private
133
204
  def discarded?
134
205
  @discarded == true
135
206
  end
136
207
 
208
+ # @api private
137
209
  def store(key)
138
210
  @store[key]
139
211
  end
140
212
 
213
+ # @api private
141
214
  def params
142
- return @params if defined?(@params)
215
+ parameters = @params || request_params
143
216
 
144
- request_params
217
+ if parameters.respond_to? :call
218
+ parameters.call
219
+ else
220
+ parameters
221
+ end
222
+ rescue => e
223
+ Appsignal.internal_logger.error("Exception while fetching params: #{e.class}: #{e}")
224
+ nil
145
225
  end
146
226
 
147
227
  # Set parameters on the transaction.
@@ -152,9 +232,17 @@ module Appsignal
152
232
  # The parameters set using {#set_params} are leading over those extracted
153
233
  # from a request's environment.
154
234
  #
235
+ # When both the `given_params` and a block is given to this method, the
236
+ # `given_params` argument is leading and the block will _not_ be called.
237
+ #
238
+ # @since 3.9.1
155
239
  # @param given_params [Hash] The parameters to set on the transaction.
240
+ # @yield This block is called when the transaction is sampled. The block's
241
+ # return value will become the new parameters.
156
242
  # @return [void]
157
- def set_params(given_params)
243
+ # @see Helpers::Instrumentation#set_params
244
+ def set_params(given_params = nil, &block)
245
+ @params = block if block
158
246
  @params = given_params if given_params
159
247
  end
160
248
 
@@ -172,14 +260,22 @@ module Appsignal
172
260
  # When no parameters are set this way, the transaction will look for
173
261
  # parameters on the {#request} environment.
174
262
  #
263
+ # @since 3.9.1
175
264
  # @param given_params [Hash] The parameters to set on the transaction if none are already set.
265
+ # @yield This block is called when the transaction is sampled. The block's
266
+ # return value will become the new parameters.
176
267
  # @return [void]
177
- def set_params_if_nil(given_params)
178
- set_params(given_params) unless @params
268
+ #
269
+ # @see #set_params
270
+ # @see Helpers::Instrumentation#set_params_if_nil
271
+ def set_params_if_nil(given_params = nil, &block)
272
+ set_params(given_params, &block) unless @params
179
273
  end
180
274
 
181
275
  # Set tags on the transaction.
182
276
  #
277
+ # When this method is called multiple times, it will merge the tags.
278
+ #
183
279
  # @param given_tags [Hash] Collection of tags.
184
280
  # @option given_tags [String, Symbol, Integer] :any
185
281
  # The name of the tag as a Symbol.
@@ -187,13 +283,112 @@ module Appsignal
187
283
  # The name of the tag as a String.
188
284
  # @return [void]
189
285
  #
190
- # @see Appsignal.tag_request
286
+ # @see Helpers::Instrumentation#tag_request
191
287
  # @see https://docs.appsignal.com/ruby/instrumentation/tagging.html
192
288
  # Tagging guide
193
289
  def set_tags(given_tags = {})
194
290
  @tags.merge!(given_tags)
195
291
  end
196
292
 
293
+ # Set session data on the transaction.
294
+ #
295
+ # When both the `given_session_data` and a block is given to this method,
296
+ # the `given_session_data` argument is leading and the block will _not_ be
297
+ # called.
298
+ #
299
+ # @param given_session_data [Hash] A hash containing session data.
300
+ # @yield This block is called when the transaction is sampled. The block's
301
+ # return value will become the new session data.
302
+ # @return [void]
303
+ #
304
+ # @since 3.10.1
305
+ # @see Helpers::Instrumentation#set_session_data
306
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
307
+ # Sample data guide
308
+ def set_session_data(given_session_data = nil, &block)
309
+ @session_data = block if block
310
+ @session_data = given_session_data if given_session_data
311
+ end
312
+
313
+ # Set session data on the transaction if not already set.
314
+ #
315
+ # When both the `given_session_data` and a block is given to this method,
316
+ # the `given_session_data` argument is leading and the block will _not_ be
317
+ # called.
318
+ #
319
+ # @param given_session_data [Hash] A hash containing session data.
320
+ # @yield This block is called when the transaction is sampled. The block's
321
+ # return value will become the new session data.
322
+ # @return [void]
323
+ #
324
+ # @since 3.10.1
325
+ # @see #set_session_data
326
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
327
+ # Sample data guide
328
+ def set_session_data_if_nil(given_session_data = nil, &block)
329
+ set_session_data(given_session_data, &block) unless @session_data
330
+ end
331
+
332
+ # Set headers on the transaction.
333
+ #
334
+ # When both the `given_headers` and a block is given to this method,
335
+ # the `given_headers` argument is leading and the block will _not_ be
336
+ # called.
337
+ #
338
+ # @param given_headers [Hash] A hash containing headers.
339
+ # @yield This block is called when the transaction is sampled. The block's
340
+ # return value will become the new headers.
341
+ # @return [void]
342
+ #
343
+ # @since 3.10.1
344
+ # @see Helpers::Instrumentation#set_headers
345
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
346
+ # Sample data guide
347
+ def set_headers(given_headers = nil, &block)
348
+ @headers = block if block
349
+ @headers = given_headers if given_headers
350
+ end
351
+
352
+ # Set headers on the transaction if not already set.
353
+ #
354
+ # When both the `given_headers` and a block is given to this method,
355
+ # the `given_headers` argument is leading and the block will _not_ be
356
+ # called.
357
+ #
358
+ # @param given_headers [Hash] A hash containing headers.
359
+ # @yield This block is called when the transaction is sampled. The block's
360
+ # return value will become the new headers.
361
+ # @return [void]
362
+ #
363
+ # @since 3.10.1
364
+ # @see #set_headers
365
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
366
+ # Sample data guide
367
+ def set_headers_if_nil(given_headers = nil, &block)
368
+ set_headers(given_headers, &block) unless @headers
369
+ end
370
+
371
+ # Set custom data on the transaction.
372
+ #
373
+ # When this method is called multiple times, it will overwrite the
374
+ # previously set value.
375
+ #
376
+ # @since 3.10.0
377
+ # @see Appsignal.set_custom_data
378
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
379
+ # Sample data guide
380
+ # @param data [Hash/Array]
381
+ # @return [void]
382
+ def set_custom_data(data)
383
+ case data
384
+ when Array, Hash
385
+ @custom_data = data
386
+ else
387
+ Appsignal.internal_logger
388
+ .error("set_custom_data: Unsupported data type #{data.class} received.")
389
+ end
390
+ end
391
+
197
392
  # Add breadcrumbs to the transaction.
198
393
  #
199
394
  # @param category [String] category of breadcrumb
@@ -284,6 +479,8 @@ module Appsignal
284
479
  @ext.set_namespace(namespace)
285
480
  end
286
481
 
482
+ # @deprecated Use the {#set_action} helper.
483
+ # @api private
287
484
  def set_http_or_background_action(from = request.params)
288
485
  return unless from
289
486
 
@@ -296,8 +493,6 @@ module Appsignal
296
493
 
297
494
  # Set queue start time for transaction.
298
495
  #
299
- # Most commononly called by {set_http_or_background_queue_start}.
300
- #
301
496
  # @param start [Integer] Queue start time in milliseconds.
302
497
  # @raise [RangeError] When the queue start time value is too big, this
303
498
  # method raises a RangeError.
@@ -327,14 +522,21 @@ module Appsignal
327
522
  # AppSignal as seconds.
328
523
  #
329
524
  # @see https://docs.appsignal.com/ruby/instrumentation/request-queue-time.html
525
+ # @deprecated Use {#set_queue_start} instead.
330
526
  # @return [void]
331
527
  def set_http_or_background_queue_start
528
+ Appsignal::Utils::StdoutAndLoggerMessage.warning \
529
+ "The Appsignal::Transaction#set_http_or_background_queue_start " \
530
+ "method has been deprecated. " \
531
+ "Please use the Appsignal::Transaction#set_queue_start method instead."
532
+
332
533
  start = http_queue_start || background_queue_start
333
534
  return unless start
334
535
 
335
536
  set_queue_start(start)
336
537
  end
337
538
 
539
+ # @api private
338
540
  def set_metadata(key, value)
339
541
  return unless key && value
340
542
  return if Appsignal.config[:filter_metadata].include?(key.to_s)
@@ -342,46 +544,29 @@ module Appsignal
342
544
  @ext.set_metadata(key, value)
343
545
  end
344
546
 
547
+ # @deprecated Use one of the set_tags, set_params, set_session_data,
548
+ # set_params or set_custom_data helpers instead.
549
+ # @api private
345
550
  def set_sample_data(key, data)
346
- return unless key && data
347
-
348
- if !data.is_a?(Array) && !data.is_a?(Hash)
349
- Appsignal.internal_logger.error(
350
- "Invalid sample data for '#{key}'. Value is not an Array or Hash: '#{data.inspect}'"
351
- )
352
- return
353
- end
354
-
355
- @ext.set_sample_data(
356
- key.to_s,
357
- Appsignal::Utils::Data.generate(data)
551
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
552
+ "Appsignal::Transaction#set_sample_data is deprecated. " \
553
+ "Please use one of the instrumentation helpers: set_tags, " \
554
+ "set_params, set_session_data, set_params or set_custom_data."
358
555
  )
359
- rescue RuntimeError => e
360
- begin
361
- inspected_data = data.inspect
362
- Appsignal.internal_logger.error(
363
- "Error generating data (#{e.class}: #{e.message}) for '#{inspected_data}'"
364
- )
365
- rescue => e
366
- Appsignal.internal_logger.error(
367
- "Error generating data (#{e.class}: #{e.message}). Can't inspect data."
368
- )
369
- end
556
+ _set_sample_data(key, data)
370
557
  end
371
558
 
559
+ # @deprecated No replacement.
560
+ # @api private
372
561
  def sample_data
373
- {
374
- :params => sanitized_params,
375
- :environment => sanitized_environment,
376
- :session_data => sanitized_session_data,
377
- :metadata => sanitized_metadata,
378
- :tags => sanitized_tags,
379
- :breadcrumbs => breadcrumbs
380
- }.each do |key, data|
381
- set_sample_data(key, data)
382
- end
562
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
563
+ "Appsignal::Transaction#sample_data is deprecated. " \
564
+ "Please remove any calls to this method."
565
+ )
566
+ _sample_data
383
567
  end
384
568
 
569
+ # @see Appsignal::Helpers::Instrumentation#set_error
385
570
  def set_error(error)
386
571
  unless error.is_a?(Exception)
387
572
  Appsignal.internal_logger.error "Appsignal::Transaction#set_error: Cannot set error. " \
@@ -428,19 +613,23 @@ module Appsignal
428
613
 
429
614
  causes_sample_data.last[:is_root_cause] = false if root_cause_missing
430
615
 
431
- set_sample_data(
616
+ _set_sample_data(
432
617
  "error_causes",
433
618
  causes_sample_data
434
619
  )
435
620
  end
436
621
  alias_method :add_exception, :set_error
437
622
 
623
+ # @see Helpers::Instrumentation#instrument
624
+ # @api private
438
625
  def start_event
439
626
  return if paused?
440
627
 
441
628
  @ext.start_event(0)
442
629
  end
443
630
 
631
+ # @see Helpers::Instrumentation#instrument
632
+ # @api private
444
633
  def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)
445
634
  return if paused?
446
635
 
@@ -453,6 +642,8 @@ module Appsignal
453
642
  )
454
643
  end
455
644
 
645
+ # @see Helpers::Instrumentation#instrument
646
+ # @api private
456
647
  def record_event(name, title, body, duration, body_format = Appsignal::EventFormatter::DEFAULT)
457
648
  return if paused?
458
649
 
@@ -466,6 +657,7 @@ module Appsignal
466
657
  )
467
658
  end
468
659
 
660
+ # @see Helpers::Instrumentation#instrument
469
661
  def instrument(name, title = nil, body = nil, body_format = Appsignal::EventFormatter::DEFAULT)
470
662
  start_event
471
663
  yield if block_given?
@@ -479,7 +671,8 @@ module Appsignal
479
671
  end
480
672
  alias_method :to_hash, :to_h
481
673
 
482
- class GenericRequest
674
+ # @api private
675
+ class InternalGenericRequest
483
676
  attr_reader :env
484
677
 
485
678
  def initialize(env)
@@ -491,8 +684,64 @@ module Appsignal
491
684
  end
492
685
  end
493
686
 
687
+ # @deprecated Use the instrumentation helpers to set metadata on the
688
+ # transaction, rather than rely on the GenericRequest automation. See the
689
+ # {Helpers::Instrumentation} module for a list of helpers.
690
+ # @api private
691
+ class GenericRequest < InternalGenericRequest
692
+ def initialize(_env)
693
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
694
+ "The use of Appsignal::Transaction::GenericRequest is deprecated. " \
695
+ "Use the `Appsignal.set_*` helpers instead. " \
696
+ "https://docs.appsignal.com/guides/custom-data/sample-data.html"
697
+ )
698
+ super
699
+ end
700
+ end
701
+
494
702
  private
495
703
 
704
+ def _set_sample_data(key, data)
705
+ return unless key && data
706
+
707
+ if !data.is_a?(Array) && !data.is_a?(Hash)
708
+ Appsignal.internal_logger.error(
709
+ "Invalid sample data for '#{key}'. Value is not an Array or Hash: '#{data.inspect}'"
710
+ )
711
+ return
712
+ end
713
+
714
+ @ext.set_sample_data(
715
+ key.to_s,
716
+ Appsignal::Utils::Data.generate(data)
717
+ )
718
+ rescue RuntimeError => e
719
+ begin
720
+ inspected_data = data.inspect
721
+ Appsignal.internal_logger.error(
722
+ "Error generating data (#{e.class}: #{e.message}) for '#{inspected_data}'"
723
+ )
724
+ rescue => e
725
+ Appsignal.internal_logger.error(
726
+ "Error generating data (#{e.class}: #{e.message}). Can't inspect data."
727
+ )
728
+ end
729
+ end
730
+
731
+ def _sample_data
732
+ {
733
+ :params => sanitized_params,
734
+ :environment => sanitized_environment,
735
+ :session_data => sanitized_session_data,
736
+ :metadata => sanitized_metadata,
737
+ :tags => sanitized_tags,
738
+ :breadcrumbs => breadcrumbs,
739
+ :custom_data => custom_data
740
+ }.each do |key, data|
741
+ _set_sample_data(key, data)
742
+ end
743
+ end
744
+
496
745
  # Returns calculated background queue start time in milliseconds, based on
497
746
  # environment values.
498
747
  #
@@ -516,25 +765,7 @@ module Appsignal
516
765
  # @return [Integer] queue start in milliseconds.
517
766
  def http_queue_start
518
767
  env = environment
519
- return unless env
520
-
521
- env_var = env["HTTP_X_QUEUE_START"] || env["HTTP_X_REQUEST_START"]
522
- return unless env_var
523
-
524
- cleaned_value = env_var.tr("^0-9", "")
525
- return if cleaned_value.empty?
526
-
527
- value = cleaned_value.to_i
528
- if value > 4_102_441_200_000
529
- # Value is in microseconds. Transform to milliseconds.
530
- value / 1_000
531
- elsif value < 946_681_200_000
532
- # Value is too low to be plausible
533
- nil
534
- else
535
- # Value is in milliseconds
536
- value
537
- end
768
+ Appsignal::Rack::Utils.queue_start_from(env)
538
769
  end
539
770
 
540
771
  def sanitized_params
@@ -550,28 +781,25 @@ module Appsignal
550
781
  begin
551
782
  request.send options[:params_method]
552
783
  rescue => e
553
- # Getting params from the request has been know to fail.
554
- Appsignal.internal_logger.debug "Exception while getting params: #{e}"
784
+ Appsignal.internal_logger.warn "Exception while getting params: #{e}"
555
785
  nil
556
786
  end
557
787
  end
558
788
 
559
- # Returns sanitized environment for a transaction.
560
- #
561
- # The environment of a transaction can contain a lot of information, not
562
- # all of it useful for debugging.
563
- #
564
- # @return [nil] if no environment is present.
565
- # @return [Hash<String, Object>]
566
- def sanitized_environment
567
- env = environment
568
- return if env.empty?
569
-
570
- {}.tap do |out|
571
- Appsignal.config[:request_headers].each do |key|
572
- out[key] = env[key] if env[key]
789
+ def session_data
790
+ if @session_data
791
+ if @session_data.respond_to? :call
792
+ @session_data.call
793
+ else
794
+ @session_data
573
795
  end
796
+ elsif request.respond_to?(:session)
797
+ request.session
574
798
  end
799
+ rescue => e
800
+ Appsignal.internal_logger.error \
801
+ "Exception while fetching session data: #{e.class}: #{e}"
802
+ nil
575
803
  end
576
804
 
577
805
  # Returns sanitized session data.
@@ -583,23 +811,21 @@ module Appsignal
583
811
  # @return [nil] if the {#request} session data is `nil`.
584
812
  # @return [Hash<String, Object>]
585
813
  def sanitized_session_data
586
- return if !Appsignal.config[:send_session_data] ||
587
- !request.respond_to?(:session)
588
-
589
- session = request.session
590
- return unless session
814
+ return unless Appsignal.config[:send_session_data]
591
815
 
592
816
  Appsignal::Utils::HashSanitizer.sanitize(
593
- session.to_hash, Appsignal.config[:filter_session_data]
817
+ session_data&.to_hash, Appsignal.config[:filter_session_data]
594
818
  )
595
819
  end
596
820
 
597
- # Returns sanitized metadata set by {#set_metadata} and from the
598
- # {#environment}.
821
+ # Returns sanitized metadata set on the request environment.
599
822
  #
600
823
  # @return [Hash<String, Object>]
601
824
  def sanitized_metadata
602
- metadata = environment[:metadata]
825
+ env = environment
826
+ return unless env
827
+
828
+ metadata = env[:metadata]
603
829
  return unless metadata
604
830
 
605
831
  metadata
@@ -607,17 +833,40 @@ module Appsignal
607
833
  .reject { |key, _value| Appsignal.config[:filter_metadata].include?(key) }
608
834
  end
609
835
 
610
- # Returns the environment for a transaction.
836
+ def environment
837
+ if @headers
838
+ if @headers.respond_to? :call
839
+ @headers.call
840
+ else
841
+ @headers
842
+ end
843
+ elsif request.respond_to?(:env)
844
+ request.env
845
+ end
846
+ rescue => e
847
+ Appsignal.internal_logger.error \
848
+ "Exception while fetching headers: #{e.class}: #{e}"
849
+ nil
850
+ end
851
+
852
+ # Returns sanitized environment for a transaction.
611
853
  #
612
- # Returns an empty Hash when the {#request} object doesn't listen to the
613
- # `#env` method or the `#env` is nil.
854
+ # The environment of a transaction can contain a lot of information, not
855
+ # all of it useful for debugging.
614
856
  #
857
+ # @return [nil] if no environment is present.
615
858
  # @return [Hash<String, Object>]
616
- def environment
617
- return {} unless request.respond_to?(:env)
618
- return {} unless request.env
859
+ def sanitized_environment
860
+ env = environment
861
+ return unless env
862
+ return unless env.respond_to?(:empty?)
863
+ return if env.empty?
619
864
 
620
- request.env
865
+ {}.tap do |out|
866
+ Appsignal.config[:request_headers].each do |key|
867
+ out[key] = env[key] if env[key]
868
+ end
869
+ end
621
870
  end
622
871
 
623
872
  # Only keep tags if they meet the following criteria: