-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Sija/develop
v0.1
- Loading branch information
Showing
27 changed files
with
1,691 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# raven.cr | ||
# raven.cr [![Build Status](https://travis-ci.org/Sija/raven.cr.svg?branch=master)](https://travis-ci.org/Sija/raven.cr) | ||
|
||
TODO: Write a description here | ||
A client and integration layer for the [Sentry](https://github.com/getsentry/sentry) error reporting API. | ||
|
||
## Installation | ||
|
||
|
@@ -18,19 +18,133 @@ dependencies: | |
require "raven" | ||
``` | ||
|
||
TODO: Write usage instructions here | ||
### Raven only runs when SENTRY_DSN is set | ||
|
||
Raven will capture and send exceptions to the Sentry server whenever its DSN is set. This makes environment-based configuration easy - if you don't want to send errors in a certain environment, just don't set the DSN in that environment! | ||
|
||
```bash | ||
# Set your SENTRY_DSN environment variable. | ||
export SENTRY_DSN=http://public:[email protected]/project-id | ||
``` | ||
|
||
```crystal | ||
# Or you can configure the client in the code (not recommended - keep your DSN secret!) | ||
Raven.configure do |config| | ||
config.server = "http://public:[email protected]/project-id" | ||
end | ||
``` | ||
|
||
### Raven doesn't report some kinds of data by default. | ||
|
||
Raven ignores some exceptions by default - most of these are related to 404s or controller actions not being found. [For a complete list, see the `IGNORE_DEFAULT` constant](https://github.com/sija/raven.cr/blob/master/src/raven/configuration.cr). | ||
|
||
Raven doesn't report POST data or cookies by default. In addition, it will attempt to remove any obviously sensitive data, such as credit card or Social Security numbers. For more information about how Sentry processes your data, [check out the documentation on the `processors` config setting.](https://docs.sentry.io/clients/ruby/config/) | ||
|
||
### Call | ||
|
||
Raven supports two methods of capturing exceptions: | ||
|
||
```crystal | ||
Raven.capture do | ||
# capture any exceptions which happen during execution of this block | ||
1 / 0 | ||
end | ||
begin | ||
1 / 0 | ||
rescue exception : DivisionByZero | ||
Raven.capture(exception) | ||
end | ||
``` | ||
|
||
### More configuration | ||
|
||
You're all set - but there's a few more settings you may want to know about too! | ||
|
||
#### DSN | ||
|
||
While we advise that you set your Sentry DSN through the `SENTRY_DSN` environment | ||
variable, there are two other configuration settings for controlling Raven: | ||
|
||
```crystal | ||
# DSN can be configured as a config setting instead. | ||
# Place in config/initializers or similar. | ||
Raven.configure do |config| | ||
config.server = "your_dsn" | ||
end | ||
``` | ||
|
||
And, while not necessary if using `SENTRY_DSN`, you can also provide an `environments` | ||
setting. Raven will only capture events when `KEMAL_ENV` matches an environment in the list. | ||
|
||
```crystal | ||
Raven.configure do |config| | ||
config.environments = %w[staging production] | ||
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 | ||
} | ||
``` | ||
|
||
#### Context | ||
|
||
Much of the usefulness of Sentry comes from additional context data with the events. Raven makes this very convenient by providing methods to set thread local context data that is then submitted automatically with all events. | ||
|
||
There are three primary methods for providing request context: | ||
|
||
```crystal | ||
# bind the logged in user | ||
Raven.user_context email: "[email protected]" | ||
# tag the request with something interesting | ||
Raven.tags_context interesting: "yes" | ||
# provide a bit of additional context | ||
Raven.extra_context happiness: "very" | ||
``` | ||
|
||
For more information, see [Context](https://docs.sentry.io/clients/ruby/context/). | ||
|
||
## TODO | ||
|
||
- [x] Configuration | ||
- [x] Connection to Sentry server | ||
- [ ] Exponential backoff in case of connection error | ||
- [x] Interfaces | ||
- [x] Connection transports | ||
- [x] Processors | ||
- [x] Breadcrumbs | ||
- [ ] Integrations (Kemal, Sidekiq) | ||
- [ ] Async | ||
|
||
## Development | ||
|
||
TODO: Write development instructions here | ||
``` | ||
crystal spec | ||
``` | ||
|
||
## More Information | ||
|
||
* [Documentation](https://docs.sentry.io/clients/ruby) | ||
* [Bug Tracker](https://github.com/sija/raven.cr/issues) | ||
* [Code](https://github.com/sija/raven.cr) | ||
* [Mailing List](https://groups.google.com/group/getsentry) | ||
* [IRC](irc://irc.freenode.net/sentry) (irc.freenode.net, #sentry) | ||
|
||
## Contributing | ||
|
||
1. Fork it ( https://github.com/sija/raven.cr/fork ) | ||
2. Create your feature branch (git checkout -b my-new-feature) | ||
3. Commit your changes (git commit -am 'Add some feature') | ||
4. Push to the branch (git push origin my-new-feature) | ||
5. Create a new Pull Request | ||
1. [Fork it](https://github.com/sija/raven.cr/fork) | ||
2. Create your feature branch (`git checkout -b my-new-feature`) | ||
3. Commit your changes (`git commit -am 'Add some feature'`) | ||
4. Push to the branch (`git push origin my-new-feature`) | ||
5. Create a new [Pull Request](https://github.com/sija/raven.cr/pulls) | ||
|
||
## Contributors | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,11 @@ version: 0.1.0 | |
authors: | ||
- Sijawusz Pur Rahnama <[email protected]> | ||
|
||
crystal: 0.20.5 | ||
dependencies: | ||
any_hash: | ||
github: sija/any_hash.cr | ||
branch: master | ||
|
||
crystal: 0.21.0 | ||
|
||
license: MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,32 @@ | ||
require "any_hash" | ||
require "./raven/*" | ||
|
||
class Exception | ||
any_json_property __raven_context | ||
end | ||
|
||
module Raven | ||
# TODO Put your code here | ||
module Delegators | ||
delegate :context, :logger, :configuration, :client, | ||
:report_status, :configure, :send_event, :capture, | ||
:last_event_id, :annotate_exception, :user_context, | ||
:tags_context, :extra_context, :breadcrumbs, to: :instance | ||
end | ||
end | ||
|
||
module Raven | ||
extend Delegators | ||
class_getter instance : Raven::Instance { Raven::Instance.new } | ||
|
||
def self.sys_command(command) | ||
result = `#{command} 2>&1`.strip rescue nil | ||
return if result.nil? || result.empty? || !$?.success? | ||
result | ||
end | ||
|
||
macro sys_command_compiled(command) | ||
%result = {{ system("#{command.id} || true").stringify.strip }} | ||
return if %result.empty? | ||
%result | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
module Raven | ||
class Backtrace | ||
IGNORED_LINES_PATTERN = /CallStack|caller:|raise<(.+?)>:NoReturn/ | ||
|
||
class_getter default_filters = [ | ||
->(line : String) { line.match(IGNORED_LINES_PATTERN) ? nil : line }, | ||
] of String -> String? | ||
|
||
getter lines : Array(Line) | ||
|
||
def self.parse(backtrace : Array(String), **options) | ||
filters = default_filters | ||
if f = options[:filters]? | ||
filters.concat(f) | ||
end | ||
|
||
filtered_lines = backtrace.map do |line| | ||
filters.reduce(line) do |nested_line, proc| | ||
proc.call(nested_line) || break | ||
end | ||
end.compact | ||
|
||
lines = filtered_lines.map do |unparsed_line| | ||
Line.parse(unparsed_line) | ||
end | ||
new(lines) | ||
end | ||
|
||
def self.parse(backtrace : String, **options) | ||
parse(backtrace.lines, **options) | ||
end | ||
|
||
def initialize(@lines) | ||
end | ||
|
||
def_equals @lines | ||
|
||
def to_s(io) | ||
@lines.join '\n', io | ||
end | ||
|
||
def inspect(io) | ||
io << "<Backtrace: " | ||
@lines.join(", ", io, &.inspect(io)) | ||
io << ">" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
module Raven | ||
# Handles backtrace parsing line by line | ||
struct Backtrace::Line | ||
# Examples: | ||
# | ||
# - `0x103a7bbee: __crystal_main at ??` | ||
# - `0x100e1ea72: *CallStack::unwind:Array(Pointer(Void)) at ??` | ||
# - `0x102dff5e7: *Foo::Bar#_baz:Foo::Bam at /home/fooBAR/code/awesome-shard.cr/lib/foo/src/foo/bar.cr 50:7` | ||
# - `0x102de9035: *Foo::Bar::bar_by_id<String>:Foo::Bam at /home/fooBAR/code/awesome-shard.cr/lib/foo/src/foo/bar.cr 29:9` | ||
# - `0x102cfe8f4: *Fiber#run:(IO::FileDescriptor | Nil) at /usr/local/Cellar/crystal-lang/0.20.5_2/src/fiber.cr 114:3` | ||
CRYSTAL_METHOD_FORMAT = /\*?(?<method>.*?) at (?<file>[^:]+)(?:\s(?<line>\d+)(?:\:(?<col>\d+))?)?$/ | ||
|
||
# Examples: | ||
# | ||
# - `0x102cee376: ~procProc(Nil)@/usr/local/Cellar/crystal-lang/0.20.5_2/src/http/server.cr:148 at ??` | ||
# - `0x102ce57db: ~procProc(HTTP::Server::Context, String)@lib/kemal/src/kemal/route.cr:11 at ??` | ||
# - `0x1002d5180: ~procProc(HTTP::Server::Context, (File::PReader | HTTP::ChunkedContent | HTTP::Server::Response | HTTP::Server::Response::Output | HTTP::UnknownLengthContent | HTTP::WebSocket::Protocol::StreamIO | IO::ARGF | IO::Delimited | IO::FileDescriptor | IO::Hexdump | IO::Memory | IO::MultiWriter | IO::Sized | Int32 | OpenSSL::SSL::Socket | String::Builder | Zip::ChecksumReader | Zip::ChecksumWriter | Zlib::Deflate | Zlib::Inflate | Nil))@src/foo/bar/baz.cr:420 at ??` | ||
CRYSTAL_PROC_FORMAT = /\~(?<proc_method>[^@]+)@(?<proc_file>[^:]+)(?:\:(?<proc_line>\d+)) at \?+$/ | ||
|
||
# See `CRYSTAL_PROC_FORMAT` and `CRYSTAL_METHOD_FORMAT`. | ||
# | ||
# Examples: | ||
# | ||
# - `0x103a7bbee: __crystal_main at ??` | ||
# - `0x102cfe8f4: *Fiber#run:(IO::FileDescriptor | Nil) at /usr/local/Cellar/crystal-lang/0.20.5_2/src/fiber.cr 114:3` | ||
# - `0x102cee376: ~procProc(Nil)@/usr/local/Cellar/crystal-lang/0.20.5_2/src/http/server.cr:148 at ??` | ||
CRYSTAL_INPUT_FORMAT = /^(?<addr>0x[a-z0-9]+): #{CRYSTAL_PROC_FORMAT + CRYSTAL_METHOD_FORMAT}/ | ||
|
||
# The file portion of the line (such as `app/models/user.cr`). | ||
getter file : String? | ||
|
||
# The line number portion of the line. | ||
getter number : Int32? | ||
|
||
# The column number portion of the line. | ||
getter column : Int32? | ||
|
||
# The method of the line (such as index). | ||
getter method : String? | ||
|
||
private def self.empty_marker?(value) | ||
value =~ /^\?+$/ | ||
end | ||
|
||
# Parses a single line of a given backtrace, where *unparsed_line* is | ||
# the raw line from `caller` or some backtrace. | ||
# Returns the parsed backtrace line. | ||
def self.parse(unparsed_line : String) : Line | ||
if match = unparsed_line.match(CRYSTAL_INPUT_FORMAT) | ||
file = match["proc_file"]? || match["file"]? | ||
file = nil if empty_marker?(file) | ||
number = match["proc_line"]? || match["line"]? | ||
column = match["col"]? | ||
method = match["proc_method"]? || match["method"]? | ||
end | ||
# pp match | ||
new(file, number.try(&.to_i), column.try(&.to_i), method) | ||
end | ||
|
||
def initialize(@file, @number, @column, @method) | ||
end | ||
|
||
def_equals_and_hash @file, @number, @column, @method | ||
|
||
# Reconstructs the line in a readable fashion | ||
def to_s(io) | ||
io << '`' << method << '`' if method | ||
if file | ||
io << " at " << file | ||
io << ':' << number if number | ||
end | ||
end | ||
|
||
def inspect(io) | ||
io << "<Line: " << self << ">" | ||
end | ||
|
||
def in_app? | ||
!!(file =~ Raven.configuration.in_app_pattern) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
module Raven | ||
class Breadcrumb | ||
# The type of breadcrumb. The default type is `Type::DEFAULT` which indicates | ||
# no specific handling. Other types are currently: | ||
# - `Type::HTTP` for HTTP requests and | ||
# - `Type::NAVIGATION` for navigation events. | ||
enum Type | ||
DEFAULT | ||
HTTP | ||
NAVIGATION | ||
end | ||
|
||
# Levels are used in the UI to emphasize and deemphasize the crumb. | ||
enum Severity | ||
DEBUG | ||
INFO | ||
WARNING | ||
ERROR | ||
CRITICAL | ||
end | ||
|
||
# A timestamp representing when the breadcrumb occurred. | ||
property timestamp : Time | ||
|
||
# The type of breadcrumb. The default type is `default` which indicates | ||
# no specific handling. Other types are currently: | ||
# - `http` for HTTP requests and | ||
# - `navigation` for navigation events. | ||
property type : Type? | ||
|
||
# If a message is provided it’s rendered as text and the whitespace is preserved. | ||
# Very long text might be abbreviated in the UI. | ||
property message : String? | ||
|
||
# Categories are dotted strings that indicate what the crumb is or where it comes from. | ||
# Typically it’s a module name or a descriptive string. | ||
# For instance `ui.click` could be used to indicate that a click happened | ||
# in the UI or `flask` could be used to indicate that the event originated | ||
# in the Flask framework. | ||
property category : String? | ||
|
||
# This defines the level of the event. If not provided it defaults | ||
# to `info` which is the middle level. | ||
property level : Severity? | ||
|
||
# Data associated with this breadcrumb. Contains a sub-object whose | ||
# contents depend on the breadcrumb `type`. Additional parameters that | ||
# are unsupported by the type are rendered as a key/value table. | ||
any_json_property :data | ||
|
||
def initialize | ||
@timestamp = Time.now | ||
end | ||
|
||
def to_hash | ||
{ | ||
"timestamp" => @timestamp.to_utc.epoch, | ||
"type" => @type.try(&.to_s.downcase), | ||
"message" => @message, | ||
"data" => data.to_h, | ||
"category" => @category, | ||
"level" => @level.try(&.to_s.downcase), | ||
} | ||
end | ||
end | ||
end | ||
|
||
require "./breadcrumbs/*" |
Oops, something went wrong.