appsignal 2.5.0.alpha.1-java

Sign up to get free protection for your applications and to get access to all the features.
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