appsignal 3.9.3-java → 3.11.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: