Skip to content

Commit

Permalink
Merge pull request #5 from Sija/develop
Browse files Browse the repository at this point in the history
v0.3
  • Loading branch information
Sija authored Mar 4, 2017
2 parents 5595125 + e5370a4 commit 0881a56
Show file tree
Hide file tree
Showing 15 changed files with 140 additions and 59 deletions.
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,47 @@ Raven.configure do |config|
end
```

#### async

When an error or message occurs, the notification is immediately sent to Sentry. Raven can be configured to send asynchronously:

```crystal
config.async = ->(event : Raven::Event) {
spawn { Raven.send_event(event) }
nil
}
```

If the `async` callback raises an exception, Raven will attempt to send synchronously.

We recommend creating a background job, using your background job processor, that will send Sentry notifications in the background. Rather than enqueuing an entire `Raven::Event` object, we recommend providing the `Hash` representation of an event as a job argument. Here’s an example for Sidekiq.cr:

```crystal
config.async = ->(event : Raven::Event) {
SentryJob.async.perform(event.to_hash)
nil
}
class SentryJob
include Sidekiq::Worker
sidekiq_options do |job|
job.queue = "sentry"
job.retry = true
end
def perform(event : Hash)
Raven.send_event(event)
end
end
```

#### transport_failure_callback

If Raven fails to send an event to Sentry for any reason (either the Sentry server has returned a 4XX or 5XX response), this Proc will be called.

```crystal
config.transport_failure_callback = ->(event : Raven::Event) {
AdminMailer.email_admins("Oh god, it's on fire!", event.to_hash).deliver_later
config.transport_failure_callback = ->(event : Raven::Event::HashType) {
AdminMailer.email_admins("Oh god, it's on fire!", event).deliver_later
}
```

Expand Down Expand Up @@ -122,7 +156,7 @@ For more information, see [Context](https://docs.sentry.io/clients/ruby/context/
- [x] Processors
- [x] Breadcrumbs
- [x] Integrations (Kemal, Sidekiq)
- [ ] Async
- [x] Async
- [ ] Tests

## Development
Expand Down
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: raven
version: 0.2.0
version: 0.3.0

authors:
- Sijawusz Pur Rahnama <[email protected]>
Expand Down
2 changes: 1 addition & 1 deletion src/raven/backtrace.cr
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ module Raven
def_equals @lines

def to_s(io)
@lines.join '\n', io
@lines.join('\n', io)
end

def inspect(io)
Expand Down
1 change: 1 addition & 0 deletions src/raven/breadcrumb_buffer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Raven
class BreadcrumbBuffer
include Enumerable(Breadcrumb)

# FIXME
# @[ThreadLocal]
@@current : self?

Expand Down
34 changes: 17 additions & 17 deletions src/raven/client.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ module Raven
property configuration : Configuration
delegate logger, to: configuration

# FIXME: why do i need to use "!"?
protected getter! processors : Array(Processor)
# FIXME: why do i need to use "!"?
protected getter! state : State
@state : State
@processors : Array(Processor)

getter transport : Transport do
case configuration.scheme
Expand All @@ -28,19 +26,19 @@ module Raven
end

def initialize(@configuration)
@processors = @configuration.processors.map &.new(self)
@state = State.new
# FIXME: why do i need this line to make compiler happy?
@processors = [] of Processor
@processors = @configuration.processors.map &.new(self)
end

def send_event(event)
return false unless configuration.capture_allowed?(event)

unless state.should_try?
def send_event(event : Event | Event::HashType)
event = event.is_a?(Event) ? event.to_hash.to_h : event
unless @state.should_try?
failed_send nil, event
return
end
logger.info "Sending event #{event.id} to Sentry"
# pp event.to_hash
logger.info "Sending event #{event[:event_id]} to Sentry"

content_type, encoded_data = encode(event)
begin
Expand All @@ -53,9 +51,8 @@ module Raven
end
end

private def encode(event)
data = event.to_hash
data = processors.reduce(data) { |v, p| p.process(v) }
private def encode(data)
data = @processors.reduce(data) { |v, p| p.process(v) }
encoded = data.to_json

case configuration.encoding
Expand Down Expand Up @@ -84,18 +81,21 @@ module Raven
end

private def successful_send
state.success
@state.success
end

private def failed_send(e, event)
state.failure
@state.failure
if e
logger.error "Unable to record event with remote Sentry server \
(#{e.class} - #{e.message}): #{e.backtrace[0..10].join('\n')}"
else
logger.error "Not sending event due to previous failure(s)"
end
logger.error "Failed to submit event: #{event.message || "<no message value>"}"

message = event[:message]? || "<no message value>"
logger.error "Failed to submit event: #{message}"

configuration.transport_failure_callback.try &.call(event)
end
end
Expand Down
17 changes: 9 additions & 8 deletions src/raven/configuration.cr
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ module Raven
# Processor::Cookies,
# Processor::PostData,
Processor::HTTPHeaders,
# Processor::UTF8Conversion,
Processor::UTF8Conversion,
Processor::SanitizeData,
Processor::Compact,
] of Processor.class
Expand All @@ -40,12 +40,13 @@ module Raven
# `Regex` pattern matched against `Backtrace::Line#file`.
property in_app_pattern : Regex { /^(#{SRC_PATH}\/)?(#{app_dirs_pattern})/ }

# Provide an object that responds to `call` to send events asynchronously.
# Provide a `Proc` object that responds to `call` to send
# events asynchronously, or pass `true` to to use standard `spawn`.
#
# ```
# ->(event : Event) { future { Raven.send_event(event) } }
# ->(event : Raven::Event) { spawn { Raven.send_event(event) }; nil }
# ```
property async : Proc(Event, Nil)?
property async : Bool | Proc(Event, Nil) | Nil

# `KEMAL_ENV` by default.
property current_environment : String?
Expand Down Expand Up @@ -157,9 +158,9 @@ module Raven
# or an `Exception`.
#
# ```
# ->(obj : Event | Exception | String) { obj.some_attr == false }
# ->(obj : Exception | String) { obj.some_attr == false }
# ```
property should_capture : Proc(Event | Exception | String, Bool)?
property should_capture : Proc(Exception | String, Bool)?

# Silences ready message when `true`.
property? silence_ready = false
Expand All @@ -173,9 +174,9 @@ module Raven
# Optional `Proc`, called when the Sentry server cannot be contacted for any reason.
#
# ```
# ->(event : Event) { future { MyJobProcessor.send_email(event) } }
# ->(event : Raven::Event::HashType) { spawn { MyJobProcessor.send_email(event); nil } }
# ```
property transport_failure_callback : Proc(Event, Nil)?
property transport_failure_callback : Proc(Event::HashType, Nil)?

# Errors object - an Array that contains error messages.
getter errors = [] of String
Expand Down
1 change: 1 addition & 0 deletions src/raven/context.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Raven
class Context
# FIXME
# @[ThreadLocal]
@@current : self?

Expand Down
33 changes: 21 additions & 12 deletions src/raven/event.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ module Raven
# Information about the SDK sending the event.
SDK = {name: "raven.cr", version: Raven::VERSION}

# `Hash` type returned by `#to_hash`.
alias HashType = Hash(AnyHash::JSON::Key, AnyHash::JSON::Value)

# Hexadecimal string representing a uuid4 value.
#
# NOTE: The length is exactly 32 characters (no dashes!)
Expand Down Expand Up @@ -103,7 +106,7 @@ module Raven

protected def self.add_exception_interface(event, exc)
exceptions = [exc] of Exception
context = Set(UInt64).new [exc.object_id]
context = Set(UInt64).new({exc.object_id})
backtraces = Set(UInt64).new

while exc = exc.cause
Expand Down Expand Up @@ -136,7 +139,7 @@ module Raven

backtrace = Backtrace.parse(backtrace)
backtrace.lines.reverse_each do |line|
iface.frames << Interface::Stacktrace::Frame.new.tap do |frame|
iface.frames << Interface::Stacktrace::Frame.new do |frame|
frame.abs_path = line.file
frame.function = line.method
frame.lineno = line.number
Expand Down Expand Up @@ -180,15 +183,21 @@ module Raven
end

def initialize_with(**attributes)
{% for var in @type.instance_vars %}
if %arg = attributes[:{{var.name.id}}]?
@{{var.name.id}} = %arg if %arg.is_a?({{var.type.id}})
end
{% end %}
{% for method in @type.methods.select { |m| m.name.ends_with?('=') && m.args.size == 1 } %}
{% ivar_name = method.name[0...-1].id %}
if %arg = attributes[:{{ivar_name}}]?
self.{{ivar_name}} = %arg
{% begin %}
%set = false
{% for method in @type.methods.select { |m| m.name.ends_with?('=') && m.args.size == 1 } %}
{% ivar_name = method.name[0...-1].id %}
if arg = attributes[:{{ivar_name}}]?
self.{{ivar_name}} = arg
%set = true
end
{% end %}
unless %set
{% for var in @type.instance_vars %}
if arg = attributes[:{{var.name.id}}]?
@{{var.name.id}} = arg if arg.is_a?({{var.type.id}})
end
{% end %}
end
{% end %}
self
Expand Down Expand Up @@ -274,7 +283,7 @@ module Raven
@interfaces.each do |name, interface|
data[name] = interface.to_hash
end
data.compact!
# data.compact!
data.to_h
end
end
Expand Down
11 changes: 8 additions & 3 deletions src/raven/instance.cr
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ module Raven
client.send_event(event)
end

# FIXME
# @[ThreadLocal]
@last_event_id : String?

Expand All @@ -98,7 +99,7 @@ module Raven
#
# ```
# Raven.capture("boo!") do |event|
# pp event.to_hash
# event.extra.merge! foo: "bar"
# end
# ```
def capture(obj : Exception | String, **options, &block)
Expand All @@ -109,9 +110,13 @@ module Raven
if (event = Event.from(obj, configuration: configuration, context: context))
event.initialize_with **options
yield event
if cb = configuration.async
async = configuration.async
if async.as?(Bool) == true
async = ->(event : Event) { spawn { send_event(event) }; nil }
end
if async.is_a?(Event -> Nil)
begin
cb.call(event)
async.call(event)
rescue ex
logger.error "Async event sending failed: #{ex.message}"
send_event(event)
Expand Down
8 changes: 8 additions & 0 deletions src/raven/integrations/kemal.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
require "kemal"

module Raven
# It's recommended to enable `Configuration#async` when using Kemal.
#
# ```
# Raven.configure do |config|
# # ...
# config.async = true
# end
# ```
module Kemal
# Returns full URL string for `HTTP::Request`.
def self.build_request_url(req : HTTP::Request)
Expand Down
7 changes: 3 additions & 4 deletions src/raven/processors/compact.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ module Raven
class Processor::Compact < Processor
def process(data)
return if data.responds_to?(:empty?) && data.empty?

case data
when AnyHash::JSON
when Hash
data.each do |k, v|
data[k] = process(v)
end
data.compact!
data.to_h
when Hash
process data.to_any_json
when Array
data.map { |v| process(v).as(typeof(v)) }
data.map! { |v| process(v).as(typeof(v)) }
else
data
end
Expand Down
8 changes: 3 additions & 5 deletions src/raven/processors/remove_circular_references.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ module Raven
return "(...)" if visited.includes? data.object_id

case data
when AnyHash::JSON
when Hash
visited << data.to_h.object_id
data.each do |k, v|
data[k] = process(v, visited) rescue "!"
data[k] = process(v, visited) rescue "!!!"
end
data.to_h
when Hash
process data.to_any_json, visited
when Array
data.map { |v| process(v, visited).as(typeof(v)) }
data.map! { |v| process(v, visited).as(typeof(v)) }
else
data
end
Expand Down
Loading

0 comments on commit 0881a56

Please sign in to comment.