appsignal 2.5.0.alpha.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 (211) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +33 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +66 -0
  5. data/.rubocop_todo.yml +124 -0
  6. data/.travis.yml +72 -0
  7. data/.yardopts +8 -0
  8. data/CHANGELOG.md +639 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +20 -0
  11. data/README.md +264 -0
  12. data/Rakefile +214 -0
  13. data/appsignal.gemspec +42 -0
  14. data/benchmark.rake +77 -0
  15. data/bin/appsignal +13 -0
  16. data/ext/Rakefile +27 -0
  17. data/ext/agent.yml +64 -0
  18. data/ext/appsignal_extension.c +692 -0
  19. data/ext/base.rb +79 -0
  20. data/ext/extconf.rb +35 -0
  21. data/gemfiles/capistrano2.gemfile +7 -0
  22. data/gemfiles/capistrano3.gemfile +7 -0
  23. data/gemfiles/grape.gemfile +7 -0
  24. data/gemfiles/no_dependencies.gemfile +5 -0
  25. data/gemfiles/padrino.gemfile +7 -0
  26. data/gemfiles/que.gemfile +5 -0
  27. data/gemfiles/rails-3.2.gemfile +6 -0
  28. data/gemfiles/rails-4.0.gemfile +6 -0
  29. data/gemfiles/rails-4.1.gemfile +6 -0
  30. data/gemfiles/rails-4.2.gemfile +10 -0
  31. data/gemfiles/rails-5.0.gemfile +5 -0
  32. data/gemfiles/rails-5.1.gemfile +5 -0
  33. data/gemfiles/resque.gemfile +12 -0
  34. data/gemfiles/sequel-435.gemfile +11 -0
  35. data/gemfiles/sequel.gemfile +11 -0
  36. data/gemfiles/sinatra.gemfile +6 -0
  37. data/gemfiles/webmachine.gemfile +5 -0
  38. data/lib/appsignal.rb +804 -0
  39. data/lib/appsignal/auth_check.rb +65 -0
  40. data/lib/appsignal/capistrano.rb +10 -0
  41. data/lib/appsignal/cli.rb +108 -0
  42. data/lib/appsignal/cli/demo.rb +63 -0
  43. data/lib/appsignal/cli/diagnose.rb +500 -0
  44. data/lib/appsignal/cli/helpers.rb +72 -0
  45. data/lib/appsignal/cli/install.rb +277 -0
  46. data/lib/appsignal/cli/notify_of_deploy.rb +113 -0
  47. data/lib/appsignal/config.rb +287 -0
  48. data/lib/appsignal/demo.rb +107 -0
  49. data/lib/appsignal/event_formatter.rb +74 -0
  50. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +24 -0
  51. data/lib/appsignal/event_formatter/active_record/instantiation_formatter.rb +14 -0
  52. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +14 -0
  53. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +32 -0
  54. data/lib/appsignal/event_formatter/faraday/request_formatter.rb +19 -0
  55. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +89 -0
  56. data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
  57. data/lib/appsignal/extension.rb +63 -0
  58. data/lib/appsignal/extension/jruby.rb +460 -0
  59. data/lib/appsignal/garbage_collection_profiler.rb +48 -0
  60. data/lib/appsignal/hooks.rb +105 -0
  61. data/lib/appsignal/hooks/action_cable.rb +113 -0
  62. data/lib/appsignal/hooks/active_support_notifications.rb +52 -0
  63. data/lib/appsignal/hooks/celluloid.rb +30 -0
  64. data/lib/appsignal/hooks/data_mapper.rb +18 -0
  65. data/lib/appsignal/hooks/delayed_job.rb +19 -0
  66. data/lib/appsignal/hooks/mongo_ruby_driver.rb +21 -0
  67. data/lib/appsignal/hooks/net_http.rb +29 -0
  68. data/lib/appsignal/hooks/passenger.rb +22 -0
  69. data/lib/appsignal/hooks/puma.rb +35 -0
  70. data/lib/appsignal/hooks/que.rb +21 -0
  71. data/lib/appsignal/hooks/rake.rb +39 -0
  72. data/lib/appsignal/hooks/redis.rb +30 -0
  73. data/lib/appsignal/hooks/sequel.rb +60 -0
  74. data/lib/appsignal/hooks/shoryuken.rb +43 -0
  75. data/lib/appsignal/hooks/sidekiq.rb +144 -0
  76. data/lib/appsignal/hooks/unicorn.rb +40 -0
  77. data/lib/appsignal/hooks/webmachine.rb +23 -0
  78. data/lib/appsignal/integrations/capistrano/appsignal.cap +39 -0
  79. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +52 -0
  80. data/lib/appsignal/integrations/data_mapper.rb +33 -0
  81. data/lib/appsignal/integrations/delayed_job_plugin.rb +54 -0
  82. data/lib/appsignal/integrations/grape.rb +53 -0
  83. data/lib/appsignal/integrations/mongo_ruby_driver.rb +55 -0
  84. data/lib/appsignal/integrations/object.rb +35 -0
  85. data/lib/appsignal/integrations/padrino.rb +84 -0
  86. data/lib/appsignal/integrations/que.rb +43 -0
  87. data/lib/appsignal/integrations/railtie.rb +41 -0
  88. data/lib/appsignal/integrations/rake.rb +2 -0
  89. data/lib/appsignal/integrations/resque.rb +20 -0
  90. data/lib/appsignal/integrations/resque_active_job.rb +30 -0
  91. data/lib/appsignal/integrations/sinatra.rb +17 -0
  92. data/lib/appsignal/integrations/webmachine.rb +38 -0
  93. data/lib/appsignal/js_exception_transaction.rb +54 -0
  94. data/lib/appsignal/marker.rb +63 -0
  95. data/lib/appsignal/minutely.rb +42 -0
  96. data/lib/appsignal/rack/generic_instrumentation.rb +49 -0
  97. data/lib/appsignal/rack/js_exception_catcher.rb +70 -0
  98. data/lib/appsignal/rack/rails_instrumentation.rb +51 -0
  99. data/lib/appsignal/rack/sinatra_instrumentation.rb +99 -0
  100. data/lib/appsignal/rack/streaming_listener.rb +73 -0
  101. data/lib/appsignal/system.rb +81 -0
  102. data/lib/appsignal/transaction.rb +498 -0
  103. data/lib/appsignal/transmitter.rb +107 -0
  104. data/lib/appsignal/utils.rb +127 -0
  105. data/lib/appsignal/utils/params_sanitizer.rb +59 -0
  106. data/lib/appsignal/utils/query_params_sanitizer.rb +55 -0
  107. data/lib/appsignal/version.rb +3 -0
  108. data/lib/sequel/extensions/appsignal_integration.rb +3 -0
  109. data/resources/appsignal.yml.erb +39 -0
  110. data/resources/cacert.pem +3866 -0
  111. data/spec/.rubocop.yml +7 -0
  112. data/spec/lib/appsignal/auth_check_spec.rb +80 -0
  113. data/spec/lib/appsignal/capistrano2_spec.rb +224 -0
  114. data/spec/lib/appsignal/capistrano3_spec.rb +237 -0
  115. data/spec/lib/appsignal/cli/demo_spec.rb +67 -0
  116. data/spec/lib/appsignal/cli/diagnose_spec.rb +988 -0
  117. data/spec/lib/appsignal/cli/helpers_spec.rb +171 -0
  118. data/spec/lib/appsignal/cli/install_spec.rb +632 -0
  119. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +168 -0
  120. data/spec/lib/appsignal/cli_spec.rb +56 -0
  121. data/spec/lib/appsignal/config_spec.rb +637 -0
  122. data/spec/lib/appsignal/demo_spec.rb +87 -0
  123. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +44 -0
  124. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +21 -0
  125. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +21 -0
  126. data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +52 -0
  127. data/spec/lib/appsignal/event_formatter/faraday/request_formatter_spec.rb +21 -0
  128. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +113 -0
  129. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +112 -0
  130. data/spec/lib/appsignal/event_formatter_spec.rb +100 -0
  131. data/spec/lib/appsignal/extension/jruby_spec.rb +43 -0
  132. data/spec/lib/appsignal/extension_spec.rb +137 -0
  133. data/spec/lib/appsignal/garbage_collection_profiler_spec.rb +66 -0
  134. data/spec/lib/appsignal/hooks/action_cable_spec.rb +370 -0
  135. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +92 -0
  136. data/spec/lib/appsignal/hooks/celluloid_spec.rb +35 -0
  137. data/spec/lib/appsignal/hooks/data_mapper_spec.rb +39 -0
  138. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +358 -0
  139. data/spec/lib/appsignal/hooks/mongo_ruby_driver_spec.rb +44 -0
  140. data/spec/lib/appsignal/hooks/net_http_spec.rb +53 -0
  141. data/spec/lib/appsignal/hooks/passenger_spec.rb +30 -0
  142. data/spec/lib/appsignal/hooks/puma_spec.rb +80 -0
  143. data/spec/lib/appsignal/hooks/que_spec.rb +19 -0
  144. data/spec/lib/appsignal/hooks/rake_spec.rb +73 -0
  145. data/spec/lib/appsignal/hooks/redis_spec.rb +55 -0
  146. data/spec/lib/appsignal/hooks/sequel_spec.rb +46 -0
  147. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +192 -0
  148. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +419 -0
  149. data/spec/lib/appsignal/hooks/unicorn_spec.rb +52 -0
  150. data/spec/lib/appsignal/hooks/webmachine_spec.rb +35 -0
  151. data/spec/lib/appsignal/hooks_spec.rb +195 -0
  152. data/spec/lib/appsignal/integrations/data_mapper_spec.rb +65 -0
  153. data/spec/lib/appsignal/integrations/grape_spec.rb +225 -0
  154. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +127 -0
  155. data/spec/lib/appsignal/integrations/object_spec.rb +249 -0
  156. data/spec/lib/appsignal/integrations/padrino_spec.rb +323 -0
  157. data/spec/lib/appsignal/integrations/que_spec.rb +174 -0
  158. data/spec/lib/appsignal/integrations/railtie_spec.rb +129 -0
  159. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +83 -0
  160. data/spec/lib/appsignal/integrations/resque_spec.rb +92 -0
  161. data/spec/lib/appsignal/integrations/sinatra_spec.rb +73 -0
  162. data/spec/lib/appsignal/integrations/webmachine_spec.rb +69 -0
  163. data/spec/lib/appsignal/js_exception_transaction_spec.rb +128 -0
  164. data/spec/lib/appsignal/marker_spec.rb +51 -0
  165. data/spec/lib/appsignal/minutely_spec.rb +50 -0
  166. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +90 -0
  167. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +147 -0
  168. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +117 -0
  169. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +213 -0
  170. data/spec/lib/appsignal/rack/streaming_listener_spec.rb +161 -0
  171. data/spec/lib/appsignal/system_spec.rb +131 -0
  172. data/spec/lib/appsignal/transaction_spec.rb +1146 -0
  173. data/spec/lib/appsignal/transmitter_spec.rb +152 -0
  174. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +136 -0
  175. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +192 -0
  176. data/spec/lib/appsignal/utils_spec.rb +150 -0
  177. data/spec/lib/appsignal_spec.rb +1049 -0
  178. data/spec/spec_helper.rb +116 -0
  179. data/spec/support/fixtures/containers/cgroups/docker +14 -0
  180. data/spec/support/fixtures/containers/cgroups/docker_systemd +8 -0
  181. data/spec/support/fixtures/containers/cgroups/lxc +10 -0
  182. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  183. data/spec/support/fixtures/containers/cgroups/none +1 -0
  184. data/spec/support/fixtures/generated_config.yml +24 -0
  185. data/spec/support/fixtures/uploaded_file.txt +0 -0
  186. data/spec/support/helpers/api_request_helper.rb +19 -0
  187. data/spec/support/helpers/cli_helpers.rb +26 -0
  188. data/spec/support/helpers/config_helpers.rb +21 -0
  189. data/spec/support/helpers/dependency_helper.rb +73 -0
  190. data/spec/support/helpers/directory_helper.rb +27 -0
  191. data/spec/support/helpers/env_helpers.rb +33 -0
  192. data/spec/support/helpers/example_exception.rb +13 -0
  193. data/spec/support/helpers/example_standard_error.rb +13 -0
  194. data/spec/support/helpers/log_helpers.rb +22 -0
  195. data/spec/support/helpers/std_streams_helper.rb +66 -0
  196. data/spec/support/helpers/system_helpers.rb +8 -0
  197. data/spec/support/helpers/time_helpers.rb +11 -0
  198. data/spec/support/helpers/transaction_helpers.rb +37 -0
  199. data/spec/support/matchers/contains_log.rb +7 -0
  200. data/spec/support/mocks/fake_gc_profiler.rb +19 -0
  201. data/spec/support/mocks/mock_extension.rb +6 -0
  202. data/spec/support/project_fixture/config/application.rb +0 -0
  203. data/spec/support/project_fixture/config/appsignal.yml +32 -0
  204. data/spec/support/project_fixture/config/environments/development.rb +0 -0
  205. data/spec/support/project_fixture/config/environments/production.rb +0 -0
  206. data/spec/support/project_fixture/config/environments/test.rb +0 -0
  207. data/spec/support/project_fixture/log/.gitkeep +0 -0
  208. data/spec/support/rails/my_app.rb +6 -0
  209. data/spec/support/shared_examples/instrument.rb +43 -0
  210. data/spec/support/stubs/delayed_job.rb +0 -0
  211. metadata +483 -0
@@ -0,0 +1,99 @@
1
+ require "rack"
2
+
3
+ module Appsignal
4
+ module Rack
5
+ # Stub old middleware. Prevents Sinatra middleware being loaded twice.
6
+ # This can happen when users use the old method of including
7
+ # `use Appsignal::Rack::SinatraInstrumentation` in their modular Sinatra
8
+ # applications. This is no longer needed. Instead Appsignal now includes
9
+ # `use Appsignal::Rack::SinatraBaseInstrumentation` automatically.
10
+ #
11
+ # @api private
12
+ class SinatraInstrumentation
13
+ def initialize(app, options = {})
14
+ @app = app
15
+ @options = options
16
+ Appsignal.logger.warn "Please remove Appsignal::Rack::SinatraInstrumentation "\
17
+ "from your Sinatra::Base class. This is no longer needed."
18
+ end
19
+
20
+ def call(env)
21
+ @app.call(env)
22
+ end
23
+
24
+ def settings
25
+ @app.settings
26
+ end
27
+ end
28
+
29
+ class SinatraBaseInstrumentation
30
+ attr_reader :raise_errors_on
31
+
32
+ def initialize(app, options = {})
33
+ Appsignal.logger.debug "Initializing Appsignal::Rack::SinatraInstrumentation"
34
+ @app = app
35
+ @options = options
36
+ @raise_errors_on = raise_errors?(@app)
37
+ end
38
+
39
+ def call(env)
40
+ if Appsignal.active?
41
+ call_with_appsignal_monitoring(env)
42
+ else
43
+ @app.call(env)
44
+ end
45
+ end
46
+
47
+ def call_with_appsignal_monitoring(env)
48
+ if @options[:params_method]
49
+ env[:params_method] = @options[:params_method]
50
+ end
51
+ request = @options.fetch(:request_class, Sinatra::Request).new(env)
52
+ transaction = Appsignal::Transaction.create(
53
+ SecureRandom.uuid,
54
+ Appsignal::Transaction::HTTP_REQUEST,
55
+ request,
56
+ :force => @options.include?(:force) && @options[:force]
57
+ )
58
+ begin
59
+ Appsignal.instrument("process_action.sinatra") do
60
+ @app.call(env)
61
+ end
62
+ rescue Exception => error # rubocop:disable Lint/RescueException
63
+ transaction.set_error(error)
64
+ raise error
65
+ ensure
66
+ # If raise_error is off versions of Sinatra don't raise errors, but store
67
+ # them in the sinatra.error env var.
68
+ if !raise_errors_on && env["sinatra.error"] && !env["sinatra.skip_appsignal_error"]
69
+ transaction.set_error(env["sinatra.error"])
70
+ end
71
+ transaction.set_action_if_nil(action_name(env))
72
+ transaction.set_metadata("path", request.path)
73
+ transaction.set_metadata("method", request.request_method)
74
+ transaction.set_http_or_background_queue_start
75
+ Appsignal::Transaction.complete_current!
76
+ end
77
+ end
78
+
79
+ def action_name(env)
80
+ return unless env["sinatra.route"]
81
+
82
+ if env["SCRIPT_NAME"]
83
+ method, route = env["sinatra.route"].split(" ")
84
+ "#{method} #{env["SCRIPT_NAME"]}#{route}"
85
+ else
86
+ env["sinatra.route"]
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def raise_errors?(app)
93
+ app.respond_to?(:settings) &&
94
+ app.settings.respond_to?(:raise_errors) &&
95
+ app.settings.raise_errors
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,73 @@
1
+ module Appsignal
2
+ module Rack
3
+ # Appsignal module that tracks exceptions in Streaming rack responses.
4
+ #
5
+ # @api private
6
+ class StreamingListener
7
+ def initialize(app, options = {})
8
+ Appsignal.logger.debug "Initializing Appsignal::Rack::StreamingListener"
9
+ @app = app
10
+ @options = options
11
+ end
12
+
13
+ def call(env)
14
+ if Appsignal.active?
15
+ call_with_appsignal_monitoring(env)
16
+ else
17
+ @app.call(env)
18
+ end
19
+ end
20
+
21
+ def call_with_appsignal_monitoring(env)
22
+ request = ::Rack::Request.new(env)
23
+ transaction = Appsignal::Transaction.create(
24
+ SecureRandom.uuid,
25
+ Appsignal::Transaction::HTTP_REQUEST,
26
+ request
27
+ )
28
+
29
+ # Instrument a `process_action`, to set params/action name
30
+ status, headers, body =
31
+ Appsignal.instrument("process_action.rack") do
32
+ begin
33
+ @app.call(env)
34
+ rescue Exception => e # rubocop:disable Lint/RescueException
35
+ transaction.set_error(e)
36
+ raise e
37
+ ensure
38
+ transaction.set_action_if_nil(env["appsignal.action"])
39
+ transaction.set_metadata("path", request.path)
40
+ transaction.set_metadata("method", request.request_method)
41
+ transaction.set_http_or_background_queue_start
42
+ end
43
+ end
44
+
45
+ # Wrap the result body with our StreamWrapper
46
+ [status, headers, StreamWrapper.new(body, transaction)]
47
+ end
48
+ end
49
+ end
50
+
51
+ class StreamWrapper
52
+ def initialize(stream, transaction)
53
+ @stream = stream
54
+ @transaction = transaction
55
+ end
56
+
57
+ def each
58
+ @stream.each { |c| yield(c) }
59
+ rescue Exception => e # rubocop:disable Lint/RescueException
60
+ @transaction.set_error(e)
61
+ raise e
62
+ end
63
+
64
+ def close
65
+ @stream.close if @stream.respond_to?(:close)
66
+ rescue Exception => e # rubocop:disable Lint/RescueException
67
+ @transaction.set_error(e)
68
+ raise e
69
+ ensure
70
+ Appsignal::Transaction.complete_current!
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,81 @@
1
+ module Appsignal
2
+ # System environment detection module.
3
+ #
4
+ # Provides useful methods to find out more about the host system.
5
+ #
6
+ # @api private
7
+ module System
8
+ MUSL_TARGET = "linux-musl".freeze
9
+ GEM_EXT_PATH = File.expand_path("../../../ext", __FILE__).freeze
10
+
11
+ def self.heroku?
12
+ ENV.key? "DYNO".freeze
13
+ end
14
+
15
+ # Returns the architecture for which the agent was installed.
16
+ #
17
+ # This value is saved when the gem is installed in `ext/extconf.rb`.
18
+ # We use this value to build the diagnose report with the installed
19
+ # CPU type and platform, rather than the detected architecture in
20
+ # {.agent_platform} during the diagnose run.
21
+ #
22
+ # @api private
23
+ # @return [String]
24
+ def self.installed_agent_architecture
25
+ architecture_file = File.join(GEM_EXT_PATH, "appsignal.architecture")
26
+ return unless File.exist?(architecture_file)
27
+ File.read(architecture_file)
28
+ end
29
+
30
+ # Detect agent and extension platform build
31
+ #
32
+ # Used by `ext/extconf.rb` to select which build it should download and
33
+ # install.
34
+ #
35
+ # Use `export APPSIGNAL_BUILD_FOR_MUSL=1` if the detection doesn't work
36
+ # and to force selection of the musl build.
37
+ #
38
+ # @api private
39
+ # @return [String]
40
+ def self.agent_platform
41
+ return MUSL_TARGET if ENV["APPSIGNAL_BUILD_FOR_MUSL"]
42
+
43
+ host_os = RbConfig::CONFIG["host_os"].downcase
44
+ local_os =
45
+ case host_os
46
+ when /linux/
47
+ "linux"
48
+ when /darwin/
49
+ "darwin"
50
+ when /freebsd/
51
+ "freebsd"
52
+ else
53
+ host_os
54
+ end
55
+ if local_os =~ /linux/
56
+ ldd_output = ldd_version_output
57
+ return MUSL_TARGET if ldd_output.include? "musl"
58
+ ldd_version = ldd_output.match(/\d+\.\d+/)
59
+ if ldd_version && versionify(ldd_version[0]) < versionify("2.15")
60
+ return MUSL_TARGET
61
+ end
62
+ end
63
+
64
+ local_os
65
+ end
66
+
67
+ # @api private
68
+ def self.versionify(version)
69
+ Gem::Version.new(version)
70
+ end
71
+
72
+ # @api private
73
+ def self.ldd_version_output
74
+ `ldd --version 2>&1`
75
+ end
76
+
77
+ def self.jruby?
78
+ RUBY_PLATFORM == "java"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,498 @@
1
+ require "json"
2
+
3
+ module Appsignal
4
+ class Transaction
5
+ HTTP_REQUEST = "http_request".freeze
6
+ BACKGROUND_JOB = "background_job".freeze
7
+ ACTION_CABLE = "action_cable".freeze
8
+ FRONTEND = "frontend".freeze
9
+ BLANK = "".freeze
10
+
11
+ # Based on what Rails uses + some variables we'd like to show
12
+ ENV_METHODS = %w[
13
+ CONTENT_LENGTH AUTH_TYPE GATEWAY_INTERFACE
14
+ PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR
15
+ REQUEST_METHOD SERVER_NAME SERVER_PORT SERVER_PROTOCOL REQUEST_URI
16
+ PATH_INFO
17
+
18
+ HTTP_X_REQUEST_START HTTP_X_MIDDLEWARE_START HTTP_X_QUEUE_START
19
+ HTTP_X_QUEUE_TIME HTTP_X_HEROKU_QUEUE_WAIT_TIME HTTP_X_APPLICATION_START
20
+ HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE
21
+ HTTP_CACHE_CONTROL HTTP_CONNECTION HTTP_USER_AGENT HTTP_FROM
22
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_X_FORWARDED_FOR
23
+ HTTP_CLIENT_IP HTTP_RANGE
24
+ ].freeze
25
+
26
+ class << self
27
+ def create(id, namespace, request, options = {})
28
+ # Allow middleware to force a new transaction
29
+ if options.include?(:force) && options[:force]
30
+ Thread.current[:appsignal_transaction] = nil
31
+ end
32
+
33
+ # Check if we already have a running transaction
34
+ if Thread.current[:appsignal_transaction] != nil
35
+ # Log the issue and return the current transaction
36
+ Appsignal.logger.debug "Trying to start new transaction with id " \
37
+ "'#{id}', but a transaction with id '#{current.transaction_id}' " \
38
+ "is already running. Using transaction '#{current.transaction_id}'."
39
+
40
+ # Return the current (running) transaction
41
+ current
42
+ else
43
+ # Otherwise, start a new transaction
44
+ Thread.current[:appsignal_transaction] = Appsignal::Transaction.new(id, namespace, request, options)
45
+ end
46
+ end
47
+
48
+ def current
49
+ Thread.current[:appsignal_transaction] || NilTransaction.new
50
+ end
51
+
52
+ def complete_current!
53
+ current.complete
54
+ rescue => e
55
+ Appsignal.logger.error("Failed to complete transaction ##{current.transaction_id}. #{e.message}")
56
+ ensure
57
+ clear_current_transaction!
58
+ end
59
+
60
+ # Remove current transaction from current Thread.
61
+ # @api private
62
+ def clear_current_transaction!
63
+ Thread.current[:appsignal_transaction] = nil
64
+ end
65
+
66
+ def garbage_collection_profiler
67
+ @garbage_collection_profiler ||= Appsignal::GarbageCollectionProfiler.new
68
+ end
69
+ end
70
+
71
+ attr_reader :ext, :transaction_id, :action, :namespace, :request, :paused, :tags, :options, :discarded
72
+
73
+ # @!attribute params
74
+ # Attribute for parameters of the transaction.
75
+ #
76
+ # When no parameters are set with {#params=} the parameters it will look
77
+ # for parameters on the {#request} environment.
78
+ #
79
+ # The parameters set using {#params=} are leading over those extracted
80
+ # from a request's environment.
81
+ #
82
+ # @return [Hash]
83
+ attr_writer :params
84
+
85
+ def initialize(transaction_id, namespace, request, options = {})
86
+ @transaction_id = transaction_id
87
+ @action = nil
88
+ @namespace = namespace
89
+ @request = request
90
+ @paused = false
91
+ @discarded = false
92
+ @tags = {}
93
+ @store = Hash.new({})
94
+ @options = options
95
+ @options[:params_method] ||= :params
96
+
97
+ @ext = Appsignal::Extension.start_transaction(
98
+ @transaction_id,
99
+ @namespace,
100
+ self.class.garbage_collection_profiler.total_time
101
+ )
102
+ end
103
+
104
+ def nil_transaction?
105
+ false
106
+ end
107
+
108
+ def complete
109
+ if discarded?
110
+ Appsignal.logger.debug "Skipping transaction '#{transaction_id}' " \
111
+ "because it was manually discarded."
112
+ return
113
+ end
114
+ if @ext.finish(self.class.garbage_collection_profiler.total_time)
115
+ sample_data
116
+ end
117
+ @ext.complete
118
+ end
119
+
120
+ def pause!
121
+ @paused = true
122
+ end
123
+
124
+ def resume!
125
+ @paused = false
126
+ end
127
+
128
+ def paused?
129
+ @paused == true
130
+ end
131
+
132
+ def discard!
133
+ @discarded = true
134
+ end
135
+
136
+ def restore!
137
+ @discarded = false
138
+ end
139
+
140
+ def discarded?
141
+ @discarded == true
142
+ end
143
+
144
+ def store(key)
145
+ @store[key]
146
+ end
147
+
148
+ def params
149
+ return @params if defined?(@params)
150
+ request_params
151
+ end
152
+
153
+ # Set tags on the transaction.
154
+ #
155
+ # @param given_tags [Hash] Collection of tags.
156
+ # @option given_tags [String, Symbol, Integer] :any
157
+ # The name of the tag as a Symbol.
158
+ # @option given_tags [String, Symbol, Integer] "any"
159
+ # The name of the tag as a String.
160
+ # @return [void]
161
+ #
162
+ # @see Appsignal.tag_request
163
+ # @see http://docs.appsignal.com/ruby/instrumentation/tagging.html
164
+ # Tagging guide
165
+ def set_tags(given_tags = {})
166
+ @tags.merge!(given_tags)
167
+ end
168
+
169
+ # Set an action name for the transaction.
170
+ #
171
+ # An action name is used to identify the location of a certain sample;
172
+ # error and performance issues.
173
+ #
174
+ # @param action [String] the action name to set.
175
+ # @return [void]
176
+ # @see Appsignal.set_action
177
+ # @see #set_action_if_nil
178
+ # @since 2.2.0
179
+ def set_action(action)
180
+ return unless action
181
+ @action = action
182
+ @ext.set_action(action)
183
+ end
184
+
185
+ # Set an action name only if there is no current action set.
186
+ #
187
+ # Commonly used by AppSignal integrations so that they don't override
188
+ # custom action names.
189
+ #
190
+ # @example
191
+ # Appsignal.set_action("foo")
192
+ # Appsignal.set_action_if_nil("bar")
193
+ # # Transaction action will be "foo"
194
+ #
195
+ # @param action [String]
196
+ # @return [void]
197
+ # @see #set_action
198
+ # @since 2.2.0
199
+ def set_action_if_nil(action)
200
+ return if @action
201
+ set_action(action)
202
+ end
203
+
204
+ # Set the namespace for this transaction.
205
+ #
206
+ # Useful to split up parts of an application into certain namespaces. For
207
+ # example: http requests, background jobs and administration panel
208
+ # controllers.
209
+ #
210
+ # Note: The "http_request" namespace gets transformed on AppSignal.com to
211
+ # "Web" and "background_job" gets transformed to "Background".
212
+ #
213
+ # @example
214
+ # transaction.set_action("admin")
215
+ #
216
+ # @param namespace [String] namespace name to use for this transaction.
217
+ # @return [void]
218
+ # @since 2.2.0
219
+ def set_namespace(namespace)
220
+ return unless namespace
221
+ @namespace = namespace
222
+ @ext.set_namespace(namespace)
223
+ end
224
+
225
+ def set_http_or_background_action(from = request.params)
226
+ return unless from
227
+ group_and_action = [
228
+ from[:controller] || from[:class],
229
+ from[:action] || from[:method]
230
+ ]
231
+ set_action(group_and_action.compact.join("#"))
232
+ end
233
+
234
+ def set_queue_start(start)
235
+ return unless start
236
+ @ext.set_queue_start(start)
237
+ rescue RangeError
238
+ Appsignal.logger.warn("Queue start value #{start} is too big")
239
+ end
240
+
241
+ def set_http_or_background_queue_start
242
+ if namespace == HTTP_REQUEST
243
+ set_queue_start(http_queue_start)
244
+ elsif namespace == BACKGROUND_JOB
245
+ set_queue_start(background_queue_start)
246
+ end
247
+ end
248
+
249
+ def set_metadata(key, value)
250
+ return unless key && value
251
+ @ext.set_metadata(key, value)
252
+ end
253
+
254
+ def set_sample_data(key, data)
255
+ return unless key && data && (data.is_a?(Array) || data.is_a?(Hash))
256
+ @ext.set_sample_data(
257
+ key.to_s,
258
+ Appsignal::Utils.data_generate(data)
259
+ )
260
+ rescue RuntimeError => e
261
+ Appsignal.logger.error("Error generating data (#{e.class}: #{e.message}) for '#{data.inspect}'")
262
+ end
263
+
264
+ def sample_data
265
+ {
266
+ :params => sanitized_params,
267
+ :environment => sanitized_environment,
268
+ :session_data => sanitized_session_data,
269
+ :metadata => metadata,
270
+ :tags => sanitized_tags
271
+ }.each do |key, data|
272
+ set_sample_data(key, data)
273
+ end
274
+ end
275
+
276
+ def set_error(error)
277
+ return unless error
278
+ return unless Appsignal.active?
279
+
280
+ backtrace = cleaned_backtrace(error.backtrace)
281
+ @ext.set_error(
282
+ error.class.name,
283
+ error.message.to_s,
284
+ backtrace ? Appsignal::Utils.data_generate(backtrace) : Appsignal::Extension.data_array_new
285
+ )
286
+ end
287
+ alias_method :add_exception, :set_error
288
+
289
+ def start_event
290
+ @ext.start_event(self.class.garbage_collection_profiler.total_time)
291
+ end
292
+
293
+ def finish_event(name, title, body, body_format = Appsignal::EventFormatter::DEFAULT)
294
+ @ext.finish_event(
295
+ name,
296
+ title || BLANK,
297
+ body || BLANK,
298
+ body_format || Appsignal::EventFormatter::DEFAULT,
299
+ self.class.garbage_collection_profiler.total_time
300
+ )
301
+ end
302
+
303
+ def record_event(name, title, body, duration, body_format = Appsignal::EventFormatter::DEFAULT)
304
+ @ext.record_event(
305
+ name,
306
+ title || BLANK,
307
+ body || BLANK,
308
+ body_format || Appsignal::EventFormatter::DEFAULT,
309
+ duration,
310
+ self.class.garbage_collection_profiler.total_time
311
+ )
312
+ end
313
+
314
+ def instrument(name, title = nil, body = nil, body_format = Appsignal::EventFormatter::DEFAULT)
315
+ start_event
316
+ yield if block_given?
317
+ ensure
318
+ finish_event(name, title, body, body_format)
319
+ end
320
+
321
+ # @api private
322
+ def to_h
323
+ JSON.parse(@ext.to_json)
324
+ end
325
+ alias_method :to_hash, :to_h
326
+
327
+ class GenericRequest
328
+ attr_reader :env
329
+
330
+ def initialize(env)
331
+ @env = env
332
+ end
333
+
334
+ def params
335
+ env[:params]
336
+ end
337
+ end
338
+
339
+ private
340
+
341
+ # Returns calculated background queue start time in milliseconds, based on
342
+ # environment values.
343
+ #
344
+ # @return [nil] if no {#environment} is present.
345
+ # @return [nil] if there is no `:queue_start` in the {#environment}.
346
+ # @return [Integer]
347
+ def background_queue_start
348
+ env = environment
349
+ return unless env
350
+ queue_start = env[:queue_start]
351
+ return unless queue_start
352
+
353
+ (queue_start.to_f * 1000.0).to_i
354
+ end
355
+
356
+ # Returns HTTP queue start time in milliseconds.
357
+ #
358
+ # @return [nil] if no queue start time is found.
359
+ # @return [nil] if begin time is too low to be plausible.
360
+ # @return [Integer] queue start in milliseconds.
361
+ def http_queue_start
362
+ env = environment
363
+ return unless env
364
+ env_var = env["HTTP_X_QUEUE_START".freeze] || env["HTTP_X_REQUEST_START".freeze]
365
+ return unless env_var
366
+ cleaned_value = env_var.tr("^0-9".freeze, "".freeze)
367
+ return if cleaned_value.empty?
368
+
369
+ value = cleaned_value.to_i
370
+ if value > 4_102_441_200_000
371
+ # Value is in microseconds. Transform to milliseconds.
372
+ value / 1_000
373
+ elsif value < 946_681_200_000
374
+ # Value is too low to be plausible
375
+ nil
376
+ else
377
+ # Value is in milliseconds
378
+ value
379
+ end
380
+ end
381
+
382
+ def sanitized_params
383
+ return unless Appsignal.config[:send_params]
384
+
385
+ options = {}
386
+ if Appsignal.config[:filter_parameters]
387
+ options[:filter_parameters] = Appsignal.config[:filter_parameters]
388
+ end
389
+ Appsignal::Utils::ParamsSanitizer.sanitize params, options
390
+ end
391
+
392
+ def request_params
393
+ return unless request.respond_to?(options[:params_method])
394
+
395
+ begin
396
+ request.send options[:params_method]
397
+ rescue => e
398
+ # Getting params from the request has been know to fail.
399
+ Appsignal.logger.debug "Exception while getting params: #{e}"
400
+ nil
401
+ end
402
+ end
403
+
404
+ # Returns sanitized environment for a transaction.
405
+ #
406
+ # The environment of a transaction can contain a lot of information, not
407
+ # all of it useful for debugging.
408
+ #
409
+ # Only the values from the keys specified in {ENV_METHODS} are returned.
410
+ #
411
+ # @return [nil] if no environment is present.
412
+ # @return [Hash<String, Object>]
413
+ def sanitized_environment
414
+ env = environment
415
+ return if env.empty?
416
+
417
+ {}.tap do |out|
418
+ ENV_METHODS.each do |key|
419
+ out[key] = env[key] if env[key]
420
+ end
421
+ end
422
+ end
423
+
424
+ # Returns sanitized session data.
425
+ #
426
+ # The session data is sanitized by the {Appsignal::Utils::ParamsSanitizer}.
427
+ #
428
+ # @return [nil] if `:skip_session_data` config is set to `true`.
429
+ # @return [nil] if the {#request} object doesn't respond to `#session`.
430
+ # @return [nil] if the {#request} session data is `nil`.
431
+ # @return [Hash<String, Object>]
432
+ def sanitized_session_data
433
+ return if Appsignal.config[:skip_session_data] ||
434
+ !request.respond_to?(:session)
435
+ session = request.session
436
+ return unless session
437
+
438
+ Appsignal::Utils::ParamsSanitizer.sanitize(session.to_hash)
439
+ end
440
+
441
+ # Returns metadata from the environment.
442
+ #
443
+ # @return [nil] if no `:metadata` key is present in the {#environment}.
444
+ # @return [Hash<String, Object>]
445
+ def metadata
446
+ environment[:metadata]
447
+ end
448
+
449
+ # Returns the environment for a transaction.
450
+ #
451
+ # Returns an empty Hash when the {#request} object doesn't listen to the
452
+ # `#env` method or the `#env` is nil.
453
+ #
454
+ # @return [Hash<String, Object>]
455
+ def environment
456
+ return {} unless request.respond_to?(:env)
457
+ return {} unless request.env
458
+
459
+ request.env
460
+ end
461
+
462
+ # Only keep tags if they meet the following criteria:
463
+ # * Key is a symbol or string with less then 100 chars
464
+ # * Value is a symbol or string with less then 100 chars
465
+ # * Value is an integer
466
+ def sanitized_tags
467
+ @tags.select do |k, v|
468
+ (k.is_a?(Symbol) || k.is_a?(String) && k.length <= 100) &&
469
+ (((v.is_a?(Symbol) || v.is_a?(String)) && v.length <= 100) || v.is_a?(Integer))
470
+ end
471
+ end
472
+
473
+ def cleaned_backtrace(backtrace)
474
+ if defined?(::Rails) && backtrace
475
+ ::Rails.backtrace_cleaner.clean(backtrace, nil)
476
+ else
477
+ backtrace
478
+ end
479
+ end
480
+
481
+ # Stub that is returned by {Transaction.current} if there is no current
482
+ # transaction, so that it's still safe to call methods on it if there is no
483
+ # current transaction.
484
+ class NilTransaction
485
+ def method_missing(m, *args, &block)
486
+ end
487
+
488
+ # Instrument should still yield
489
+ def instrument(*_args)
490
+ yield
491
+ end
492
+
493
+ def nil_transaction?
494
+ true
495
+ end
496
+ end
497
+ end
498
+ end