opbeat 0.4

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -0,0 +1,164 @@
1
+ # Opbeat
2
+
3
+ <!-- [![Build Status](https://secure.travis-ci.org/opbeat/opbeat_ruby-ruby.png?branch=master)](http://travis-ci.org/opbeat/opbeat_ruby-ruby) -->
4
+
5
+ A client and integration layer for [Opbeat](https://opbeat.com). Forked from the [raven-ruby](https://github.com/getsentry/raven-ruby) project.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add the following to your `Gemfile`:
11
+
12
+ ```ruby
13
+ gem "opbeat", :git => "https://github.com/opbeat/opbeat_ruby.git"
14
+ ```
15
+
16
+ <!-- Or install manually
17
+ ```bash
18
+ $ gem install sentry-opbeat_ruby
19
+ ```
20
+ -->
21
+ ## Usage
22
+
23
+ ### Rails 3
24
+
25
+ Add a `config/initializers/opbeat.rb` containing:
26
+
27
+ ```ruby
28
+ require 'opbeat'
29
+
30
+ Opbeat.configure do |config|
31
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
32
+ config.app_id = '094e250818'
33
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
34
+ end
35
+ ```
36
+
37
+ ### Rails 2
38
+
39
+ No support for Rails 2 yet.
40
+
41
+ ### Rack
42
+
43
+ Basic RackUp file.
44
+
45
+ ```ruby
46
+ require 'opbeat'
47
+
48
+ Opbeat.configure do |config|
49
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
50
+ config.app_id = '094e250818'
51
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
52
+ end
53
+
54
+ use Opbeat::Rack
55
+ ```
56
+
57
+ ### Sinatra
58
+
59
+ ```ruby
60
+ require 'sinatra'
61
+ require 'opbeat'
62
+
63
+ Opbeat.configure do |config|
64
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
65
+ config.app_id = '094e250818'
66
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
67
+ end
68
+
69
+ use Opbeat::Rack
70
+
71
+ get '/' do
72
+ 1 / 0
73
+ end
74
+ ```
75
+
76
+ ### Other Ruby
77
+
78
+ ```ruby
79
+ require 'opbeat'
80
+
81
+ Opbeat.configure do |config|
82
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
83
+ config.app_id = '094e250818'
84
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
85
+
86
+ # manually configure environment if ENV['RACK_ENV'] is not defined
87
+ config.current_environment = 'production'
88
+ end
89
+
90
+ Opbeat.capture # Global style
91
+
92
+ Opbeat.capture do # Block style
93
+ 1 / 0
94
+ end
95
+ ```
96
+
97
+ ## Testing
98
+
99
+ ```bash
100
+ $ bundle install
101
+ $ rake spec
102
+ ```
103
+
104
+ ## Notifications in development mode
105
+
106
+ By default events will only be sent to Opbeat if your application is running in a production environment. This is configured by default if you are running a Rack application (i.e. anytime `ENV['RACK_ENV']` is set).
107
+
108
+ You can configure Opbeat to run in non-production environments by configuring the `environments` whitelist:
109
+
110
+ ```ruby
111
+ require 'opbeat'
112
+
113
+ Opbeat.configure do |config|
114
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
115
+ config.app_id = '094e250818'
116
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
117
+
118
+ config.environments = %w[ development production ]
119
+ end
120
+ ```
121
+
122
+ ## Excluding Exceptions
123
+
124
+ If you never wish to be notified of certain exceptions, specify 'excluded_exceptions' in your config file.
125
+
126
+ In the example below, the exceptions Rails uses to generate 404 responses will be suppressed.
127
+
128
+ ```ruby
129
+ require 'opbeat'
130
+
131
+ Opbeat.configure do |config|
132
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
133
+ config.app_id = '094e250818'
134
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
135
+
136
+ config.excluded_exceptions = ['ActionController::RoutingError', 'ActiveRecord::RecordNotFound']
137
+ end
138
+ ```
139
+
140
+ ## Sanitizing Data (Processors)
141
+
142
+ If you need to sanitize or pre-process (before its sent to the server) data, you can do so using the Processors
143
+ implementation. By default, a single processor is installed (Opbeat::Processors::SanitizeData), which will attempt to
144
+ sanitize keys that match various patterns (e.g. password) and values that resemble credit card numbers.
145
+
146
+ To specify your own (or to remove the defaults), simply pass them with your configuration:
147
+
148
+ ```ruby
149
+ require 'opbeat'
150
+
151
+ Opbeat.configure do |config|
152
+ config.organization_id = '094e250818f44e82bfae13919f55fb35'
153
+ config.app_id = '094e250818'
154
+ config.secret_token = 'f0f5237a221637f561a15614f5fef218f8d6317d'
155
+
156
+ config.processors = [Opbeat::Processors::SanitizeData]
157
+ end
158
+ ```
159
+
160
+ ## Resources
161
+
162
+ * [Bug Tracker](http://github.com/opbeat/opbeat_ruby/issues)
163
+ * [Code](http://github.com/opbeat/opbeat_ruby)
164
+
@@ -0,0 +1,101 @@
1
+ require 'opbeat/version'
2
+ require 'opbeat/configuration'
3
+ require 'opbeat/logger'
4
+ require 'opbeat/client'
5
+ require 'opbeat/event'
6
+ require 'opbeat/rack'
7
+ require 'opbeat/interfaces/message'
8
+ require 'opbeat/interfaces/exception'
9
+ require 'opbeat/interfaces/stack_trace'
10
+ require 'opbeat/interfaces/http'
11
+ require 'opbeat/processors/sanitizedata'
12
+
13
+ require 'opbeat/railtie' if defined?(Rails::Railtie)
14
+
15
+ module Opbeat
16
+ class << self
17
+ # The client object is responsible for delivering formatted data to the Opbeat server.
18
+ # Must respond to #send. See Opbeat::Client.
19
+ attr_accessor :client
20
+
21
+ # A Opbeat configuration object. Must act like a hash and return sensible
22
+ # values for all Opbeat configuration options. See Opbeat::Configuration.
23
+ attr_writer :configuration
24
+
25
+ def logger
26
+ @logger ||= Logger.new
27
+ end
28
+
29
+ # Tell the log that the client is good to go
30
+ def report_ready
31
+ self.logger.info "Opbeat #{VERSION} ready to catch errors"
32
+ end
33
+
34
+ # The configuration object.
35
+ # @see Opbeat.configure
36
+ def configuration
37
+ @configuration ||= Configuration.new
38
+ end
39
+
40
+ # Call this method to modify defaults in your initializers.
41
+ #
42
+ # @example
43
+ # Opbeat.configure do |config|
44
+ # config.server = 'http://...'
45
+ # end
46
+ def configure(silent = false)
47
+ yield(configuration)
48
+ self.client = Client.new(configuration)
49
+ report_ready unless silent
50
+ self.client
51
+ end
52
+
53
+ # Send an event to the configured Opbeat server
54
+ #
55
+ # @example
56
+ # evt = Opbeat::Event.new(:message => "An error")
57
+ # Opbeat.send(evt)
58
+ def send(evt)
59
+ @client.send(evt) if @client
60
+ end
61
+
62
+ # Capture and process any exceptions from the given block, or globally if
63
+ # no block is given
64
+ #
65
+ # @example
66
+ # Opbeat.capture do
67
+ # MyApp.run
68
+ # end
69
+ def capture(&block)
70
+ if block
71
+ begin
72
+ block.call
73
+ rescue Error => e
74
+ raise # Don't capture Opbeat errors
75
+ rescue Exception => e
76
+ self.captureException(e)
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
+ self.captureException($!)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def captureException(exception)
91
+ evt = Event.capture_exception(exception)
92
+ send(evt) if evt
93
+ end
94
+
95
+ def captureMessage(message)
96
+ evt = Event.capture_message(message)
97
+ send(evt) if evt
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,61 @@
1
+ require 'openssl'
2
+ require 'uri'
3
+ require 'multi_json'
4
+ require 'faraday'
5
+
6
+ require 'opbeat/version'
7
+ require 'opbeat/error'
8
+
9
+ module Opbeat
10
+
11
+ class Client
12
+
13
+ PROTOCOL_VERSION = '1.0'
14
+ USER_AGENT = "opbeat/#{Opbeat::VERSION}"
15
+ AUTH_HEADER_KEY = 'Authorization'
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 secret token specified') unless self.configuration[:secret_token]
27
+ raise Error.new('No organization ID specified') unless self.configuration[:organization_id]
28
+ raise Error.new('No app ID specified') unless self.configuration[:app_id]
29
+
30
+ Opbeat.logger.debug "Opbeat client connecting to #{self.configuration[:server]}"
31
+ @url = self.configuration[:server] + "/api/v1/organizations/" + self.configuration[:organization_id] + "/apps/" + self.configuration[:app_id] + "/errors/"
32
+ @conn ||= Faraday.new(:url => @url) do |builder|
33
+ builder.adapter Faraday.default_adapter
34
+ end
35
+ end
36
+
37
+ def generate_auth_header(data)
38
+ 'Bearer ' + self.configuration[:secret_token]
39
+ end
40
+
41
+ def send(event)
42
+ return unless configuration.send_in_current_environment?
43
+
44
+ # Set the project ID correctly
45
+ event.organization = self.configuration[:organization_id]
46
+ event.app = self.configuration[:app_id]
47
+ Opbeat.logger.debug "Sending event #{event.id} to Opbeat"
48
+
49
+ response = self.conn.post @url do |req|
50
+ req.headers['Content-Type'] = 'application/json'
51
+ req.body = MultiJson.encode(event.to_hash)
52
+ req.headers[AUTH_HEADER_KEY] = self.generate_auth_header(req.body)
53
+ req.headers["User-Agent"] = USER_AGENT
54
+ end
55
+ raise Error.new("Error from Opbeat server (#{response.status}): #{response.body}") unless response.status == 202
56
+ response
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,65 @@
1
+ module Opbeat
2
+ class Configuration
3
+
4
+ # Base URL of the Opbeat server
5
+ attr_accessor :server
6
+
7
+ # Secret access token for authentication with Opbeat
8
+ attr_accessor :secret_token
9
+
10
+ # Organization ID to use with Opbeat
11
+ attr_accessor :organization_id
12
+
13
+ # App ID to use with Opbeat
14
+ attr_accessor :app_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
+ # Whitelist of environments that will send notifications to Opbeat
23
+ attr_accessor :environments
24
+
25
+ # Include module versions in reports?
26
+ attr_accessor :send_modules
27
+
28
+ # Which exceptions should never be sent
29
+ attr_accessor :excluded_exceptions
30
+
31
+ # Processors to run on data before sending upstream
32
+ attr_accessor :processors
33
+
34
+ attr_reader :current_environment
35
+
36
+ def initialize
37
+ self.server = ENV['OPBEAT_SERVER'] || "https://opbeat.com"
38
+ self.secret_token = ENV['OPBEAT_SECRET_TOKEN'] if ENV['OPBEAT_SECRET_TOKEN']
39
+ self.organization_id = ENV['OPBEAT_ORGANIZATION_ID'] if ENV['OPBEAT_ORGANIZATION_ID']
40
+ self.app_id = ENV['OPBEAT_APP_ID'] if ENV['OPBEAT_APP_ID']
41
+ @context_lines = 3
42
+ self.environments = %w[ production ]
43
+ self.current_environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
44
+ self.send_modules = true
45
+ self.excluded_exceptions = []
46
+ self.processors = [Opbeat::Processor::SanitizeData]
47
+ end
48
+
49
+ # Allows config options to be read like a hash
50
+ #
51
+ # @param [Symbol] option Key for a given attribute
52
+ def [](option)
53
+ send(option)
54
+ end
55
+
56
+ def current_environment=(environment)
57
+ @current_environment = environment.to_s
58
+ end
59
+
60
+ def send_in_current_environment?
61
+ environments.include? current_environment
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,6 @@
1
+ module Opbeat
2
+
3
+ class Error < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,192 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'uuidtools'
4
+
5
+ require 'opbeat/error'
6
+ require 'opbeat/linecache'
7
+
8
+ module Opbeat
9
+
10
+ class Event
11
+
12
+ LOG_LEVELS = {
13
+ "debug" => "debug",
14
+ "info" => "info",
15
+ "warn" => "warn",
16
+ "warning" => "warn",
17
+ "error" => "error",
18
+ }
19
+
20
+ BACKTRACE_RE = /^(.+?):(\d+)(?::in `(.+?)')?$/
21
+
22
+ attr_reader :id
23
+ attr_accessor :organization, :app, :message, :timestamp, :level
24
+ attr_accessor :logger, :culprit, :hostname, :modules, :extra
25
+
26
+ def initialize(options={}, configuration=nil, &block)
27
+ @configuration = configuration || Opbeat.configuration
28
+ @interfaces = {}
29
+
30
+ @id = options[:id] || UUIDTools::UUID.random_create.hexdigest
31
+ @message = options[:message]
32
+ @timestamp = options[:timestamp] || Time.now.utc
33
+ @level = options[:level] || :error
34
+ @logger = options[:logger] || 'root'
35
+ @culprit = options[:culprit]
36
+ @extra = options[:extra]
37
+
38
+ # Try to resolve the hostname to an FQDN, but fall back to whatever the load name is
39
+ hostname = Socket.gethostname
40
+ hostname = Socket.gethostbyname(hostname).first rescue hostname
41
+ @hostname = options[:hostname] || hostname
42
+
43
+ # Older versions of Rubygems don't support iterating over all specs
44
+ if @configuration.send_modules && Gem::Specification.respond_to?(:map)
45
+ options[:modules] ||= Hash[Gem::Specification.map {|spec| [spec.name, spec.version.to_s]}]
46
+ end
47
+ @modules = options[:modules]
48
+
49
+ block.call(self) if block
50
+
51
+ # Some type coercion
52
+ @timestamp = @timestamp.strftime('%Y-%m-%dT%H:%M:%S') if @timestamp.is_a?(Time)
53
+ @level = LOG_LEVELS[@level.to_s.downcase] if @level.is_a?(String) || @level.is_a?(Symbol)
54
+
55
+ # Basic sanity checking
56
+ raise Error.new('A message is required for all events') unless @message && !@message.empty?
57
+ raise Error.new('A timestamp is required for all events') unless @timestamp
58
+ end
59
+
60
+ def interface(name, value=nil, &block)
61
+ int = Opbeat::find_interface(name)
62
+ Opbeat.logger.info "Unknown interface: #{name}" unless int
63
+ raise Error.new("Unknown interface: #{name}") unless int
64
+ @interfaces[int.name] = int.new(value, &block) if value || block
65
+ @interfaces[int.name]
66
+ end
67
+
68
+ def [](key)
69
+ interface(key)
70
+ end
71
+
72
+ def []=(key, value)
73
+ interface(key, value)
74
+ end
75
+
76
+ def to_hash
77
+ data = {
78
+ 'client_supplied_id' => self.id,
79
+ 'message' => self.message,
80
+ 'timestamp' => self.timestamp,
81
+ 'level' => self.level,
82
+ 'logger' => self.logger,
83
+ }
84
+ data['culprit'] = self.culprit if self.culprit
85
+ data['machine'] = {'hostname' => self.hostname } if self.hostname
86
+ data['extra'] = self.extra if self.extra
87
+ @interfaces.each_pair do |name, int_data|
88
+ data[name] = int_data.to_hash
89
+ end
90
+ data
91
+ end
92
+
93
+ def self.capture_exception(exc, configuration=nil, &block)
94
+ configuration ||= Opbeat.configuration
95
+ if exc.is_a?(Opbeat::Error)
96
+ # Try to prevent error reporting loops
97
+ Opbeat.logger.info "Refusing to capture Opbeat error: #{exc.inspect}"
98
+ return nil
99
+ end
100
+ if configuration[:excluded_exceptions].include? exc.class.name
101
+ Opbeat.logger.info "User excluded error: #{exc.inspect}"
102
+ return nil
103
+ end
104
+ self.new({}, configuration) do |evt|
105
+ evt.message = "#{exc.class.to_s}: #{exc.message}"
106
+ evt.level = :error
107
+ evt.parse_exception(exc)
108
+ if (exc.backtrace)
109
+ evt.interface :stack_trace do |int|
110
+ int.frames = exc.backtrace.reverse.map do |trace_line|
111
+ int.frame {|frame| evt.parse_backtrace_line(trace_line, frame) }
112
+ end
113
+ evt.culprit = evt.get_culprit(int.frames)
114
+ end
115
+ end
116
+ block.call(evt) if block
117
+ end
118
+ end
119
+
120
+ def self.capture_rack_exception(exc, rack_env, configuration=nil, &block)
121
+ configuration ||= Opbeat.configuration
122
+ capture_exception(exc, configuration) do |evt|
123
+ evt.interface :http do |int|
124
+ int.from_rack(rack_env)
125
+ end
126
+ block.call(evt) if block
127
+ end
128
+ end
129
+
130
+ def self.capture_message(message, configuration=nil)
131
+ configuration ||= Opbeat.configuration
132
+ self.new({}, configuration) do |evt|
133
+ evt.message = message
134
+ evt.level = :error
135
+ evt.interface :message do |int|
136
+ int.message = message
137
+ end
138
+ end
139
+ end
140
+
141
+ def get_culprit(frames)
142
+ lastframe = frames[-2]
143
+ "#{lastframe.filename} in #{lastframe.function}" if lastframe
144
+ end
145
+
146
+ def parse_exception(exception)
147
+ interface(:exception) do |int|
148
+ int.type = exception.class.to_s
149
+ int.value = exception.message
150
+ int.module = exception.class.to_s.split('::')[0...-1].join('::')
151
+ end
152
+ end
153
+
154
+ def parse_backtrace_line(line, frame)
155
+ md = BACKTRACE_RE.match(line)
156
+ raise Error.new("Unable to parse backtrace line: #{line.inspect}") unless md
157
+ frame.abs_path = md[1]
158
+ frame.lineno = md[2].to_i
159
+ frame.function = md[3] if md[3]
160
+ frame.filename = strip_load_path_from(frame.abs_path)
161
+ if context_lines = @configuration[:context_lines]
162
+ frame.pre_context, frame.context_line, frame.post_context = \
163
+ get_context(frame.abs_path, frame.lineno, context_lines)
164
+ end
165
+ frame
166
+ end
167
+
168
+ # For cross-language compat
169
+ class << self
170
+ alias :captionException :capture_exception
171
+ alias :captureMessage :capture_message
172
+ end
173
+
174
+ private
175
+
176
+ # Because linecache can go to hell
177
+ def self._source_lines(path, from, to)
178
+ end
179
+
180
+ def get_context(path, line, context)
181
+ lines = (2 * context + 1).times.map do |i|
182
+ Opbeat::LineCache::getline(path, line - context + i)
183
+ end
184
+ [lines[0..(context-1)], lines[context], lines[(context+1)..-1]]
185
+ end
186
+
187
+ def strip_load_path_from(path)
188
+ prefix = $:.select {|s| path.start_with?(s)}.sort_by {|s| s.length}.last
189
+ prefix ? path[prefix.chomp(File::SEPARATOR).length+1..-1] : path
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,37 @@
1
+ require 'hashie'
2
+
3
+ module Opbeat
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 'opbeat/interfaces'
2
+
3
+ module Opbeat
4
+
5
+ class ExceptionInterface < Interface
6
+
7
+ name '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,57 @@
1
+ require 'opbeat/interfaces'
2
+
3
+ module Opbeat
4
+
5
+ class HttpInterface < Interface
6
+
7
+ name 'http'
8
+ property :url, :required => true
9
+ property :method, :required => true
10
+ property :data
11
+ property :query_string
12
+ property :cookies
13
+ property :headers
14
+ property :remote_host
15
+ property :http_host
16
+ property :env
17
+
18
+ def initialize(*arguments)
19
+ self.headers = {}
20
+ self.env = {}
21
+ super(*arguments)
22
+ end
23
+
24
+ def from_rack(env)
25
+ require 'rack'
26
+ req = ::Rack::Request.new(env)
27
+ self.url = req.url.split('?').first
28
+ self.method = req.request_method
29
+ self.query_string = req.query_string
30
+ self.cookies = req.cookies.collect {|k,v| "#{k}=#{v}"}.join(';')
31
+ self.remote_host = req.ip
32
+ self.http_host = req.host_with_port
33
+ env.each_pair do |key, value|
34
+ next unless key.upcase == key # Non-upper case stuff isn't either
35
+ if key.start_with?('HTTP_')
36
+ # Header
37
+ http_key = key[5..key.length-1].split('_').map{|s| s.capitalize}.join('-')
38
+ self.headers[http_key] = value.to_s
39
+ else
40
+ # Environment
41
+ self.env[key] = value.to_s
42
+ end
43
+ end
44
+ self.data = if req.form_data?
45
+ req.POST
46
+ elsif req.body
47
+ data = req.body.read
48
+ req.body.rewind
49
+ data
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ register_interface :http => HttpInterface
56
+
57
+ end
@@ -0,0 +1,19 @@
1
+ require 'opbeat/interfaces'
2
+
3
+ module Opbeat
4
+
5
+ class MessageInterface < Interface
6
+
7
+ name 'param_message'
8
+ property :message, :required => true
9
+ property :params
10
+
11
+ def initialize(*arguments)
12
+ self.params = []
13
+ super(*arguments)
14
+ end
15
+ end
16
+
17
+ register_interface :message => MessageInterface
18
+
19
+ end
@@ -0,0 +1,59 @@
1
+ require 'hashie'
2
+
3
+ require 'opbeat/interfaces'
4
+
5
+ module Opbeat
6
+
7
+ class StacktraceInterface < Interface
8
+
9
+ name 'stacktrace'
10
+ property :frames, :default => []
11
+
12
+ def initialize(*arguments)
13
+ self.frames = []
14
+ super(*arguments)
15
+ end
16
+
17
+ def to_hash
18
+ data = super
19
+ data['frames'] = data['frames'].map{|frame| frame.to_hash}
20
+ data
21
+ end
22
+
23
+ def frame(attributes=nil, &block)
24
+ Frame.new(attributes, &block)
25
+ end
26
+
27
+ # Not actually an interface, but I want to use the same style
28
+ class Frame < Interface
29
+ property :abs_path
30
+ property :filename, :required => true
31
+ property :function
32
+ property :vars
33
+ property :pre_context
34
+ property :post_context
35
+ property :context_line
36
+ property :lineno, :required => true
37
+
38
+ def initialize(*arguments)
39
+ self.vars= {}
40
+ self.pre_context = []
41
+ self.post_context = []
42
+ super(*arguments)
43
+ end
44
+
45
+ def to_hash
46
+ data = super
47
+ data.delete('vars') unless self.vars && !self.vars.empty?
48
+ data.delete('pre_context') unless self.pre_context && !self.pre_context.empty?
49
+ data.delete('post_context') unless self.post_context && !self.post_context.empty?
50
+ data.delete('context_line') unless self.context_line && !self.context_line.empty?
51
+ data
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ register_interface :stack_trace => StacktraceInterface
58
+
59
+ end
@@ -0,0 +1,25 @@
1
+ # A much simpler source line cacher because linecache sucks at platform compat
2
+
3
+ module Opbeat
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 Opbeat
2
+ class Logger
3
+ LOG_PREFIX = "** [Opbeat] "
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 = Opbeat.configuration[:logger]
16
+ logger.send(level, "#{LOG_PREFIX}#{msg}") if logger
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module Opbeat
2
+
3
+ module Processor
4
+ class Processor
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def process(data)
10
+ data
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'opbeat/processor'
2
+
3
+ module Opbeat
4
+ module Processor
5
+ class SanitizeData < Processor
6
+
7
+ MASK = '********'
8
+ FIELDS_RE = /(authorization|password|passwd|secret)/i
9
+ VALUES_RE = /^\d{16}$/
10
+
11
+ def apply(value, key=nil, &block)
12
+ if value.is_a?(Hash)
13
+ value.each.inject({}) do |memo, (k, v)|
14
+ memo[k] = apply(v, k, &block)
15
+ memo
16
+ end
17
+ elsif value.is_a?(Array)
18
+ value.map do |value|
19
+ apply(value, key, &block)
20
+ end
21
+ else
22
+ block.call(key, value)
23
+ end
24
+ end
25
+
26
+ def sanitize(key, value)
27
+ if !value || value.empty?
28
+ value
29
+ elsif VALUES_RE.match(value) or FIELDS_RE.match(key)
30
+ MASK
31
+ else
32
+ value
33
+ end
34
+ end
35
+
36
+ def process(data)
37
+ apply(data) do |key, value|
38
+ sanitize(key, value)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ module Opbeat
2
+ # Middleware for Rack applications. Any errors raised by the upstream
3
+ # application will be delivered to Opbeat and re-raised.
4
+ #
5
+ # Synopsis:
6
+ #
7
+ # require 'rack'
8
+ # require 'opbeat'
9
+ #
10
+ # Opbeat.configure do |config|
11
+ # config.server = 'http://my_dsn'
12
+ # end
13
+ #
14
+ # app = Rack::Builder.app do
15
+ # use Opbeat::Rack
16
+ # run lambda { |env| raise "Rack down" }
17
+ # end
18
+ #
19
+ # Use a standard Opbeat.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 Opbeat errors
30
+ rescue Exception => e
31
+ evt = Event.capture_rack_exception(e, env)
32
+ Opbeat.send(evt)
33
+ raise
34
+ end
35
+
36
+ if env['rack.exception']
37
+ evt = Event.capture_rack_exception(env['rack.exception'], env)
38
+ Opbeat.send(evt) if evt
39
+ end
40
+
41
+ response
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module Opbeat
2
+ module Rails
3
+ module Middleware
4
+ module DebugExceptionsCatcher
5
+ def self.included(base)
6
+ base.send(:alias_method_chain, :render_exception, :raven)
7
+ end
8
+
9
+ def render_exception_with_raven(env, exception)
10
+ begin
11
+ evt = Opbeat::Event.capture_rack_exception(exception, env)
12
+ Opbeat.send(evt) if evt
13
+ rescue
14
+ ::Rails::logger.debug "Error capturing or sending exception #{$!}"
15
+ end
16
+
17
+ render_exception_without_raven(env, exception)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ require 'opbeat'
2
+ require 'rails'
3
+
4
+ module Opbeat
5
+ class Railtie < ::Rails::Railtie
6
+ initializer "opbeat.use_rack_middleware" do |app|
7
+ app.config.middleware.insert 0, "Opbeat::Rack"
8
+ end
9
+ config.after_initialize do
10
+ Opbeat.configure(true) do |config|
11
+ config.logger ||= ::Rails.logger
12
+ end
13
+
14
+ if defined?(::ActionDispatch::DebugExceptions)
15
+ require 'opbeat/rails/middleware/debug_exceptions_catcher'
16
+ ::ActionDispatch::DebugExceptions.send(:include, Opbeat::Rails::Middleware::DebugExceptionsCatcher)
17
+ elsif defined?(::ActionDispatch::ShowExceptions)
18
+ require 'opbeat/rails/middleware/debug_exceptions_catcher'
19
+ ::ActionDispatch::ShowExceptions.send(:include, Opbeat::Rails::Middleware::DebugExceptionsCatcher)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Opbeat
2
+ VERSION = "0.4"
3
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opbeat
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.4'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Noah Kantrowitz
9
+ - Ron Cohen
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-02-23 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: faraday
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.8.0.rc2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 0.8.0.rc2
31
+ - !ruby/object:Gem::Dependency
32
+ name: uuidtools
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: multi_json
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '1.0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: hashie
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ description:
80
+ email: ron@opbeat.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files:
84
+ - README.md
85
+ - LICENSE
86
+ files:
87
+ - lib/opbeat/client.rb
88
+ - lib/opbeat/configuration.rb
89
+ - lib/opbeat/error.rb
90
+ - lib/opbeat/event.rb
91
+ - lib/opbeat/interfaces/exception.rb
92
+ - lib/opbeat/interfaces/http.rb
93
+ - lib/opbeat/interfaces/message.rb
94
+ - lib/opbeat/interfaces/stack_trace.rb
95
+ - lib/opbeat/interfaces.rb
96
+ - lib/opbeat/linecache.rb
97
+ - lib/opbeat/logger.rb
98
+ - lib/opbeat/processor.rb
99
+ - lib/opbeat/processors/sanitizedata.rb
100
+ - lib/opbeat/rack.rb
101
+ - lib/opbeat/rails/middleware/debug_exceptions_catcher.rb
102
+ - lib/opbeat/railtie.rb
103
+ - lib/opbeat/version.rb
104
+ - lib/opbeat.rb
105
+ - README.md
106
+ - LICENSE
107
+ homepage: http://github.com/opbeat/opbeat_ruby
108
+ licenses: []
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.8.25
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: A gem that provides a client interface for the Opbeat error logger
131
+ test_files: []