squash_ruby 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|