sentry-raven 0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sentry-raven might be problematic. Click here for more details.

data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Raven-Ruby
2
+
3
+ [![Build Status](https://secure.travis-ci.org/coderanger/raven-ruby.png?branch=master)](http://travis-ci.org/coderanger/raven-ruby)
4
+
5
+ A client and integration layer for the [Sentry](https://github.com/dcramer/sentry) error reporting API.
6
+
7
+ This library is still forming, so if you are looking to just use it, please check back in a few weeks.
8
+
9
+ ## Installation
10
+
11
+ Add the following to your `Gemfile`:
12
+
13
+ ```ruby
14
+ gem "raven", :git => "https://github.com/coderanger/raven-ruby.git"
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Rails 3
20
+
21
+ Add a `config/initializers/raven.rb` containing:
22
+
23
+ ```ruby
24
+ require 'raven'
25
+
26
+ Raven.configure do |config|
27
+ config.dsn = 'http://public:secret@example.com/project-id'
28
+ end
29
+ ```
30
+
31
+ ### Rails 2
32
+
33
+ No support for Rails 2 yet.
34
+
35
+ ### Other Rack Servers
36
+
37
+ Basic RackUp file.
38
+
39
+ ```ruby
40
+ require 'raven'
41
+
42
+ Raven.configure do |config|
43
+ config.dsn = 'http://public:secret@example.com/project-id'
44
+ end
45
+
46
+ use Raven::Rack
47
+ ```
48
+
49
+ ### Other Ruby
50
+
51
+ ```ruby
52
+ require 'raven'
53
+
54
+ Raven.configure do |config|
55
+ config.dsn = 'http://public:secret@example.com/project-id'
56
+ end
57
+
58
+ Raven.capture # Global style
59
+
60
+ Raven.capture do # Block style
61
+ ...
62
+ end
63
+ ```
64
+
65
+ ## Testing
66
+
67
+ ```bash
68
+ $ bundle install
69
+ $ rake spec
70
+ ```
data/lib/raven.rb ADDED
@@ -0,0 +1,92 @@
1
+ require 'raven/version'
2
+ require 'raven/configuration'
3
+ require 'raven/logger'
4
+ require 'raven/client'
5
+ require 'raven/event'
6
+ require 'raven/rack'
7
+ require 'raven/interfaces/message'
8
+ require 'raven/interfaces/exception'
9
+ require 'raven/interfaces/stack_trace'
10
+ require 'raven/interfaces/http'
11
+
12
+ require 'raven/railtie' if defined?(Rails::Railtie)
13
+
14
+ module Raven
15
+ class << self
16
+ # The client object is responsible for delivering formatted data to the Sentry server.
17
+ # Must respond to #send. See Raven::Client.
18
+ attr_accessor :client
19
+
20
+ # A Raven configuration object. Must act like a hash and return sensible
21
+ # values for all Raven configuration options. See Raven::Configuration.
22
+ attr_writer :configuration
23
+
24
+ def logger
25
+ @logger ||= Logger.new
26
+ end
27
+
28
+ # Tell the log that the client is good to go
29
+ def report_ready
30
+ self.logger.info "Raven #{VERSION} ready to catch errors"
31
+ end
32
+
33
+ # The configuration object.
34
+ # @see Raven.configure
35
+ def configuration
36
+ @configuration ||= Configuration.new
37
+ end
38
+
39
+ # Call this method to modify defaults in your initializers.
40
+ #
41
+ # @example
42
+ # Raven.configure do |config|
43
+ # config.server = 'http://...'
44
+ # end
45
+ def configure(silent = false)
46
+ yield(configuration)
47
+ self.client = Client.new(configuration)
48
+ report_ready unless silent
49
+ self.client
50
+ end
51
+
52
+ # Send an event to the configured Sentry server
53
+ #
54
+ # @example
55
+ # evt = Raven::Event.new(:message => "An error")
56
+ # Raven.send(evt)
57
+ def send(evt)
58
+ @client.send(evt) if @client
59
+ end
60
+
61
+ # Capture and process any exceptions from the given block, or globally if
62
+ # no block is given
63
+ #
64
+ # @example
65
+ # Raven.capture do
66
+ # MyApp.run
67
+ # end
68
+ def capture(&block)
69
+ if block
70
+ begin
71
+ block.call
72
+ rescue Error => e
73
+ raise # Don't capture Raven errors
74
+ rescue Exception => e
75
+ evt = Event.capture_exception(e)
76
+ send(evt) if evt
77
+ raise
78
+ end
79
+ else
80
+ # Install at_exit hook
81
+ at_exit do
82
+ if $!
83
+ logger.debug "Caught a post-mortem exception: #{$!.inspect}"
84
+ evt = Event.capture_exception($!)
85
+ send(evt) if evt
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,69 @@
1
+ require 'openssl'
2
+ require 'uri'
3
+ require 'yajl'
4
+ require 'faraday'
5
+
6
+ require 'raven/version'
7
+ require 'raven/error'
8
+
9
+ module Raven
10
+
11
+ class Client
12
+
13
+ PROTOCOL_VERSION = '2.0'
14
+ USER_AGENT = "raven-ruby/#{Raven::VERSION}"
15
+ AUTH_HEADER_KEY = 'X-Sentry-Auth'
16
+
17
+ attr_accessor :configuration
18
+
19
+ def initialize(configuration)
20
+ @configuration = configuration
21
+ end
22
+
23
+ def conn
24
+ # Error checking
25
+ raise Error.new('No server specified') unless self.configuration[:server]
26
+ raise Error.new('No public key specified') unless self.configuration[:public_key]
27
+ raise Error.new('No secret key specified') unless self.configuration[:secret_key]
28
+ raise Error.new('No project ID specified') unless self.configuration[:project_id]
29
+
30
+ Raven.logger.debug "Raven client connecting to #{self.configuration[:server]}"
31
+
32
+ @conn ||= Faraday.new(:url => self.configuration[:server]) do |builder|
33
+ builder.adapter Faraday.default_adapter
34
+ end
35
+ end
36
+
37
+
38
+ def generate_signature(timestamp, data)
39
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), self.configuration[:secret_key], "#{timestamp} #{data}")
40
+ end
41
+
42
+ def generate_auth_header(data)
43
+ now = Time.now.to_i.to_s
44
+ fields = {
45
+ 'sentry_version' => PROTOCOL_VERSION,
46
+ 'sentry_client' => USER_AGENT,
47
+ 'sentry_timestamp' => now,
48
+ 'sentry_key' => self.configuration[:public_key],
49
+ 'sentry_signature' => generate_signature(now, data)
50
+ }
51
+ 'Sentry ' + fields.map{|key, value| "#{key}=#{value}"}.join(', ')
52
+ end
53
+
54
+ def send(event)
55
+ # Set the project ID correctly
56
+ event.project = self.configuration[:project_id]
57
+ Raven.logger.debug "Sending event #{event.id} to Sentry"
58
+ response = self.conn.post '/api/store/' do |req|
59
+ req.headers['Content-Type'] = 'application/json'
60
+ req.body = Yajl::Encoder.encode(event.to_hash)
61
+ req.headers[AUTH_HEADER_KEY] = self.generate_auth_header(req.body)
62
+ end
63
+ raise Error.new("Error from Sentry server (#{response.status}): #{response.body}") unless response.status == 200
64
+ response
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,53 @@
1
+ module Raven
2
+ class Configuration
3
+
4
+ # Base URL of the Sentry server
5
+ attr_accessor :server
6
+
7
+ # Public key for authentication with the Sentry server
8
+ attr_accessor :public_key
9
+
10
+ # Secret key for authentication with the Sentry server
11
+ attr_accessor :secret_key
12
+
13
+ # Project ID number to send to the Sentry server
14
+ attr_accessor :project_id
15
+
16
+ # Logger to use internally
17
+ attr_accessor :logger
18
+
19
+ # Number of lines of code context to capture, or nil for none
20
+ attr_accessor :context_lines
21
+
22
+ def initialize
23
+ self.server = ENV['SENTRY_DSN'] if ENV['SENTRY_DSN']
24
+ @context_lines = 3
25
+ end
26
+
27
+ def server=(value)
28
+ uri = URI::parse(value)
29
+ if uri.user
30
+ # DSN-style string
31
+ uri_path = uri.path.split('/')
32
+ @project_id = uri_path.pop
33
+ @server = "#{uri.scheme}://#{uri.host}"
34
+ @server << ":#{uri.port}" unless uri.port == {'http'=>80,'https'=>443}[uri.scheme]
35
+ @server << uri_path.join('/')
36
+ @public_key = uri.user
37
+ @secret_key = uri.password
38
+ else
39
+ @server = value
40
+ end
41
+ end
42
+
43
+ alias_method :dsn=, :server=
44
+
45
+ # Allows config options to be read like a hash
46
+ #
47
+ # @param [Symbol] option Key for a given attribute
48
+ def [](option)
49
+ send(option)
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ module Raven
2
+
3
+ class Error < Exception
4
+ end
5
+
6
+ end
@@ -0,0 +1,154 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'uuidtools'
4
+
5
+ require 'raven/error'
6
+ require 'raven/linecache'
7
+
8
+ module Raven
9
+
10
+ class Event
11
+
12
+ LOG_LEVELS = {
13
+ "debug" => 10,
14
+ "info" => 20,
15
+ "warn" => 30,
16
+ "warning" => 30,
17
+ "error" => 40,
18
+ }
19
+
20
+ BACKTRACE_RE = /^(.+?):(\d+)(?::in `(.+?)')$/
21
+
22
+ attr_reader :id
23
+ attr_accessor :project, :message, :timestamp, :level
24
+ attr_accessor :logger, :culprit, :server_name, :modules, :extra
25
+
26
+ def initialize(options={}, &block)
27
+ @id = options[:id] || UUIDTools::UUID.random_create.hexdigest
28
+
29
+ @message = options[:message]
30
+
31
+ @timestamp = options[:timestamp] || Time.now.utc
32
+
33
+ @level = options[:level] || :error
34
+
35
+ @logger = options[:logger] || 'root'
36
+ @culprit = options[:culprit]
37
+ @server_name = options[:server_name] || Socket.gethostbyname(Socket.gethostname).first
38
+ @modules = options[:modules] || Gem::Specification.each.inject({}){|memo, spec| memo[spec.name] = spec.version; memo}
39
+ @extra = options[:extra]
40
+
41
+ @interfaces = {}
42
+
43
+ block.call(self) if block
44
+
45
+ # Some type coercion
46
+ @timestamp = @timestamp.strftime('%Y-%m-%dT%H:%M:%S') if @timestamp.is_a?(Time)
47
+ @level = LOG_LEVELS[@level.to_s.downcase] if @level.is_a?(String) || @level.is_a?(Symbol)
48
+
49
+ # Basic sanity checking
50
+ raise Error.new('A message is required for all events') unless @message && !@message.empty?
51
+ raise Error.new('A timestamp is required for all events') unless @timestamp
52
+ end
53
+
54
+ def interface(name, value=nil, &block)
55
+ int = Raven::find_interface(name)
56
+ raise Error.new("Unknown interface: #{name}") unless int
57
+ @interfaces[int.name] = int.new(value, &block) if value || block
58
+ @interfaces[int.name]
59
+ end
60
+
61
+ def [](key)
62
+ interface(key)
63
+ end
64
+
65
+ def []=(key, value)
66
+ interface(key, value)
67
+ end
68
+
69
+ def to_hash
70
+ data = {
71
+ 'event_id' => self.id,
72
+ 'message' => self.message,
73
+ 'timestamp' => self.timestamp,
74
+ 'level' => self.level,
75
+ 'project' => self.project,
76
+ 'logger' => self.logger,
77
+ }
78
+ data['culprit'] = self.culprit if self.culprit
79
+ data['server_name'] = self.server_name if self.server_name
80
+ data['modules'] = self.modules if self.modules
81
+ data['extra'] = self.extra if self.extra
82
+ @interfaces.each_pair do |name, int_data|
83
+ data[name] = int_data.to_hash
84
+ end
85
+ data
86
+ end
87
+
88
+ def self.capture_exception(exc, configuration=nil, &block)
89
+ configuration ||= Raven.configuration
90
+ if exc.is_a?(Raven::Error)
91
+ # Try to prevent error reporting loops
92
+ Raven.logger.info "Refusing to capture Raven error: #{exc.inspect}"
93
+ return nil
94
+ end
95
+ self.new do |evt|
96
+ evt.message = exc.message
97
+ evt.level = :error
98
+ evt.interface :exception do |int|
99
+ int.type = exc.class.to_s
100
+ int.value = exc.message
101
+ class_parts = exc.class.to_s.split('::')
102
+ class_parts.pop
103
+ int.module = class_parts.join('::')
104
+ end
105
+ evt.interface :stack_trace do |int|
106
+ int.frames = exc.backtrace.reverse.map do |trace_line|
107
+ md = BACKTRACE_RE.match(trace_line)
108
+ raise Error.new("Unable to parse backtrace line: #{trace_line.inspect}") unless md
109
+ int.frame do |frame|
110
+ frame.abs_path = md[1]
111
+ frame.lineno = md[2].to_i
112
+ frame.function = md[3] if md[3]
113
+ lib_path = $:.select{|s| frame.abs_path.start_with?(s)}.sort_by{|s| s.length}.last
114
+ if lib_path
115
+ frame.filename = frame.abs_path[lib_path.chomp(File::SEPARATOR).length+1..frame.abs_path.length]
116
+ else
117
+ frame.filename = frame.abs_path
118
+ end
119
+ if configuration[:context_lines]
120
+ frame.context_line = Raven::LineCache::getline(frame.abs_path, frame.lineno)
121
+ frame.pre_context = (frame.lineno-configuration[:context_lines]..frame.lineno-1).map{|i| Raven::LineCache.getline(frame.abs_path, i)}.select{|line| line}
122
+ frame.post_context = (frame.lineno+1..frame.lineno+configuration[:context_lines]).map{|i| Raven::LineCache.getline(frame.abs_path, i)}.select{|line| line}
123
+ end
124
+ end
125
+ end
126
+ end
127
+ block.call(evt) if block
128
+ end
129
+ end
130
+
131
+ def self.capture_rack_exception(exc, rack_env, configuration=nil, &block)
132
+ configuration ||= Raven.configuration
133
+ capture_exception(exc, configuration) do |evt|
134
+ evt.interface :http do |int|
135
+ int.from_rack(rack_env)
136
+ end
137
+ block.call(evt) if block
138
+ end
139
+ end
140
+
141
+ # For cross-language compat
142
+ class << self
143
+ alias :captionException :capture_exception
144
+ end
145
+
146
+ private
147
+
148
+ # Because linecache can go to hell
149
+ def self._source_lines(path, from, to)
150
+ end
151
+
152
+ end
153
+
154
+ end
@@ -0,0 +1,37 @@
1
+ require 'hashie'
2
+
3
+ module Raven
4
+
5
+ INTERFACES = {}
6
+
7
+ class Interface < Hashie::Dash
8
+ def initialize(attributes={}, &block)
9
+ @check_required = false
10
+ super(attributes)
11
+ block.call(self) if block
12
+ @check_required = true
13
+ assert_required_properties_set!
14
+ end
15
+
16
+ def assert_required_properties_set!
17
+ super if @check_required
18
+ end
19
+
20
+ def self.name(value=nil)
21
+ @interface_name = value if value
22
+ @interface_name
23
+ end
24
+ end
25
+
26
+ def self.register_interface(mapping)
27
+ mapping.each_pair do |key, klass|
28
+ INTERFACES[key.to_s] = klass
29
+ INTERFACES[klass.name] = klass
30
+ end
31
+ end
32
+
33
+ def self.find_interface(name)
34
+ INTERFACES[name.to_s]
35
+ end
36
+
37
+ end
@@ -0,0 +1,16 @@
1
+ require 'raven/interfaces'
2
+
3
+ module Raven
4
+
5
+ class ExceptionInterface < Interface
6
+
7
+ name 'sentry.interfaces.Exception'
8
+ property :type, :required => true
9
+ property :value, :required => true
10
+ property :module
11
+
12
+ end
13
+
14
+ register_interface :exception => ExceptionInterface
15
+
16
+ end
@@ -0,0 +1,46 @@
1
+ require 'raven/interfaces'
2
+
3
+ module Raven
4
+
5
+ class HttpInterface < Interface
6
+
7
+ name 'sentry.interfaces.Http'
8
+ property :url, :required => true
9
+ property :method, :required => true
10
+ property :data
11
+ property :query_string
12
+ property :cookies
13
+ property :headers, :default => {}
14
+ property :env, :default => {}
15
+
16
+ def from_rack(env)
17
+ require 'rack'
18
+ req = ::Rack::Request.new(env)
19
+ self.url = req.url.split('?').first
20
+ self.method = req.request_method
21
+ self.query_string = req.query_string
22
+ env.each_pair do |key, value|
23
+ next unless key.upcase == key # Non-upper case stuff isn't either
24
+ if key.start_with?('HTTP_')
25
+ # Header
26
+ http_key = key[5..key.length-1].split('_').map{|s| s.capitalize}.join('-')
27
+ self.headers[http_key] = value.to_s
28
+ else
29
+ # Environment
30
+ self.env[key] = value.to_s
31
+ end
32
+ end
33
+ self.data = if req.form_data?
34
+ req.POST
35
+ elsif req.body
36
+ data = req.body.read
37
+ req.body.rewind
38
+ data
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ register_interface :http => HttpInterface
45
+
46
+ end
@@ -0,0 +1,15 @@
1
+ require 'raven/interfaces'
2
+
3
+ module Raven
4
+
5
+ class MessageInterface < Interface
6
+
7
+ name 'sentry.interfaces.Message'
8
+ property :message, :required => true
9
+ property :params, :default => []
10
+
11
+ end
12
+
13
+ register_interface :message => MessageInterface
14
+
15
+ end
@@ -0,0 +1,47 @@
1
+ require 'hashie'
2
+
3
+ require 'raven/interfaces'
4
+
5
+ module Raven
6
+
7
+ class StacktraceInterface < Interface
8
+
9
+ name 'sentry.interfaces.Stacktrace'
10
+ property :frames, :default => []
11
+
12
+ def to_hash
13
+ data = super
14
+ data['frames'] = data['frames'].map{|frame| frame.to_hash}
15
+ data
16
+ end
17
+
18
+ def frame(attributes=nil, &block)
19
+ Frame.new(attributes, &block)
20
+ end
21
+
22
+ # Not actually an interface, but I want to use the same style
23
+ class Frame < Interface
24
+ property :abs_path
25
+ property :filename, :required => true
26
+ property :function
27
+ property :vars, :default => {}
28
+ property :pre_context, :default => []
29
+ property :post_context, :default => []
30
+ property :context_line
31
+ property :lineno, :required => true
32
+
33
+ def to_hash
34
+ data = super
35
+ data.delete('vars') unless self.vars && !self.vars.empty?
36
+ data.delete('pre_context') unless self.pre_context && !self.pre_context.empty?
37
+ data.delete('post_context') unless self.post_context && !self.post_context.empty?
38
+ data.delete('context_line') unless self.context_line && !self.context_line.empty?
39
+ data
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ register_interface :stack_trace => StacktraceInterface
46
+
47
+ end
@@ -0,0 +1,25 @@
1
+ # A much simpler source line cacher because linecache sucks at platform compat
2
+
3
+ module Raven
4
+
5
+ class LineCache
6
+ class << self
7
+ CACHE = {}
8
+
9
+ def getlines(path)
10
+ CACHE[path] ||= begin
11
+ IO.readlines(path)
12
+ rescue
13
+ []
14
+ end
15
+ end
16
+
17
+ def getline(path, n)
18
+ return nil if n < 1
19
+ getlines(path)[n-1]
20
+ end
21
+
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,21 @@
1
+ module Raven
2
+ class Logger
3
+ LOG_PREFIX = "** [Raven] "
4
+
5
+ [
6
+ :fatal,
7
+ :error,
8
+ :warn,
9
+ :info,
10
+ :debug,
11
+ ].each do |level|
12
+ define_method level do |*args, &block|
13
+ msg = args[0] # Block-level default args is a 1.9 feature
14
+ msg ||= block.call if block
15
+ logger = Raven.configuration[:logger]
16
+ logger.send(level, "#{LOG_PREFIX}#{msg}") if logger
17
+ end
18
+ end
19
+
20
+ end
21
+ end
data/lib/raven/rack.rb ADDED
@@ -0,0 +1,44 @@
1
+ module Raven
2
+ # Middleware for Rack applications. Any errors raised by the upstream
3
+ # application will be delivered to Sentry and re-raised.
4
+ #
5
+ # Synopsis:
6
+ #
7
+ # require 'rack'
8
+ # require 'raven'
9
+ #
10
+ # Raven.configure do |config|
11
+ # config.server = 'http://my_dsn'
12
+ # end
13
+ #
14
+ # app = Rack::Builder.app do
15
+ # use Raven::Rack
16
+ # run lambda { |env| raise "Rack down" }
17
+ # end
18
+ #
19
+ # Use a standard Raven.configure call to configure your server credentials.
20
+ class Rack
21
+ def initialize(app)
22
+ @app = app
23
+ end
24
+
25
+ def call(env)
26
+ begin
27
+ response = @app.call(env)
28
+ rescue Error => e
29
+ raise # Don't capture Raven errors
30
+ rescue Exception => e
31
+ evt = Event.capture_rack_exception(e, env)
32
+ Raven.send(evt) if evt
33
+ raise
34
+ end
35
+
36
+ if env['rack.exception']
37
+ evt = Event.capture_rack_exception(e, env)
38
+ Raven.send(evt) if evt
39
+ end
40
+
41
+ response
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,17 @@
1
+ module Raven
2
+ module Rails
3
+ module Middleware
4
+ module DebugExceptionsCatcher
5
+ def self.included(base)
6
+ base.send(:alias_method_chain, :render_exception, :airbrake)
7
+ end
8
+
9
+ def render_exception_with_airbrake(env, exception)
10
+ evt = Raven::Event.capture_rack_exception(exception, env)
11
+ Raven.send(evt) if evt
12
+ render_exception_without_airbrake(env, exception)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'raven'
2
+ require 'rails'
3
+
4
+ module Raven
5
+ class Railtie < ::Rails::Railtie
6
+ initializer "raven.use_rack_middleware" do |app|
7
+ app.config.middleware.use "Raven::Rack" unless defined?(::ActionDispatch::DebugExceptions)
8
+ end
9
+
10
+ config.after_initialize do
11
+ Raven.configure(true) do |config|
12
+ config.logger ||= ::Rails.logger
13
+ end
14
+
15
+ if defined?(::ActionDispatch::DebugExceptions)
16
+ require 'raven/rails/middleware/debug_exceptions_catcher'
17
+ ::ActionDispatch::DebugExceptions.send(:include, Raven::Rails::Middleware::DebugExceptionsCatcher)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module Raven
2
+ VERSION = "0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sentry-raven
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "0.1"
6
+ platform: ruby
7
+ authors:
8
+ - Noah Kantrowitz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-05-03 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: faraday
17
+ prerelease: false
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.8.0.rc2
24
+ type: :runtime
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: uuidtools
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ type: :runtime
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: yajl-ruby
39
+ prerelease: false
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ type: :runtime
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: hashie
50
+ prerelease: false
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id004
59
+ description:
60
+ email: noah@coderanger.net
61
+ executables: []
62
+
63
+ extensions: []
64
+
65
+ extra_rdoc_files:
66
+ - README.md
67
+ - LICENSE
68
+ files:
69
+ - lib/raven/client.rb
70
+ - lib/raven/configuration.rb
71
+ - lib/raven/error.rb
72
+ - lib/raven/event.rb
73
+ - lib/raven/interfaces/exception.rb
74
+ - lib/raven/interfaces/http.rb
75
+ - lib/raven/interfaces/message.rb
76
+ - lib/raven/interfaces/stack_trace.rb
77
+ - lib/raven/interfaces.rb
78
+ - lib/raven/linecache.rb
79
+ - lib/raven/logger.rb
80
+ - lib/raven/rack.rb
81
+ - lib/raven/rails/middleware/debug_exceptions_catcher.rb
82
+ - lib/raven/railtie.rb
83
+ - lib/raven/version.rb
84
+ - lib/raven.rb
85
+ - README.md
86
+ - LICENSE
87
+ homepage: http://github.com/coderanger/raven-ruby
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options: []
92
+
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.10
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: A gem that provides a client interface for the Sentry error logger
114
+ test_files: []
115
+