Skip to content

Commit 797ef7e

Browse files
authored
Merge pull request #113 from MindscapeHQ/breadcrumbs-support
Breadcrumbs support
2 parents 795fb70 + 50c19fe commit 797ef7e

20 files changed

+632
-64
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
## 2.1.0
2+
3+
Features
4+
- Ability to record breadcrumbs in your code that will be sent to Raygun along with a raised exception ([#113](https://github.com/MindscapeHQ/raygun4ruby/pull/113))
5+
16
## 2.0.0 (20/04/2017)
27

38
Bugfixes:
49
- Fix broken handling of raw request body reading in Rack applications ([#116](https://github.com/MindscapeHQ/raygun4ruby/pull/116))
510
- This is a breaking change to how raw data was being read before so it requires a major version bump
611
- Raw request data reading is now disabled by default and can be enabled via the `record_raw_data` configuration option
12+
713
## 1.5.0 (16/03/2017)
814

915
Features

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,49 @@ Raygun.setup do |config|
9898
end
9999
```
100100

101+
### Recording Breadcrumbs
102+
103+
Breadcrumbs let you provide logging points in your code that will be collected and sent along with any exception sent to Raygun. This lets you have a better understanding of the events that happened in the system that lead up to the exception.
104+
105+
1. Include it as a module in your class
106+
```ruby
107+
class SomeClass
108+
include Raygun::Breadcrumbs
109+
110+
def some_method
111+
record_breadcrumb(
112+
message: "<log message goes here>",
113+
category: "some category to group them by, maybe authentication or external-apis for example",
114+
level: :info, # or debug or warning etc, you can configure what level will get sent
115+
metadata: {custom_data: 'can go here'},
116+
)
117+
end
118+
end
119+
```
120+
This has the added benefit of recording the class the breadcrumb was recorded from automatically
121+
122+
2. Call the `record_breadcrumb` method manually
123+
```ruby
124+
def some_method
125+
Raygun.record_breadcrumb(
126+
message: "<log message goes here>",
127+
category: "some category to group them by, maybe authentication or external-apis for example",
128+
level: :info, # or debug or warning etc, you can configure what level will get sent
129+
metadata: {custom_data: 'can go here'},
130+
131+
# You can also set the class the breadcrumb was logged from
132+
# It will only be set automatically using the included module approach
133+
# Method and line number will get added automatically
134+
class_name: self.class.name
135+
)
136+
end
137+
```
138+
139+
If you are using Sinatra or another rack framework you will need to include the Breadcrumbs middleware, this is used for storing the breadcrumbs during a request
140+
`use Raygun::Middleware::BreadcrumbsStoreInitializer`
141+
142+
If you are using a non web based Ruby application you will have to call `Raygun::Breadcrumbs::Store.initialize` during your applications boot process. The store is per thread, but I have not tested it in a multi threaded application.
143+
101144
### Filtering the payload by whitelist
102145

103146
As an alternative to the above, you can also opt-in to the keys/values to be sent to Raygun by providing a specific whitelist of the keys you want to transmit.

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace :test do
77

88
desc "Test the basics of the adapter"
99
Rake::TestTask.new(:units) do |t|
10-
t.test_files = FileList["test/unit/*_test.rb"]
10+
t.test_files = FileList["test/unit/*_test.rb", "specs/**/*_spec.rb"]
1111
t.verbose = true
1212
end
1313

lib/raygun.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
require "raygun/configuration"
1515
require "raygun/client"
1616
require "raygun/middleware/rack_exception_interceptor"
17+
require "raygun/middleware/breadcrumbs_store_initializer"
1718
require "raygun/testable"
1819
require "raygun/error"
1920
require "raygun/affected_user"
2021
require "raygun/services/apply_whitelist_filter_to_payload"
22+
require "raygun/breadcrumbs/breadcrumb"
23+
require "raygun/breadcrumbs/store"
24+
require "raygun/breadcrumbs"
2125
require "raygun/railtie" if defined?(Rails)
2226

2327
module Raygun
@@ -78,6 +82,28 @@ def track_exceptions
7882
track_exception(e)
7983
end
8084

85+
def record_breadcrumb(
86+
message: nil,
87+
category: '',
88+
level: :info,
89+
timestamp: Time.now.utc,
90+
metadata: {},
91+
class_name: nil,
92+
method_name: nil,
93+
line_number: nil
94+
)
95+
Raygun::Breadcrumbs::Store.record(
96+
message: message,
97+
category: category,
98+
level: level,
99+
timestamp: timestamp,
100+
metadata: metadata,
101+
class_name: class_name,
102+
method_name: method_name,
103+
line_number: line_number,
104+
)
105+
end
106+
81107
def log(message)
82108
configuration.logger.info(message) if configuration.logger
83109
end

lib/raygun/breadcrumbs.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module Raygun
2+
module Breadcrumbs
3+
BREADCRUMB_LEVELS = [
4+
:debug,
5+
:info,
6+
:warning,
7+
:error,
8+
:fatal
9+
]
10+
11+
def record_breadcrumb(
12+
message: nil,
13+
category: '',
14+
level: :info,
15+
timestamp: Time.now.utc.to_i,
16+
metadata: {},
17+
class_name: nil,
18+
method_name: nil,
19+
line_number: nil
20+
)
21+
class_name = class_name || self.class.name
22+
Raygun::Breadcrumbs::Store.record(
23+
message: message,
24+
category: category,
25+
level: level,
26+
timestamp: timestamp,
27+
metadata: metadata,
28+
class_name: class_name,
29+
method_name: method_name,
30+
line_number: line_number,
31+
)
32+
end
33+
end
34+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module Raygun
2+
module Breadcrumbs
3+
class Breadcrumb
4+
ATTRIBUTES = [
5+
:message, :category, :metadata, :class_name,
6+
:method_name, :line_number, :timestamp, :level,
7+
:type
8+
]
9+
attr_accessor(*ATTRIBUTES)
10+
11+
def build_payload
12+
payload = {
13+
message: message,
14+
category: category,
15+
level: Breadcrumbs::BREADCRUMB_LEVELS.index(level),
16+
CustomData: metadata,
17+
timestamp: timestamp,
18+
type: type
19+
}
20+
21+
payload[:location] = "#{class_name}:#{method_name}" unless class_name == nil
22+
payload[:location] += ":#{line_number}" if payload.has_key?(:location) && line_number != nil
23+
24+
Hash[payload.select do |k, v|
25+
v != nil
26+
end]
27+
end
28+
end
29+
end
30+
end

lib/raygun/breadcrumbs/store.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require_relative 'breadcrumb'
2+
3+
module Raygun
4+
module Breadcrumbs
5+
class Store
6+
def self.initialize
7+
Thread.current[:breadcrumbs] ||= []
8+
end
9+
10+
def self.clear
11+
Thread.current[:breadcrumbs] = nil
12+
end
13+
14+
def self.stored
15+
Thread.current[:breadcrumbs]
16+
end
17+
18+
def self.record(
19+
message: nil,
20+
category: '',
21+
level: :info,
22+
timestamp: Time.now.utc.to_i,
23+
metadata: {},
24+
class_name: nil,
25+
method_name: nil,
26+
line_number: nil
27+
)
28+
raise ArgumentError.new('missing keyword: message') if message == nil
29+
crumb = Breadcrumb.new
30+
31+
crumb.message = message
32+
crumb.category = category
33+
crumb.level = level
34+
crumb.metadata = metadata
35+
crumb.timestamp = timestamp
36+
crumb.type = 'manual'
37+
38+
caller = caller_locations[1]
39+
crumb.class_name = class_name
40+
crumb.method_name = method_name || caller.label
41+
crumb.line_number = line_number || caller.lineno
42+
43+
Thread.current[:breadcrumbs] << crumb if should_record?(crumb)
44+
end
45+
46+
def self.any?
47+
stored != nil && stored.length > 0
48+
end
49+
50+
private
51+
52+
def self.should_record?(crumb)
53+
levels = Raygun::Breadcrumbs::BREADCRUMB_LEVELS
54+
55+
active_level = levels.index(Raygun.configuration.breadcrumb_level)
56+
crumb_level = levels.index(crumb.level) || -1
57+
58+
discard = crumb_level < active_level
59+
60+
if discard && Raygun.configuration.debug
61+
Raygun.log("[Raygun.breadcrumbs] discarding breadcrumb because #{crumb.level} is below active breadcrumb level (#{Raygun.configuration.breadcrumb_level})")
62+
end
63+
64+
!discard
65+
end
66+
end
67+
end
68+
end

lib/raygun/client.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ def build_payload_hash(exception_instance, env = {}, user = nil)
193193
utcOffset: Time.now.utc_offset / 3600
194194
}
195195
}
196+
store = ::Raygun::Breadcrumbs::Store
197+
error_details[:breadcrumbs] = store.stored.map(&:build_payload) if store.any?
196198

197199
error_details.merge!(groupingKey: grouping_key) if grouping_key
198200

lib/raygun/configuration.rb

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,20 @@ def initialize
112112

113113
# set default attribute values
114114
@defaults = OpenStruct.new({
115-
ignore: IGNORE_DEFAULT,
116-
custom_data: {},
117-
tags: [],
118-
enable_reporting: true,
119-
affected_user_method: :current_user,
120-
affected_user_mapping: AffectedUser::DEFAULT_MAPPING,
121-
filter_parameters: DEFAULT_FILTER_PARAMETERS,
122-
filter_payload_with_whitelist: false,
123-
whitelist_payload_shape: DEFAULT_WHITELIST_PAYLOAD_SHAPE,
124-
proxy_settings: {},
125-
debug: false,
126-
api_url: 'https://api.raygun.io/',
127-
record_raw_data: false
115+
ignore: IGNORE_DEFAULT,
116+
custom_data: {},
117+
tags: [],
118+
enable_reporting: true,
119+
affected_user_method: :current_user,
120+
affected_user_mapping: AffectedUser::DEFAULT_MAPPING,
121+
filter_parameters: DEFAULT_FILTER_PARAMETERS,
122+
filter_payload_with_whitelist: false,
123+
whitelist_payload_shape: DEFAULT_WHITELIST_PAYLOAD_SHAPE,
124+
proxy_settings: {},
125+
debug: false,
126+
api_url: 'https://api.raygun.io/',
127+
breadcrumb_level: :info,
128+
record_raw_data: false
128129
})
129130
end
130131

@@ -144,6 +145,18 @@ def silence_reporting=(value)
144145
self.enable_reporting = !value
145146
end
146147

148+
def breadcrumb_level
149+
read_value(:breadcrumb_level)
150+
end
151+
152+
def breadcrumb_level=(value)
153+
if Raygun::Breadcrumbs::BREADCRUMB_LEVELS.include?(value)
154+
set_value(:breadcrumb_level, value)
155+
elsif read_value(:debug)
156+
Raygun.log("[Raygun.configuration] unknown breadcrumb level: #{value} not setting")
157+
end
158+
end
159+
147160
def affected_user_identifier_methods
148161
Raygun.deprecation_warning("Please note: You should now user config.affected_user_method_mapping.Identifier instead of config.affected_user_identifier_methods")
149162
read_value(:affected_user_method_mapping).Identifier
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module Raygun
2+
module Middleware
3+
class BreadcrumbsStoreInitializer
4+
def initialize(app)
5+
@app = app
6+
end
7+
8+
def call(env)
9+
Breadcrumbs::Store.initialize
10+
11+
begin
12+
@app.call(env)
13+
ensure
14+
Breadcrumbs::Store.clear
15+
end
16+
end
17+
end
18+
end
19+
end

0 commit comments

Comments
 (0)