appsignal 3.12.6-java → 4.0.0.beta.1-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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +499 -487
  3. data/CHANGELOG.md +151 -0
  4. data/Rakefile +31 -7
  5. data/benchmark.rake +4 -6
  6. data/build_matrix.yml +45 -39
  7. data/ext/agent.rb +27 -27
  8. data/ext/appsignal_extension.c +25 -0
  9. data/gemfiles/rails-7.2.gemfile +11 -0
  10. data/lib/appsignal/check_in/cron.rb +67 -0
  11. data/lib/appsignal/check_in.rb +46 -0
  12. data/lib/appsignal/cli/diagnose.rb +37 -28
  13. data/lib/appsignal/cli/install.rb +5 -1
  14. data/lib/appsignal/config.rb +57 -119
  15. data/lib/appsignal/demo.rb +2 -2
  16. data/lib/appsignal/extension/jruby.rb +14 -0
  17. data/lib/appsignal/helpers/instrumentation.rb +139 -414
  18. data/lib/appsignal/helpers/metrics.rb +0 -16
  19. data/lib/appsignal/hooks/action_cable.rb +8 -8
  20. data/lib/appsignal/hooks/active_job.rb +2 -2
  21. data/lib/appsignal/hooks/at_exit.rb +37 -0
  22. data/lib/appsignal/hooks.rb +1 -16
  23. data/lib/appsignal/integrations/action_cable.rb +2 -2
  24. data/lib/appsignal/integrations/capistrano/appsignal.cap +2 -4
  25. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +1 -4
  26. data/lib/appsignal/integrations/delayed_job_plugin.rb +3 -3
  27. data/lib/appsignal/integrations/http.rb +2 -7
  28. data/lib/appsignal/integrations/que.rb +2 -2
  29. data/lib/appsignal/integrations/railtie.rb +26 -59
  30. data/lib/appsignal/integrations/rake.rb +2 -2
  31. data/lib/appsignal/integrations/resque.rb +2 -2
  32. data/lib/appsignal/integrations/shoryuken.rb +4 -4
  33. data/lib/appsignal/integrations/sidekiq.rb +3 -3
  34. data/lib/appsignal/integrations/webmachine.rb +2 -2
  35. data/lib/appsignal/loaders.rb +1 -1
  36. data/lib/appsignal/probes.rb +0 -9
  37. data/lib/appsignal/rack/abstract_middleware.rb +4 -26
  38. data/lib/appsignal/rack/event_handler.rb +4 -4
  39. data/lib/appsignal/rack/rails_instrumentation.rb +1 -1
  40. data/lib/appsignal/rack.rb +0 -25
  41. data/lib/appsignal/sample_data.rb +95 -0
  42. data/lib/appsignal/transaction.rb +235 -361
  43. data/lib/appsignal/utils/rails_helper.rb +4 -0
  44. data/lib/appsignal/version.rb +1 -1
  45. data/lib/appsignal.rb +20 -62
  46. data/spec/lib/appsignal/auth_check_spec.rb +1 -1
  47. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  48. data/spec/lib/appsignal/capistrano3_spec.rb +53 -13
  49. data/spec/lib/appsignal/{heartbeat_spec.rb → check_in_spec.rb} +45 -36
  50. data/spec/lib/appsignal/cli/demo_spec.rb +7 -27
  51. data/spec/lib/appsignal/cli/diagnose_spec.rb +145 -110
  52. data/spec/lib/appsignal/config_spec.rb +304 -379
  53. data/spec/lib/appsignal/extension_install_failure_spec.rb +5 -1
  54. data/spec/lib/appsignal/extension_spec.rb +5 -1
  55. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +1 -1
  56. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +1 -2
  57. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +1 -0
  58. data/spec/lib/appsignal/hooks/activejob_spec.rb +7 -12
  59. data/spec/lib/appsignal/hooks/at_exit_spec.rb +72 -0
  60. data/spec/lib/appsignal/hooks/gvl_spec.rb +10 -5
  61. data/spec/lib/appsignal/hooks/http_spec.rb +3 -3
  62. data/spec/lib/appsignal/hooks/net_http_spec.rb +3 -3
  63. data/spec/lib/appsignal/hooks/rake_spec.rb +6 -9
  64. data/spec/lib/appsignal/hooks/redis_client_spec.rb +5 -10
  65. data/spec/lib/appsignal/hooks/redis_spec.rb +4 -7
  66. data/spec/lib/appsignal/hooks/resque_spec.rb +3 -5
  67. data/spec/lib/appsignal/hooks_spec.rb +0 -41
  68. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +29 -20
  69. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +4 -9
  70. data/spec/lib/appsignal/integrations/http_spec.rb +0 -21
  71. data/spec/lib/appsignal/integrations/railtie_spec.rb +179 -157
  72. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +3 -5
  73. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +48 -62
  74. data/spec/lib/appsignal/loaders/hanami_spec.rb +6 -9
  75. data/spec/lib/appsignal/loaders/padrino_spec.rb +6 -10
  76. data/spec/lib/appsignal/loaders/sinatra_spec.rb +6 -9
  77. data/spec/lib/appsignal/loaders_spec.rb +8 -1
  78. data/spec/lib/appsignal/marker_spec.rb +1 -1
  79. data/spec/lib/appsignal/probes_spec.rb +4 -83
  80. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +4 -63
  81. data/spec/lib/appsignal/rack/event_handler_spec.rb +18 -15
  82. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +3 -11
  83. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +4 -5
  84. data/spec/lib/appsignal/sample_data_spec.rb +174 -0
  85. data/spec/lib/appsignal/transaction_spec.rb +791 -1031
  86. data/spec/lib/appsignal/transmitter_spec.rb +6 -8
  87. data/spec/lib/appsignal_spec.rb +294 -643
  88. data/spec/spec_helper.rb +1 -3
  89. data/spec/support/fixtures/projects/valid/config/appsignal.yml +4 -7
  90. data/spec/support/fixtures/projects/valid_with_rails_app/config/application.rb +16 -0
  91. data/spec/support/fixtures/projects/valid_with_rails_app/config/appsignal.yml +56 -0
  92. data/spec/support/fixtures/projects/valid_with_rails_app/config/environment.rb +5 -0
  93. data/spec/support/helpers/api_request_helper.rb +3 -2
  94. data/spec/support/helpers/config_helpers.rb +41 -11
  95. data/spec/support/helpers/dependency_helper.rb +8 -0
  96. data/spec/support/helpers/log_helpers.rb +1 -0
  97. data/spec/support/helpers/rails_helper.rb +6 -6
  98. data/spec/support/helpers/transaction_helpers.rb +2 -24
  99. data/spec/support/matchers/transaction.rb +3 -3
  100. data/spec/support/mocks/appsignal_mock.rb +3 -3
  101. data/spec/support/mocks/mock_probe.rb +2 -0
  102. data/spec/support/testing.rb +2 -2
  103. metadata +14 -23
  104. data/gemfiles/que_beta.gemfile +0 -5
  105. data/lib/appsignal/heartbeat.rb +0 -59
  106. data/lib/appsignal/helpers/heartbeats.rb +0 -44
  107. data/lib/appsignal/integrations/grape.rb +0 -35
  108. data/lib/appsignal/integrations/hanami.rb +0 -13
  109. data/lib/appsignal/integrations/padrino.rb +0 -13
  110. data/lib/appsignal/integrations/sinatra.rb +0 -13
  111. data/lib/appsignal/rack/generic_instrumentation.rb +0 -22
  112. data/lib/appsignal/rack/streaming_listener.rb +0 -28
  113. data/spec/lib/appsignal/integrations/grape_spec.rb +0 -36
  114. data/spec/lib/appsignal/integrations/hanami_spec.rb +0 -17
  115. data/spec/lib/appsignal/integrations/padrino_spec.rb +0 -15
  116. data/spec/lib/appsignal/integrations/sinatra_spec.rb +0 -15
  117. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +0 -81
  118. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +0 -69
  119. data/spec/support/fixtures/projects/valid/config/environments/development.rb +0 -0
  120. data/spec/support/fixtures/projects/valid/config/environments/production.rb +0 -0
  121. data/spec/support/fixtures/projects/valid/config/environments/test.rb +0 -0
  122. data/spec/support/rails/my_app.rb +0 -6
  123. /data/spec/support/fixtures/projects/{valid/config/application.rb → valid_with_rails_app/log/.gitkeep} +0 -0
@@ -9,8 +9,6 @@ module Appsignal
9
9
  # @api private
10
10
  ACTION_CABLE = "action_cable"
11
11
  # @api private
12
- FRONTEND = "frontend"
13
- # @api private
14
12
  BLANK = ""
15
13
  # @api private
16
14
  ALLOWED_TAG_KEY_TYPES = [Symbol, String].freeze
@@ -20,69 +18,27 @@ module Appsignal
20
18
  BREADCRUMB_LIMIT = 20
21
19
  # @api private
22
20
  ERROR_CAUSES_LIMIT = 10
21
+ ERRORS_LIMIT = 10
23
22
 
24
23
  class << self
25
24
  # Create a new transaction and set it as the currently active
26
25
  # transaction.
27
26
  #
28
- # @param id_or_namespace [String] Namespace of the to be created transaction.
27
+ # @param namespace [String] Namespace of the to be created transaction.
29
28
  # @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
53
- # Allow middleware to force a new transaction
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
69
-
29
+ def create(namespace)
70
30
  # Check if we already have a running transaction
71
31
  if Thread.current[:appsignal_transaction].nil?
72
32
  # If not, start a new transaction
73
- Thread.current[:appsignal_transaction] =
74
- Appsignal::Transaction.new(
75
- id,
76
- namespace,
77
- request,
78
- options
79
- )
33
+ set_current_transaction(
34
+ Appsignal::Transaction.new(SecureRandom.uuid, namespace)
35
+ )
80
36
  else
81
37
  # Otherwise, log the issue about trying to start another transaction
82
38
  Appsignal.internal_logger.warn(
83
- "Trying to start new transaction with id " \
84
- "'#{id}', but a transaction with id '#{current.transaction_id}' " \
85
- "is already running. Using transaction '#{current.transaction_id}'."
39
+ "Trying to start new transaction, but a transaction " \
40
+ "with id '#{current.transaction_id}' is already running. " \
41
+ "Using transaction '#{current.transaction_id}'."
86
42
  )
87
43
 
88
44
  # And return the current transaction instead
@@ -90,6 +46,23 @@ module Appsignal
90
46
  end
91
47
  end
92
48
 
49
+ # @api private
50
+ def set_current_transaction(transaction)
51
+ Thread.current[:appsignal_transaction] = transaction
52
+ end
53
+
54
+ # Set the current for the duration of the given block.
55
+ # It restores the original transaction (if any) when the block has executed.
56
+ #
57
+ # @api private
58
+ def with_transaction(transaction)
59
+ original_transaction = current if current?
60
+ set_current_transaction(transaction)
61
+ yield
62
+ ensure
63
+ set_current_transaction(original_transaction)
64
+ end
65
+
93
66
  # Returns currently active transaction or a {NilTransaction} if none is
94
67
  # active.
95
68
  #
@@ -125,11 +98,19 @@ module Appsignal
125
98
  def clear_current_transaction!
126
99
  Thread.current[:appsignal_transaction] = nil
127
100
  end
101
+
102
+ # @api private
103
+ def last_errors
104
+ @last_errors ||= []
105
+ end
106
+
107
+ # @api private
108
+ attr_writer :last_errors
128
109
  end
129
110
 
130
111
  # @api private
131
112
  attr_reader :ext, :transaction_id, :action, :namespace, :request, :paused, :tags, :options,
132
- :breadcrumbs, :custom_data
113
+ :breadcrumbs, :is_duplicate, :error_blocks
133
114
 
134
115
  # Use {.create} to create new transactions.
135
116
  #
@@ -137,24 +118,25 @@ module Appsignal
137
118
  # @param namespace [String] Namespace of the to be created transaction.
138
119
  # @see create
139
120
  # @api private
140
- def initialize(transaction_id, namespace, request = nil, options = {})
121
+ def initialize(transaction_id, namespace, ext: nil)
141
122
  @transaction_id = transaction_id
142
123
  @action = nil
143
124
  @namespace = namespace
144
- @request = request || InternalGenericRequest.new({})
145
125
  @paused = false
146
126
  @discarded = false
147
127
  @tags = {}
148
- @custom_data = nil
149
128
  @breadcrumbs = []
150
129
  @store = Hash.new({})
151
- @options = options
152
- @options[:params_method] ||= :params
153
- @params = nil
154
- @session_data = nil
155
- @headers = nil
130
+ @error_blocks = Hash.new { |hash, key| hash[key] = [] }
131
+ @is_duplicate = false
132
+ @error_set = nil
133
+
134
+ @params = Appsignal::SampleData.new(:params)
135
+ @session_data = Appsignal::SampleData.new(:session_data, Hash)
136
+ @headers = Appsignal::SampleData.new(:headers, Hash)
137
+ @custom_data = Appsignal::SampleData.new(:custom_data)
156
138
 
157
- @ext = Appsignal::Extension.start_transaction(
139
+ @ext = ext || Appsignal::Extension.start_transaction(
158
140
  @transaction_id,
159
141
  @namespace,
160
142
  0
@@ -171,8 +153,44 @@ module Appsignal
171
153
  "because it was manually discarded."
172
154
  return
173
155
  end
174
- _sample_data if @ext.finish(0)
175
- @ext.complete
156
+
157
+ # If the transaction is a duplicate, we don't want to finish it,
158
+ # because we want its finish time to be the finish time of the
159
+ # original transaction.
160
+ # Duplicate transactions should always be sampled, as we only
161
+ # create duplicates for errors, which are always sampled.
162
+ should_sample = true
163
+
164
+ unless is_duplicate
165
+ self.class.last_errors = @error_blocks.keys
166
+ should_sample = ext.finish(0)
167
+ end
168
+
169
+ @error_blocks.each do |error, blocks|
170
+ # Ignore the error that is already set in this transaction.
171
+ next if error == @error_set
172
+
173
+ duplicate.tap do |transaction|
174
+ # In the duplicate transaction for each error, set an error
175
+ # with a block that calls all the blocks set for that error
176
+ # in the original transaction.
177
+ transaction.set_error(error) do
178
+ blocks.each { |block| block.call(transaction) }
179
+ end
180
+
181
+ transaction.complete
182
+ end
183
+ end
184
+
185
+ if @error_set && @error_blocks[@error_set].any?
186
+ self.class.with_transaction(self) do
187
+ @error_blocks[@error_set].each do |block|
188
+ block.call(self)
189
+ end
190
+ end
191
+ end
192
+ sample_data if should_sample
193
+ ext.complete
176
194
  end
177
195
 
178
196
  # @api private
@@ -210,69 +228,40 @@ module Appsignal
210
228
  @store[key]
211
229
  end
212
230
 
213
- # @api private
214
- def params
215
- parameters = @params || request_params
216
-
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
225
- end
226
-
227
- # Set parameters on the transaction.
228
- #
229
- # When no parameters are set this way, the transaction will look for
230
- # parameters on the {#request} environment.
231
+ # Add parameters to the transaction.
231
232
  #
232
- # The parameters set using {#set_params} are leading over those extracted
233
- # from a request's environment.
233
+ # When this method is called multiple times, it will merge the request parameters.
234
234
  #
235
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.
236
+ # block is leading and the argument will _not_ be used.
237
237
  #
238
- # @since 3.9.1
238
+ # @since 4.0.0
239
239
  # @param given_params [Hash] The parameters to set on the transaction.
240
240
  # @yield This block is called when the transaction is sampled. The block's
241
241
  # return value will become the new parameters.
242
242
  # @return [void]
243
- # @see Helpers::Instrumentation#set_params
244
- def set_params(given_params = nil, &block)
245
- @params = block if block
246
- @params = given_params if given_params
247
- end
248
-
249
- # @deprecated Use {#set_params} or {#set_params_if_nil} instead.
250
- def params=(given_params)
251
- Appsignal::Utils::StdoutAndLoggerMessage.warning(
252
- "Transaction#params= is deprecated." \
253
- "Use Transaction#set_params or #set_params_if_nil instead."
254
- )
255
- set_params(given_params)
243
+ # @see Helpers::Instrumentation#add_params
244
+ def add_params(given_params = nil, &block)
245
+ @params.add(given_params, &block)
256
246
  end
247
+ alias :set_params :add_params
257
248
 
258
- # Set parameters on the transaction if not already set
249
+ # Add parameters to the transaction if not already set.
259
250
  #
260
- # When no parameters are set this way, the transaction will look for
261
- # parameters on the {#request} environment.
262
- #
263
- # @since 3.9.1
251
+ # @api private
252
+ # @since 4.0.0
264
253
  # @param given_params [Hash] The parameters to set on the transaction if none are already set.
265
254
  # @yield This block is called when the transaction is sampled. The block's
266
255
  # return value will become the new parameters.
267
256
  # @return [void]
268
257
  #
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
258
+ # @see #add_params
259
+ def add_params_if_nil(given_params = nil, &block)
260
+ add_params(given_params, &block) unless @params.value?
273
261
  end
262
+ alias :set_params_if_nil :add_params_if_nil
274
263
 
275
- # Set tags on the transaction.
264
+ # Add tags to the transaction.
276
265
  #
277
266
  # When this method is called multiple times, it will merge the tags.
278
267
  #
@@ -283,32 +272,34 @@ module Appsignal
283
272
  # The name of the tag as a String.
284
273
  # @return [void]
285
274
  #
286
- # @see Helpers::Instrumentation#tag_request
275
+ # @see Helpers::Instrumentation#add_tags
287
276
  # @see https://docs.appsignal.com/ruby/instrumentation/tagging.html
288
277
  # Tagging guide
289
- def set_tags(given_tags = {})
278
+ def add_tags(given_tags = {})
290
279
  @tags.merge!(given_tags)
291
280
  end
281
+ alias :set_tags add_tags
292
282
 
293
- # Set session data on the transaction.
283
+ # Add session data to the transaction.
284
+ #
285
+ # When this method is called multiple times, it will merge the session data.
294
286
  #
295
287
  # 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.
288
+ # the block is leading and the argument will _not_ be used.
298
289
  #
299
290
  # @param given_session_data [Hash] A hash containing session data.
300
291
  # @yield This block is called when the transaction is sampled. The block's
301
292
  # return value will become the new session data.
302
293
  # @return [void]
303
294
  #
304
- # @since 3.10.1
305
- # @see Helpers::Instrumentation#set_session_data
295
+ # @since 4.0.0
296
+ # @see Helpers::Instrumentation#add_session_data
306
297
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
307
298
  # 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
299
+ def add_session_data(given_session_data = nil, &block)
300
+ @session_data.add(given_session_data, &block)
311
301
  end
302
+ alias :set_session_data :add_session_data
312
303
 
313
304
  # Set session data on the transaction if not already set.
314
305
  #
@@ -321,73 +312,64 @@ module Appsignal
321
312
  # return value will become the new session data.
322
313
  # @return [void]
323
314
  #
324
- # @since 3.10.1
325
- # @see #set_session_data
315
+ # @api private
316
+ # @since 4.0.0
317
+ # @see #add_session_data
326
318
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
327
319
  # 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
320
+ def add_session_data_if_nil(given_session_data = nil, &block)
321
+ add_session_data(given_session_data, &block) unless @session_data.value?
330
322
  end
323
+ alias :set_session_data_if_nil :add_session_data_if_nil
331
324
 
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.
325
+ # Add headers to the transaction.
337
326
  #
338
327
  # @param given_headers [Hash] A hash containing headers.
339
328
  # @yield This block is called when the transaction is sampled. The block's
340
329
  # return value will become the new headers.
341
330
  # @return [void]
342
331
  #
343
- # @since 3.10.1
344
- # @see Helpers::Instrumentation#set_headers
332
+ # @since 4.0.0
333
+ # @see Helpers::Instrumentation#add_headers
345
334
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
346
335
  # Sample data guide
347
- def set_headers(given_headers = nil, &block)
348
- @headers = block if block
349
- @headers = given_headers if given_headers
336
+ def add_headers(given_headers = nil, &block)
337
+ @headers.add(given_headers, &block)
350
338
  end
339
+ alias :set_headers :add_headers
351
340
 
352
- # Set headers on the transaction if not already set.
341
+ # Add headers to the transaction if not already set.
353
342
  #
354
343
  # 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.
344
+ # the block is leading and the argument will _not_ be used.
357
345
  #
358
346
  # @param given_headers [Hash] A hash containing headers.
359
347
  # @yield This block is called when the transaction is sampled. The block's
360
348
  # return value will become the new headers.
361
349
  # @return [void]
362
350
  #
363
- # @since 3.10.1
364
- # @see #set_headers
351
+ # @api private
352
+ # @since 4.0.0
353
+ # @see #add_headers
365
354
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
366
355
  # Sample data guide
367
- def set_headers_if_nil(given_headers = nil, &block)
368
- set_headers(given_headers, &block) unless @headers
356
+ def add_headers_if_nil(given_headers = nil, &block)
357
+ add_headers(given_headers, &block) unless @headers.value?
369
358
  end
359
+ alias :set_headers_if_nil :add_headers_if_nil
370
360
 
371
- # Set custom data on the transaction.
361
+ # Add custom data to the transaction.
372
362
  #
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
363
+ # @since 4.0.0
364
+ # @see Helpers::Instrumentation#add_custom_data
378
365
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
379
366
  # Sample data guide
380
367
  # @param data [Hash/Array]
381
368
  # @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
369
+ def add_custom_data(data)
370
+ @custom_data.add(data)
390
371
  end
372
+ alias :set_custom_data :add_custom_data
391
373
 
392
374
  # Add breadcrumbs to the transaction.
393
375
  #
@@ -479,18 +461,6 @@ module Appsignal
479
461
  @ext.set_namespace(namespace)
480
462
  end
481
463
 
482
- # @deprecated Use the {#set_action} helper.
483
- # @api private
484
- def set_http_or_background_action(from = request.params)
485
- return unless from
486
-
487
- group_and_action = [
488
- from[:controller] || from[:class],
489
- from[:action] || from[:method]
490
- ]
491
- set_action_if_nil(group_and_action.compact.join("#"))
492
- end
493
-
494
464
  # Set queue start time for transaction.
495
465
  #
496
466
  # @param start [Integer] Queue start time in milliseconds.
@@ -507,35 +477,6 @@ module Appsignal
507
477
  Appsignal.internal_logger.warn("Queue start value #{start} is too big")
508
478
  end
509
479
 
510
- # Set the queue time based on the HTTP header or `:queue_start` env key
511
- # value.
512
- #
513
- # This method will first try to read the queue time from the HTTP headers
514
- # `X-Request-Start` or `X-Queue-Start`. Which are parsed by Rack as
515
- # `HTTP_X_QUEUE_START` and `HTTP_X_REQUEST_START`.
516
- # The header value is parsed by AppSignal as either milliseconds or
517
- # microseconds.
518
- #
519
- # If no headers are found, or the value could not be parsed, it falls back
520
- # on the `:queue_start` env key on this Transaction's {request} environment
521
- # (called like `request.env[:queue_start]`). This value is parsed by
522
- # AppSignal as seconds.
523
- #
524
- # @see https://docs.appsignal.com/ruby/instrumentation/request-queue-time.html
525
- # @deprecated Use {#set_queue_start} instead.
526
- # @return [void]
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
-
533
- start = http_queue_start || background_queue_start
534
- return unless start
535
-
536
- set_queue_start(start)
537
- end
538
-
539
480
  # @api private
540
481
  def set_metadata(key, value)
541
482
  return unless key && value
@@ -544,81 +485,33 @@ module Appsignal
544
485
  @ext.set_metadata(key, value)
545
486
  end
546
487
 
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
550
- def set_sample_data(key, 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."
555
- )
556
- _set_sample_data(key, data)
557
- end
558
-
559
- # @deprecated No replacement.
560
- # @api private
561
- def sample_data
562
- Appsignal::Utils::StdoutAndLoggerMessage.warning(
563
- "Appsignal::Transaction#sample_data is deprecated. " \
564
- "Please remove any calls to this method."
565
- )
566
- _sample_data
567
- end
568
-
569
- # @see Appsignal::Helpers::Instrumentation#set_error
570
- def set_error(error)
488
+ # @see Appsignal::Helpers::Instrumentation#add_error
489
+ def add_error(error, &block)
571
490
  unless error.is_a?(Exception)
572
- Appsignal.internal_logger.error "Appsignal::Transaction#set_error: Cannot set error. " \
491
+ Appsignal.internal_logger.error "Appsignal::Transaction#add_error: Cannot add error. " \
573
492
  "The given value is not an exception: #{error.inspect}"
574
493
  return
575
494
  end
495
+
576
496
  return unless error
577
497
  return unless Appsignal.active?
578
498
 
579
- backtrace = cleaned_backtrace(error.backtrace)
580
- @ext.set_error(
581
- error.class.name,
582
- cleaned_error_message(error),
583
- backtrace ? Appsignal::Utils::Data.generate(backtrace) : Appsignal::Extension.data_array_new
584
- )
585
-
586
- root_cause_missing = false
587
-
588
- causes = []
589
- while error
590
- error = error.cause
591
-
592
- break unless error
499
+ _set_error(error) if @error_blocks.empty?
593
500
 
594
- if causes.length >= ERROR_CAUSES_LIMIT
595
- Appsignal.internal_logger.debug "Appsignal::Transaction#set_error: Error has more " \
596
- "than #{ERROR_CAUSES_LIMIT} error causes. Only the first #{ERROR_CAUSES_LIMIT} " \
597
- "will be reported."
598
- root_cause_missing = true
599
- break
600
- end
601
-
602
- causes << error
501
+ if !@error_blocks.include?(error) && @error_blocks.length >= ERRORS_LIMIT
502
+ Appsignal.internal_logger.warn "Appsignal::Transaction#add_error: Transaction has more " \
503
+ "than #{ERRORS_LIMIT} distinct errors. Only the first " \
504
+ "#{ERRORS_LIMIT} distinct errors will be reported."
505
+ return
603
506
  end
604
507
 
605
- return if causes.empty?
606
-
607
- causes_sample_data = causes.map do |e|
608
- {
609
- :name => e.class.name,
610
- :message => cleaned_error_message(e)
611
- }
612
- end
508
+ @error_blocks[error] << block
509
+ @error_blocks[error].compact!
510
+ end
613
511
 
614
- causes_sample_data.last[:is_root_cause] = false if root_cause_missing
512
+ alias :set_error :add_error
615
513
 
616
- _set_sample_data(
617
- "error_causes",
618
- causes_sample_data
619
- )
620
- end
621
- alias_method :add_exception, :set_error
514
+ alias_method :add_exception, :add_error
622
515
 
623
516
  # @see Helpers::Instrumentation#instrument
624
517
  # @api private
@@ -671,37 +564,56 @@ module Appsignal
671
564
  end
672
565
  alias_method :to_hash, :to_h
673
566
 
674
- # @api private
675
- class InternalGenericRequest
676
- attr_reader :env
567
+ protected
677
568
 
678
- def initialize(env)
679
- @env = env
680
- end
569
+ attr_writer :is_duplicate, :tags, :custom_data, :breadcrumbs, :params, :session_data, :headers
570
+
571
+ private
572
+
573
+ def _set_error(error)
574
+ backtrace = cleaned_backtrace(error.backtrace)
575
+ @ext.set_error(
576
+ error.class.name,
577
+ cleaned_error_message(error),
578
+ backtrace ? Appsignal::Utils::Data.generate(backtrace) : Appsignal::Extension.data_array_new
579
+ )
580
+ @error_set = error
581
+
582
+ root_cause_missing = false
583
+
584
+ causes = []
585
+ while error
586
+ error = error.cause
587
+
588
+ break unless error
589
+
590
+ if causes.length >= ERROR_CAUSES_LIMIT
591
+ Appsignal.internal_logger.debug "Appsignal::Transaction#add_error: Error has more " \
592
+ "than #{ERROR_CAUSES_LIMIT} error causes. Only the first #{ERROR_CAUSES_LIMIT} " \
593
+ "will be reported."
594
+ root_cause_missing = true
595
+ break
596
+ end
681
597
 
682
- def params
683
- env[:params]
598
+ causes << error
684
599
  end
685
- end
686
600
 
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
601
+ causes_sample_data = causes.map do |e|
602
+ {
603
+ :name => e.class.name,
604
+ :message => cleaned_error_message(e)
605
+ }
699
606
  end
700
- end
701
607
 
702
- private
608
+ causes_sample_data.last[:is_root_cause] = false if root_cause_missing
609
+
610
+ set_sample_data(
611
+ "error_causes",
612
+ causes_sample_data
613
+ )
614
+ end
703
615
 
704
- def _set_sample_data(key, data)
616
+ def set_sample_data(key, data)
705
617
  return unless key && data
706
618
 
707
619
  if !data.is_a?(Array) && !data.is_a?(Hash)
@@ -728,44 +640,43 @@ module Appsignal
728
640
  end
729
641
  end
730
642
 
731
- def _sample_data
643
+ def sample_data
732
644
  {
733
645
  :params => sanitized_params,
734
- :environment => sanitized_environment,
646
+ :environment => sanitized_request_headers,
735
647
  :session_data => sanitized_session_data,
736
- :metadata => sanitized_metadata,
737
648
  :tags => sanitized_tags,
738
649
  :breadcrumbs => breadcrumbs,
739
650
  :custom_data => custom_data
740
651
  }.each do |key, data|
741
- _set_sample_data(key, data)
652
+ set_sample_data(key, data)
742
653
  end
743
654
  end
744
655
 
745
- # Returns calculated background queue start time in milliseconds, based on
746
- # environment values.
747
- #
748
- # @return [nil] if no {#environment} is present.
749
- # @return [nil] if there is no `:queue_start` in the {#environment}.
750
- # @return [Integer] `:queue_start` time (in seconds) converted to milliseconds
751
- def background_queue_start
752
- env = environment
753
- return unless env
754
-
755
- queue_start = env[:queue_start]
756
- return unless queue_start
757
-
758
- (queue_start.to_f * 1000.0).to_i # Convert seconds to milliseconds
656
+ def duplicate
657
+ new_transaction_id = SecureRandom.uuid
658
+
659
+ self.class.new(
660
+ new_transaction_id,
661
+ namespace,
662
+ :ext => ext.duplicate(new_transaction_id)
663
+ ).tap do |transaction|
664
+ transaction.is_duplicate = true
665
+ transaction.tags = @tags.dup
666
+ transaction.custom_data = @custom_data.dup
667
+ transaction.breadcrumbs = @breadcrumbs.dup
668
+ transaction.params = @params.dup
669
+ transaction.session_data = @session_data.dup
670
+ transaction.headers = @headers.dup
671
+ end
759
672
  end
760
673
 
761
- # Returns HTTP queue start time in milliseconds.
762
- #
763
- # @return [nil] if no queue start time is found.
764
- # @return [nil] if begin time is too low to be plausible.
765
- # @return [Integer] queue start in milliseconds.
766
- def http_queue_start
767
- env = environment
768
- Appsignal::Rack::Utils.queue_start_from(env)
674
+ # @api private
675
+ def params
676
+ @params.value
677
+ rescue => e
678
+ Appsignal.internal_logger.error("Exception while fetching params: #{e.class}: #{e}")
679
+ nil
769
680
  end
770
681
 
771
682
  def sanitized_params
@@ -775,27 +686,8 @@ module Appsignal
775
686
  Appsignal::Utils::HashSanitizer.sanitize params, filter_keys
776
687
  end
777
688
 
778
- def request_params
779
- return unless request.respond_to?(options[:params_method])
780
-
781
- begin
782
- request.send options[:params_method]
783
- rescue => e
784
- Appsignal.internal_logger.warn "Exception while getting params: #{e}"
785
- nil
786
- end
787
- end
788
-
789
689
  def session_data
790
- if @session_data
791
- if @session_data.respond_to? :call
792
- @session_data.call
793
- else
794
- @session_data
795
- end
796
- elsif request.respond_to?(:session)
797
- request.session
798
- end
690
+ @session_data.value
799
691
  rescue => e
800
692
  Appsignal.internal_logger.error \
801
693
  "Exception while fetching session data: #{e.class}: #{e}"
@@ -814,35 +706,12 @@ module Appsignal
814
706
  return unless Appsignal.config[:send_session_data]
815
707
 
816
708
  Appsignal::Utils::HashSanitizer.sanitize(
817
- session_data&.to_hash, Appsignal.config[:filter_session_data]
709
+ session_data, Appsignal.config[:filter_session_data]
818
710
  )
819
711
  end
820
712
 
821
- # Returns sanitized metadata set on the request environment.
822
- #
823
- # @return [Hash<String, Object>]
824
- def sanitized_metadata
825
- env = environment
826
- return unless env
827
-
828
- metadata = env[:metadata]
829
- return unless metadata
830
-
831
- metadata
832
- .transform_keys(&:to_s)
833
- .reject { |key, _value| Appsignal.config[:filter_metadata].include?(key) }
834
- end
835
-
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
713
+ def request_headers
714
+ @headers.value
846
715
  rescue => e
847
716
  Appsignal.internal_logger.error \
848
717
  "Exception while fetching headers: #{e.class}: #{e}"
@@ -856,15 +725,13 @@ module Appsignal
856
725
  #
857
726
  # @return [nil] if no environment is present.
858
727
  # @return [Hash<String, Object>]
859
- def sanitized_environment
860
- env = environment
861
- return unless env
862
- return unless env.respond_to?(:empty?)
863
- return if env.empty?
728
+ def sanitized_request_headers
729
+ headers = request_headers
730
+ return unless headers
864
731
 
865
732
  {}.tap do |out|
866
733
  Appsignal.config[:request_headers].each do |key|
867
- out[key] = env[key] if env[key]
734
+ out[key] = headers[key] if headers[key]
868
735
  end
869
736
  end
870
737
  end
@@ -890,6 +757,13 @@ module Appsignal
890
757
  end
891
758
  end
892
759
 
760
+ def custom_data
761
+ @custom_data.value
762
+ rescue => e
763
+ Appsignal.internal_logger.error("Exception while fetching custom data: #{e.class}: #{e}")
764
+ nil
765
+ end
766
+
893
767
  # Clean error messages that are known to potentially contain user data.
894
768
  # Returns an unchanged message otherwise.
895
769
  def cleaned_error_message(error)