appsignal 2.8.4-java → 2.9.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop_todo.yml +7 -16
  4. data/.travis.yml +4 -1
  5. data/CHANGELOG.md +16 -0
  6. data/README.md +23 -0
  7. data/Rakefile +10 -7
  8. data/appsignal.gemspec +3 -0
  9. data/build_matrix.yml +5 -1
  10. data/ext/Rakefile +23 -16
  11. data/ext/agent.yml +37 -37
  12. data/ext/base.rb +86 -24
  13. data/ext/extconf.rb +33 -26
  14. data/gemfiles/rails-6.0.gemfile +5 -0
  15. data/lib/appsignal.rb +14 -489
  16. data/lib/appsignal/cli/diagnose.rb +84 -4
  17. data/lib/appsignal/cli/diagnose/paths.rb +0 -5
  18. data/lib/appsignal/cli/diagnose/utils.rb +17 -0
  19. data/lib/appsignal/cli/helpers.rb +6 -0
  20. data/lib/appsignal/cli/install.rb +13 -7
  21. data/lib/appsignal/config.rb +1 -2
  22. data/lib/appsignal/event_formatter.rb +4 -5
  23. data/lib/appsignal/event_formatter/moped/query_formatter.rb +60 -59
  24. data/lib/appsignal/extension.rb +2 -2
  25. data/lib/appsignal/helpers/instrumentation.rb +485 -0
  26. data/lib/appsignal/helpers/metrics.rb +55 -0
  27. data/lib/appsignal/hooks.rb +9 -8
  28. data/lib/appsignal/hooks/puma.rb +65 -9
  29. data/lib/appsignal/hooks/sidekiq.rb +90 -0
  30. data/lib/appsignal/integrations/mongo_ruby_driver.rb +7 -0
  31. data/lib/appsignal/integrations/railtie.rb +2 -1
  32. data/lib/appsignal/marker.rb +2 -3
  33. data/lib/appsignal/minutely.rb +164 -14
  34. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -1
  35. data/lib/appsignal/system.rb +16 -18
  36. data/lib/appsignal/utils/rails_helper.rb +16 -0
  37. data/lib/appsignal/version.rb +1 -1
  38. data/spec/lib/appsignal/cli/diagnose_spec.rb +129 -22
  39. data/spec/lib/appsignal/cli/install_spec.rb +6 -1
  40. data/spec/lib/appsignal/config_spec.rb +3 -3
  41. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +6 -0
  42. data/spec/lib/appsignal/event_formatter_spec.rb +168 -69
  43. data/spec/lib/appsignal/hooks/puma_spec.rb +129 -0
  44. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +147 -0
  45. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +24 -1
  46. data/spec/lib/appsignal/minutely_spec.rb +251 -21
  47. data/spec/lib/appsignal/system_spec.rb +0 -35
  48. data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +39 -31
  49. data/spec/lib/appsignal/utils/json_spec.rb +7 -3
  50. data/spec/lib/appsignal_spec.rb +27 -2
  51. data/spec/spec_helper.rb +13 -0
  52. data/spec/support/helpers/log_helpers.rb +6 -0
  53. data/spec/support/project_fixture/config/appsignal.yml +1 -0
  54. data/spec/support/stubs/sidekiq/api.rb +4 -0
  55. metadata +8 -2
@@ -82,6 +82,8 @@ module Appsignal
82
82
  print_empty_line
83
83
 
84
84
  library_information
85
+ data[:installation] = fetch_installation_report
86
+ print_installation_report
85
87
  print_empty_line
86
88
 
87
89
  host_information
@@ -180,7 +182,8 @@ module Appsignal
180
182
  if rails_app?
181
183
  data[:app][:rails] = true
182
184
  current_path = Rails.root
183
- initial_config[:name] = Rails.application.class.parent_name
185
+ initial_config[:name] =
186
+ Appsignal::Utils::RailsHelper.detected_rails_app_name
184
187
  initial_config[:log_path] = current_path.join("log")
185
188
  end
186
189
 
@@ -325,12 +328,89 @@ module Appsignal
325
328
  save :language, "ruby"
326
329
  puts_and_save :package_version, "Gem version", Appsignal::VERSION
327
330
  puts_and_save :agent_version, "Agent version", Appsignal::Extension.agent_version
328
- puts_and_save :agent_architecture, "Agent architecture",
329
- Appsignal::System.installed_agent_architecture
330
331
  puts_and_save :extension_loaded, "Extension loaded", Appsignal.extension_loaded
331
332
  end
332
333
  end
333
334
 
335
+ def fetch_installation_report
336
+ path = File.expand_path("../../../../ext/install.report", __FILE__)
337
+ raw_report = File.read(path)
338
+ Utils.parse_yaml(raw_report)
339
+ rescue => e
340
+ {
341
+ "parsing_error" => {
342
+ "error" => "#{e.class}: #{e}",
343
+ "backtrace" => e.backtrace
344
+ }.tap do |r|
345
+ r["raw"] = raw_report if raw_report
346
+ end
347
+ }
348
+ end
349
+
350
+ def print_installation_report
351
+ puts "\nExtension installation report"
352
+ install_report = data[:installation]
353
+ if install_report.key? "parsing_error"
354
+ print_installation_report_parsing_error(install_report)
355
+ return
356
+ end
357
+
358
+ print_installation_result_report(install_report)
359
+ print_installation_language_report(install_report)
360
+ print_installation_download_report(install_report)
361
+ print_installation_build_report(install_report)
362
+ print_installation_host_report(install_report)
363
+ end
364
+
365
+ def print_installation_report_parsing_error(report)
366
+ report = report["parsing_error"]
367
+ puts " Error found while parsing the report."
368
+ puts " Error: #{report["error"]}"
369
+ puts " Raw report:\n#{report["raw"]}" if report["raw"]
370
+ end
371
+
372
+ def print_installation_result_report(report)
373
+ report = report.fetch("download", {})
374
+ puts " Installation result"
375
+ puts " Status: #{report["status"]}"
376
+ puts " Message: #{report["message"]}" if report["message"]
377
+ puts " Error: #{report["error"]}" if report["error"]
378
+ end
379
+
380
+ def print_installation_language_report(report)
381
+ report = report.fetch("language", {})
382
+ puts " Language details"
383
+ puts " Implementation: #{report["implementation"]}"
384
+ puts " Ruby version: #{report["version"]}"
385
+ end
386
+
387
+ def print_installation_download_report(report)
388
+ report = report.fetch("download", {})
389
+ puts " Download details"
390
+ puts " Download URL: #{report["download_url"]}"
391
+ puts " Checksum: #{report["checksum"]}"
392
+ end
393
+
394
+ def print_installation_build_report(report)
395
+ report = report.fetch("build", {})
396
+ puts " Build details"
397
+ puts " Install time: #{report["time"]}"
398
+ puts " Architecture: #{report["architecture"]}"
399
+ puts " Target: #{report["target"]}"
400
+ puts " Musl override: #{report["musl_override"]}"
401
+ puts " Library type: #{report["library_type"]}"
402
+ puts " Source: #{report["source"]}" if report["source"] != "remote"
403
+ puts " Dependencies: #{report["dependencies"]}"
404
+ puts " Flags: #{report["flags"]}"
405
+ end
406
+
407
+ def print_installation_host_report(report)
408
+ report = report.fetch("host", {})
409
+ puts " Host details"
410
+ puts " Root user: #{report["root_user"]}"
411
+ puts " Dependencies: #{report["dependencies"]}"
412
+ end
413
+
334
414
  def host_information
335
415
  rbconfig = RbConfig::CONFIG
336
416
  puts "Host information"
@@ -349,7 +429,7 @@ module Appsignal
349
429
  save :heroku, Appsignal::System.heroku?
350
430
 
351
431
  save :root, Process.uid.zero?
352
- puts_value "root user",
432
+ puts_value "Root user",
353
433
  Process.uid.zero? ? "true (not recommended)" : "false"
354
434
  puts_and_save :running_in_container, "Running in container",
355
435
  Appsignal::Extension.running_in_container?
@@ -17,7 +17,6 @@ module Appsignal
17
17
  begin
18
18
  config = Appsignal.config
19
19
  log_file_path = config.log_file_path
20
- install_log_path = File.join("ext", "install.log")
21
20
  makefile_log_path = File.join("ext", "mkmf.log")
22
21
  {
23
22
  :package_install_path => {
@@ -36,10 +35,6 @@ module Appsignal
36
35
  :label => "Log directory",
37
36
  :path => log_file_path ? File.dirname(log_file_path) : ""
38
37
  },
39
- install_log_path => {
40
- :label => "Extension install log",
41
- :path => File.join(gem_path, install_log_path)
42
- },
43
38
  makefile_log_path => {
44
39
  :label => "Makefile install log",
45
40
  :path => File.join(gem_path, makefile_log_path)
@@ -30,6 +30,23 @@ module Appsignal
30
30
 
31
31
  IO.binread(path, length, offset)
32
32
  end
33
+
34
+ def self.parse_yaml(contents)
35
+ arguments = [contents]
36
+ if YAML.respond_to? :safe_load
37
+ method = :safe_load
38
+ arguments << \
39
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
40
+ # Use keyword params for Ruby 2.6 and up
41
+ { :permitted_classes => [Time] }
42
+ else
43
+ [Time]
44
+ end
45
+ else
46
+ method = :load
47
+ end
48
+ YAML.send(method, *arguments)
49
+ end
33
50
  end
34
51
  end
35
52
  end
@@ -1,10 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "appsignal/utils/rails_helper"
4
+
3
5
  module Appsignal
4
6
  class CLI
5
7
  module Helpers
6
8
  private
7
9
 
10
+ def ruby_2_6_or_up?
11
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
12
+ end
13
+
8
14
  def colorize(text, color)
9
15
  return text if Gem.win_platform?
10
16
 
@@ -77,8 +77,7 @@ module Appsignal
77
77
 
78
78
  require File.expand_path(File.join(Dir.pwd, "config/application.rb"))
79
79
 
80
- config[:name] = Rails.application.class.parent_name
81
-
80
+ config[:name] = Appsignal::Utils::RailsHelper.detected_rails_app_name
82
81
  name_overwritten = yes_or_no(" Your app's name is: '#{config[:name]}' \n Do you want to change how this is displayed in AppSignal? (y/n): ")
83
82
  puts
84
83
  if name_overwritten
@@ -249,12 +248,19 @@ module Appsignal
249
248
  end
250
249
 
251
250
  def write_config_file(data)
252
- template = ERB.new(
253
- File.read(File.join(File.dirname(__FILE__), "../../../resources/appsignal.yml.erb")),
254
- nil,
255
- "-"
251
+ filename = File.join(
252
+ File.dirname(__FILE__),
253
+ "../../../resources/appsignal.yml.erb"
256
254
  )
257
-
255
+ file_contents = File.read(filename)
256
+ arguments = [file_contents]
257
+ if ruby_2_6_or_up?
258
+ arguments << { :trim_mode => "-" }
259
+ else
260
+ arguments << nil
261
+ arguments << "-"
262
+ end
263
+ template = ERB.new(*arguments)
258
264
  config = template.result(OpenStruct.new(data).instance_eval { binding })
259
265
 
260
266
  FileUtils.mkdir_p(File.join(Dir.pwd, "config"))
@@ -36,7 +36,7 @@ module Appsignal
36
36
  :enable_allocation_tracking => true,
37
37
  :enable_gc_instrumentation => false,
38
38
  :enable_host_metrics => true,
39
- :enable_minutely_probes => false,
39
+ :enable_minutely_probes => true,
40
40
  :ca_file_path => File.expand_path(File.join("../../../resources/cacert.pem"), __FILE__),
41
41
  :dns_servers => [],
42
42
  :files_world_accessible => true
@@ -214,7 +214,6 @@ module Appsignal
214
214
  ENV["_APPSIGNAL_WORKING_DIR_PATH"] = config_hash[:working_dir_path] if config_hash[:working_dir_path]
215
215
  ENV["_APPSIGNAL_WORKING_DIRECTORY_PATH"] = config_hash[:working_directory_path] if config_hash[:working_directory_path]
216
216
  ENV["_APPSIGNAL_ENABLE_HOST_METRICS"] = config_hash[:enable_host_metrics].to_s
217
- ENV["_APPSIGNAL_ENABLE_MINUTELY_PROBES"] = config_hash[:enable_minutely_probes].to_s
218
217
  ENV["_APPSIGNAL_HOSTNAME"] = config_hash[:hostname].to_s
219
218
  ENV["_APPSIGNAL_PROCESS_NAME"] = $PROGRAM_NAME
220
219
  ENV["_APPSIGNAL_CA_FILE_PATH"] = config_hash[:ca_file_path].to_s
@@ -75,16 +75,15 @@ module Appsignal
75
75
 
76
76
  def initialize_formatter(name, formatter)
77
77
  format_method = formatter.instance_method(:format)
78
- if format_method && format_method.arity == 1
79
- formatter_classes[name] = formatter
80
- formatters[name] = formatter.new
81
- else
78
+ if !format_method || format_method.arity != 1
82
79
  raise "#{formatter} does not have a format(payload) method"
83
80
  end
81
+ formatter_classes[name] = formatter
82
+ formatters[name] = formatter.new
84
83
  rescue => ex
85
84
  formatter_classes.delete(name)
86
85
  formatters.delete(name)
87
- logger.warn("'#{ex.message}' when initializing #{name} event formatter")
86
+ logger.error("'#{ex.message}' when initializing #{name} event formatter")
88
87
  end
89
88
 
90
89
  def register_deprecated_formatter(name)
@@ -6,65 +6,66 @@ module Appsignal
6
6
  module Moped
7
7
  class QueryFormatter
8
8
  def format(payload)
9
- if payload[:ops] && !payload[:ops].empty?
10
- op = payload[:ops].first
11
- case op.class.to_s
12
- when "Moped::Protocol::Command"
13
- [
14
- "Command", {
15
- :database => op.full_collection_name,
16
- :selector => sanitize(op.selector, true, :mongodb)
17
- }.inspect
18
- ]
19
- when "Moped::Protocol::Query"
20
- [
21
- "Query", {
22
- :database => op.full_collection_name,
23
- :selector => sanitize(op.selector, false, :mongodb),
24
- :flags => op.flags,
25
- :limit => op.limit,
26
- :skip => op.skip,
27
- :fields => op.fields
28
- }.inspect
29
- ]
30
- when "Moped::Protocol::Delete"
31
- [
32
- "Delete", {
33
- :database => op.full_collection_name,
34
- :selector => sanitize(op.selector, false, :mongodb),
35
- :flags => op.flags
36
- }.inspect
37
- ]
38
- when "Moped::Protocol::Insert"
39
- [
40
- "Insert", {
41
- :database => op.full_collection_name,
42
- :documents => sanitize(op.documents, true, :mongodb),
43
- :count => op.documents.count,
44
- :flags => op.flags
45
- }.inspect
46
- ]
47
- when "Moped::Protocol::Update"
48
- [
49
- "Update",
50
- {
51
- :database => op.full_collection_name,
52
- :selector => sanitize(op.selector, false, :mongodb),
53
- :update => sanitize(op.update, true, :mongodb),
54
- :flags => op.flags
55
- }.inspect
56
- ]
57
- when "Moped::Protocol::KillCursors"
58
- [
59
- "KillCursors",
60
- { :number_of_cursor_ids => op.number_of_cursor_ids }.inspect
61
- ]
62
- else
63
- [
64
- op.class.to_s.sub("Moped::Protocol::", ""),
65
- { :database => op.full_collection_name }.inspect
66
- ]
67
- end
9
+ return unless payload[:ops]
10
+ return if payload[:ops].empty?
11
+
12
+ op = payload[:ops].first
13
+ case op.class.to_s
14
+ when "Moped::Protocol::Command"
15
+ [
16
+ "Command", {
17
+ :database => op.full_collection_name,
18
+ :selector => sanitize(op.selector, true, :mongodb)
19
+ }.inspect
20
+ ]
21
+ when "Moped::Protocol::Query"
22
+ [
23
+ "Query", {
24
+ :database => op.full_collection_name,
25
+ :selector => sanitize(op.selector, false, :mongodb),
26
+ :flags => op.flags,
27
+ :limit => op.limit,
28
+ :skip => op.skip,
29
+ :fields => op.fields
30
+ }.inspect
31
+ ]
32
+ when "Moped::Protocol::Delete"
33
+ [
34
+ "Delete", {
35
+ :database => op.full_collection_name,
36
+ :selector => sanitize(op.selector, false, :mongodb),
37
+ :flags => op.flags
38
+ }.inspect
39
+ ]
40
+ when "Moped::Protocol::Insert"
41
+ [
42
+ "Insert", {
43
+ :database => op.full_collection_name,
44
+ :documents => sanitize(op.documents, true, :mongodb),
45
+ :count => op.documents.count,
46
+ :flags => op.flags
47
+ }.inspect
48
+ ]
49
+ when "Moped::Protocol::Update"
50
+ [
51
+ "Update",
52
+ {
53
+ :database => op.full_collection_name,
54
+ :selector => sanitize(op.selector, false, :mongodb),
55
+ :update => sanitize(op.update, true, :mongodb),
56
+ :flags => op.flags
57
+ }.inspect
58
+ ]
59
+ when "Moped::Protocol::KillCursors"
60
+ [
61
+ "KillCursors",
62
+ { :number_of_cursor_ids => op.number_of_cursor_ids }.inspect
63
+ ]
64
+ else
65
+ [
66
+ op.class.to_s.sub("Moped::Protocol::", ""),
67
+ { :database => op.full_collection_name }.inspect
68
+ ]
68
69
  end
69
70
  end
70
71
 
@@ -12,8 +12,8 @@ begin
12
12
  end
13
13
  rescue LoadError => err
14
14
  Appsignal.logger.error(
15
- "Failed to load extension (#{err}), please check the install.log file in " \
16
- "the ext directory of the gem and email us at support@appsignal.com"
15
+ "Failed to load extension (#{err}), please run `appsignal diagnose` " \
16
+ "and email us at support@appsignal.com"
17
17
  )
18
18
  Appsignal.extension_loaded = false
19
19
  end
@@ -0,0 +1,485 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Helpers
5
+ # @api private
6
+ module Instrumentation # rubocop:disable Metrics/ModuleLength
7
+ # Creates an AppSignal transaction for the given block.
8
+ #
9
+ # If AppSignal is not {.active?} it will still execute the block, but not
10
+ # create a transaction for it.
11
+ #
12
+ # A event is created for this transaction with the name given in the
13
+ # `name` argument. The event name must start with either `perform_job` or
14
+ # `process_action` to differentiate between the "web" and "background"
15
+ # namespace. Custom namespaces are not supported by this helper method.
16
+ #
17
+ # This helper method also captures any exception that occurs in the given
18
+ # block.
19
+ #
20
+ # @example
21
+ # Appsignal.monitor_transaction("perform_job.nightly_update") do
22
+ # # your code
23
+ # end
24
+ #
25
+ # @example with an environment
26
+ # Appsignal.monitor_transaction(
27
+ # "perform_job.nightly_update",
28
+ # :metadata => { "user_id" => 1 }
29
+ # ) do
30
+ # # your code
31
+ # end
32
+ #
33
+ # @param name [String] main event name.
34
+ # @param env [Hash<Symbol, Object>]
35
+ # @option env [Hash<Symbol/String, Object>] :params Params for the
36
+ # monitored request/job, see {Appsignal::Transaction#params=} for more
37
+ # information.
38
+ # @option env [String] :controller name of the controller in which the
39
+ # transaction was recorded.
40
+ # @option env [String] :class name of the Ruby class in which the
41
+ # transaction was recorded. If `:controller` is also given,
42
+ # `:controller` is used instead.
43
+ # @option env [String] :action name of the controller action in which the
44
+ # transaction was recorded.
45
+ # @option env [String] :method name of the Ruby method in which the
46
+ # transaction was recorded. If `:action` is also given, `:action`
47
+ # is used instead.
48
+ # @option env [Integer] :queue_start the moment the request/job was
49
+ # queued. Used to track how long requests/jobs were queued before being
50
+ # executed.
51
+ # @option env [Hash<Symbol/String, String/Fixnum>] :metadata Additional
52
+ # metadata for the transaction, see
53
+ # {Appsignal::Transaction#set_metadata} for more information.
54
+ # @yield the block to monitor.
55
+ # @raise [Exception] any exception that occurs within the given block is
56
+ # re-raised by this method.
57
+ # @return [Object] the value of the given block is returned.
58
+ # @since 0.10.0
59
+ def monitor_transaction(name, env = {})
60
+ return yield unless active?
61
+
62
+ if name.start_with?("perform_job".freeze)
63
+ namespace = Appsignal::Transaction::BACKGROUND_JOB
64
+ request = Appsignal::Transaction::GenericRequest.new(env)
65
+ elsif name.start_with?("process_action".freeze)
66
+ namespace = Appsignal::Transaction::HTTP_REQUEST
67
+ request = ::Rack::Request.new(env)
68
+ else
69
+ logger.error("Unrecognized name '#{name}'")
70
+ return
71
+ end
72
+ transaction = Appsignal::Transaction.create(
73
+ SecureRandom.uuid,
74
+ namespace,
75
+ request
76
+ )
77
+ begin
78
+ Appsignal.instrument(name) do
79
+ yield
80
+ end
81
+ rescue Exception => error # rubocop:disable Lint/RescueException
82
+ transaction.set_error(error)
83
+ raise error
84
+ ensure
85
+ transaction.set_http_or_background_action(request.env)
86
+ transaction.set_http_or_background_queue_start
87
+ Appsignal::Transaction.complete_current!
88
+ end
89
+ end
90
+
91
+ # Monitor a transaction, stop AppSignal and wait for this single
92
+ # transaction to be flushed.
93
+ #
94
+ # Useful for cases such as Rake tasks and Resque-like systems where a
95
+ # process is forked and immediately exits after the transaction finishes.
96
+ #
97
+ # @see monitor_transaction
98
+ def monitor_single_transaction(name, env = {}, &block)
99
+ monitor_transaction(name, env, &block)
100
+ ensure
101
+ stop("monitor_single_transaction")
102
+ end
103
+
104
+ # Listen for an error to occur and send it to AppSignal.
105
+ #
106
+ # Uses {.send_error} to directly send the error in a separate
107
+ # transaction. Does not add the error to the current transaction.
108
+ #
109
+ # Make sure that AppSignal is integrated in your application beforehand.
110
+ # AppSignal won't record errors unless {Config#active?} is `true`.
111
+ #
112
+ # @example
113
+ # # my_app.rb
114
+ # # setup AppSignal beforehand
115
+ #
116
+ # Appsignal.listen_for_error do
117
+ # # my code
118
+ # raise "foo"
119
+ # end
120
+ #
121
+ # @see Transaction.set_tags
122
+ # @see Transaction.set_namespace
123
+ # @see .send_error
124
+ # @see https://docs.appsignal.com/ruby/instrumentation/integrating-appsignal.html
125
+ # AppSignal integration guide
126
+ #
127
+ # @param tags [Hash, nil]
128
+ # @param namespace [String] the namespace for this error.
129
+ # @yield yields the given block.
130
+ # @return [Object] returns the return value of the given block.
131
+ def listen_for_error(
132
+ tags = nil,
133
+ namespace = Appsignal::Transaction::HTTP_REQUEST
134
+ )
135
+ yield
136
+ rescue Exception => error # rubocop:disable Lint/RescueException
137
+ send_error(error, tags, namespace)
138
+ raise error
139
+ end
140
+ alias :listen_for_exception :listen_for_error
141
+
142
+ # Send an error to AppSignal regardless of the context.
143
+ #
144
+ # Records and send the exception to AppSignal.
145
+ #
146
+ # This instrumentation helper does not require a transaction to be
147
+ # active, it starts a new transaction by itself.
148
+ #
149
+ # Use {.set_error} if your want to add an exception to the current
150
+ # transaction.
151
+ #
152
+ # **Note**: Does not do anything if AppSignal is not active or when the
153
+ # "error" is not a class extended from Ruby's Exception class.
154
+ #
155
+ # @example Send an exception
156
+ # begin
157
+ # raise "oh no!"
158
+ # rescue => e
159
+ # Appsignal.send_error(e)
160
+ # end
161
+ #
162
+ # @example Send an exception with tags
163
+ # begin
164
+ # raise "oh no!"
165
+ # rescue => e
166
+ # Appsignal.send_error(e, :key => "value")
167
+ # end
168
+ #
169
+ # @example Add more metadata to transaction
170
+ # Appsignal.send_error(e, :key => "value") do |transaction|
171
+ # transaction.params(:search_query => params[:search_query])
172
+ # transaction.set_action("my_action_name")
173
+ # transaction.set_namespace("my_namespace")
174
+ # end
175
+ #
176
+ # @param error [Exception] The error to send to AppSignal.
177
+ # @param tags [Hash{String, Symbol => String, Symbol, Integer}]
178
+ # Additional tags to add to the error. See also {.tag_request}.
179
+ # @param namespace [String] The namespace in which the error occurred.
180
+ # See also {.set_namespace}.
181
+ # @yield [transaction] yields block to allow modification of the
182
+ # transaction before it's send.
183
+ # @yieldparam transaction [Transaction] yields the AppSignal transaction
184
+ # used to send the error.
185
+ # @return [void]
186
+ #
187
+ # @see http://docs.appsignal.com/ruby/instrumentation/exception-handling.html
188
+ # Exception handling guide
189
+ # @see http://docs.appsignal.com/ruby/instrumentation/tagging.html
190
+ # Tagging guide
191
+ # @since 0.6.0
192
+ def send_error(
193
+ error,
194
+ tags = nil,
195
+ namespace = Appsignal::Transaction::HTTP_REQUEST
196
+ )
197
+ return unless active?
198
+ unless error.is_a?(Exception)
199
+ logger.error("Can't send error, given value is not an exception")
200
+ return
201
+ end
202
+ transaction = Appsignal::Transaction.new(
203
+ SecureRandom.uuid,
204
+ namespace,
205
+ Appsignal::Transaction::GenericRequest.new({})
206
+ )
207
+ transaction.set_tags(tags) if tags
208
+ transaction.set_error(error)
209
+ yield transaction if block_given?
210
+ transaction.complete
211
+ end
212
+ alias :send_exception :send_error
213
+
214
+ # Set an error on the current transaction.
215
+ #
216
+ # **Note**: Does not do anything if AppSignal is not active, no
217
+ # transaction is currently active or when the "error" is not a class
218
+ # extended from Ruby's Exception class.
219
+ #
220
+ # @example Manual instrumentation of set_error.
221
+ # # Manually starting AppSignal here
222
+ # # Manually starting a transaction here.
223
+ # begin
224
+ # raise "oh no!"
225
+ # rescue => e
226
+ # Appsignal.set_error(error)
227
+ # end
228
+ # # Manually completing the transaction here.
229
+ # # Manually stopping AppSignal here
230
+ #
231
+ # @example In a Rails application
232
+ # class SomeController < ApplicationController
233
+ # # The AppSignal transaction is created by our integration for you.
234
+ # def create
235
+ # # Do something that breaks
236
+ # rescue => e
237
+ # Appsignal.set_error(e)
238
+ # end
239
+ # end
240
+ #
241
+ # @param exception [Exception] The error to add to the current
242
+ # transaction.
243
+ # @param tags [Hash{String, Symbol => String, Symbol, Integer}]
244
+ # Additional tags to add to the error. See also {.tag_request}.
245
+ # @param namespace [String] The namespace in which the error occurred.
246
+ # See also {.set_namespace}.
247
+ # @return [void]
248
+ #
249
+ # @see Transaction#set_error
250
+ # @see http://docs.appsignal.com/ruby/instrumentation/exception-handling.html
251
+ # Exception handling guide
252
+ # @since 0.6.6
253
+ def set_error(exception, tags = nil, namespace = nil)
254
+ return if !active? ||
255
+ Appsignal::Transaction.current.nil? ||
256
+ exception.nil?
257
+ transaction = Appsignal::Transaction.current
258
+ transaction.set_error(exception)
259
+ transaction.set_tags(tags) if tags
260
+ transaction.set_namespace(namespace) if namespace
261
+ end
262
+ alias :set_exception :set_error
263
+ alias :add_exception :set_error
264
+
265
+ # Set a custom action name for the current transaction.
266
+ #
267
+ # When using an integration such as the Rails or Sinatra AppSignal will
268
+ # try to find the action name from the controller or endpoint for you.
269
+ #
270
+ # If you want to customize the action name as it appears on AppSignal.com
271
+ # you can use this method. This overrides the action name AppSignal
272
+ # generates in an integration.
273
+ #
274
+ # @example in a Rails controller
275
+ # class SomeController < ApplicationController
276
+ # before_action :set_appsignal_action
277
+ #
278
+ # def set_appsignal_action
279
+ # Appsignal.set_action("DynamicController#dynamic_method")
280
+ # end
281
+ # end
282
+ #
283
+ # @param action [String]
284
+ # @return [void]
285
+ # @see Transaction#set_action
286
+ # @since 2.2.0
287
+ def set_action(action)
288
+ return if !active? ||
289
+ Appsignal::Transaction.current.nil? ||
290
+ action.nil?
291
+ Appsignal::Transaction.current.set_action(action)
292
+ end
293
+
294
+ # Set a custom namespace for the current transaction.
295
+ #
296
+ # When using an integration such as Rails or Sidekiq AppSignal will try
297
+ # to find a appropriate namespace for the transaction.
298
+ #
299
+ # A Rails controller will be automatically put in the "http_request"
300
+ # namespace, while a Sidekiq background job is put in the
301
+ # "background_job" namespace.
302
+ #
303
+ # Note: The "http_request" namespace gets transformed on AppSignal.com to
304
+ # "Web" and "background_job" gets transformed to "Background".
305
+ #
306
+ # If you want to customize the namespace in which transactions appear you
307
+ # can use this method. This overrides the namespace AppSignal uses by
308
+ # default.
309
+ #
310
+ # A common request we've seen is to split the administration panel from
311
+ # the main application.
312
+ #
313
+ # @example create a custom admin namespace
314
+ # class AdminController < ApplicationController
315
+ # before_action :set_appsignal_namespace
316
+ #
317
+ # def set_appsignal_namespace
318
+ # Appsignal.set_namespace("admin")
319
+ # end
320
+ # end
321
+ #
322
+ # @param namespace [String]
323
+ # @return [void]
324
+ # @see Transaction#set_namespace
325
+ # @since 2.2.0
326
+ def set_namespace(namespace)
327
+ return if !active? ||
328
+ Appsignal::Transaction.current.nil? ||
329
+ namespace.nil?
330
+ Appsignal::Transaction.current.set_namespace(namespace)
331
+ end
332
+
333
+ # Set tags on the current transaction.
334
+ #
335
+ # Tags are extra bits of information that are added to transaction and
336
+ # appear on sample details pages on AppSignal.com.
337
+ #
338
+ # @example
339
+ # Appsignal.tag_request(:locale => "en")
340
+ # Appsignal.tag_request("locale" => "en")
341
+ # Appsignal.tag_request("user_id" => 1)
342
+ #
343
+ # @example Nested hashes are not supported
344
+ # # Bad
345
+ # Appsignal.tag_request(:user => { :locale => "en" })
346
+ #
347
+ # @example in a Rails controller
348
+ # class SomeController < ApplicationController
349
+ # before_action :set_appsignal_tags
350
+ #
351
+ # def set_appsignal_tags
352
+ # Appsignal.tag_request(:locale => I18n.locale)
353
+ # end
354
+ # end
355
+ #
356
+ # @param tags [Hash] Collection of tags.
357
+ # @option tags [String, Symbol, Integer] :any
358
+ # The name of the tag as a Symbol.
359
+ # @option tags [String, Symbol, Integer] "any"
360
+ # The name of the tag as a String.
361
+ # @return [void]
362
+ #
363
+ # @see Transaction.set_tags
364
+ # @see http://docs.appsignal.com/ruby/instrumentation/tagging.html
365
+ # Tagging guide
366
+ def tag_request(tags = {})
367
+ return unless active?
368
+ transaction = Appsignal::Transaction.current
369
+ return false unless transaction
370
+ transaction.set_tags(tags)
371
+ end
372
+ alias :tag_job :tag_request
373
+
374
+ # Instrument helper for AppSignal.
375
+ #
376
+ # For more help, read our custom instrumentation guide, listed under "See
377
+ # also".
378
+ #
379
+ # @example Simple instrumentation
380
+ # Appsignal.instrument("fetch.issue_fetcher") do
381
+ # # To be instrumented code
382
+ # end
383
+ #
384
+ # @example Instrumentation with title and body
385
+ # Appsignal.instrument(
386
+ # "fetch.issue_fetcher",
387
+ # "Fetching issue",
388
+ # "GitHub API"
389
+ # ) do
390
+ # # To be instrumented code
391
+ # end
392
+ #
393
+ # @param name [String] Name of the instrumented event. Read our event
394
+ # naming guide listed under "See also".
395
+ # @param title [String, nil] Human readable name of the event.
396
+ # @param body [String, nil] Value of importance for the event, such as
397
+ # the server against an API call is made.
398
+ # @param body_format [Integer] Enum for the type of event that is
399
+ # instrumented. Accepted values are {EventFormatter::DEFAULT} and
400
+ # {EventFormatter::SQL_BODY_FORMAT}, but we recommend you use
401
+ # {.instrument_sql} instead of {EventFormatter::SQL_BODY_FORMAT}.
402
+ # @yield yields the given block of code instrumented in an AppSignal
403
+ # event.
404
+ # @return [Object] Returns the block's return value.
405
+ #
406
+ # @see Appsignal::Transaction#instrument
407
+ # @see .instrument_sql
408
+ # @see http://docs.appsignal.com/ruby/instrumentation/instrumentation.html
409
+ # AppSignal custom instrumentation guide
410
+ # @see http://docs.appsignal.com/api/event-names.html
411
+ # AppSignal event naming guide
412
+ # @since 1.3.0
413
+ def instrument(
414
+ name,
415
+ title = nil,
416
+ body = nil,
417
+ body_format = Appsignal::EventFormatter::DEFAULT
418
+ )
419
+ Appsignal::Transaction.current.start_event
420
+ yield if block_given?
421
+ ensure
422
+ Appsignal::Transaction
423
+ .current
424
+ .finish_event(name, title, body, body_format)
425
+ end
426
+
427
+ # Instrumentation helper for SQL queries.
428
+ #
429
+ # This helper filters out values from SQL queries so you don't have to.
430
+ #
431
+ # @example SQL query instrumentation
432
+ # body = "SELECT * FROM ..."
433
+ # Appsignal.instrument_sql("perform.query", nil, body) do
434
+ # # To be instrumented code
435
+ # end
436
+ #
437
+ # @example SQL query instrumentation
438
+ # body = "WHERE email = 'foo@..'"
439
+ # Appsignal.instrument_sql("perform.query", nil, body) do
440
+ # # query value will replace 'foo..' with a question mark `?`.
441
+ # end
442
+ #
443
+ # @param name [String] Name of the instrumented event. Read our event
444
+ # naming guide listed under "See also".
445
+ # @param title [String, nil] Human readable name of the event.
446
+ # @param body [String, nil] SQL query that's being executed.
447
+ # @yield yields the given block of code instrumented in an AppSignal
448
+ # event.
449
+ # @return [Object] Returns the block's return value.
450
+ #
451
+ # @see .instrument
452
+ # @see http://docs.appsignal.com/ruby/instrumentation/instrumentation.html
453
+ # AppSignal custom instrumentation guide
454
+ # @see http://docs.appsignal.com/api/event-names.html
455
+ # AppSignal event naming guide
456
+ # @since 2.0.0
457
+ def instrument_sql(name, title = nil, body = nil, &block)
458
+ instrument(
459
+ name,
460
+ title,
461
+ body,
462
+ Appsignal::EventFormatter::SQL_BODY_FORMAT,
463
+ &block
464
+ )
465
+ end
466
+
467
+ # Convenience method for skipping instrumentations around a block of code.
468
+ #
469
+ # @example
470
+ # Appsignal.without_instrumentation do
471
+ # # Complex code here
472
+ # end
473
+ #
474
+ # @yield block of code that shouldn't be instrumented.
475
+ # @return [Object] Returns the return value of the block.
476
+ # @since 0.8.7
477
+ def without_instrumentation
478
+ Appsignal::Transaction.current.pause! if Appsignal::Transaction.current
479
+ yield
480
+ ensure
481
+ Appsignal::Transaction.current.resume! if Appsignal::Transaction.current
482
+ end
483
+ end
484
+ end
485
+ end