opbeat 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -3
  3. data/.travis.yml +19 -28
  4. data/.yardopts +3 -0
  5. data/Gemfile +4 -2
  6. data/HISTORY.md +3 -0
  7. data/LICENSE +7 -196
  8. data/README.md +96 -177
  9. data/Rakefile +19 -13
  10. data/gemfiles/Gemfile.base +28 -0
  11. data/gemfiles/Gemfile.rails-3.2.x +3 -0
  12. data/gemfiles/Gemfile.rails-4.0.x +3 -0
  13. data/gemfiles/Gemfile.rails-4.1.x +3 -0
  14. data/gemfiles/Gemfile.rails-4.2.x +3 -0
  15. data/lib/opbeat.rb +113 -93
  16. data/lib/opbeat/capistrano.rb +3 -4
  17. data/lib/opbeat/client.rb +243 -82
  18. data/lib/opbeat/configuration.rb +51 -64
  19. data/lib/opbeat/data_builders.rb +16 -0
  20. data/lib/opbeat/data_builders/error.rb +27 -0
  21. data/lib/opbeat/data_builders/transactions.rb +85 -0
  22. data/lib/opbeat/error.rb +1 -2
  23. data/lib/opbeat/error_message.rb +71 -0
  24. data/lib/opbeat/error_message/exception.rb +12 -0
  25. data/lib/opbeat/error_message/http.rb +62 -0
  26. data/lib/opbeat/error_message/stacktrace.rb +75 -0
  27. data/lib/opbeat/error_message/user.rb +23 -0
  28. data/lib/opbeat/filter.rb +53 -43
  29. data/lib/opbeat/http_client.rb +141 -0
  30. data/lib/opbeat/injections.rb +83 -0
  31. data/lib/opbeat/injections/json.rb +19 -0
  32. data/lib/opbeat/injections/net_http.rb +43 -0
  33. data/lib/opbeat/injections/redis.rb +23 -0
  34. data/lib/opbeat/injections/sequel.rb +32 -0
  35. data/lib/opbeat/injections/sinatra.rb +56 -0
  36. data/lib/opbeat/{capistrano → integration}/capistrano2.rb +6 -6
  37. data/lib/opbeat/{capistrano → integration}/capistrano3.rb +3 -3
  38. data/lib/opbeat/{integrations → integration}/delayed_job.rb +6 -11
  39. data/lib/opbeat/integration/rails/inject_exceptions_catcher.rb +23 -0
  40. data/lib/opbeat/integration/railtie.rb +53 -0
  41. data/lib/opbeat/integration/resque.rb +16 -0
  42. data/lib/opbeat/integration/sidekiq.rb +38 -0
  43. data/lib/opbeat/line_cache.rb +21 -0
  44. data/lib/opbeat/logging.rb +37 -0
  45. data/lib/opbeat/middleware.rb +59 -0
  46. data/lib/opbeat/normalizers.rb +65 -0
  47. data/lib/opbeat/normalizers/action_controller.rb +21 -0
  48. data/lib/opbeat/normalizers/action_view.rb +71 -0
  49. data/lib/opbeat/normalizers/active_record.rb +41 -0
  50. data/lib/opbeat/sql_summarizer.rb +27 -0
  51. data/lib/opbeat/subscriber.rb +80 -0
  52. data/lib/opbeat/tasks.rb +20 -18
  53. data/lib/opbeat/trace.rb +47 -0
  54. data/lib/opbeat/trace_helpers.rb +29 -0
  55. data/lib/opbeat/transaction.rb +99 -0
  56. data/lib/opbeat/util.rb +26 -0
  57. data/lib/opbeat/util/constantize.rb +54 -0
  58. data/lib/opbeat/util/inspector.rb +75 -0
  59. data/lib/opbeat/version.rb +1 -1
  60. data/lib/opbeat/worker.rb +55 -0
  61. data/opbeat.gemspec +6 -14
  62. data/spec/opbeat/client_spec.rb +216 -29
  63. data/spec/opbeat/configuration_spec.rb +34 -38
  64. data/spec/opbeat/data_builders/error_spec.rb +43 -0
  65. data/spec/opbeat/data_builders/transactions_spec.rb +51 -0
  66. data/spec/opbeat/error_message/exception_spec.rb +22 -0
  67. data/spec/opbeat/error_message/http_spec.rb +65 -0
  68. data/spec/opbeat/error_message/stacktrace_spec.rb +56 -0
  69. data/spec/opbeat/error_message/user_spec.rb +28 -0
  70. data/spec/opbeat/error_message_spec.rb +78 -0
  71. data/spec/opbeat/filter_spec.rb +21 -99
  72. data/spec/opbeat/http_client_spec.rb +64 -0
  73. data/spec/opbeat/injections/net_http_spec.rb +37 -0
  74. data/spec/opbeat/injections/sequel_spec.rb +33 -0
  75. data/spec/opbeat/injections/sinatra_spec.rb +13 -0
  76. data/spec/opbeat/injections_spec.rb +49 -0
  77. data/spec/opbeat/integration/delayed_job_spec.rb +35 -0
  78. data/spec/opbeat/integration/json_spec.rb +41 -0
  79. data/spec/opbeat/integration/rails_spec.rb +88 -0
  80. data/spec/opbeat/integration/redis_spec.rb +20 -0
  81. data/spec/opbeat/integration/resque_spec.rb +42 -0
  82. data/spec/opbeat/integration/sidekiq_spec.rb +40 -0
  83. data/spec/opbeat/integration/sinatra_spec.rb +66 -0
  84. data/spec/opbeat/line_cache_spec.rb +38 -0
  85. data/spec/opbeat/logging_spec.rb +47 -0
  86. data/spec/opbeat/middleware_spec.rb +32 -0
  87. data/spec/opbeat/normalizers/action_controller_spec.rb +32 -0
  88. data/spec/opbeat/normalizers/action_view_spec.rb +77 -0
  89. data/spec/opbeat/normalizers/active_record_spec.rb +70 -0
  90. data/spec/opbeat/normalizers_spec.rb +16 -0
  91. data/spec/opbeat/sql_summarizer_spec.rb +6 -0
  92. data/spec/opbeat/subscriber_spec.rb +83 -0
  93. data/spec/opbeat/trace_spec.rb +43 -0
  94. data/spec/opbeat/transaction_spec.rb +98 -0
  95. data/spec/opbeat/util/inspector_spec.rb +40 -0
  96. data/spec/opbeat/util_spec.rb +20 -0
  97. data/spec/opbeat/worker_spec.rb +54 -0
  98. data/spec/opbeat_spec.rb +49 -0
  99. data/spec/spec_helper.rb +79 -6
  100. metadata +89 -149
  101. data/Makefile +0 -3
  102. data/gemfiles/rails30.gemfile +0 -9
  103. data/gemfiles/rails31.gemfile +0 -9
  104. data/gemfiles/rails32.gemfile +0 -9
  105. data/gemfiles/rails40.gemfile +0 -9
  106. data/gemfiles/rails41.gemfile +0 -9
  107. data/gemfiles/rails42.gemfile +0 -9
  108. data/gemfiles/ruby192_rails31.gemfile +0 -10
  109. data/gemfiles/ruby192_rails32.gemfile +0 -10
  110. data/gemfiles/sidekiq31.gemfile +0 -11
  111. data/lib/opbeat/better_attr_accessor.rb +0 -44
  112. data/lib/opbeat/event.rb +0 -223
  113. data/lib/opbeat/integrations/resque.rb +0 -22
  114. data/lib/opbeat/integrations/sidekiq.rb +0 -32
  115. data/lib/opbeat/interfaces.rb +0 -35
  116. data/lib/opbeat/interfaces/exception.rb +0 -16
  117. data/lib/opbeat/interfaces/http.rb +0 -57
  118. data/lib/opbeat/interfaces/message.rb +0 -19
  119. data/lib/opbeat/interfaces/stack_trace.rb +0 -50
  120. data/lib/opbeat/linecache.rb +0 -25
  121. data/lib/opbeat/logger.rb +0 -21
  122. data/lib/opbeat/rack.rb +0 -46
  123. data/lib/opbeat/rails/middleware/debug_exceptions_catcher.rb +0 -22
  124. data/lib/opbeat/railtie.rb +0 -26
  125. data/spec/opbeat/better_attr_accessor_spec.rb +0 -99
  126. data/spec/opbeat/event_spec.rb +0 -138
  127. data/spec/opbeat/integrations/delayed_job_spec.rb +0 -38
  128. data/spec/opbeat/logger_spec.rb +0 -55
  129. data/spec/opbeat/opbeat_spec.rb +0 -64
  130. data/spec/opbeat/rack_spec.rb +0 -117
@@ -0,0 +1,23 @@
1
+ module Opbeat
2
+ class ErrorMessage
3
+ class User < Struct.new(:is_authenticated, :id, :username, :email)
4
+ CONTROLLER_KEY = 'action_controller.instance'.freeze
5
+
6
+ def self.from_rack_env config, env
7
+ controller = env[CONTROLLER_KEY]
8
+ method = config.current_user_method.to_sym
9
+
10
+ return unless controller && controller.respond_to?(method)
11
+
12
+ user = controller.send method
13
+
14
+ new(
15
+ true,
16
+ user.respond_to?(:id) ? user.id : nil,
17
+ user.respond_to?(:username) ? user.username : nil,
18
+ user.respond_to?(:email) ? user.email : nil
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/opbeat/filter.rb CHANGED
@@ -1,63 +1,73 @@
1
1
  module Opbeat
2
+ # @api private
2
3
  class Filter
3
- MASK = '[FILTERED]'
4
- DEFAULT_FILTER = [/(authorization|password|passwd|secret)/i]
5
4
 
6
- def initialize(filters=nil)
7
- if defined?(::Rails)
8
- rails_filters = ::Rails.application.config.filter_parameters
9
- rails_filters = nil if rails_filters.count == 0
10
- end
11
- @filters = filters || rails_filters || DEFAULT_FILTER
5
+ MASK = '[FILTERED]'.freeze
6
+
7
+ def initialize config
8
+ @config = config
9
+ @params = rails_filters || config.filter_parameters
12
10
  end
13
11
 
14
- def apply(value, key=nil, &block)
15
- if value.is_a?(Hash)
16
- value.each.inject({}) do |memo, (k, v)|
17
- memo[k] = apply(v, k, &block)
18
- memo
19
- end
20
- elsif value.is_a?(Array)
21
- value.map do |value|
22
- apply(value, key, &block)
23
- end
24
- else
25
- block.call(key, value)
12
+ attr_reader :config
13
+
14
+ def apply data, opts = {}
15
+ case data
16
+ when String
17
+ apply_to_string data, opts = {}
18
+ when Hash
19
+ apply_to_hash data
26
20
  end
27
21
  end
28
22
 
29
- def sanitize(key, value)
30
- if !value.is_a?(String) || value.empty?
31
- value
32
- elsif @filters.any? { |filter| filter.is_a?(Regexp) ? filter.match(key) : filter.to_s == key.to_s }
33
- MASK
34
- else
35
- value
36
- end
23
+ def apply_to_string str, opts = {}
24
+ sep = opts[:separator] || '&'.freeze
25
+ kv_sep = opts[:kv_separator] || '='.freeze
26
+
27
+ str.split(sep).map do |kv|
28
+ key, value = kv.split(kv_sep)
29
+ [key, kv_sep, sanitize(key, value)].join
30
+ end.join(sep)
37
31
  end
38
32
 
39
- def process_event_hash(data)
40
- return data unless data.has_key? 'http'
41
- if data['http'].has_key? 'data'
42
- data['http']['data'] = process_hash(data['http']['data'])
33
+ def apply_to_hash hsh
34
+ hsh.inject({}) do |filtered, kv|
35
+ key, value = kv
36
+ filtered[key] = sanitize(key, value)
37
+ filtered
43
38
  end
44
- if data['http'].has_key? 'query_string'
45
- data['http']['query_string'] = process_string(data['http']['query_string'], '&')
46
- end
47
- if data['http'].has_key? 'cookies'
48
- data['http']['cookies'] = process_string(data['http']['cookies'], ';')
39
+ end
40
+
41
+ def sanitize key, value
42
+ return value unless value.is_a?(String)
43
+
44
+ if should_filter?(key)
45
+ return MASK
49
46
  end
50
- data
47
+
48
+ value
51
49
  end
52
50
 
53
- def process_hash(data)
54
- apply(data) do |key, value|
55
- sanitize(key, value)
51
+ private
52
+
53
+ def should_filter? key
54
+ @params.any? do |param|
55
+ case param
56
+ when String
57
+ key.to_s == param.to_s
58
+ when Regexp
59
+ param.match(key)
60
+ end
56
61
  end
57
62
  end
58
63
 
59
- def process_string(str, separator='&')
60
- str.split(separator).map { |s| s.split('=') }.map { |a| a[0]+'='+sanitize(a[0], a[1]) }.join(separator)
64
+ def rails_filters
65
+ if defined?(::Rails) && Rails.application
66
+ if filters = ::Rails.application.config.filter_parameters
67
+ filters.any? ? filters : nil
68
+ end
69
+ end
61
70
  end
71
+
62
72
  end
63
73
  end
@@ -0,0 +1,141 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+
5
+ module Opbeat
6
+ # @api private
7
+ class HttpClient
8
+ include Logging
9
+
10
+ USER_AGENT = "opbeat-ruby/#{Opbeat::VERSION}".freeze
11
+
12
+ attr_reader :state
13
+ attr_reader :adapter
14
+
15
+ def initialize(config)
16
+ @config = config
17
+ @adapter = HTTPAdapter.new(config)
18
+ @state = ClientState.new config
19
+ end
20
+
21
+ attr_reader :config
22
+
23
+ def post(resource, body)
24
+ path = abs_path(resource)
25
+ debug "POST #{resource}"
26
+
27
+ unless state.should_try?
28
+ info "Temporarily skipping sending to Opbeat due to previous failure."
29
+ return
30
+ end
31
+
32
+ if body.is_a?(Hash) || body.is_a?(Array)
33
+ body = JSON.dump(body)
34
+ end
35
+
36
+ request = adapter.post path do |req|
37
+ req['Authorization'] = auth_header
38
+ req['Content-Type'] = 'application/json'.freeze
39
+ req['Content-Length'] = body.bytesize.to_s
40
+ req['User-Agent'] = USER_AGENT
41
+ req.body = body
42
+ end
43
+
44
+ begin
45
+ response = adapter.perform_request request
46
+ unless response.code.to_i.between?(200, 299)
47
+ raise Error.new("Error from Opbeat server (#{response.code}): #{response.body}")
48
+ end
49
+ rescue
50
+ debug { JSON.parse(body).inspect }
51
+ @state.fail!
52
+ raise
53
+ end
54
+
55
+ @state.success!
56
+
57
+ response
58
+ end
59
+
60
+ private
61
+
62
+ def auth_header
63
+ "Bearer #{@config.secret_token}"
64
+ end
65
+
66
+ def abs_path path
67
+ "/api/v1/organizations/#{@config.organization_id}" +
68
+ "/apps/#{@config.app_id}#{path}"
69
+ end
70
+
71
+ def encode(event)
72
+ event_hash = @filter.process_event_hash(event.to_hash)
73
+ event_hash.to_json
74
+ end
75
+
76
+ class HTTPAdapter
77
+ def initialize conf
78
+ @config = conf
79
+ end
80
+
81
+ def post path
82
+ req = Net::HTTP::Post.new path
83
+ yield req if block_given?
84
+ req
85
+ end
86
+
87
+ def perform_request req
88
+ http.start do |http|
89
+ http.request req
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def http
96
+ return @http if @http
97
+
98
+ http = Net::HTTP.new server_uri.host, server_uri.port
99
+ http.use_ssl = @config.use_ssl
100
+ http.read_timeout = @config.timeout
101
+ http.open_timeout = @config.open_timeout
102
+
103
+ @http = http
104
+ end
105
+
106
+ def server_uri
107
+ @uri ||= URI(@config.server)
108
+ end
109
+ end
110
+
111
+ class ClientState
112
+ def initialize(config)
113
+ @config = config
114
+ @retry_number = 0
115
+ @last_check = Time.now.utc
116
+ end
117
+
118
+ def should_try?
119
+ return true if @status == :online
120
+
121
+ interval = ([@retry_number, 6].min() ** 2) * @config.backoff_multiplier
122
+ return true if Time.now.utc - @last_check > interval
123
+
124
+ false
125
+ end
126
+
127
+ def fail!
128
+ @status = :error
129
+ @retry_number += 1
130
+ @last_check = Time.now.utc
131
+ end
132
+
133
+ def success!
134
+ @status = :online
135
+ @retry_number = 0
136
+ @last_check = nil
137
+ end
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,83 @@
1
+ require 'opbeat/util/constantize'
2
+
3
+ module Opbeat
4
+ # @api private
5
+ module Injections
6
+ class Registration
7
+ def initialize const_name, require_paths, injector
8
+ @const_name = const_name
9
+ @require_paths = Array(require_paths)
10
+ @injector = injector
11
+ end
12
+
13
+ attr_reader :const_name, :require_paths, :injector
14
+
15
+ def install
16
+ injector.install
17
+ end
18
+ end
19
+
20
+ def self.require_hooks
21
+ @require_hooks ||= {}
22
+ end
23
+
24
+ def self.installed
25
+ @installed ||= {}
26
+ end
27
+
28
+ def self.register(*args)
29
+ registration = Registration.new(*args)
30
+
31
+ if const_defined?(registration.const_name)
32
+ installed[registration.const_name] = registration
33
+ registration.install
34
+ else
35
+ register_require_hook registration
36
+ end
37
+ end
38
+
39
+ def self.register_require_hook registration
40
+ registration.require_paths.each do |p|
41
+ require_hooks[p] = registration
42
+ end
43
+ end
44
+
45
+ def self.hook_into name
46
+ return unless registration = lookup(name)
47
+
48
+ if const_defined?(registration.const_name)
49
+ installed[registration.const_name] = registration
50
+ registration.install
51
+
52
+ registration.require_paths.each do |p|
53
+ require_hooks.delete p
54
+ end
55
+ end
56
+ end
57
+
58
+ def self.lookup require_path
59
+ require_hooks[require_path]
60
+ end
61
+
62
+ def self.const_defined? const_name
63
+ const = Util.constantize(const_name) rescue nil
64
+ !!const
65
+ end
66
+ end
67
+ end
68
+
69
+ # @api private
70
+ module ::Kernel
71
+ alias require_without_op require
72
+
73
+ def require name
74
+ res = require_without_op name
75
+
76
+ begin
77
+ Opbeat::Injections.hook_into name
78
+ rescue Exception
79
+ end
80
+
81
+ res
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ module Opbeat
2
+ module Injections
3
+ module JSON
4
+ class Injector
5
+ def install
6
+ ::JSON.class_eval do
7
+ include TraceHelpers
8
+
9
+ trace_class_method :parse, 'JSON#parse', 'json.parse'
10
+ trace_class_method :parse!, 'JSON#parse!', 'json.parse'
11
+ trace_class_method :generate, 'JSON#generate', 'json.generate'
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ register 'JSON', 'json', JSON::Injector.new
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ module Opbeat
2
+ module Injections
3
+ module NetHTTP
4
+ class Injector
5
+ def install
6
+ Net::HTTP.class_eval do
7
+ alias request_without_opb request
8
+
9
+ def request req, body = nil, &block
10
+ unless Opbeat.started?
11
+ return request_without_opb req, body, &block
12
+ end
13
+
14
+ host, port = req['host'] && req['host'].split(':')
15
+ method = req.method
16
+ path = req.path
17
+ scheme = use_ssl? ? 'https' : 'http'
18
+
19
+ # inside a session
20
+ host ||= self.address
21
+ port ||= self.port
22
+
23
+ extra = {
24
+ scheme: scheme,
25
+ port: port,
26
+ path: path
27
+ }
28
+
29
+ signature = "#{method} #{host}".freeze
30
+ kind = "ext.net_http.#{method}".freeze
31
+
32
+ Opbeat.trace signature, kind, extra do
33
+ request_without_opb(req, body, &block)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ register 'Net::HTTP', 'net/http', NetHTTP::Injector.new
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module Opbeat
2
+ module Injections
3
+ module Redis
4
+ class Injector
5
+ def install
6
+ ::Redis::Client.class_eval do
7
+ alias call_without_opbeat call
8
+
9
+ def call(command, &block)
10
+ signature = command[0]
11
+
12
+ Opbeat.trace signature.to_s, 'cache.redis'.freeze do
13
+ call_without_opbeat(command, &block)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ register 'Redis', 'redis', Redis::Injector.new
22
+ end
23
+ end