appsignal 2.11.0.beta.4 → 2.11.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +254 -1
  3. data/CHANGELOG.md +27 -0
  4. data/README.md +20 -5
  5. data/Rakefile +27 -9
  6. data/appsignal.gemspec +1 -1
  7. data/build_matrix.yml +15 -2
  8. data/ext/Rakefile +2 -0
  9. data/ext/agent.yml +17 -25
  10. data/ext/appsignal_extension.c +1 -1
  11. data/ext/base.rb +19 -9
  12. data/ext/extconf.rb +2 -0
  13. data/gemfiles/no_dependencies.gemfile +7 -0
  14. data/gemfiles/resque-2.gemfile +0 -1
  15. data/gemfiles/webmachine.gemfile +1 -0
  16. data/lib/appsignal.rb +1 -0
  17. data/lib/appsignal/auth_check.rb +4 -2
  18. data/lib/appsignal/cli/diagnose.rb +1 -1
  19. data/lib/appsignal/cli/diagnose/utils.rb +8 -11
  20. data/lib/appsignal/cli/install.rb +5 -8
  21. data/lib/appsignal/config.rb +82 -17
  22. data/lib/appsignal/extension.rb +6 -5
  23. data/lib/appsignal/extension/jruby.rb +6 -5
  24. data/lib/appsignal/helpers/instrumentation.rb +32 -0
  25. data/lib/appsignal/hooks.rb +24 -0
  26. data/lib/appsignal/hooks/action_mailer.rb +22 -0
  27. data/lib/appsignal/hooks/active_job.rb +32 -9
  28. data/lib/appsignal/hooks/active_support_notifications.rb +72 -0
  29. data/lib/appsignal/hooks/puma.rb +0 -1
  30. data/lib/appsignal/hooks/sidekiq.rb +0 -1
  31. data/lib/appsignal/probes.rb +7 -0
  32. data/lib/appsignal/probes/puma.rb +1 -1
  33. data/lib/appsignal/probes/sidekiq.rb +3 -1
  34. data/lib/appsignal/transaction.rb +30 -2
  35. data/lib/appsignal/utils/deprecation_message.rb +1 -1
  36. data/lib/appsignal/version.rb +1 -1
  37. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  38. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  39. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  40. data/spec/lib/appsignal/cli/diagnose_spec.rb +42 -0
  41. data/spec/lib/appsignal/config_spec.rb +39 -1
  42. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  43. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  44. data/spec/lib/appsignal/hooks/action_mailer_spec.rb +54 -0
  45. data/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +35 -0
  46. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +145 -0
  47. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +69 -0
  48. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +9 -137
  49. data/spec/lib/appsignal/hooks/activejob_spec.rb +44 -1
  50. data/spec/lib/appsignal/hooks/resque_spec.rb +10 -2
  51. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +4 -2
  52. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  53. data/spec/lib/appsignal/marker_spec.rb +1 -1
  54. data/spec/lib/appsignal/transaction_spec.rb +55 -0
  55. data/spec/lib/appsignal_spec.rb +30 -0
  56. data/spec/spec_helper.rb +5 -0
  57. data/spec/support/helpers/config_helpers.rb +3 -2
  58. data/spec/support/helpers/dependency_helper.rb +4 -0
  59. data/spec/support/helpers/transaction_helpers.rb +1 -1
  60. data/spec/support/testing.rb +19 -19
  61. metadata +17 -5
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'webmachine'
4
+ gem 'webrick'
4
5
 
5
6
  gemspec :path => '../'
@@ -339,6 +339,7 @@ require "appsignal/auth_check"
339
339
  require "appsignal/config"
340
340
  require "appsignal/event_formatter"
341
341
  require "appsignal/hooks"
342
+ require "appsignal/probes"
342
343
  require "appsignal/marker"
343
344
  require "appsignal/minutely"
344
345
  require "appsignal/garbage_collection_profiler"
@@ -25,8 +25,10 @@ module Appsignal
25
25
  def initialize(config, logger = nil)
26
26
  @config = config
27
27
  if logger # rubocop:disable Style/GuardClause
28
- warn "Deprecated: `logger` argument will be removed in the next " \
29
- "major version."
28
+ Appsignal::Utils::DeprecationMessage.message \
29
+ "`Appsignal::AuthCheck.new`'s `logger` argument will be removed " \
30
+ "in the next major version. Please configure the logger " \
31
+ "using `Appsignal.logger`."
30
32
  end
31
33
  end
32
34
 
@@ -371,7 +371,7 @@ module Appsignal
371
371
  end
372
372
 
373
373
  def print_installation_result_report(report)
374
- report = report.fetch("download", {})
374
+ report = report.fetch("result", {})
375
375
  puts " Installation result"
376
376
  puts " Status: #{report["status"]}"
377
377
  puts " Message: #{report["message"]}" if report["message"]
@@ -34,20 +34,17 @@ module Appsignal
34
34
  end
35
35
 
36
36
  def self.parse_yaml(contents)
37
- arguments = [contents]
38
37
  if YAML.respond_to? :safe_load
39
- method = :safe_load
40
- arguments << \
41
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
42
- # Use keyword params for Ruby 2.6 and up
43
- { :permitted_classes => [Time] }
44
- else
45
- [Time]
46
- end
38
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
39
+ # Use keyword params for Ruby 2.6 and up
40
+ YAML.safe_load(contents, :permitted_classes => [Time])
41
+ else
42
+ YAML.safe_load(contents, [Time])
43
+ end
47
44
  else
48
- method = :load
45
+ # Support for Ruby versions without YAML.safe_load
46
+ YAML.load(contents) # rubocop:disable Security/YAMLLoad
49
47
  end
50
- YAML.send(method, *arguments)
51
48
  end
52
49
  end
53
50
  end
@@ -278,14 +278,11 @@ module Appsignal
278
278
  "../../../resources/appsignal.yml.erb"
279
279
  )
280
280
  file_contents = File.read(filename)
281
- arguments = [file_contents]
282
- if ruby_2_6_or_up?
283
- arguments << { :trim_mode => "-" }
284
- else
285
- arguments << nil
286
- arguments << "-"
287
- end
288
- template = ERB.new(*arguments)
281
+ template = if ruby_2_6_or_up?
282
+ ERB.new(file_contents, :trim_mode => "-")
283
+ else
284
+ ERB.new(file_contents, nil, "-")
285
+ end
289
286
  config = template.result(OpenStruct.new(data).instance_eval { binding })
290
287
 
291
288
  FileUtils.mkdir_p(File.join(Dir.pwd, "config"))
@@ -81,6 +81,50 @@ module Appsignal
81
81
  "APPSIGNAL_TRANSACTION_DEBUG_MODE" => :transaction_debug_mode,
82
82
  "APP_REVISION" => :revision
83
83
  }.freeze
84
+ # @api private
85
+ ENV_STRING_KEYS = %w[
86
+ APPSIGNAL_APP_NAME
87
+ APPSIGNAL_CA_FILE_PATH
88
+ APPSIGNAL_DNS_SERVERS
89
+ APPSIGNAL_FRONTEND_ERROR_CATCHING_PATH
90
+ APPSIGNAL_HOSTNAME
91
+ APPSIGNAL_HTTP_PROXY
92
+ APPSIGNAL_LOG
93
+ APPSIGNAL_LOG_PATH
94
+ APPSIGNAL_PUSH_API_ENDPOINT
95
+ APPSIGNAL_PUSH_API_KEY
96
+ APPSIGNAL_WORKING_DIRECTORY_PATH
97
+ APPSIGNAL_WORKING_DIR_PATH
98
+ APP_REVISION
99
+ ].freeze
100
+ # @api private
101
+ ENV_BOOLEAN_KEYS = %w[
102
+ APPSIGNAL_ACTIVE
103
+ APPSIGNAL_DEBUG
104
+ APPSIGNAL_ENABLE_ALLOCATION_TRACKING
105
+ APPSIGNAL_ENABLE_FRONTEND_ERROR_CATCHING
106
+ APPSIGNAL_ENABLE_GC_INSTRUMENTATION
107
+ APPSIGNAL_ENABLE_HOST_METRICS
108
+ APPSIGNAL_ENABLE_MINUTELY_PROBES
109
+ APPSIGNAL_FILES_WORLD_ACCESSIBLE
110
+ APPSIGNAL_INSTRUMENT_NET_HTTP
111
+ APPSIGNAL_INSTRUMENT_REDIS
112
+ APPSIGNAL_INSTRUMENT_SEQUEL
113
+ APPSIGNAL_RUNNING_IN_CONTAINER
114
+ APPSIGNAL_SEND_ENVIRONMENT_METADATA
115
+ APPSIGNAL_SEND_PARAMS
116
+ APPSIGNAL_SKIP_SESSION_DATA
117
+ APPSIGNAL_TRANSACTION_DEBUG_MODE
118
+ ].freeze
119
+ # @api private
120
+ ENV_ARRAY_KEYS = %w[
121
+ APPSIGNAL_FILTER_PARAMETERS
122
+ APPSIGNAL_FILTER_SESSION_DATA
123
+ APPSIGNAL_IGNORE_ACTIONS
124
+ APPSIGNAL_IGNORE_ERRORS
125
+ APPSIGNAL_IGNORE_NAMESPACES
126
+ APPSIGNAL_REQUEST_HEADERS
127
+ ].freeze
84
128
 
85
129
  # Mapping of old and deprecated AppSignal configuration keys
86
130
  DEPRECATED_CONFIG_KEY_MAPPING = {
@@ -121,8 +165,41 @@ module Appsignal
121
165
  :initial_config, :file_config, :env_config
122
166
  attr_accessor :logger
123
167
 
124
- def initialize(root_path, env, initial_config = {}, logger = Appsignal.logger)
168
+ # Initialize a new configuration object for AppSignal.
169
+ #
170
+ # If this is manually initialized, and not by {Appsignal.start}, it needs
171
+ # to be assigned to the {Appsignal.config} attribute.
172
+ #
173
+ # @example
174
+ # require "appsignal"
175
+ # Appsignal.config = Appsignal::Config.new(
176
+ # app_path,
177
+ # "production"
178
+ # )
179
+ # Appsignal.start
180
+ #
181
+ # @param root_path [String] Root path of the app.
182
+ # @param env [String] The environment to load when AppSignal is started. It
183
+ # will look for an environment with this name in the `config/appsignal.yml`
184
+ # config file.
185
+ # @param initial_config [Hash<String, Object>] The initial configuration to
186
+ # use. This will be overwritten by the file config and environment
187
+ # variables config.
188
+ # @param logger [Logger] The logger to use for the AppSignal gem. This is
189
+ # used by the configuration class only. Default: {Appsignal.logger}. See
190
+ # also {Appsignal.start_logger}.
191
+ # @param config_file [String] Custom config file location. Default
192
+ # `config/appsignal.yml`.
193
+ #
194
+ # @see https://docs.appsignal.com/ruby/configuration/
195
+ # Configuration documentation
196
+ # @see https://docs.appsignal.com/ruby/configuration/load-order.html
197
+ # Configuration load order
198
+ # @see https://docs.appsignal.com/ruby/instrumentation/integrating-appsignal.html
199
+ # How to integrate AppSignal manually
200
+ def initialize(root_path, env, initial_config = {}, logger = Appsignal.logger, config_file = nil)
125
201
  @root_path = root_path
202
+ @config_file = config_file
126
203
  @logger = logger
127
204
  @valid = false
128
205
  @config_hash = Hash[DEFAULT_CONFIG]
@@ -288,7 +365,7 @@ module Appsignal
288
365
  " Skipping file config.\n" \
289
366
  "File: #{config_file.inspect}\n" \
290
367
  "#{e.class.name}: #{e}"
291
- $stderr.puts "appsignal: #{message}"
368
+ Kernel.warn "appsignal: #{message}"
292
369
  logger.error "#{message}\n#{e.backtrace.join("\n")}"
293
370
  nil
294
371
  end
@@ -325,33 +402,21 @@ module Appsignal
325
402
  config = {}
326
403
 
327
404
  # Configuration with string type
328
- %w[APPSIGNAL_PUSH_API_KEY APPSIGNAL_APP_NAME APPSIGNAL_PUSH_API_ENDPOINT
329
- APPSIGNAL_FRONTEND_ERROR_CATCHING_PATH APPSIGNAL_HTTP_PROXY
330
- APPSIGNAL_LOG APPSIGNAL_LOG_PATH APPSIGNAL_WORKING_DIR_PATH
331
- APPSIGNAL_HOSTNAME APPSIGNAL_CA_FILE_PATH APP_REVISION].each do |var|
405
+ ENV_STRING_KEYS.each do |var|
332
406
  env_var = ENV[var]
333
407
  next unless env_var
334
408
  config[ENV_TO_KEY_MAPPING[var]] = env_var
335
409
  end
336
410
 
337
411
  # Configuration with boolean type
338
- %w[APPSIGNAL_ACTIVE APPSIGNAL_DEBUG APPSIGNAL_INSTRUMENT_NET_HTTP
339
- APPSIGNAL_INSTRUMENT_REDIS APPSIGNAL_INSTRUMENT_SEQUEL
340
- APPSIGNAL_SKIP_SESSION_DATA APPSIGNAL_ENABLE_FRONTEND_ERROR_CATCHING
341
- APPSIGNAL_ENABLE_ALLOCATION_TRACKING APPSIGNAL_ENABLE_GC_INSTRUMENTATION
342
- APPSIGNAL_RUNNING_IN_CONTAINER APPSIGNAL_ENABLE_HOST_METRICS
343
- APPSIGNAL_SEND_ENVIRONMENT_METADATA APPSIGNAL_SEND_PARAMS
344
- APPSIGNAL_ENABLE_MINUTELY_PROBES APPSIGNAL_FILES_WORLD_ACCESSIBLE
345
- APPSIGNAL_TRANSACTION_DEBUG_MODE].each do |var|
412
+ ENV_BOOLEAN_KEYS.each do |var|
346
413
  env_var = ENV[var]
347
414
  next unless env_var
348
415
  config[ENV_TO_KEY_MAPPING[var]] = env_var.casecmp("true").zero?
349
416
  end
350
417
 
351
418
  # Configuration with array of strings type
352
- %w[APPSIGNAL_IGNORE_ACTIONS APPSIGNAL_IGNORE_ERRORS
353
- APPSIGNAL_IGNORE_NAMESPACES APPSIGNAL_FILTER_PARAMETERS
354
- APPSIGNAL_FILTER_SESSION_DATA APPSIGNAL_REQUEST_HEADERS].each do |var|
419
+ ENV_ARRAY_KEYS.each do |var|
355
420
  env_var = ENV[var]
356
421
  next unless env_var
357
422
  config[ENV_TO_KEY_MAPPING[var]] = env_var.split(",")
@@ -10,11 +10,12 @@ begin
10
10
  require "appsignal_extension"
11
11
  Appsignal.extension_loaded = true
12
12
  end
13
- rescue LoadError => err
14
- Appsignal.logger.error(
15
- "Failed to load extension (#{err}), please run `appsignal diagnose` " \
16
- "and email us at support@appsignal.com"
17
- )
13
+ rescue LoadError => error
14
+ error_message = "ERROR: AppSignal failed to load extension. " \
15
+ "Please run `appsignal diagnose` and email us at support@appsignal.com\n" \
16
+ "#{error.class}: #{error.message}"
17
+ Appsignal.logger.error(error_message)
18
+ Kernel.warn error_message
18
19
  Appsignal.extension_loaded = false
19
20
  end
20
21
 
@@ -179,11 +179,12 @@ module Appsignal
179
179
  :appsignal_string
180
180
 
181
181
  Appsignal.extension_loaded = true
182
- rescue LoadError => err
183
- Appsignal.logger.error(
184
- "Failed to load extension (#{err}), please email us at " \
185
- "support@appsignal.com"
186
- )
182
+ rescue LoadError => error
183
+ error_message = "ERROR: AppSignal failed to load extension. " \
184
+ "Please run `appsignal diagnose` and email us at support@appsignal.com\n" \
185
+ "#{error.class}: #{error.message}"
186
+ Appsignal.logger.error(error_message)
187
+ Kernel.warn error_message
187
188
  Appsignal.extension_loaded = false
188
189
  end
189
190
 
@@ -380,6 +380,38 @@ module Appsignal
380
380
  end
381
381
  alias :tag_job :tag_request
382
382
 
383
+ # Add breadcrumbs to the transaction.
384
+ #
385
+ # Breadcrumbs can be used to trace what path a user has taken
386
+ # before encounterin an error.
387
+ #
388
+ # Only the last 20 added breadcrumbs will be saved.
389
+ #
390
+ # @example
391
+ # Appsignal.add_breadcrumb("Navigation", "http://blablabla.com", "", { :response => 200 }, Time.now.utc)
392
+ # Appsignal.add_breadcrumb("Network", "[GET] http://blablabla.com", "", { :response => 500 })
393
+ # Appsignal.add_breadcrumb("UI", "closed modal(change_password)", "User closed modal without actions")
394
+ #
395
+ # @param category [String] category of breadcrumb
396
+ # e.g. "UI", "Network", "Navigation", "Console".
397
+ # @param action [String] name of breadcrumb
398
+ # e.g "The user clicked a button", "HTTP 500 from http://blablabla.com"
399
+ # @option message [String] optional message in string format
400
+ # @option metadata [Hash<String,String>] key/value metadata in <string, string> format
401
+ # @option time [Time] time of breadcrumb, should respond to `.to_i` defaults to `Time.now.utc`
402
+ # @return [void]
403
+ #
404
+ # @see Transaction#add_breadcrumb
405
+ # @see http://docs.appsignal.com/ruby/instrumentation/breadcrumbs.html
406
+ # Breadcrumb reference
407
+ # @since 2.12.0
408
+ def add_breadcrumb(category, action, message = "", metadata = {}, time = Time.now.utc)
409
+ return unless active?
410
+ transaction = Appsignal::Transaction.current
411
+ return false unless transaction
412
+ transaction.add_breadcrumb(category, action, message, metadata, time)
413
+ end
414
+
383
415
  # Instrument helper for AppSignal.
384
416
  #
385
417
  # For more help, read our custom instrumentation guide, listed under "See
@@ -69,10 +69,34 @@ module Appsignal
69
69
  text.size > 200 ? "#{text[0...197]}..." : text
70
70
  end
71
71
  end
72
+
73
+ # Alias Probes constants that have moved to their own module in version
74
+ # 2.11.0.
75
+ def self.const_missing(name)
76
+ case name
77
+ when :SidekiqProbe
78
+ callers = caller
79
+ Appsignal::Utils::DeprecationMessage.message \
80
+ "The constant Appsignal::Hooks::SidekiqProbe has been deprecated. " \
81
+ "Please update the constant name to Appsignal::Probes::SidekiqProbe " \
82
+ "in the following file to remove this message.\n#{callers.first}"
83
+ Appsignal::Probes::SidekiqProbe
84
+ when :PumaProbe
85
+ callers = caller
86
+ Appsignal::Utils::DeprecationMessage.message \
87
+ "The constant Appsignal::Hooks::PumaProbe has been deprecated. " \
88
+ "Please update the constant name to Appsignal::Probes::PumaProbe " \
89
+ "in the following file to remove this message.\n#{callers.first}"
90
+ Appsignal::Probes::PumaProbe
91
+ else
92
+ super
93
+ end
94
+ end
72
95
  end
73
96
  end
74
97
 
75
98
  require "appsignal/hooks/action_cable"
99
+ require "appsignal/hooks/action_mailer"
76
100
  require "appsignal/hooks/active_job"
77
101
  require "appsignal/hooks/active_support_notifications"
78
102
  require "appsignal/hooks/celluloid"
@@ -0,0 +1,22 @@
1
+ module Appsignal
2
+ class Hooks
3
+ class ActionMailerHook < Appsignal::Hooks::Hook
4
+ register :action_mailer
5
+
6
+ def dependencies_present?
7
+ defined?(::ActionMailer)
8
+ end
9
+
10
+ def install
11
+ ActiveSupport::Notifications
12
+ .subscribe("process.action_mailer") do |_, _, _, _, payload|
13
+ Appsignal.increment_counter(
14
+ :action_mailer_process,
15
+ 1,
16
+ :mailer => payload[:mailer], :action => payload[:action]
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -41,8 +41,6 @@ module Appsignal
41
41
  transaction.set_error(exception)
42
42
  raise exception
43
43
  ensure
44
- tags = ActiveJobHelpers.tags_for_job(job)
45
-
46
44
  if transaction
47
45
  transaction.params =
48
46
  Appsignal::Utils::HashSanitizer.sanitize(
@@ -50,7 +48,7 @@ module Appsignal
50
48
  Appsignal.config[:filter_parameters]
51
49
  )
52
50
 
53
- transaction_tags = tags.dup
51
+ transaction_tags = ActiveJobHelpers.transaction_tags_for(job)
54
52
  transaction_tags["active_job_id"] = job["job_id"]
55
53
  provider_job_id = job["provider_job_id"]
56
54
  if provider_job_id
@@ -71,12 +69,15 @@ module Appsignal
71
69
  end
72
70
  end
73
71
 
74
- if job_status
75
- ActiveJobHelpers.increment_counter "queue_job_count", 1,
76
- tags.merge(:status => job_status)
72
+ metrics = ActiveJobHelpers.metrics_for(job)
73
+ metrics.each do |(metric_name, tags)|
74
+ if job_status
75
+ ActiveJobHelpers.increment_counter metric_name, 1,
76
+ tags.merge(:status => job_status)
77
+ end
78
+ ActiveJobHelpers.increment_counter metric_name, 1,
79
+ tags.merge(:status => :processed)
77
80
  end
78
- ActiveJobHelpers.increment_counter "queue_job_count", 1,
79
- tags.merge(:status => :processed)
80
81
  end
81
82
  end
82
83
 
@@ -96,7 +97,29 @@ module Appsignal
96
97
  end
97
98
  end
98
99
 
99
- def self.tags_for_job(job)
100
+ # Returns an array of metrics with tags used to report the job metrics
101
+ #
102
+ # If job ONLY has a queue, it will return `queue_job_count` with tags.
103
+ # If job has a queue AND priority, it will ALSO return
104
+ # `queue_priority_job_count` with tags.
105
+ #
106
+ # @return [Array] Array of metrics with tags to report.
107
+ def self.metrics_for(job)
108
+ tags = { :queue => job["queue_name"] }
109
+ metrics = [["queue_job_count", tags]]
110
+
111
+ priority = job["priority"]
112
+ if priority
113
+ metrics << [
114
+ "queue_priority_job_count",
115
+ tags.merge(:priority => priority)
116
+ ]
117
+ end
118
+
119
+ metrics
120
+ end
121
+
122
+ def self.transaction_tags_for(job)
100
123
  tags = {}
101
124
  queue = job["queue_name"]
102
125
  tags[:queue] = queue if queue
@@ -23,6 +23,21 @@ module Appsignal
23
23
  end
24
24
  end
25
25
 
26
+ instrumenter = ::ActiveSupport::Notifications::Instrumenter
27
+
28
+ if instrumenter.method_defined?(:start) && instrumenter.method_defined?(:finish)
29
+ install_start_finish
30
+ else
31
+ install_instrument
32
+ end
33
+
34
+ # rubocop:disable Style/GuardClause
35
+ if instrumenter.method_defined?(:finish_with_state)
36
+ install_finish_with_state
37
+ end
38
+ end
39
+
40
+ def install_instrument
26
41
  ::ActiveSupport::Notifications::Instrumenter.class_eval do
27
42
  alias instrument_without_appsignal instrument
28
43
 
@@ -46,6 +61,63 @@ module Appsignal
46
61
  end
47
62
  end
48
63
  end
64
+
65
+ def install_start_finish
66
+ ::ActiveSupport::Notifications::Instrumenter.class_eval do
67
+ alias start_without_appsignal start
68
+
69
+ def start(name, payload = {})
70
+ # Events that start with a bang are internal to Rails
71
+ instrument_this = name[0] != BANG
72
+
73
+ Appsignal::Transaction.current.start_event if instrument_this
74
+
75
+ start_without_appsignal(name, payload)
76
+ end
77
+
78
+ alias finish_without_appsignal finish
79
+
80
+ def finish(name, payload = {})
81
+ # Events that start with a bang are internal to Rails
82
+ instrument_this = name[0] != BANG
83
+
84
+ if instrument_this
85
+ title, body, body_format = Appsignal::EventFormatter.format(name, payload)
86
+ Appsignal::Transaction.current.finish_event(
87
+ name.to_s,
88
+ title,
89
+ body,
90
+ body_format
91
+ )
92
+ end
93
+
94
+ finish_without_appsignal(name, payload)
95
+ end
96
+ end
97
+ end
98
+
99
+ def install_finish_with_state
100
+ ::ActiveSupport::Notifications::Instrumenter.class_eval do
101
+ alias finish_with_state_without_appsignal finish_with_state
102
+
103
+ def finish_with_state(listeners_state, name, payload = {})
104
+ # Events that start with a bang are internal to Rails
105
+ instrument_this = name[0] != BANG
106
+
107
+ if instrument_this
108
+ title, body, body_format = Appsignal::EventFormatter.format(name, payload)
109
+ Appsignal::Transaction.current.finish_event(
110
+ name.to_s,
111
+ title,
112
+ body,
113
+ body_format
114
+ )
115
+ end
116
+
117
+ finish_with_state_without_appsignal(listeners_state, name, payload)
118
+ end
119
+ end
120
+ end
49
121
  end
50
122
  end
51
123
  end