appsignal 3.13.0-java → 4.0.0.beta.2-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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +499 -487
  3. data/CHANGELOG.md +121 -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 +2 -15
  11. data/lib/appsignal/cli/diagnose.rb +37 -28
  12. data/lib/appsignal/cli/install.rb +5 -1
  13. data/lib/appsignal/config.rb +57 -119
  14. data/lib/appsignal/demo.rb +2 -2
  15. data/lib/appsignal/extension/jruby.rb +14 -0
  16. data/lib/appsignal/helpers/instrumentation.rb +152 -416
  17. data/lib/appsignal/helpers/metrics.rb +0 -16
  18. data/lib/appsignal/hooks/action_cable.rb +8 -8
  19. data/lib/appsignal/hooks/active_job.rb +2 -2
  20. data/lib/appsignal/hooks/at_exit.rb +37 -0
  21. data/lib/appsignal/hooks.rb +1 -16
  22. data/lib/appsignal/integrations/action_cable.rb +2 -2
  23. data/lib/appsignal/integrations/capistrano/appsignal.cap +2 -4
  24. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +1 -4
  25. data/lib/appsignal/integrations/delayed_job_plugin.rb +3 -3
  26. data/lib/appsignal/integrations/que.rb +2 -2
  27. data/lib/appsignal/integrations/railtie.rb +26 -59
  28. data/lib/appsignal/integrations/rake.rb +2 -2
  29. data/lib/appsignal/integrations/resque.rb +2 -2
  30. data/lib/appsignal/integrations/shoryuken.rb +4 -4
  31. data/lib/appsignal/integrations/sidekiq.rb +3 -3
  32. data/lib/appsignal/integrations/webmachine.rb +2 -2
  33. data/lib/appsignal/loaders.rb +1 -1
  34. data/lib/appsignal/probes.rb +0 -9
  35. data/lib/appsignal/rack/abstract_middleware.rb +4 -26
  36. data/lib/appsignal/rack/event_handler.rb +4 -4
  37. data/lib/appsignal/rack/rails_instrumentation.rb +1 -1
  38. data/lib/appsignal/rack.rb +0 -25
  39. data/lib/appsignal/sample_data.rb +108 -0
  40. data/lib/appsignal/transaction.rb +241 -359
  41. data/lib/appsignal/utils/rails_helper.rb +4 -0
  42. data/lib/appsignal/version.rb +1 -1
  43. data/lib/appsignal.rb +19 -71
  44. data/spec/lib/appsignal/auth_check_spec.rb +1 -1
  45. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  46. data/spec/lib/appsignal/capistrano3_spec.rb +53 -13
  47. data/spec/lib/appsignal/check_in_spec.rb +1 -207
  48. data/spec/lib/appsignal/cli/demo_spec.rb +7 -27
  49. data/spec/lib/appsignal/cli/diagnose_spec.rb +145 -110
  50. data/spec/lib/appsignal/config_spec.rb +304 -379
  51. data/spec/lib/appsignal/extension_install_failure_spec.rb +5 -1
  52. data/spec/lib/appsignal/extension_spec.rb +5 -1
  53. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +1 -1
  54. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +1 -2
  55. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +1 -0
  56. data/spec/lib/appsignal/hooks/activejob_spec.rb +7 -12
  57. data/spec/lib/appsignal/hooks/at_exit_spec.rb +72 -0
  58. data/spec/lib/appsignal/hooks/gvl_spec.rb +10 -5
  59. data/spec/lib/appsignal/hooks/http_spec.rb +3 -3
  60. data/spec/lib/appsignal/hooks/net_http_spec.rb +3 -3
  61. data/spec/lib/appsignal/hooks/rake_spec.rb +6 -9
  62. data/spec/lib/appsignal/hooks/redis_client_spec.rb +5 -10
  63. data/spec/lib/appsignal/hooks/redis_spec.rb +4 -7
  64. data/spec/lib/appsignal/hooks/resque_spec.rb +3 -5
  65. data/spec/lib/appsignal/hooks_spec.rb +0 -41
  66. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +29 -20
  67. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +4 -9
  68. data/spec/lib/appsignal/integrations/railtie_spec.rb +179 -157
  69. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +3 -5
  70. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +48 -62
  71. data/spec/lib/appsignal/loaders/hanami_spec.rb +6 -9
  72. data/spec/lib/appsignal/loaders/padrino_spec.rb +6 -10
  73. data/spec/lib/appsignal/loaders/sinatra_spec.rb +6 -9
  74. data/spec/lib/appsignal/loaders_spec.rb +8 -1
  75. data/spec/lib/appsignal/marker_spec.rb +1 -1
  76. data/spec/lib/appsignal/probes_spec.rb +4 -83
  77. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +4 -63
  78. data/spec/lib/appsignal/rack/event_handler_spec.rb +18 -15
  79. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +3 -11
  80. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +4 -5
  81. data/spec/lib/appsignal/sample_data_spec.rb +213 -0
  82. data/spec/lib/appsignal/transaction_spec.rb +817 -1046
  83. data/spec/lib/appsignal/transmitter_spec.rb +6 -8
  84. data/spec/lib/appsignal_spec.rb +311 -643
  85. data/spec/spec_helper.rb +1 -3
  86. data/spec/support/fixtures/projects/valid/config/appsignal.yml +4 -7
  87. data/spec/support/fixtures/projects/valid_with_rails_app/config/application.rb +16 -0
  88. data/spec/support/fixtures/projects/valid_with_rails_app/config/appsignal.yml +56 -0
  89. data/spec/support/fixtures/projects/valid_with_rails_app/config/environment.rb +5 -0
  90. data/spec/support/helpers/api_request_helper.rb +3 -2
  91. data/spec/support/helpers/config_helpers.rb +41 -11
  92. data/spec/support/helpers/dependency_helper.rb +8 -0
  93. data/spec/support/helpers/log_helpers.rb +1 -0
  94. data/spec/support/helpers/rails_helper.rb +6 -6
  95. data/spec/support/helpers/transaction_helpers.rb +2 -24
  96. data/spec/support/matchers/transaction.rb +3 -3
  97. data/spec/support/mocks/appsignal_mock.rb +3 -3
  98. data/spec/support/mocks/mock_probe.rb +2 -0
  99. data/spec/support/testing.rb +2 -2
  100. metadata +12 -22
  101. data/gemfiles/que_beta.gemfile +0 -5
  102. data/lib/appsignal/helpers/heartbeat.rb +0 -20
  103. data/lib/appsignal/integrations/grape.rb +0 -35
  104. data/lib/appsignal/integrations/hanami.rb +0 -13
  105. data/lib/appsignal/integrations/padrino.rb +0 -13
  106. data/lib/appsignal/integrations/sinatra.rb +0 -13
  107. data/lib/appsignal/rack/generic_instrumentation.rb +0 -22
  108. data/lib/appsignal/rack/streaming_listener.rb +0 -28
  109. data/spec/lib/appsignal/integrations/grape_spec.rb +0 -36
  110. data/spec/lib/appsignal/integrations/hanami_spec.rb +0 -17
  111. data/spec/lib/appsignal/integrations/padrino_spec.rb +0 -15
  112. data/spec/lib/appsignal/integrations/sinatra_spec.rb +0 -15
  113. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +0 -81
  114. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +0 -69
  115. data/spec/support/fixtures/projects/valid/config/environments/development.rb +0 -0
  116. data/spec/support/fixtures/projects/valid/config/environments/production.rb +0 -0
  117. data/spec/support/fixtures/projects/valid/config/environments/test.rb +0 -0
  118. data/spec/support/rails/my_app.rb +0 -6
  119. /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,48 @@ 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
243
+ # @see Helpers::Instrumentation#add_params
244
+ def add_params(given_params = nil, &block)
245
+ @params.add(given_params, &block)
247
246
  end
247
+ alias :set_params :add_params
248
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)
249
+ # @api private
250
+ # @since 4.0.0
251
+ # @return [void]
252
+ # @see Helpers::Instrumentation#set_empty_params!
253
+ def set_empty_params!
254
+ @params.set_empty_value!
256
255
  end
257
256
 
258
- # Set parameters on the transaction if not already set
257
+ # Add parameters to the transaction if not already set.
259
258
  #
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
259
+ # @api private
260
+ # @since 4.0.0
264
261
  # @param given_params [Hash] The parameters to set on the transaction if none are already set.
265
262
  # @yield This block is called when the transaction is sampled. The block's
266
263
  # return value will become the new parameters.
267
264
  # @return [void]
268
265
  #
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
266
+ # @see #add_params
267
+ def add_params_if_nil(given_params = nil, &block)
268
+ add_params(given_params, &block) if !@params.value? && !@params.empty?
273
269
  end
270
+ alias :set_params_if_nil :add_params_if_nil
274
271
 
275
- # Set tags on the transaction.
272
+ # Add tags to the transaction.
276
273
  #
277
274
  # When this method is called multiple times, it will merge the tags.
278
275
  #
@@ -283,32 +280,34 @@ module Appsignal
283
280
  # The name of the tag as a String.
284
281
  # @return [void]
285
282
  #
286
- # @see Helpers::Instrumentation#tag_request
283
+ # @see Helpers::Instrumentation#add_tags
287
284
  # @see https://docs.appsignal.com/ruby/instrumentation/tagging.html
288
285
  # Tagging guide
289
- def set_tags(given_tags = {})
286
+ def add_tags(given_tags = {})
290
287
  @tags.merge!(given_tags)
291
288
  end
289
+ alias :set_tags add_tags
292
290
 
293
- # Set session data on the transaction.
291
+ # Add session data to the transaction.
292
+ #
293
+ # When this method is called multiple times, it will merge the session data.
294
294
  #
295
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.
296
+ # the block is leading and the argument will _not_ be used.
298
297
  #
299
298
  # @param given_session_data [Hash] A hash containing session data.
300
299
  # @yield This block is called when the transaction is sampled. The block's
301
300
  # return value will become the new session data.
302
301
  # @return [void]
303
302
  #
304
- # @since 3.10.1
305
- # @see Helpers::Instrumentation#set_session_data
303
+ # @since 4.0.0
304
+ # @see Helpers::Instrumentation#add_session_data
306
305
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
307
306
  # 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
307
+ def add_session_data(given_session_data = nil, &block)
308
+ @session_data.add(given_session_data, &block)
311
309
  end
310
+ alias :set_session_data :add_session_data
312
311
 
313
312
  # Set session data on the transaction if not already set.
314
313
  #
@@ -321,73 +320,64 @@ module Appsignal
321
320
  # return value will become the new session data.
322
321
  # @return [void]
323
322
  #
324
- # @since 3.10.1
325
- # @see #set_session_data
323
+ # @api private
324
+ # @since 4.0.0
325
+ # @see #add_session_data
326
326
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
327
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
328
+ def add_session_data_if_nil(given_session_data = nil, &block)
329
+ add_session_data(given_session_data, &block) unless @session_data.value?
330
330
  end
331
+ alias :set_session_data_if_nil :add_session_data_if_nil
331
332
 
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.
333
+ # Add headers to the transaction.
337
334
  #
338
335
  # @param given_headers [Hash] A hash containing headers.
339
336
  # @yield This block is called when the transaction is sampled. The block's
340
337
  # return value will become the new headers.
341
338
  # @return [void]
342
339
  #
343
- # @since 3.10.1
344
- # @see Helpers::Instrumentation#set_headers
340
+ # @since 4.0.0
341
+ # @see Helpers::Instrumentation#add_headers
345
342
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
346
343
  # Sample data guide
347
- def set_headers(given_headers = nil, &block)
348
- @headers = block if block
349
- @headers = given_headers if given_headers
344
+ def add_headers(given_headers = nil, &block)
345
+ @headers.add(given_headers, &block)
350
346
  end
347
+ alias :set_headers :add_headers
351
348
 
352
- # Set headers on the transaction if not already set.
349
+ # Add headers to the transaction if not already set.
353
350
  #
354
351
  # 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.
352
+ # the block is leading and the argument will _not_ be used.
357
353
  #
358
354
  # @param given_headers [Hash] A hash containing headers.
359
355
  # @yield This block is called when the transaction is sampled. The block's
360
356
  # return value will become the new headers.
361
357
  # @return [void]
362
358
  #
363
- # @since 3.10.1
364
- # @see #set_headers
359
+ # @api private
360
+ # @since 4.0.0
361
+ # @see #add_headers
365
362
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
366
363
  # Sample data guide
367
- def set_headers_if_nil(given_headers = nil, &block)
368
- set_headers(given_headers, &block) unless @headers
364
+ def add_headers_if_nil(given_headers = nil, &block)
365
+ add_headers(given_headers, &block) unless @headers.value?
369
366
  end
367
+ alias :set_headers_if_nil :add_headers_if_nil
370
368
 
371
- # Set custom data on the transaction.
369
+ # Add custom data to the transaction.
372
370
  #
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
371
+ # @since 4.0.0
372
+ # @see Helpers::Instrumentation#add_custom_data
378
373
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
379
374
  # Sample data guide
380
375
  # @param data [Hash/Array]
381
376
  # @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
377
+ def add_custom_data(data)
378
+ @custom_data.add(data)
390
379
  end
380
+ alias :set_custom_data :add_custom_data
391
381
 
392
382
  # Add breadcrumbs to the transaction.
393
383
  #
@@ -479,18 +469,6 @@ module Appsignal
479
469
  @ext.set_namespace(namespace)
480
470
  end
481
471
 
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
472
  # Set queue start time for transaction.
495
473
  #
496
474
  # @param start [Integer] Queue start time in milliseconds.
@@ -507,35 +485,6 @@ module Appsignal
507
485
  Appsignal.internal_logger.warn("Queue start value #{start} is too big")
508
486
  end
509
487
 
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
488
  # @api private
540
489
  def set_metadata(key, value)
541
490
  return unless key && value
@@ -544,81 +493,33 @@ module Appsignal
544
493
  @ext.set_metadata(key, value)
545
494
  end
546
495
 
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)
496
+ # @see Appsignal::Helpers::Instrumentation#add_error
497
+ def add_error(error, &block)
571
498
  unless error.is_a?(Exception)
572
- Appsignal.internal_logger.error "Appsignal::Transaction#set_error: Cannot set error. " \
499
+ Appsignal.internal_logger.error "Appsignal::Transaction#add_error: Cannot add error. " \
573
500
  "The given value is not an exception: #{error.inspect}"
574
501
  return
575
502
  end
503
+
576
504
  return unless error
577
505
  return unless Appsignal.active?
578
506
 
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
507
+ _set_error(error) if @error_blocks.empty?
593
508
 
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
509
+ if !@error_blocks.include?(error) && @error_blocks.length >= ERRORS_LIMIT
510
+ Appsignal.internal_logger.warn "Appsignal::Transaction#add_error: Transaction has more " \
511
+ "than #{ERRORS_LIMIT} distinct errors. Only the first " \
512
+ "#{ERRORS_LIMIT} distinct errors will be reported."
513
+ return
603
514
  end
604
515
 
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
516
+ @error_blocks[error] << block
517
+ @error_blocks[error].compact!
518
+ end
613
519
 
614
- causes_sample_data.last[:is_root_cause] = false if root_cause_missing
520
+ alias :set_error :add_error
615
521
 
616
- _set_sample_data(
617
- "error_causes",
618
- causes_sample_data
619
- )
620
- end
621
- alias_method :add_exception, :set_error
522
+ alias_method :add_exception, :add_error
622
523
 
623
524
  # @see Helpers::Instrumentation#instrument
624
525
  # @api private
@@ -671,37 +572,56 @@ module Appsignal
671
572
  end
672
573
  alias_method :to_hash, :to_h
673
574
 
674
- # @api private
675
- class InternalGenericRequest
676
- attr_reader :env
575
+ protected
677
576
 
678
- def initialize(env)
679
- @env = env
680
- end
577
+ attr_writer :is_duplicate, :tags, :custom_data, :breadcrumbs, :params, :session_data, :headers
578
+
579
+ private
580
+
581
+ def _set_error(error)
582
+ backtrace = cleaned_backtrace(error.backtrace)
583
+ @ext.set_error(
584
+ error.class.name,
585
+ cleaned_error_message(error),
586
+ backtrace ? Appsignal::Utils::Data.generate(backtrace) : Appsignal::Extension.data_array_new
587
+ )
588
+ @error_set = error
589
+
590
+ root_cause_missing = false
591
+
592
+ causes = []
593
+ while error
594
+ error = error.cause
595
+
596
+ break unless error
597
+
598
+ if causes.length >= ERROR_CAUSES_LIMIT
599
+ Appsignal.internal_logger.debug "Appsignal::Transaction#add_error: Error has more " \
600
+ "than #{ERROR_CAUSES_LIMIT} error causes. Only the first #{ERROR_CAUSES_LIMIT} " \
601
+ "will be reported."
602
+ root_cause_missing = true
603
+ break
604
+ end
681
605
 
682
- def params
683
- env[:params]
606
+ causes << error
684
607
  end
685
- end
686
608
 
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
609
+ causes_sample_data = causes.map do |e|
610
+ {
611
+ :name => e.class.name,
612
+ :message => cleaned_error_message(e)
613
+ }
699
614
  end
700
- end
701
615
 
702
- private
616
+ causes_sample_data.last[:is_root_cause] = false if root_cause_missing
617
+
618
+ set_sample_data(
619
+ "error_causes",
620
+ causes_sample_data
621
+ )
622
+ end
703
623
 
704
- def _set_sample_data(key, data)
624
+ def set_sample_data(key, data)
705
625
  return unless key && data
706
626
 
707
627
  if !data.is_a?(Array) && !data.is_a?(Hash)
@@ -728,44 +648,43 @@ module Appsignal
728
648
  end
729
649
  end
730
650
 
731
- def _sample_data
651
+ def sample_data
732
652
  {
733
653
  :params => sanitized_params,
734
- :environment => sanitized_environment,
654
+ :environment => sanitized_request_headers,
735
655
  :session_data => sanitized_session_data,
736
- :metadata => sanitized_metadata,
737
656
  :tags => sanitized_tags,
738
657
  :breadcrumbs => breadcrumbs,
739
658
  :custom_data => custom_data
740
659
  }.each do |key, data|
741
- _set_sample_data(key, data)
660
+ set_sample_data(key, data)
742
661
  end
743
662
  end
744
663
 
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
664
+ def duplicate
665
+ new_transaction_id = SecureRandom.uuid
666
+
667
+ self.class.new(
668
+ new_transaction_id,
669
+ namespace,
670
+ :ext => ext.duplicate(new_transaction_id)
671
+ ).tap do |transaction|
672
+ transaction.is_duplicate = true
673
+ transaction.tags = @tags.dup
674
+ transaction.custom_data = @custom_data.dup
675
+ transaction.breadcrumbs = @breadcrumbs.dup
676
+ transaction.params = @params.dup
677
+ transaction.session_data = @session_data.dup
678
+ transaction.headers = @headers.dup
679
+ end
759
680
  end
760
681
 
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)
682
+ # @api private
683
+ def params
684
+ @params.value
685
+ rescue => e
686
+ Appsignal.internal_logger.error("Exception while fetching params: #{e.class}: #{e}")
687
+ nil
769
688
  end
770
689
 
771
690
  def sanitized_params
@@ -775,27 +694,8 @@ module Appsignal
775
694
  Appsignal::Utils::HashSanitizer.sanitize params, filter_keys
776
695
  end
777
696
 
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
697
  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
698
+ @session_data.value
799
699
  rescue => e
800
700
  Appsignal.internal_logger.error \
801
701
  "Exception while fetching session data: #{e.class}: #{e}"
@@ -814,35 +714,12 @@ module Appsignal
814
714
  return unless Appsignal.config[:send_session_data]
815
715
 
816
716
  Appsignal::Utils::HashSanitizer.sanitize(
817
- session_data&.to_hash, Appsignal.config[:filter_session_data]
717
+ session_data, Appsignal.config[:filter_session_data]
818
718
  )
819
719
  end
820
720
 
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
721
+ def request_headers
722
+ @headers.value
846
723
  rescue => e
847
724
  Appsignal.internal_logger.error \
848
725
  "Exception while fetching headers: #{e.class}: #{e}"
@@ -856,15 +733,13 @@ module Appsignal
856
733
  #
857
734
  # @return [nil] if no environment is present.
858
735
  # @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?
736
+ def sanitized_request_headers
737
+ headers = request_headers
738
+ return unless headers
864
739
 
865
740
  {}.tap do |out|
866
741
  Appsignal.config[:request_headers].each do |key|
867
- out[key] = env[key] if env[key]
742
+ out[key] = headers[key] if headers[key]
868
743
  end
869
744
  end
870
745
  end
@@ -890,6 +765,13 @@ module Appsignal
890
765
  end
891
766
  end
892
767
 
768
+ def custom_data
769
+ @custom_data.value
770
+ rescue => e
771
+ Appsignal.internal_logger.error("Exception while fetching custom data: #{e.class}: #{e}")
772
+ nil
773
+ end
774
+
893
775
  # Clean error messages that are known to potentially contain user data.
894
776
  # Returns an unchanged message otherwise.
895
777
  def cleaned_error_message(error)