contrast-exceptional 0.0.1 → 0.0.6

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.
@@ -0,0 +1,6 @@
1
+ 0.0.6 - Added a better 'safe' to_json method which works more effectively for rails integration avoiding cyclic reference and un-jsonable data
2
+ 0.0.5 - Fixed a bug in the plugin where request.env was being modified directly. dup to solve issues.
3
+ 0.0.4 - Restructured the plugin to extract all different concerns into self contained modules, will give a better base to extend. Also plays nicer with Rdoc + singleton classes
4
+ 0.0.3 - Dropped background queing of exceptions, send them all directly. Fixed issues with session/environment data. Working with rails 2.3 (some hacks needed to get there, need to refactor.)
5
+ 0.0.2 - Fix a bug introduced with nil sessions
6
+ 0.0.1 - First Version
data/Manifest CHANGED
@@ -3,18 +3,24 @@ exceptional.yml
3
3
  History.txt
4
4
  init.rb
5
5
  install.rb
6
- lib/exceptional/agent/worker.rb
7
- lib/exceptional/deployed_environment.rb
6
+ lib/exceptional/api.rb
7
+ lib/exceptional/bootstrap.rb
8
+ lib/exceptional/config.rb
8
9
  lib/exceptional/exception_data.rb
9
10
  lib/exceptional/integration/rails.rb
10
- lib/exceptional/rails.rb
11
+ lib/exceptional/log.rb
12
+ lib/exceptional/remote.rb
11
13
  lib/exceptional/version.rb
12
14
  lib/exceptional.rb
13
15
  Manifest
14
16
  Rakefile
15
17
  README
16
- spec/deployed_environment_spec.rb
18
+ spec/api_spec.rb
19
+ spec/bootstrap_spec.rb
20
+ spec/config_spec.rb
17
21
  spec/exception_data_spec.rb
22
+ spec/exceptional_rescue_from_spec.rb
18
23
  spec/exceptional_spec.rb
24
+ spec/log_spec.rb
25
+ spec/remote_spec.rb
19
26
  spec/spec_helper.rb
20
- spec/worker_spec.rb
data/README CHANGED
@@ -4,10 +4,10 @@ This plugin posts exception data to Exceptional (http://getexceptional.com). Dat
4
4
 
5
5
  = Exceptional
6
6
 
7
- Exceptional (http://getexceptional.com) is a web-based exception tracking and managing system for Ruby on Rails folks and their apps. It's currently in early, private beta.
7
+ Exceptional (http://getexceptional.com) is a web-based exception tracking and managing system for Ruby on Rails folks and their apps.
8
8
 
9
9
  = Installation
10
10
 
11
- For installation instructions, follow the steps displayed after adding a new app to your account. Please send any questions or comments to hello@getexceptional.com.
11
+ For installation instructions, follow the steps displayed after adding a new app to your account. Please send any questions or comments to feedback@getexceptional.com.
12
12
 
13
- Copyright © 2008 Contrast.
13
+ Copyright © 2008, 2009 Contrast.
data/Rakefile CHANGED
@@ -1,13 +1,13 @@
1
1
  begin
2
2
  require 'echoe'
3
3
 
4
- Echoe.new('exceptional', '0.0.1') do |p|
4
+ Echoe.new('exceptional', '0.0.6') do |p|
5
5
  p.rubyforge_name = 'exceptional'
6
6
  p.summary = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
7
7
  p.description = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
8
8
  p.url = "http://getexceptional.com/"
9
- p.author = ['David Rice']
10
- p.email = "david@contrast.ie"
9
+ p.author = ["Contrast"]
10
+ p.email = "hello@contrast.ie"
11
11
  p.dependencies = ["json"]
12
12
  end
13
13
 
@@ -24,17 +24,16 @@ begin
24
24
  t.spec_files = FileList['spec/**/*_spec.rb']
25
25
  t.spec_opts = ['--color']
26
26
  end
27
-
27
+
28
28
  task :test do
29
29
  Rake::Task['spec'].invoke
30
30
  end
31
-
31
+
32
32
  Spec::Rake::SpecTask.new("coverage") do |t|
33
33
  t.spec_files = FileList['spec/**/*_spec.rb']
34
34
  t.spec_opts = ['--color']
35
35
  t.rcov = true
36
36
  t.rcov_opts = ['--exclude', '^spec,/gems/']
37
37
  end
38
-
39
38
 
40
39
  end
@@ -1,34 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  Gem::Specification.new do |s|
2
- s.name = "exceptional"
3
- s.version = "0.0.1"
4
- s.date = "2008-10-13"
5
- s.summary = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
6
- s.email = "david@getexceptional.com"
7
- s.homepage = "http://github.com/contrast/exceptional"
8
- s.description = "Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)"
4
+ s.name = %q{exceptional}
5
+ s.version = "0.0.6"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Contrast"]
9
+ s.date = %q{2009-07-09}
10
+ s.description = %q{Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)}
11
+ s.email = %q{hello@contrast.ie}
12
+ s.extra_rdoc_files = ["lib/exceptional/api.rb", "lib/exceptional/bootstrap.rb", "lib/exceptional/config.rb", "lib/exceptional/exception_data.rb", "lib/exceptional/integration/rails.rb", "lib/exceptional/log.rb", "lib/exceptional/remote.rb", "lib/exceptional/version.rb", "lib/exceptional.rb", "README"]
13
+ s.files = ["exceptional.gemspec", "exceptional.yml", "History.txt", "init.rb", "install.rb", "lib/exceptional/api.rb", "lib/exceptional/bootstrap.rb", "lib/exceptional/config.rb", "lib/exceptional/exception_data.rb", "lib/exceptional/integration/rails.rb", "lib/exceptional/log.rb", "lib/exceptional/remote.rb", "lib/exceptional/version.rb", "lib/exceptional.rb", "Manifest", "Rakefile", "README", "spec/api_spec.rb", "spec/bootstrap_spec.rb", "spec/config_spec.rb", "spec/exception_data_spec.rb", "spec/exceptional_rescue_from_spec.rb", "spec/exceptional_spec.rb", "spec/log_spec.rb", "spec/remote_spec.rb", "spec/spec_helper.rb"]
9
14
  s.has_rdoc = true
10
- s.authors = ["David Rice", "Paul Campbell"]
11
- s.files = ["History.txt",
12
- "Manifest",
13
- "README",
14
- "Rakefile",
15
- "exceptional.gemspec",
16
- "exceptional.yml",
17
- "init.rb",
18
- "install.rb",
19
- "lib/exceptional/agent/worker.rb",
20
- "lib/exceptional/deployed_environment.rb",
21
- "lib/exceptional/exception_data.rb",
22
- "lib/exceptional/integration/rails.rb",
23
- "lib/exceptional/rails.rb",
24
- "lib/exceptional/version.rb",
25
- "lib/exceptional.rb"]
26
- s.test_files = ["spec/deployed_environment_spec.rb",
27
- "spec/exception_data_spec.rb",
28
- "spec/exceptional_spec.rb",
29
- "spec/spec_helper.rb",
30
- "spec/worker_spec.rb"]
31
- s.rdoc_options = ["--main", "README"]
32
- s.extra_rdoc_files = ["History.txt", "Manifest", "README"]
33
- s.add_dependency("json", ["> 0.0.0"])
34
- end
15
+ s.homepage = %q{http://getexceptional.com/}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Exceptional", "--main", "README"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{exceptional}
19
+ s.rubygems_version = %q{1.3.1}
20
+ s.summary = %q{Exceptional is the core Ruby library for communicating with http://getexceptional.com (hosted error tracking service)}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<json>, [">= 0"])
28
+ else
29
+ s.add_dependency(%q<json>, [">= 0"])
30
+ end
31
+ else
32
+ s.add_dependency(%q<json>, [">= 0"])
33
+ end
34
+ end
data/init.rb CHANGED
@@ -1,20 +1,2 @@
1
1
  require 'exceptional'
2
-
3
- def to_stderr(s)
4
- STDERR.puts "** [Exceptional] " + s
5
- end
6
-
7
- config_file = File.join(RAILS_ROOT,"/config/exceptional.yml")
8
-
9
- begin
10
- Exceptional.application_root = RAILS_ROOT
11
- Exceptional.environment = RAILS_ENV
12
-
13
- Exceptional.load_config(config_file)
14
- if Exceptional.enabled?
15
- Exceptional::Rails.init
16
- end
17
- rescue Exception => e
18
- to_stderr e
19
- to_stderr "Plugin disabled."
20
- end
2
+ Exceptional.bootstrap(RAILS_ENV, RAILS_ROOT)
@@ -1,231 +1,20 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
- require 'exceptional/rails'
3
- require 'exceptional/deployed_environment'
4
- require 'exceptional/agent/worker'
2
+
5
3
  require 'exceptional/exception_data'
6
4
  require 'exceptional/version'
7
- require 'rubygems'
8
-
9
- require 'zlib'
10
- require 'cgi'
11
- require 'net/http'
12
- require 'logger'
13
- require 'yaml'
14
- require 'json' unless defined? Rails
15
- # Hack to force Rails version prior to 2.0 to use quoted JSON as per the JSON standard... (TODO: could be cleaner!)
16
- ActiveSupport::JSON.unquote_hash_key_identifiers = false if (defined?(ActiveSupport::JSON) && ActiveSupport::JSON.respond_to?(:unquote_hash_key_identifiers))
5
+ require 'exceptional/log'
6
+ require 'exceptional/config'
7
+ require 'exceptional/remote'
8
+ require 'exceptional/api'
9
+ require 'exceptional/bootstrap'
17
10
 
18
11
  module Exceptional
19
- class LicenseException < StandardError; end
20
- class ConfigurationException < StandardError; end
21
-
22
- ::PROTOCOL_VERSION = 3
23
- # Defaults for configuration variables
24
- ::REMOTE_HOST = "getexceptional.com"
25
- ::REMOTE_PORT = 80
26
- ::REMOTE_SSL_PORT = 443
27
- ::SSL = false
28
- ::LOG_LEVEL = 'info'
29
- ::LOG_PATH = nil
30
- ::WORKER_TIMEOUT = 10 # seconds
31
- ::MODE = :direct
32
-
33
- class << self
34
- attr_accessor :api_key, :log, :deployed_environment, :log_path, :worker,
35
- :worker_thread, :environment, :application_root
36
- attr_writer :remote_host, :remote_port, :ssl_enabled, :log_level
37
-
38
- # rescue any exceptions within the given block,
39
- # send it to exceptional,
40
- # then raise
41
- def rescue(&block)
42
- begin
43
- block.call
44
- rescue Exception => e
45
- self.catch(e)
46
- raise(e)
47
- end
48
- end
49
-
50
- # parse an exception into an ExceptionData object
51
- def parse(exception)
52
- exception_data = ExceptionData.new
53
- exception_data.exception_backtrace = exception.backtrace
54
- exception_data.exception_message = exception.message
55
- exception_data.exception_class = exception.class.to_s
56
- exception_data
57
- end
58
-
59
- # authenticate with getexceptional.com
60
- # returns true if the configured api_key is registered and can send data
61
- # otherwise false
62
- def authenticate
63
- begin
64
- # TODO No data required to authenticate, send a nil string? hacky
65
- # TODO should retry if a http connection failed
66
- return @authenticated if @authenticated
67
- authenticated = call_remote(:authenticate, "")
68
- @authenticated = authenticated =~ /true/
69
- rescue
70
- @authenticated = false
71
- ensure
72
- return @authenticated
73
- end
74
- end
75
-
76
- # post the given exception data to getexceptional.com
77
- def post(exception_data)
78
- call_remote(:errors, exception_data.to_json)
79
- end
80
-
81
- # given a regular ruby Exception class, will parse into an ExceptionData
82
- # object and post to getexceptional.com
83
- def catch(exception)
84
- exception_data = parse(exception)
85
- exception_data.controller_name = File.basename($0)
86
- post(exception_data)
87
- end
88
-
89
- # used with Rails, takes an exception, controller, request and parameters
90
- # creates an ExceptionData object
91
- # if Exceptional is running in :direct mode, will post to getexceptional.com
92
- # if Exceptional is running in :queue mode, the data will be queued and posted later
93
- def handle(exception, controller, request, params)
94
- log! "Handling #{exception.message}", 'info'
95
- e = parse(exception)
96
- # Additional data for Rails Exceptions
97
- e.framework = "rails"
98
- e.controller_name = controller.controller_name
99
- e.action_name = controller.action_name
100
- e.application_root = self.application_root
101
- e.occurred_at = Time.now.strftime("%Y%m%d %H:%M:%S %Z")
102
- e.environment = request.env.to_hash
103
- e.url = "#{request.protocol}#{request.host}#{request.request_uri}"
104
- # Need to remove rack data from environment hash
105
- safe_environment = request.env.to_hash
106
- safe_environment.delete_if { |k,v| k =~ /rack/ }
107
- e.environment = safe_environment
108
-
109
- safe_session = {}
110
- request.session.instance_variables.each do |v|
111
- next if v =~ /cgi/
112
- next if v =~ /db/
113
- # remove prepended @'s
114
- var = v.sub("@","")
115
- safe_session[var] = request.session.instance_variable_get(v)
116
- end
117
-
118
- e.session = safe_session
119
- e.parameters = params.to_hash
120
12
 
121
- if mode == :queue
122
- worker.add_exception(e)
123
- else # :direct mode
124
- begin
125
- post e
126
- rescue Exception => exception
127
- log! "Error posting data to Exceptional."
128
- log! exception.message
129
- log! exception.backtace.join("\n"), 'debug'
130
- end
131
- end
132
- end
133
-
134
- # TODO these configuration methods & defaults should have their own class
135
- def remote_host
136
- @remote_host || ::REMOTE_HOST
137
- end
138
-
139
- def remote_port
140
- @remote_port || default_port
141
- end
142
-
143
- def log_level
144
- @log_level || ::LOG_LEVEL
145
- end
146
-
147
- def mode
148
- deployed_environment ? deployed_environment.determine_mode : ::MODE
149
- end
150
-
151
- def default_port
152
- ssl_enabled? ? ::REMOTE_SSL_PORT : ::REMOTE_PORT
153
- end
154
-
155
- def ssl_enabled?
156
- @ssl_enabled || ::SSL
157
- end
158
-
159
- def enabled?
160
- @enabled
161
- end
162
-
163
- def log!(msg, level = 'info')
164
- to_stderr msg
165
- log.send level, msg if log
166
- end
167
-
168
- def to_stderr(msg)
169
- if deployed_environment && deployed_environment.server != :unknown
170
- STDERR.puts "** [Exceptional] " + msg
171
- end
172
- end
173
-
174
- def log_config_info
175
- log! "API Key: #{api_key}", 'debug'
176
- log! "Deployed Environment: #{deployed_environment.to_s}", 'debug'
177
- log! "Remote Host: #{remote_host}:#{remote_port}", 'debug'
178
- log! "Mode: #{mode}", 'debug'
179
- log! "Log level: #{log_level}", 'debug'
180
- log! "Log path: #{log_path}", 'debug'
181
- end
182
-
183
- def load_config(file)
184
- begin
185
- config = YAML::load(File.open(file))[self.environment]
186
- @api_key = config['api-key'] unless config['api-key'].nil?
187
- @ssl_enabled = config['ssl'] unless config['ssl'].nil?
188
- @log_level = config['log-level'] unless config['log-level'].nil?
189
- @enabled = config['enabled'] unless config['enabled'].nil?
190
- @remote_port = config['remote-port'].to_i unless config['remote-port'].nil?
191
- @remote_host = config['remote-host'] unless config['remote-host'].nil?
192
- rescue Exception => e
193
- raise ConfigurationException.new("Unable to load configuration file:#{file} for environment:#{environment}")
194
- end
195
- end
196
-
197
- protected
198
-
199
- def valid_api_key?
200
- @api_key && @api_key.length == 40
201
- end
202
-
203
-
204
- def call_remote(method, data)
205
- if @api_key.nil?
206
- raise LicenseException.new("API Key must be configured")
207
- end
208
-
209
- http = Net::HTTP.new(remote_host, remote_port)
210
- uri = "/#{method.to_s}?&api_key=#{@api_key}&protocol_version=#{::PROTOCOL_VERSION}"
211
- headers = { 'Content-Type' => 'application/x-gzip', 'Accept' => 'application/x-gzip' }
212
- compressed_data = CGI::escape(Zlib::Deflate.deflate(data, Zlib::BEST_SPEED))
213
- response = http.start do |http|
214
- http.post(uri, compressed_data, headers)
215
- end
216
-
217
- if response.kind_of? Net::HTTPSuccess
218
- return response.body
219
- else
220
- raise Exception.new("#{response.code}: #{response.message}")
221
- end
222
-
223
- rescue Exception => e
224
- log! "Error contacting Exceptional: #{e}", 'info'
225
- log! e.backtrace.join("\n"), 'debug'
226
- raise e
227
- end
228
-
13
+ class << self
14
+ include Exceptional::Config
15
+ include Exceptional::Api
16
+ include Exceptional::Remote
17
+ include Exceptional::Log
18
+ include Exceptional::Bootstrap
229
19
  end
230
-
231
20
  end
@@ -0,0 +1,108 @@
1
+ require 'json' unless defined? Rails
2
+
3
+
4
+ module Exceptional
5
+
6
+ module Api
7
+ # parse an exception into an ExceptionData object
8
+ def parse(exception)
9
+ exception_data = ExceptionData.new
10
+ exception_data.exception_backtrace = exception.backtrace
11
+ exception_data.exception_message = exception.message
12
+ exception_data.exception_class = exception.class.to_s
13
+ exception_data
14
+ end
15
+
16
+ # post the given exception data to getexceptional.com
17
+ def post(exception_data)
18
+ hash = exception_data.to_hash
19
+ if hash[:session]
20
+ hash[:session].delete("initialization_options")
21
+ hash[:session].delete("request")
22
+ end
23
+
24
+ Exceptional.post_exception(hash.to_json)
25
+ end
26
+
27
+ # used with Rails, takes an exception, controller, request and parameters
28
+ # creates an ExceptionData object
29
+ def handle(exception, controller, request, params)
30
+ Exceptional.log! "Handling #{exception.message}", 'info'
31
+ begin
32
+ e = parse(exception)
33
+ # Additional data for Rails Exceptions
34
+ e.framework = "rails"
35
+ e.controller_name = controller.controller_name
36
+ e.action_name = controller.action_name
37
+ e.application_root = Exceptional.application_root
38
+ e.occurred_at = Time.now.strftime("%Y%m%d %H:%M:%S %Z")
39
+ e.environment = request.env.to_hash
40
+ e.url = "#{request.protocol}#{request.host}#{request.request_uri}"
41
+ e.environment = safe_environment(request)
42
+ e.session = safe_session(request.session)
43
+ e.parameters = sanitize_hash(params.to_hash)
44
+
45
+ post(e)
46
+ rescue Exception => exception
47
+ Exceptional.log! "Error preparing exception data."
48
+ Exceptional.log! exception.message
49
+ Exceptional.log! exception.backtrace.join("\n"), 'debug'
50
+ end
51
+ end
52
+
53
+ # rescue any exceptions within the given block,
54
+ # send it to exceptional,
55
+ # then raise
56
+ def rescue(&block)
57
+ begin
58
+ block.call
59
+ rescue Exception => e
60
+ self.catch(e)
61
+ raise(e)
62
+ end
63
+ end
64
+
65
+ def catch(exception)
66
+ exception_data = parse(exception)
67
+ exception_data.controller_name = File.basename($0)
68
+ post(exception_data)
69
+ end
70
+
71
+ protected
72
+
73
+ def safe_environment(request)
74
+ safe_environment = request.env.dup.to_hash
75
+ # From Rails 2.3 these objects that cause a circular reference error on .to_json need removed
76
+ # TODO potentially remove this case, should be covered by sanitize_hash
77
+ safe_environment.delete_if { |k,v| k =~ /rack/ || k =~ /action_controller/ || k == "_"}
78
+ # needed to add a filter for the hash for "_", causing invalid xml.
79
+ sanitize_hash(safe_environment)
80
+ end
81
+
82
+ def safe_session(session)
83
+ result = {}
84
+ session.instance_variables.each do |v|
85
+ next if v =~ /cgi/ || v =~ /db/ || v =~ /env/
86
+ var = v.sub("@","") # remove prepended @'s
87
+ result[var] = session.instance_variable_get(v)
88
+ end
89
+ sanitize_hash(result)
90
+ end
91
+
92
+ private
93
+
94
+ def sanitize_hash(hash)
95
+ return {} if hash.nil?
96
+ hash.reject { |key, val| !ensure_json_able(val) }
97
+ end
98
+
99
+ def ensure_json_able(value)
100
+ begin
101
+ value.to_json
102
+ true && value.instance_values.all? { |e| ensure_json_able(e)}
103
+ rescue Exception => e
104
+ false
105
+ end
106
+ end
107
+ end
108
+ end