sentry-raven 0.3.1 → 0.4.0

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/README.md CHANGED
@@ -79,14 +79,58 @@ Raven.configure do |config|
79
79
  # manually configure environment if ENV['RACK_ENV'] is not defined
80
80
  config.current_environment = 'production'
81
81
  end
82
+ ```
83
+
84
+ ## Capturing Events
85
+
86
+ Many implementations will automatically capture uncaught exceptions (such as Rails, Sidekiq or by using
87
+ the Rack middleware). Sometimes you may want to catch those exceptions, but still report on them.
88
+
89
+ Several helps are available to assist with this.
82
90
 
83
- Raven.capture # Global style
91
+ ### Capture Exceptions in a Block
84
92
 
85
- Raven.capture do # Block style
93
+ ```ruby
94
+ Raven.capture do
95
+ # capture any exceptions which happen during execution of this block
86
96
  1 / 0
87
97
  end
88
98
  ```
89
99
 
100
+ ### Capture an Exception by Value
101
+
102
+ ```ruby
103
+ begin
104
+ 1 / 0
105
+ rescue ZeroDivisionError => exception
106
+ Raven.capture_exception(exception)
107
+ end
108
+ ```
109
+
110
+ ### Additional Context
111
+
112
+ Additional context can be passed to the capture methods.
113
+
114
+ ```ruby
115
+ Raven.capture_message("My event", {
116
+ :logger => 'logger',
117
+ :extra => {
118
+ 'my_custom_variable' => 'value'
119
+ },
120
+ :tags => {
121
+ 'environment' => 'production',
122
+ }
123
+ })
124
+ ```
125
+
126
+ The following attributes are available:
127
+
128
+ * `logger`: the logger name to record this event under
129
+ * `level`: a string representing the level of this event (fatal, error, warning, info, debug)
130
+ * `server_name`: the hostname of the server
131
+ * `tags`: a mapping of tags describing this event
132
+ * `extra`: a mapping of arbitrary context
133
+
90
134
  ## Testing
91
135
 
92
136
  ```bash
@@ -127,7 +171,7 @@ end
127
171
  ## Sanitizing Data (Processors)
128
172
 
129
173
  If you need to sanitize or pre-process (before its sent to the server) data, you can do so using the Processors
130
- implementation. By default, a single processor is installed (Raven::Processors::SanitizeData), which will attempt to
174
+ implementation. By default, a single processor is installed (Raven::Processor::SanitizeData), which will attempt to
131
175
  sanitize keys that match various patterns (e.g. password) and values that resemble credit card numbers.
132
176
 
133
177
  To specify your own (or to remove the defaults), simply pass them with your configuration:
@@ -137,14 +181,22 @@ require 'raven'
137
181
 
138
182
  Raven.configure do |config|
139
183
  config.dsn = 'http://public:secret@example.com/project-id'
140
- config.processors = [Raven::Processors::SanitizeData]
184
+ config.processors = [Raven::Processor::SanitizeData]
141
185
  end
186
+ ```
187
+
188
+ ## Command Line Interface
189
+
190
+ Raven includes a basic CLI for testing your DSN:
191
+
192
+ ```ruby
193
+ ruby -Ilib ./bin/raven test <DSN>
194
+ ```
142
195
 
143
196
  Resources
144
197
  ---------
145
198
 
146
- * `Bug Tracker <http://github.com/getsentry/raven-ruby/issues>`_
147
- * `Code <http://github.com/getsentry/raven-ruby>`_
148
- * `Mailing List <https://groups.google.com/group/getsentry>`_
149
- * `IRC <irc://irc.freenode.net/sentry>`_ (irc.freenode.net, #sentry)
150
-
199
+ * [Bug Tracker](http://github.com/getsentry/raven-ruby/issues>)
200
+ * [Code](http://github.com/getsentry/raven-ruby>)
201
+ * [Mailing List](https://groups.google.com/group/getsentry>)
202
+ * [IRC](irc://irc.freenode.net/sentry>) (irc.freenode.net, #sentry)
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "raven"
4
+ require "optparse"
5
+
6
+ options = {}
7
+
8
+ parser = OptionParser.new do |opt|
9
+ opt.banner = "Usage: raven COMMAND [OPTIONS]"
10
+ opt.separator ""
11
+ opt.separator "Commands"
12
+ opt.separator " test: send a test event"
13
+ opt.separator ""
14
+ opt.separator "Options"
15
+
16
+ # opt.on("-e","--environment ENVIRONMENT","which environment you want server run") do |environment|
17
+ # options[:environment] = environment
18
+ # end
19
+
20
+ # opt.on("-d","--daemon","runing on daemon mode?") do
21
+ # options[:daemon] = true
22
+ # end
23
+
24
+ opt.on("-h","--help","help") do
25
+ puts parser
26
+ end
27
+ end
28
+
29
+ parser.parse!
30
+
31
+ case ARGV[0]
32
+ when "test"
33
+ dsn = ARGV[1] if ARGV.length > 1
34
+ if !dsn
35
+ puts "Usage: raven test <dsn>"
36
+ else
37
+ Raven.configure do |config|
38
+ config.dsn = dsn
39
+ config.current_environment = 'production'
40
+ end
41
+
42
+ puts "Sending test event"
43
+
44
+ begin
45
+ 1 / 0
46
+ rescue ZeroDivisionError => exception
47
+ Raven.capture_exception(exception)
48
+ end
49
+
50
+ puts "Done!"
51
+
52
+ end
53
+ else
54
+ puts parser
55
+ end
@@ -1,4 +1,5 @@
1
1
  require 'raven/version'
2
+ require 'raven/backtrace'
2
3
  require 'raven/configuration'
3
4
  require 'raven/logger'
4
5
  require 'raven/client'
@@ -11,6 +12,8 @@ require 'raven/interfaces/http'
11
12
  require 'raven/processors/sanitizedata'
12
13
 
13
14
  require 'raven/railtie' if defined?(Rails::Railtie)
15
+ require 'raven/sidekiq' if defined?(Sidekiq)
16
+
14
17
 
15
18
  module Raven
16
19
  class << self
@@ -66,14 +69,14 @@ module Raven
66
69
  # Raven.capture do
67
70
  # MyApp.run
68
71
  # end
69
- def capture(&block)
72
+ def capture(options={}, &block)
70
73
  if block
71
74
  begin
72
75
  block.call
73
76
  rescue Error => e
74
77
  raise # Don't capture Raven errors
75
78
  rescue Exception => e
76
- self.captureException(e)
79
+ capture_exception(e, options)
77
80
  raise
78
81
  end
79
82
  else
@@ -81,21 +84,24 @@ module Raven
81
84
  at_exit do
82
85
  if $!
83
86
  logger.debug "Caught a post-mortem exception: #{$!.inspect}"
84
- self.captureException($!)
87
+ capture_exception($!, options)
85
88
  end
86
89
  end
87
90
  end
88
91
  end
89
92
 
90
- def captureException(exception)
91
- evt = Event.capture_exception(exception)
93
+ def capture_exception(exception, options={})
94
+ evt = Event.capture_exception(exception, options)
92
95
  send(evt) if evt
93
96
  end
94
97
 
95
- def captureMessage(message)
96
- evt = Event.capture_message(message)
98
+ def capture_message(message, options={})
99
+ evt = Event.capture_message(message, options)
97
100
  send(evt) if evt
98
101
  end
99
102
 
103
+ # For cross-language compat
104
+ alias :captureException :capture_exception
105
+ alias :captureMessage :capture_message
100
106
  end
101
107
  end
@@ -0,0 +1,121 @@
1
+ ## Inspired by Rails' and Airbrake's backtrace parsers.
2
+
3
+ module Raven
4
+
5
+ # Front end to parsing the backtrace for each notice
6
+ class Backtrace
7
+
8
+ # Handles backtrace parsing line by line
9
+ class Line
10
+
11
+ # regexp (optionnally allowing leading X: for windows support)
12
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
13
+
14
+ APP_DIRS_PATTERN = /^\/?(bin|app|config|lib|test)/
15
+
16
+ # The file portion of the line (such as app/models/user.rb)
17
+ attr_reader :file
18
+
19
+ # The line number portion of the line
20
+ attr_reader :number
21
+
22
+ # The method of the line (such as index)
23
+ attr_reader :method
24
+
25
+ # Parses a single line of a given backtrace
26
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
27
+ # @return [Line] The parsed backtrace line
28
+ def self.parse(unparsed_line)
29
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
30
+ new(file, number, method)
31
+ end
32
+
33
+ def initialize(file, number, method)
34
+ self.file = file
35
+ self.number = number.to_i
36
+ self.method = method
37
+ end
38
+
39
+ def in_app
40
+ if self.file =~ APP_DIRS_PATTERN
41
+ true
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ # Reconstructs the line in a readable fashion
48
+ def to_s
49
+ "#{file}:#{number}:in `#{method}'"
50
+ end
51
+
52
+ def ==(other)
53
+ to_s == other.to_s
54
+ end
55
+
56
+ def inspect
57
+ "<Line:#{to_s}>"
58
+ end
59
+
60
+ private
61
+
62
+ attr_writer :file, :number, :method
63
+ end
64
+
65
+ # holder for an Array of Backtrace::Line instances
66
+ attr_reader :lines
67
+
68
+ def self.parse(ruby_backtrace, opts = {})
69
+ ruby_lines = split_multiline_backtrace(ruby_backtrace)
70
+
71
+ filters = opts[:filters] || []
72
+ filtered_lines = ruby_lines.to_a.map do |line|
73
+ filters.inject(line) do |line, proc|
74
+ proc.call(line)
75
+ end
76
+ end.compact
77
+
78
+ lines = filtered_lines.collect do |unparsed_line|
79
+ Line.parse(unparsed_line)
80
+ end
81
+
82
+ instance = new(lines)
83
+ end
84
+
85
+ def initialize(lines)
86
+ self.lines = lines
87
+ end
88
+
89
+ def inspect
90
+ "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
91
+ end
92
+
93
+ def to_s
94
+ content = []
95
+ lines.each do |line|
96
+ content << line
97
+ end
98
+ content.join("\n")
99
+ end
100
+
101
+ def ==(other)
102
+ if other.respond_to?(:lines)
103
+ lines == other.lines
104
+ else
105
+ false
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ attr_writer :lines
112
+
113
+ def self.split_multiline_backtrace(backtrace)
114
+ if backtrace.to_a.size == 1
115
+ backtrace.to_a.first.split(/\n\s*/)
116
+ else
117
+ backtrace
118
+ end
119
+ end
120
+ end
121
+ end
@@ -29,9 +29,14 @@ module Raven
29
29
 
30
30
  Raven.logger.debug "Raven client connecting to #{self.configuration[:server]}"
31
31
 
32
- @conn ||= Faraday.new(:url => self.configuration[:server]) do |builder|
33
- builder.adapter Faraday.default_adapter
34
- end
32
+ @conn ||= Faraday.new(
33
+ :url => self.configuration[:server],
34
+ :ssl => {:verify => self.configuration.ssl_verification}
35
+ ) do |builder|
36
+ builder.adapter Faraday.default_adapter
37
+ builder.options[:timeout] = self.configuration.timeout if self.configuration.timeout
38
+ builder.options[:open_timeout] = self.configuration.open_timeout if self.configuration.open_timeout
39
+ end
35
40
  end
36
41
 
37
42
 
@@ -31,16 +31,34 @@ module Raven
31
31
  # Processors to run on data before sending upstream
32
32
  attr_accessor :processors
33
33
 
34
+ # Timeout when waiting for the server to return data in seconds
35
+ attr_accessor :timeout
36
+
37
+ # Timeout waiting for the connection to open in seconds
38
+ attr_accessor :open_timeout
39
+
40
+ # Should the SSL certificate of the server be verified?
41
+ attr_accessor :ssl_verification
42
+
34
43
  attr_reader :current_environment
35
44
 
45
+ IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
46
+ 'ActionController::RoutingError',
47
+ 'ActionController::InvalidAuthenticityToken',
48
+ 'CGI::Session::CookieStore::TamperedWithCookie',
49
+ 'ActionController::UnknownAction',
50
+ 'AbstractController::ActionNotFound',
51
+ 'Mongoid::Errors::DocumentNotFound']
52
+
36
53
  def initialize
37
54
  self.server = ENV['SENTRY_DSN'] if ENV['SENTRY_DSN']
38
55
  @context_lines = 3
39
56
  self.environments = %w[ production ]
40
57
  self.current_environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
41
58
  self.send_modules = true
42
- self.excluded_exceptions = []
59
+ self.excluded_exceptions = IGNORE_DEFAULT
43
60
  self.processors = [Raven::Processor::SanitizeData]
61
+ self.ssl_verification = true
44
62
  end
45
63
 
46
64
  def server=(value)
@@ -15,16 +15,17 @@ module Raven
15
15
  "warn" => 30,
16
16
  "warning" => 30,
17
17
  "error" => 40,
18
+ "fatal" => 50,
18
19
  }
19
20
 
20
21
  BACKTRACE_RE = /^(.+?):(\d+)(?::in `(.+?)')?$/
21
22
 
22
23
  attr_reader :id
23
24
  attr_accessor :project, :message, :timestamp, :level
24
- attr_accessor :logger, :culprit, :server_name, :modules, :extra
25
+ attr_accessor :logger, :culprit, :server_name, :modules, :extra, :tags
25
26
 
26
- def initialize(options={}, configuration=nil, &block)
27
- @configuration = configuration || Raven.configuration
27
+ def initialize(options={}, &block)
28
+ @configuration = options[:configuration] || Raven.configuration
28
29
  @interfaces = {}
29
30
 
30
31
  @id = options[:id] || UUIDTools::UUID.random_create.hexdigest
@@ -34,6 +35,7 @@ module Raven
34
35
  @logger = options[:logger] || 'root'
35
36
  @culprit = options[:culprit]
36
37
  @extra = options[:extra]
38
+ @tags = options[:tags]
37
39
 
38
40
  # Try to resolve the hostname to an FQDN, but fall back to whatever the load name is
39
41
  hostname = Socket.gethostname
@@ -85,31 +87,45 @@ module Raven
85
87
  data['server_name'] = self.server_name if self.server_name
86
88
  data['modules'] = self.modules if self.modules
87
89
  data['extra'] = self.extra if self.extra
90
+ data['tags'] = self.tags if self.tags
88
91
  @interfaces.each_pair do |name, int_data|
89
92
  data[name] = int_data.to_hash
90
93
  end
91
94
  data
92
95
  end
93
96
 
94
- def self.capture_exception(exc, configuration=nil, &block)
95
- configuration ||= Raven.configuration
97
+ def self.capture_exception(exc, options={}, &block)
98
+ configuration = options[:configuration] || Raven.configuration
96
99
  if exc.is_a?(Raven::Error)
97
100
  # Try to prevent error reporting loops
98
101
  Raven.logger.info "Refusing to capture Raven error: #{exc.inspect}"
99
102
  return nil
100
103
  end
101
- if configuration[:excluded_exceptions].include? exc.class.name
104
+ if configuration[:excluded_exceptions].any? { |x| x === exc || x == exc.class.name }
102
105
  Raven.logger.info "User excluded error: #{exc.inspect}"
103
106
  return nil
104
107
  end
105
- self.new({}, configuration) do |evt|
108
+
109
+ context_lines = configuration[:context_lines]
110
+
111
+ new(options) do |evt|
106
112
  evt.message = "#{exc.class.to_s}: #{exc.message}"
107
- evt.level = :error
113
+ evt.level = options[:level] || :error
108
114
  evt.parse_exception(exc)
109
115
  if (exc.backtrace)
110
116
  evt.interface :stack_trace do |int|
111
- int.frames = exc.backtrace.reverse.map do |trace_line|
112
- int.frame {|frame| evt.parse_backtrace_line(trace_line, frame) }
117
+ backtrace = Backtrace.parse(exc.backtrace)
118
+ int.frames = backtrace.lines.reverse.map do |line|
119
+ int.frame do |frame|
120
+ frame.abs_path = line.file
121
+ frame.function = line.method
122
+ frame.lineno = line.number
123
+ frame.in_app = line.in_app
124
+ if context_lines
125
+ frame.pre_context, frame.context_line, frame.post_context = \
126
+ evt.get_context(frame.abs_path, frame.lineno, context_lines)
127
+ end
128
+ end
113
129
  end
114
130
  evt.culprit = evt.get_culprit(int.frames)
115
131
  end
@@ -118,9 +134,8 @@ module Raven
118
134
  end
119
135
  end
120
136
 
121
- def self.capture_rack_exception(exc, rack_env, configuration=nil, &block)
122
- configuration ||= Raven.configuration
123
- capture_exception(exc, configuration) do |evt|
137
+ def self.capture_rack_exception(exc, rack_env, options={}, &block)
138
+ capture_exception(exc, options) do |evt|
124
139
  evt.interface :http do |int|
125
140
  int.from_rack(rack_env)
126
141
  end
@@ -128,20 +143,30 @@ module Raven
128
143
  end
129
144
  end
130
145
 
131
- def self.capture_message(message, configuration=nil)
132
- configuration ||= Raven.configuration
133
- self.new({}, configuration) do |evt|
146
+ def self.capture_message(message, options={})
147
+ new(options) do |evt|
134
148
  evt.message = message
135
- evt.level = :error
149
+ evt.level = options[:level] || :error
136
150
  evt.interface :message do |int|
137
151
  int.message = message
138
152
  end
139
153
  end
140
154
  end
141
155
 
156
+ # Because linecache can go to hell
157
+ def self._source_lines(path, from, to)
158
+ end
159
+
160
+ def get_context(filename, lineno, context)
161
+ lines = (2 * context + 1).times.map do |i|
162
+ Raven::LineCache::getline(filename, lineno - context + i)
163
+ end
164
+ [lines[0..(context-1)], lines[context], lines[(context+1)..-1]]
165
+ end
166
+
142
167
  def get_culprit(frames)
143
- lastframe = frames[-2]
144
- "#{lastframe.filename} in #{lastframe.function}" if lastframe
168
+ lastframe = frames[-1]
169
+ "#{lastframe.filename} in #{lastframe.function}" if lastframe
145
170
  end
146
171
 
147
172
  def parse_exception(exception)
@@ -152,42 +177,12 @@ module Raven
152
177
  end
153
178
  end
154
179
 
155
- def parse_backtrace_line(line, frame)
156
- md = BACKTRACE_RE.match(line)
157
- raise Error.new("Unable to parse backtrace line: #{line.inspect}") unless md
158
- frame.abs_path = md[1]
159
- frame.lineno = md[2].to_i
160
- frame.function = md[3] if md[3]
161
- frame.filename = strip_load_path_from(frame.abs_path)
162
- if context_lines = @configuration[:context_lines]
163
- frame.pre_context, frame.context_line, frame.post_context = \
164
- get_context(frame.abs_path, frame.lineno, context_lines)
165
- end
166
- frame
167
- end
168
-
169
180
  # For cross-language compat
170
181
  class << self
171
- alias :captionException :capture_exception
182
+ alias :captureException :capture_exception
172
183
  alias :captureMessage :capture_message
173
184
  end
174
185
 
175
186
  private
176
-
177
- # Because linecache can go to hell
178
- def self._source_lines(path, from, to)
179
- end
180
-
181
- def get_context(path, line, context)
182
- lines = (2 * context + 1).times.map do |i|
183
- Raven::LineCache::getline(path, line - context + i)
184
- end
185
- [lines[0..(context-1)], lines[context], lines[(context+1)..-1]]
186
- end
187
-
188
- def strip_load_path_from(path)
189
- prefix = $:.select {|s| path.start_with?(s)}.sort_by {|s| s.length}.last
190
- prefix ? path[prefix.chomp(File::SEPARATOR).length+1..-1] : path
191
- end
192
187
  end
193
188
  end
@@ -27,23 +27,29 @@ module Raven
27
27
  # Not actually an interface, but I want to use the same style
28
28
  class Frame < Interface
29
29
  property :abs_path
30
- property :filename, :required => true
31
30
  property :function
32
31
  property :vars
33
32
  property :pre_context
34
33
  property :post_context
35
34
  property :context_line
36
35
  property :lineno, :required => true
36
+ property :in_app
37
37
 
38
38
  def initialize(*arguments)
39
- self.vars= {}
39
+ self.vars = {}
40
40
  self.pre_context = []
41
41
  self.post_context = []
42
42
  super(*arguments)
43
43
  end
44
44
 
45
+ def filename
46
+ prefix = $:.select {|s| self.abs_path.start_with?(s)}.sort_by {|s| s.length}.last
47
+ prefix ? self.abs_path[prefix.chomp(File::SEPARATOR).length+1..-1] : self.abs_path
48
+ end
49
+
45
50
  def to_hash
46
51
  data = super
52
+ data['filename'] = self.filename
47
53
  data.delete('vars') unless self.vars && !self.vars.empty?
48
54
  data.delete('pre_context') unless self.pre_context && !self.pre_context.empty?
49
55
  data.delete('post_context') unless self.post_context && !self.post_context.empty?
@@ -0,0 +1,18 @@
1
+ module Raven
2
+ class Sidekiq
3
+ def call(worker, msg, queue)
4
+ begin
5
+ yield
6
+ rescue => ex
7
+ Raven.capture_exception(ex, :extra => {:sidekiq => msg})
8
+ raise
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ ::Sidekiq.configure_server do |config|
15
+ config.server_middleware do |chain|
16
+ chain.add ::Raven::Sidekiq
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Raven
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -4,17 +4,18 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 1
9
- version: 0.3.1
7
+ - 4
8
+ - 0
9
+ version: 0.4.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Noah Kantrowitz
13
+ - David Cramer
13
14
  autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2012-11-05 00:00:00 -08:00
18
+ date: 2013-01-04 00:00:00 -08:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -22,14 +23,13 @@ dependencies:
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
24
25
  requirements:
25
- - - ~>
26
+ - - ">="
26
27
  - !ruby/object:Gem::Version
27
28
  segments:
28
29
  - 0
29
- - 8
30
- - 0
31
- - rc2
32
- version: 0.8.0.rc2
30
+ - 7
31
+ - 6
32
+ version: 0.7.6
33
33
  type: :runtime
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
@@ -71,14 +71,15 @@ dependencies:
71
71
  version_requirements: *id004
72
72
  description:
73
73
  email: noah@coderanger.net
74
- executables: []
75
-
74
+ executables:
75
+ - raven
76
76
  extensions: []
77
77
 
78
78
  extra_rdoc_files:
79
79
  - README.md
80
80
  - LICENSE
81
81
  files:
82
+ - lib/raven/backtrace.rb
82
83
  - lib/raven/client.rb
83
84
  - lib/raven/configuration.rb
84
85
  - lib/raven/error.rb
@@ -95,6 +96,7 @@ files:
95
96
  - lib/raven/rack.rb
96
97
  - lib/raven/rails/middleware/debug_exceptions_catcher.rb
97
98
  - lib/raven/railtie.rb
99
+ - lib/raven/sidekiq.rb
98
100
  - lib/raven/version.rb
99
101
  - lib/raven.rb
100
102
  - lib/sentry-raven.rb