squash_ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +202 -0
- data/README.md +232 -0
- data/lib/squash/ruby.rb +531 -0
- data/lib/squash/ruby/exception_additions.rb +55 -0
- metadata +138 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
|
2
|
+
Apache License
|
3
|
+
Version 2.0, January 2004
|
4
|
+
http://www.apache.org/licenses/
|
5
|
+
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
7
|
+
|
8
|
+
1. Definitions.
|
9
|
+
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
12
|
+
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
14
|
+
the copyright owner that is granting the License.
|
15
|
+
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
17
|
+
other entities that control, are controlled by, or are under common
|
18
|
+
control with that entity. For the purposes of this definition,
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
20
|
+
direction or management of such entity, whether by contract or
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
23
|
+
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
25
|
+
exercising permissions granted by this License.
|
26
|
+
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
28
|
+
including but not limited to software source code, documentation
|
29
|
+
source, and configuration files.
|
30
|
+
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
32
|
+
transformation or translation of a Source form, including but
|
33
|
+
not limited to compiled object code, generated documentation,
|
34
|
+
and conversions to other media types.
|
35
|
+
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
37
|
+
Object form, made available under the License, as indicated by a
|
38
|
+
copyright notice that is included in or attached to the work
|
39
|
+
(an example is provided in the Appendix below).
|
40
|
+
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
47
|
+
the Work and Derivative Works thereof.
|
48
|
+
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
50
|
+
the original version of the Work and any modifications or additions
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
62
|
+
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
65
|
+
subsequently incorporated within the Work.
|
66
|
+
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
73
|
+
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
79
|
+
where such license applies only to those patent claims licensable
|
80
|
+
by such Contributor that are necessarily infringed by their
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
83
|
+
institute patent litigation against any entity (including a
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
86
|
+
or contributory patent infringement, then any patent licenses
|
87
|
+
granted to You under this License for that Work shall terminate
|
88
|
+
as of the date such litigation is filed.
|
89
|
+
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
92
|
+
modifications, and in Source or Object form, provided that You
|
93
|
+
meet the following conditions:
|
94
|
+
|
95
|
+
(a) You must give any other recipients of the Work or
|
96
|
+
Derivative Works a copy of this License; and
|
97
|
+
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
99
|
+
stating that You changed the files; and
|
100
|
+
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
103
|
+
attribution notices from the Source form of the Work,
|
104
|
+
excluding those notices that do not pertain to any part of
|
105
|
+
the Derivative Works; and
|
106
|
+
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
109
|
+
include a readable copy of the attribution notices contained
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
112
|
+
of the following places: within a NOTICE text file distributed
|
113
|
+
as part of the Derivative Works; within the Source form or
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
115
|
+
within a display generated by the Derivative Works, if and
|
116
|
+
wherever such third-party notices normally appear. The contents
|
117
|
+
of the NOTICE file are for informational purposes only and
|
118
|
+
do not modify the License. You may add Your own attribution
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
121
|
+
that such additional attribution notices cannot be construed
|
122
|
+
as modifying the License.
|
123
|
+
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
125
|
+
may provide additional or different license terms and conditions
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
129
|
+
the conditions stated in this License.
|
130
|
+
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
134
|
+
this License, without any additional terms or conditions.
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
136
|
+
the terms of any separate license agreement you may have executed
|
137
|
+
with Licensor regarding such Contributions.
|
138
|
+
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
141
|
+
except as required for reasonable and customary use in describing the
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
143
|
+
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
153
|
+
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
159
|
+
incidental, or consequential damages of any character arising as a
|
160
|
+
result of this License or out of the use or inability to use the
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
163
|
+
other commercial damages or losses), even if such Contributor
|
164
|
+
has been advised of the possibility of such damages.
|
165
|
+
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
169
|
+
or other liability obligations and/or rights consistent with this
|
170
|
+
License. However, in accepting such obligations, You may act only
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
175
|
+
of your accepting any such warranty or additional liability.
|
176
|
+
|
177
|
+
END OF TERMS AND CONDITIONS
|
178
|
+
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
180
|
+
|
181
|
+
To apply the Apache License to your work, attach the following
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
183
|
+
replaced with your own identifying information. (Don't include
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
185
|
+
comment syntax for the file format. We also recommend that a
|
186
|
+
file or class name and description of purpose be included on the
|
187
|
+
same "printed page" as the copyright notice for easier
|
188
|
+
identification within third-party archives.
|
189
|
+
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
191
|
+
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
193
|
+
you may not use this file except in compliance with the License.
|
194
|
+
You may obtain a copy of the License at
|
195
|
+
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
197
|
+
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
201
|
+
See the License for the specific language governing permissions and
|
202
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
Squash Client Library: Ruby
|
2
|
+
===========================
|
3
|
+
|
4
|
+
This client library reports exceptions to Squash, the Squarish exception
|
5
|
+
reporting and management system.
|
6
|
+
|
7
|
+
Documentation
|
8
|
+
-------------
|
9
|
+
|
10
|
+
Comprehensive documentation is written in YARD- and Markdown-formatted comments
|
11
|
+
throughout the source. To view this documentation as an HTML site, run
|
12
|
+
`rake doc`.
|
13
|
+
|
14
|
+
For an overview of the various components of Squash, see the website
|
15
|
+
documentation at https://github.com/SquareSquash/web.
|
16
|
+
|
17
|
+
Compatibility
|
18
|
+
-------------
|
19
|
+
|
20
|
+
This library is compatible with Ruby 1.8.6 and later, including Ruby Enterprise
|
21
|
+
Edition.
|
22
|
+
|
23
|
+
Requirements
|
24
|
+
------------
|
25
|
+
|
26
|
+
The only dependency is the `json` gem (http://rubygems.org/gems/json). You can use
|
27
|
+
any JSON gem that conforms to the typical standard (`require 'json';
|
28
|
+
object.to_json`).
|
29
|
+
|
30
|
+
Usage
|
31
|
+
-----
|
32
|
+
|
33
|
+
Add the Squash client to your Gemfile with
|
34
|
+
`gem 'squash_ruby', :require => 'squash/ruby'`. Before you can use Squash, you
|
35
|
+
must configure it (see **Configuration** below). At a minimum, you must specify
|
36
|
+
the API host and which project you are recording exceptions for:
|
37
|
+
|
38
|
+
```` ruby
|
39
|
+
Squash::Ruby.configure :api_key => 'YOUR_API_KEY',
|
40
|
+
:api_host => 'https://your.squash.host',
|
41
|
+
:environment => 'production'
|
42
|
+
````
|
43
|
+
|
44
|
+
To use Squash to manage your exceptions, place a `begin::rescue` statement at
|
45
|
+
the highest level of your code. Inside the `rescue` block, make a call to
|
46
|
+
{Squash::Ruby.notify} with the exception. In general, you probably want to
|
47
|
+
rescue all subclasses of `Object`, so you catch every possible exception.
|
48
|
+
Example:
|
49
|
+
|
50
|
+
```` ruby
|
51
|
+
begin
|
52
|
+
all_of_your_code
|
53
|
+
rescue Object => err
|
54
|
+
Squash::Ruby.notify err
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
````
|
58
|
+
|
59
|
+
In this example the exception is re-raised to take advantage of Ruby's typical
|
60
|
+
last-resort exception handling as well.
|
61
|
+
|
62
|
+
There are many additional features you can take advantage of; see **Additional
|
63
|
+
Features** below.
|
64
|
+
|
65
|
+
### Additional Features
|
66
|
+
|
67
|
+
There are a number of other features you can take advantage of to help you debug
|
68
|
+
your exceptions:
|
69
|
+
|
70
|
+
#### User Data
|
71
|
+
|
72
|
+
Exceptions can be annotated with freeform user data. This data can take any
|
73
|
+
format and have any meaning, typically being relevant to the exception at hand.
|
74
|
+
This is in fact the system that `squash_rails` uses to annotate an exception
|
75
|
+
with information about the Rails request.
|
76
|
+
|
77
|
+
There are multiple ways to add user data to an exception. By default, user data
|
78
|
+
is culled from any instance variables set in the exception. This means that for
|
79
|
+
those exceptions that store additional information in instance variables, you
|
80
|
+
get user data "for free." An example is the `ActiveRecord::RecordInvalid`
|
81
|
+
exception, which stores the invalid record as an instance variable.
|
82
|
+
|
83
|
+
You can also add user data using the {Squash::Ruby.add_user_data} method:
|
84
|
+
|
85
|
+
```` ruby
|
86
|
+
input = gets
|
87
|
+
Squash::Ruby.add_user_data(:input => input) do
|
88
|
+
process_input # may raise an exception
|
89
|
+
end
|
90
|
+
````
|
91
|
+
|
92
|
+
And lastly, if you require `squash/ruby/exception_additions`, you can add user
|
93
|
+
data directly in the exception constructor:
|
94
|
+
|
95
|
+
```` ruby
|
96
|
+
require 'squash/ruby/exception_additions'
|
97
|
+
|
98
|
+
def process_value(value)
|
99
|
+
raise ArgumentError.new("value must be a number", :value => value) unless value.kind_of?(Fixnum)
|
100
|
+
# [...]
|
101
|
+
end
|
102
|
+
````
|
103
|
+
|
104
|
+
Requiring that file also lets you add user data to exceptions you catch and
|
105
|
+
re-raise:
|
106
|
+
|
107
|
+
```` ruby
|
108
|
+
require 'squash/ruby/exception_additions'
|
109
|
+
|
110
|
+
begin
|
111
|
+
do_something_with_input(input)
|
112
|
+
rescue ArgumentError => err
|
113
|
+
err.user_data :input => input
|
114
|
+
raise # assumed that Squash::Ruby.notify is called somewhere further up in the stack
|
115
|
+
end
|
116
|
+
````
|
117
|
+
|
118
|
+
If monkey-patching doesn't appeal to you, then don't load
|
119
|
+
`squash/ruby/exception_additions`; it's not required for the client to work.
|
120
|
+
|
121
|
+
#### Ignoring Exceptions
|
122
|
+
|
123
|
+
You can ignore certain exceptions within a block of code if those exceptions are
|
124
|
+
not worth sending to Squash. Use the {Squash::Ruby.ignore_exceptions} method:
|
125
|
+
|
126
|
+
```` ruby
|
127
|
+
Squash::Ruby.ignore_exceptions(SocketError, Net::HTTPError) do
|
128
|
+
some_http_code_that_could_fail
|
129
|
+
end
|
130
|
+
````
|
131
|
+
|
132
|
+
The exceptions _will_ be raised (not eaten) but will _not_ be reported to
|
133
|
+
Squash.
|
134
|
+
|
135
|
+
You can also globally ignore exceptions using the `ignored_exceptions`
|
136
|
+
configuration; see **Configuration** below.
|
137
|
+
|
138
|
+
Configuration
|
139
|
+
-------------
|
140
|
+
|
141
|
+
You can configure the client with the {Squash::Ruby.configure} method. Calling
|
142
|
+
this method multiple times will merge new values in with the existing
|
143
|
+
configuration. The method takes a hash, which accepts the following (symbol)
|
144
|
+
keys:
|
145
|
+
|
146
|
+
### General
|
147
|
+
|
148
|
+
* `disabled`: If `true`, the Squash client will not report any errors.
|
149
|
+
* `api_key`: The API key of the project that exceptions will be associated with.
|
150
|
+
This configuration option is required. The value can be found by going to the
|
151
|
+
project's home page on Squash.
|
152
|
+
* `environment`: The environment that exceptions will be associated with.
|
153
|
+
* `project_root`: The path to your project's root directory. This path will be
|
154
|
+
stripped from backtrace lines. By default it's set to the working directory.
|
155
|
+
|
156
|
+
### Revision Information
|
157
|
+
|
158
|
+
Squash can determine the current code revision using one of two methods. Specify
|
159
|
+
only one of the following configuration keys:
|
160
|
+
|
161
|
+
* `revision_file`: The path to a file storing the SHA1 of the current Git
|
162
|
+
revision. This is the revision of the code that is currently running.
|
163
|
+
* `repository_root`: The path to the working directory of the Git repository
|
164
|
+
that is currently running. Use this option if your deployed code is a working
|
165
|
+
Git repository.
|
166
|
+
|
167
|
+
By default, `repository_root` is assumed and is set to `Dir.getwd`.
|
168
|
+
`revision_file` overrides `repository_root`.
|
169
|
+
|
170
|
+
### Error Transmission
|
171
|
+
|
172
|
+
* `api_host`: The host on which Squash is running. This field is required.
|
173
|
+
* `notify_path`: The path to post new exception notifications to. By default
|
174
|
+
it's set to `/api/1.0/notify`.
|
175
|
+
* `transmit_timeout`: The amount of time to wait before giving up on trasmitting
|
176
|
+
an error. By default this is treated as both an open and a read timeout.
|
177
|
+
|
178
|
+
### Ignored Exceptions
|
179
|
+
|
180
|
+
* `ignored_exception_classes`: An array of exception class names that will not
|
181
|
+
be reported to Squash.
|
182
|
+
* `ignored_exception_messages`: A hash mapping an exception class name to an
|
183
|
+
array of regexes. Exceptions of that class whose messages match a regex in the
|
184
|
+
list will not be reported to Squash.
|
185
|
+
* `ignored_exception_procs`: An array of `Proc` objects that can be used to
|
186
|
+
filter exceptions. Takes as arguments 1) the exception and 2) the user data
|
187
|
+
hash. Should return `true` if the exception should be ignored (_not_ reported)
|
188
|
+
and false otherwise. The user data hash can include stuff useful to extended
|
189
|
+
client libraries (e.g., Squash Rails client); an example:
|
190
|
+
|
191
|
+
```` ruby
|
192
|
+
Squash::Ruby.configure :ignored_exception_procs => lambda do |exception, user_data|
|
193
|
+
exception.kind_of?(ActiveRecord::RecordNotFound) && user_data[:headers]['X-Testing'].blank?
|
194
|
+
end
|
195
|
+
````
|
196
|
+
|
197
|
+
### Failsafe Reporting
|
198
|
+
|
199
|
+
* `failsafe_log`: The pathname of a log file where failsafe exceptions will be
|
200
|
+
recorded (see **Failsafe Reporting** below). By default, records to a file
|
201
|
+
named `squash.failsafe.log` in the current working directory.
|
202
|
+
* `disable_failsafe`: If `true`, the failsafe handler will be disabled.
|
203
|
+
Exceptions raised when Squash is processing another exception will be handled
|
204
|
+
normally by the Ruby interpreter.
|
205
|
+
|
206
|
+
Error Transmission
|
207
|
+
------------------
|
208
|
+
|
209
|
+
Exceptions are transmitted to Squash using JSON-over-HTTPS. A default API
|
210
|
+
endpoint is pre-configured, though you can always set your own (see
|
211
|
+
**Configuration** above).
|
212
|
+
|
213
|
+
By default, `Net::HTTP` is used to transmit errors to the API server. If you
|
214
|
+
would prefer to use your own HTTP library, you can override the
|
215
|
+
{Squash::Ruby.http_transmit} method. This method is also used for deploy
|
216
|
+
notification.
|
217
|
+
|
218
|
+
Failsafe Reporting
|
219
|
+
------------------
|
220
|
+
|
221
|
+
In the event that the Squash client itself raises an exception when processing
|
222
|
+
an exception, it will log that exception to the failsafe log. (See the
|
223
|
+
`failsafe_log` configuration option, described above.) Both the original
|
224
|
+
exception and the failsafe error will be logged. The original exception will
|
225
|
+
still be re-raised, but the failsafe error will be "eaten."
|
226
|
+
|
227
|
+
If for some reason the exceptions cannot be logged (e.g., a permissions error),
|
228
|
+
they will be logged to standard error.
|
229
|
+
|
230
|
+
It would behoove the engineers of a project using Squash to periodically check
|
231
|
+
the failsafe log, as it may contain exceptions that couldn't be reported due
|
232
|
+
to, e.g., bugs in generating user data.
|
data/lib/squash/ruby.rb
ADDED
@@ -0,0 +1,531 @@
|
|
1
|
+
# Copyright 2012 Square Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'yaml'
|
16
|
+
require 'socket'
|
17
|
+
require 'net/https'
|
18
|
+
|
19
|
+
require 'json'
|
20
|
+
begin
|
21
|
+
Gem::Specification.find_by_name('system_timer')
|
22
|
+
require 'system_timer'
|
23
|
+
rescue Gem::LoadError
|
24
|
+
end
|
25
|
+
|
26
|
+
# Container for methods relating to notifying Squash of exceptions.
|
27
|
+
|
28
|
+
module Squash
|
29
|
+
module Ruby
|
30
|
+
# Reserved instance variables that cannot be keys in a user-data hash.
|
31
|
+
EXCEPTION_RESERVED_IVARS = %W( mesg bt )
|
32
|
+
# Default values for different configuration variables.
|
33
|
+
CONFIGURATION_DEFAULTS = {
|
34
|
+
:notify_path => "/api/1.0/notify",
|
35
|
+
:deploy_path => "/api/1.0/deploy",
|
36
|
+
:open_timeout => 15,
|
37
|
+
:transmit_timeout => 15,
|
38
|
+
:ignored_exception_classes => [],
|
39
|
+
:ignored_exception_messages => {},
|
40
|
+
:ignored_exception_procs => [],
|
41
|
+
:failsafe_log => "squash.failsafe.log",
|
42
|
+
:repository_root => Dir.getwd,
|
43
|
+
:project_root => Dir.getwd
|
44
|
+
}
|
45
|
+
# Types that are serialized directly to JSON, rather than to a hash of
|
46
|
+
# object information. Subclasses are not considered members of this array.
|
47
|
+
JSON_NATIVE_TYPES = [String, NilClass, TrueClass, FalseClass, Integer,
|
48
|
+
Fixnum, Float]
|
49
|
+
# Array of user-data fields that should be moved out of the user data to
|
50
|
+
# become top-level attributes. A Rails client library would expand this
|
51
|
+
# constant to include Rails-specific fields, for example.
|
52
|
+
TOP_LEVEL_USER_DATA = []
|
53
|
+
|
54
|
+
# Notifies Squash of an exception.
|
55
|
+
#
|
56
|
+
# @param [Object] exception The exception. Must at least duck-type an
|
57
|
+
# `Exception` subclass.
|
58
|
+
# @param [Hash] user_data Any additional context-specific information about
|
59
|
+
# the exception.
|
60
|
+
# @return [true, false] Whether the exception was reported to Squash. (Some
|
61
|
+
# exceptions are ignored and not reported to Squash.)
|
62
|
+
# @raise [StandardError] If Squash has not yet been fully configured (see
|
63
|
+
# {.configure}).
|
64
|
+
|
65
|
+
def self.notify(exception, user_data={})
|
66
|
+
occurred = Time.now
|
67
|
+
|
68
|
+
return false if configuration(:disabled)
|
69
|
+
unless exception.respond_to?(:backtrace)
|
70
|
+
failsafe_log 'notify', "Tried to pass notify something other than an exception: #{exception.inspect}"
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
unless exception.backtrace
|
74
|
+
failsafe_log 'notify', "Tried to pass notify an exception with no backtrace: #{exception}"
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
raise "The :api_key configuration is required" unless configuration(:api_key)
|
79
|
+
raise "The :api_host configuration is required" unless configuration(:api_host)
|
80
|
+
raise "The :environment configuration is required" unless configuration(:environment)
|
81
|
+
|
82
|
+
begin
|
83
|
+
exception, parents = unroll(exception)
|
84
|
+
return false if ignored?(exception, user_data)
|
85
|
+
check_user_data user_data
|
86
|
+
|
87
|
+
hsh = exception_info_hash(exception, occurred, user_data, parents)
|
88
|
+
http_transmit configuration(:api_host) + configuration(:notify_path), {}, hsh.inject({}) { |h, (k, v)| h[k.to_s] = v; h }.to_json
|
89
|
+
return true
|
90
|
+
rescue Object => nested_error
|
91
|
+
raise if configuration(:disable_failsafe)
|
92
|
+
failsafe_handler exception, nested_error
|
93
|
+
:failsafe # a perfect example of http://thedailywtf.com/Articles/What_Is_Truth_0x3f_.aspx
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Raises an exception and immediately catches it and sends it to Squash. The
|
98
|
+
# exception is then eaten. This is meant to be used as a hackneyed form of
|
99
|
+
# event logging. You can pass in any user data you wish to record with the
|
100
|
+
# event.
|
101
|
+
#
|
102
|
+
# It should be emphasized that Squash is not a logging system, and there are
|
103
|
+
# far more appropriate products for this kind of thing, but this method is
|
104
|
+
# here nonetheless.
|
105
|
+
#
|
106
|
+
# @overload record(exception_class, message, user_data={})
|
107
|
+
# Specify both the exception class and the message.
|
108
|
+
# @param [Class] exception_class The exception class to raise.
|
109
|
+
# @param [String] message The exception message.
|
110
|
+
# @param [Hash] user_data Additional information to give to {.notify}.
|
111
|
+
# @overload record(message, user_data={})
|
112
|
+
# Specify only the message. The exception class will be `StandardError`.
|
113
|
+
# @param [String] message The exception message.
|
114
|
+
# @param [Hash] user_data Additional information to give to {.notify}.
|
115
|
+
|
116
|
+
def self.record(exception_class_or_message, message_or_options=nil, data=nil)
|
117
|
+
if message_or_options && data
|
118
|
+
exception_class = exception_class_or_message
|
119
|
+
message = message_or_options
|
120
|
+
elsif message_or_options.kind_of?(String)
|
121
|
+
message = message_or_options
|
122
|
+
exception_class = exception_class_or_message
|
123
|
+
elsif message_or_options.kind_of?(Hash)
|
124
|
+
data = message_or_options
|
125
|
+
message = exception_class_or_message
|
126
|
+
exception_class = StandardError
|
127
|
+
elsif message_or_options.nil?
|
128
|
+
message = exception_class_or_message
|
129
|
+
exception_class = StandardError
|
130
|
+
else
|
131
|
+
raise ArgumentError
|
132
|
+
end
|
133
|
+
|
134
|
+
begin
|
135
|
+
raise exception_class, message
|
136
|
+
rescue exception_class => error
|
137
|
+
notify error, data || {}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Suppresses reporting of certain exceptions within a block of code. Any
|
142
|
+
# exceptions raised in the block will continue to be raised, however.
|
143
|
+
#
|
144
|
+
# Let's take a few examples. If `exception_classes` is `[RangeError]`, then
|
145
|
+
# obviously any raised `RangeError`s will not be reported. If
|
146
|
+
# `StandardError` is raised, it _will_ be reported, because it's a
|
147
|
+
# superclass of `RangeError`. If `FloatDomainError` is raised, it _will not_
|
148
|
+
# be reported because it is a _subclass_ of `RangeError`. Confusing? Sure,
|
149
|
+
# but I'm pretty sure this is the behavior most people would expect.
|
150
|
+
#
|
151
|
+
# @param [Array<Class>] exception_classes A list of exception classes to
|
152
|
+
# ignore. If not provided, ignores all exceptions raised in the block.
|
153
|
+
# @yield The code to ignore exceptions in.
|
154
|
+
# @return The result of the block.
|
155
|
+
|
156
|
+
def self.ignore_exceptions(exception_classes=nil)
|
157
|
+
raise ArgumentError, "Squash::Ruby.ignore_exceptions must be called with a block" unless block_given?
|
158
|
+
exception_classes = [exception_classes] if exception_classes.kind_of?(Class)
|
159
|
+
|
160
|
+
begin
|
161
|
+
yield
|
162
|
+
rescue Object => err
|
163
|
+
err.instance_variable_set(:@_squash_do_not_report, true) if exception_classes.nil? || exception_classes.map { |e| e.ancestors }.flatten.include?(err.class)
|
164
|
+
raise
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Adds user data to any exception raised within a block of code, and
|
169
|
+
# re-raises the exception.
|
170
|
+
#
|
171
|
+
# @param [Hash] user_data User data to add to an exception.
|
172
|
+
# @yield The code to run.
|
173
|
+
# @return The result of the block.
|
174
|
+
# @raise [ArgumentError] If `data` contains the keys `mesg` or `bt`.
|
175
|
+
|
176
|
+
def self.add_user_data(user_data)
|
177
|
+
raise ArgumentError, "Squash::Ruby.add_user_data must be called with a block" unless block_given?
|
178
|
+
check_user_data user_data
|
179
|
+
|
180
|
+
begin
|
181
|
+
yield
|
182
|
+
rescue Object => err
|
183
|
+
user_data.each { |ivar, val| err.send :instance_variable_set, :"@#{ivar}", val }
|
184
|
+
raise
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Sets configuration options for the client from a hash. See the README for
|
189
|
+
# a list of configuration options. Subsequent calls will merge in new
|
190
|
+
# configuration options.
|
191
|
+
#
|
192
|
+
# You must at a minimum specify the `:api_key` and `:environment` settings
|
193
|
+
# (see the README.md file).
|
194
|
+
#
|
195
|
+
# @param [Hash] options Configuration options.
|
196
|
+
|
197
|
+
def self.configure(options)
|
198
|
+
@configuration = (@configuration || CONFIGURATION_DEFAULTS.dup).merge(options.inject({}) { |hsh, (k, v)| hsh[(k.to_sym rescue k)] = v; hsh })
|
199
|
+
end
|
200
|
+
|
201
|
+
# @private
|
202
|
+
def self.check_user_data(data)
|
203
|
+
bad_ivars = EXCEPTION_RESERVED_IVARS.select { |name| data.keys.map { |k| k.to_s }.include? name }
|
204
|
+
raise ArgumentError, "The following cannot be used as user-data keys: #{bad_ivars.join(', ')}" unless bad_ivars.empty?
|
205
|
+
end
|
206
|
+
|
207
|
+
protected
|
208
|
+
|
209
|
+
# Posts an exception or deploy notification to the API endpoint. Only POST
|
210
|
+
# requests are supported. This method is used internally only. It is
|
211
|
+
# documented so that, in the event you wish to use an alternative HTTP
|
212
|
+
# library (other than `Net::HTTP`), you can override this method.
|
213
|
+
#
|
214
|
+
# This method will make a `POST` request to the given URL. The request will
|
215
|
+
# contain the given headers and body. It should not eat any exceptions
|
216
|
+
# relating to HTTP connectivity issues.
|
217
|
+
#
|
218
|
+
# Your implementation should also respect the value of the
|
219
|
+
# `transmit_timeout` configuration, which is accessible using
|
220
|
+
# `configuration(:transmit_timeout)`.
|
221
|
+
#
|
222
|
+
# @param [String] url The URL to post to. Could be an HTTP or HTTPS URL.
|
223
|
+
# @param [Hash<String, String>] headers The request headers.
|
224
|
+
# `Content-Type: application/json` is added by default.
|
225
|
+
# @param [String] body The request body.
|
226
|
+
# @return [true, false] Whether or not the response was successful.
|
227
|
+
|
228
|
+
def self.http_transmit(url, headers, body)
|
229
|
+
uri = URI.parse(url)
|
230
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
231
|
+
http_options(uri).each { |k, v| http.send :"#{k}=", v }
|
232
|
+
|
233
|
+
block = lambda do
|
234
|
+
http.start do |http|
|
235
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
236
|
+
request.add_field 'Content-Type', 'application/json'
|
237
|
+
headers.each { |k, v| request.add_field k, v }
|
238
|
+
request.body = body
|
239
|
+
response = http.request request
|
240
|
+
if response.kind_of?(Net::HTTPSuccess)
|
241
|
+
return true
|
242
|
+
else
|
243
|
+
self.failsafe_log 'http_transmit', "Response from server: #{response.code}"
|
244
|
+
return false
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
if defined?(SystemTimer)
|
250
|
+
SystemTimer.timeout_after configuration(:open_timeout), &block
|
251
|
+
else
|
252
|
+
block.call
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Notifies Squash of a new deploy. Squash will then determine which bug
|
257
|
+
# fixes have been deployed and then mark those bugs as fix-deployed.
|
258
|
+
#
|
259
|
+
# @param [String] env The name of the environment that was deployed.
|
260
|
+
# @param [String] revision The repository revision that was deployed.
|
261
|
+
# @param [String] from_host The hostname of the computer that performed the
|
262
|
+
# deploy.
|
263
|
+
# @raise [StandardError] If an invalid response is received from the HTTP
|
264
|
+
# request.
|
265
|
+
|
266
|
+
def self.notify_deploy(env, revision, from_host)
|
267
|
+
return if configuration(:disabled)
|
268
|
+
|
269
|
+
success = http_transmit(
|
270
|
+
configuration(:api_host) + configuration(:deploy_path),
|
271
|
+
{},
|
272
|
+
{
|
273
|
+
'project' => {'api_key' => configuration(:api_key)},
|
274
|
+
'environment' => {'name' => env},
|
275
|
+
'deploy' => {
|
276
|
+
'deployed_at' => Time.now,
|
277
|
+
'revision' => revision,
|
278
|
+
'hostname' => from_host
|
279
|
+
}
|
280
|
+
}.to_json
|
281
|
+
)
|
282
|
+
$stderr.puts "[Squash] Bad response; see failsafe log" unless success
|
283
|
+
rescue Timeout::Error
|
284
|
+
$stderr.puts "[Squash] Timeout when trying to notify of the deploy"
|
285
|
+
end
|
286
|
+
|
287
|
+
# @abstract
|
288
|
+
#
|
289
|
+
# Override this method to filter sensitive information from any data that
|
290
|
+
# Squash intends to add to an occurrence. This method receives every object
|
291
|
+
# that Squash is about to serialize -- instance variables, user data,
|
292
|
+
# (for Rails) sessions, params, etc., just before serialization and
|
293
|
+
# transmission.
|
294
|
+
#
|
295
|
+
# This method gives you the opportunity to alter the object before
|
296
|
+
# serialization, for example to remove sensitive information. It's probably a
|
297
|
+
# good idea to clone the object, modify it, and then return the clone, so that
|
298
|
+
# the original object remains unmodified.
|
299
|
+
#
|
300
|
+
# The base implementation returns `value` unmodified.
|
301
|
+
#
|
302
|
+
# @param value A value that is about to be serialized for transmission.
|
303
|
+
# @return The object to serialize (filtered as necessary). May be a different
|
304
|
+
# object.
|
305
|
+
def self.value_filter(value) value end
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
def self.http_options(uri)
|
310
|
+
options = {:use_ssl => uri.scheme == 'https',
|
311
|
+
:open_timeout => configuration(:transmit_timeout),
|
312
|
+
:read_timeout => configuration(:transmit_timeout)}
|
313
|
+
options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if configuration(:skip_ssl_verification)
|
314
|
+
options
|
315
|
+
end
|
316
|
+
|
317
|
+
def self.configuration(key)
|
318
|
+
(@configuration || CONFIGURATION_DEFAULTS)[key] || CONFIGURATION_DEFAULTS[key]
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.ignored?(exception, user_data)
|
322
|
+
return true if exception.instance_variable_get(:@_squash_do_not_report)
|
323
|
+
|
324
|
+
return true if Array(configuration(:ignored_exception_classes)).map do |class_name|
|
325
|
+
constantize class_name
|
326
|
+
end.compact.any? { |klass| exception.kind_of?(klass) }
|
327
|
+
|
328
|
+
return true if configuration(:ignored_exception_messages).any? do |class_name, ignored_messages|
|
329
|
+
ignored_messages = Array(ignored_messages).map { |str| str.kind_of?(String) ? Regexp.compile(str) : str }
|
330
|
+
(klass = constantize(class_name)) && exception.kind_of?(klass) && ignored_messages.any? { |msg| exception.to_s =~ msg }
|
331
|
+
end
|
332
|
+
|
333
|
+
return true if Array(configuration(:ignored_exception_procs)).any? do |proc|
|
334
|
+
proc.call(exception, user_data)
|
335
|
+
end
|
336
|
+
|
337
|
+
return false
|
338
|
+
end
|
339
|
+
|
340
|
+
def self.exception_info_hash(exception, occurred, user_data, parents)
|
341
|
+
top_level_user_data = Hash.new
|
342
|
+
user_data.delete_if do |key, value|
|
343
|
+
if TOP_LEVEL_USER_DATA.include?(key.to_s)
|
344
|
+
top_level_user_data[key.to_s] = valueify(value, true)
|
345
|
+
true
|
346
|
+
else
|
347
|
+
false
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
environment_data.merge(top_level_user_data).merge(
|
352
|
+
'class_name' => exception.class.to_s,
|
353
|
+
'message' => exception.to_s,
|
354
|
+
'backtraces' => [["Active Thread/Fiber", true, prepare_backtrace(exception.backtrace)]],
|
355
|
+
'occurred_at' => occurred,
|
356
|
+
'revision' => current_revision,
|
357
|
+
|
358
|
+
'environment' => configuration(:environment).to_s,
|
359
|
+
'api_key' => configuration(:api_key).to_s,
|
360
|
+
'client' => client_name,
|
361
|
+
|
362
|
+
'ivars' => instance_variable_hash(exception),
|
363
|
+
'user_data' => valueify(user_data, true),
|
364
|
+
|
365
|
+
'parent_exceptions' => parents.nil? ? nil : parents.map do |parent|
|
366
|
+
{'class_name' => parent.class.to_s,
|
367
|
+
'message' => parent.to_s,
|
368
|
+
'backtraces' => [["Active Thread/Fiber", true, prepare_backtrace(parent.backtrace)]],
|
369
|
+
'association' => 'original_exception',
|
370
|
+
'ivars' => instance_variable_hash(parent)}
|
371
|
+
end
|
372
|
+
)
|
373
|
+
end
|
374
|
+
|
375
|
+
def self.prepare_backtrace(bt)
|
376
|
+
if defined?(JRuby)
|
377
|
+
bt.map do |element|
|
378
|
+
if element =~ /^((?:[a-z0-9_$]+\.)*(?:[a-z0-9_$]+))\.(\w+)\((\w+.java):(\d+)\)$/i
|
379
|
+
# special JRuby backtrace element of the form "org.jruby.RubyHash$27.visit(RubyHash.java:1646)"
|
380
|
+
['_JAVA_', $3, $4.to_i, $2, $1]
|
381
|
+
else
|
382
|
+
if element.include?(' at ')
|
383
|
+
method, fileline = element.split(' at ')
|
384
|
+
method.lstrip!
|
385
|
+
file, line = fileline.split(':')
|
386
|
+
else
|
387
|
+
file, line, method = element.split(':')
|
388
|
+
if method =~ /^in `(.+)'$/
|
389
|
+
method = $1
|
390
|
+
end
|
391
|
+
method = nil if method && method.empty?
|
392
|
+
end
|
393
|
+
line = line.to_i
|
394
|
+
line = nil if line < 1
|
395
|
+
if method =~ /^in `(.+)'$/
|
396
|
+
method = $1
|
397
|
+
end
|
398
|
+
method = nil if method && method.empty?
|
399
|
+
|
400
|
+
# it could still be a java backtrace, even if it's not the special format
|
401
|
+
if file[-5, 5] == '.java'
|
402
|
+
['_JAVA_', file.split('/').last, line, method, file.sub(/\.java$/, '').gsub('/', '.')]
|
403
|
+
else
|
404
|
+
# ok now we're sure it's a ruby backtrace
|
405
|
+
file.slice! 0, configuration(:project_root).length + 1 if file[0, configuration(:project_root).length + 1] == configuration(:project_root) + '/'
|
406
|
+
[file, line, method]
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
else
|
411
|
+
bt.map do |element|
|
412
|
+
file, line, method = element.split(':')
|
413
|
+
line = line.to_i
|
414
|
+
line = nil if line < 1
|
415
|
+
|
416
|
+
file.slice! 0, configuration(:project_root).length + 1 if file[0, configuration(:project_root).length + 1] == configuration(:project_root) + '/'
|
417
|
+
|
418
|
+
if method =~ /^in `(.+)'$/
|
419
|
+
method = $1
|
420
|
+
end
|
421
|
+
method = nil if method && method.empty?
|
422
|
+
[file, line, method]
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def self.valueify(instance, elements_only=false)
|
428
|
+
if JSON_NATIVE_TYPES.any? { |klass| instance.class == klass }
|
429
|
+
instance
|
430
|
+
elsif instance.kind_of?(Hash) && elements_only
|
431
|
+
instance.inject({}) { |hsh, (k, v)| hsh[k.to_s] = valueify(v); hsh }
|
432
|
+
elsif instance.kind_of?(Array) && elements_only
|
433
|
+
instance.map { |i| valueify(i) }
|
434
|
+
else
|
435
|
+
filtered = value_filter(instance)
|
436
|
+
{
|
437
|
+
'language' => 'ruby',
|
438
|
+
'class_name' => filtered.class.to_s,
|
439
|
+
'inspect' => filtered.inspect,
|
440
|
+
'yaml' => (filtered.to_yaml rescue nil),
|
441
|
+
'json' => (filtered.to_json rescue nil),
|
442
|
+
'to_s' => (filtered.to_s rescue nil)
|
443
|
+
}
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def self.instance_variable_hash(object)
|
448
|
+
object.instance_variables.inject({}) do |hsh, cur|
|
449
|
+
hsh[cur.to_s[1..-1]] = valueify(object.send(:instance_variable_get, cur.to_sym))
|
450
|
+
hsh
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def self.constantize(class_name)
|
455
|
+
return class_name if class_name.kind_of?(Class)
|
456
|
+
|
457
|
+
parts = class_name.split('::').reject { |i| i.empty? }
|
458
|
+
constant = Object
|
459
|
+
while parts.any? do
|
460
|
+
begin
|
461
|
+
constant = constant.const_get(parts.shift)
|
462
|
+
rescue NameError
|
463
|
+
return nil
|
464
|
+
end
|
465
|
+
end
|
466
|
+
constant
|
467
|
+
end
|
468
|
+
|
469
|
+
# @private
|
470
|
+
def self.environment_data
|
471
|
+
{
|
472
|
+
'pid' => Process.pid,
|
473
|
+
'hostname' => Socket.gethostname,
|
474
|
+
'env_vars' => ENV.inject({}) { |hsh, (k, v)| hsh[k.to_s] = valueify(v); hsh },
|
475
|
+
'arguments' => ARGV.join(' ')
|
476
|
+
}
|
477
|
+
end
|
478
|
+
|
479
|
+
# @private
|
480
|
+
def self.failsafe_handler(original_error, nested_error)
|
481
|
+
log_entries = [
|
482
|
+
"#{Time.now.to_s} - Original error: (#{original_error.class.to_s}) #{original_error.to_s}",
|
483
|
+
(original_error.backtrace || []).map { |l| ' ' + l }.join("\n"),
|
484
|
+
"#{Time.now.to_s} - Error raised when reporting original error: (#{nested_error.class.to_s}) #{nested_error.to_s}",
|
485
|
+
nested_error.backtrace.map { |l| ' ' + l }.join("\n"),
|
486
|
+
"--- END SQUASH FAILSAFE ERROR ---"
|
487
|
+
]
|
488
|
+
log_entries.each { |message| self.failsafe_log 'failsafe_handler', message }
|
489
|
+
end
|
490
|
+
|
491
|
+
# @private
|
492
|
+
def self.failsafe_log(tag, message)
|
493
|
+
File.open(configuration(:failsafe_log), 'a') do |f|
|
494
|
+
f.puts "#{Time.now.to_s}\t[#{tag}]\t#{message}"
|
495
|
+
end
|
496
|
+
rescue Object => err
|
497
|
+
$stderr.puts "Couldn't write to failsafe log (#{err.to_s}); writing to stderr instead."
|
498
|
+
$stderr.puts "#{Time.now.to_s}\t[#{tag}]\t#{message}"
|
499
|
+
end
|
500
|
+
|
501
|
+
# @private
|
502
|
+
def self.current_revision
|
503
|
+
revision = if configuration(:revision_file)
|
504
|
+
File.read(configuration(:revision_file)).chomp.strip
|
505
|
+
else
|
506
|
+
head_file = File.join(configuration(:repository_root), '.git', 'HEAD')
|
507
|
+
if File.exist?(head_file)
|
508
|
+
rev = File.read(head_file).chomp.strip
|
509
|
+
rev = File.read(File.join(configuration(:repository_root), '.git', $1)).chomp.strip if rev =~ /^ref: (.+?)$/
|
510
|
+
rev
|
511
|
+
else
|
512
|
+
raise "You must set the :revision_file configuration if the code is not running in a Git checkout"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
raise "Unknown Git revision #{revision.inspect}" unless revision =~ /^[0-9a-f]{40}$/
|
516
|
+
revision
|
517
|
+
end
|
518
|
+
|
519
|
+
# @private
|
520
|
+
def self.unroll(exception)
|
521
|
+
if exception.respond_to?(:original_exception)
|
522
|
+
[exception.original_exception, [exception]]
|
523
|
+
else
|
524
|
+
[exception, nil]
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
# @private
|
529
|
+
def self.client_name() 'ruby' end
|
530
|
+
end
|
531
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Copyright 2012 Square Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Reopens the `Exception` class to add a convenient way of appending user data
|
16
|
+
# to an exception at the time of the raise.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# raise ArgumentError.new("value must be a number", :value => value) unless value.kind_of?(Fixnum)
|
20
|
+
|
21
|
+
class Exception
|
22
|
+
|
23
|
+
# @overload new(message, user_data={})
|
24
|
+
# Creates a new exception instance, optionally with user data.
|
25
|
+
# @param [String] message The exception message.
|
26
|
+
# @param [Hash] user_data Additional data to report to Squash about the
|
27
|
+
# exception.
|
28
|
+
# @return [Exception] The initialized exception.
|
29
|
+
# @raise [ArgumentError] If `data` contains the keys `mesg` or `bt`.
|
30
|
+
|
31
|
+
def self.new(*args)
|
32
|
+
user_data = if args.last.is_a?(Hash)
|
33
|
+
args.pop
|
34
|
+
else
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
Squash::Ruby.check_user_data user_data
|
38
|
+
super(*args).user_data(user_data)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Annotates this exception with user data. Merges in any new data with
|
42
|
+
# existing user data.
|
43
|
+
#
|
44
|
+
# @param [Hash] data The user data to add.
|
45
|
+
# @return [Exception] The receiver.
|
46
|
+
# @raise [ArgumentError] If `data` contains the keys `mesg` or `bt`.
|
47
|
+
|
48
|
+
def user_data(data)
|
49
|
+
Squash::Ruby.check_user_data data
|
50
|
+
data.each do |ivar, value|
|
51
|
+
instance_variable_set :"@#{ivar}", value
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: squash_ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Tim Morgan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-12-07 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 3
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
name: json
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 3
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
type: :development
|
46
|
+
name: rspec
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :development
|
60
|
+
name: yard
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
type: :development
|
74
|
+
name: redcarpet
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
prerelease: false
|
78
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: 3
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
type: :development
|
88
|
+
name: jeweler
|
89
|
+
version_requirements: *id005
|
90
|
+
description: This client library records Ruby exceptions to Squash.
|
91
|
+
email: tim@squareup.com
|
92
|
+
executables: []
|
93
|
+
|
94
|
+
extensions: []
|
95
|
+
|
96
|
+
extra_rdoc_files:
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.md
|
99
|
+
files:
|
100
|
+
- LICENSE.txt
|
101
|
+
- README.md
|
102
|
+
- lib/squash/ruby.rb
|
103
|
+
- lib/squash/ruby/exception_additions.rb
|
104
|
+
homepage: http://github.com/SquareSquash/ruby
|
105
|
+
licenses:
|
106
|
+
- Apache 2.0
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
118
|
+
segments:
|
119
|
+
- 0
|
120
|
+
version: "0"
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
requirements: []
|
131
|
+
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.24
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: Squash client for Ruby projects
|
137
|
+
test_files: []
|
138
|
+
|