raises 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +14 -1
- data/lib/raises/delivery.rb +15 -12
- data/lib/raises/railtie.rb +1 -1
- data/lib/raises/spool.rb +3 -3
- data/lib/raises/spool_storage.rb +16 -3
- data/lib/raises/subscriber.rb +39 -4
- data/lib/raises/version.rb +1 -1
- data/lib/raises.rb +9 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 826d161b194d0bb9f3d3b1e00f3326dc986da4efb420e9ae1c8ff0dccf59708e
|
|
4
|
+
data.tar.gz: b0b9b1dbe0e03571e0585c6982e59091495337e14118765a0ff1274427a1b956
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee447f476885abbda2516cbe5b92600b824b879507954f23c8f276225adb718f78652f2cc53bf79e1bf55fe11a6203bad9519b4adffbbcb64e4eae20ca9b653a
|
|
7
|
+
data.tar.gz: 15d056b1826db28175a104359215f1a3eebd7497358a8c260057b0811e5a3758bef330c9412372393f7de077a81ea7741bcd8a82a20a9e0f2a0e0898c14c6098
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- Add `Raises.notify` for informational, warning, and error-level events.
|
|
6
|
+
- Preserve event and exception routes in the optional disk spool.
|
|
7
|
+
- Keep informational events separate from exception grouping and GitHub issues.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
- Add opt-in, bounded disk spooling through `RAISES_SPOOL_DIR`.
|
data/README.md
CHANGED
|
@@ -8,8 +8,21 @@ gem "raises"
|
|
|
8
8
|
|
|
9
9
|
Set `RAISES_TOKEN` to the ingestion credential created for the project. Production errors are reported to `https://raises.dev` automatically through `Rails.error`. Set `RAISES_REPORT=1` to exercise the integration outside production.
|
|
10
10
|
|
|
11
|
+
Send a one-off operational notice without raising an exception:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
Raises.notify(
|
|
15
|
+
"Import finished",
|
|
16
|
+
level: :info,
|
|
17
|
+
source: "nightly-import",
|
|
18
|
+
context: { imported: 412, skipped: 3 }
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Valid levels are `:info`, `:warning`, and `:error`. Notices are retained and delivered to configured outbound integrations, but they do not create error groups, acknowledgements, or GitHub issues.
|
|
23
|
+
|
|
11
24
|
Optional settings are `RAISES_URL`, `RAISES_ENV`, `RAISES_REVISION`, `RAISES_OPEN_TIMEOUT`, and `RAISES_READ_TIMEOUT`.
|
|
12
25
|
|
|
13
|
-
Set `RAISES_SPOOL_DIR` to add restart-safe, disk-backed delivery retries. The directory must be on persistent storage if
|
|
26
|
+
Set `RAISES_SPOOL_DIR` to add restart-safe, disk-backed delivery retries. The directory must be on persistent storage if payloads should survive a deploy. Raises stores event or exception JSON there with private permissions, never stores the ingestion token, retries network errors, HTTP 408/429, and 5xx responses, and bounds the spool at 1,000 payloads or 100 MB. Leave it unset to preserve synchronous best-effort delivery.
|
|
14
27
|
|
|
15
28
|
The reporter fails open: network or serialization failures never replace the application exception. Rails filtered parameters are included; headers, raw bodies, query strings, and the request object are not.
|
data/lib/raises/delivery.rb
CHANGED
|
@@ -10,33 +10,36 @@ module Raises
|
|
|
10
10
|
@warn = warn
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def call(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
13
|
+
def call(payload, path: "v1/notices")
|
|
14
|
+
item = { "path" => path, "payload" => payload }
|
|
15
|
+
response = @post.call(path, payload)
|
|
16
|
+
return true unless response.respond_to?(:code)
|
|
17
|
+
return true if response.is_a?(Net::HTTPSuccess) || response.code.to_i.between?(200, 299)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
response
|
|
19
|
+
response_accepted?(item, response.code.to_i)
|
|
20
20
|
rescue StandardError => e
|
|
21
|
-
|
|
22
|
-
nil
|
|
21
|
+
exception_accepted?(item || { "path" => path, "payload" => payload }, e)
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
private
|
|
26
25
|
|
|
27
|
-
def
|
|
28
|
-
if retryable_status?(status) && @spool&.enqueue(
|
|
26
|
+
def response_accepted?(item, status)
|
|
27
|
+
if retryable_status?(status) && @spool&.enqueue(item)
|
|
29
28
|
@warn.call("raises queued notice after HTTP #{status}")
|
|
29
|
+
true
|
|
30
30
|
else
|
|
31
31
|
@warn.call("raises rejected notice: HTTP #{status}")
|
|
32
|
+
false
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
def
|
|
36
|
-
if @spool&.enqueue(
|
|
36
|
+
def exception_accepted?(item, error)
|
|
37
|
+
if @spool&.enqueue(item)
|
|
37
38
|
@warn.call("raises queued notice after #{error.class}")
|
|
39
|
+
true
|
|
38
40
|
else
|
|
39
41
|
@warn.call("raises subscriber failed: #{error.class}: #{error.message}")
|
|
42
|
+
false
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
45
|
|
data/lib/raises/railtie.rb
CHANGED
data/lib/raises/spool.rb
CHANGED
|
@@ -38,8 +38,8 @@ module Raises
|
|
|
38
38
|
thread&.join(1)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def enqueue(
|
|
42
|
-
accepted = @storage.store(
|
|
41
|
+
def enqueue(item)
|
|
42
|
+
accepted = @storage.store(item) == :stored
|
|
43
43
|
if accepted
|
|
44
44
|
start if @start_on_enqueue
|
|
45
45
|
wake if @start_on_enqueue
|
|
@@ -69,7 +69,7 @@ module Raises
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def deliver(path, envelope)
|
|
72
|
-
response = @deliver.call(envelope.fetch("
|
|
72
|
+
response = @deliver.call(envelope.fetch("path"), envelope.fetch("payload"))
|
|
73
73
|
status = response_code(response)
|
|
74
74
|
if status.between?(200, 299)
|
|
75
75
|
@storage.delete(path)
|
data/lib/raises/spool_storage.rb
CHANGED
|
@@ -16,9 +16,15 @@ module Raises
|
|
|
16
16
|
prepare
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def store(
|
|
19
|
+
def store(item)
|
|
20
20
|
prepare
|
|
21
|
-
raw = JSON.generate(
|
|
21
|
+
raw = JSON.generate(
|
|
22
|
+
"version" => 2,
|
|
23
|
+
"attempts" => 0,
|
|
24
|
+
"next_attempt_at" => @now.call.to_f,
|
|
25
|
+
"path" => item.fetch("path"),
|
|
26
|
+
"payload" => item.fetch("payload")
|
|
27
|
+
)
|
|
22
28
|
with_directory_lock do
|
|
23
29
|
return :full unless capacity_for?(raw)
|
|
24
30
|
|
|
@@ -107,7 +113,14 @@ module Raises
|
|
|
107
113
|
|
|
108
114
|
def parse(raw, path)
|
|
109
115
|
envelope = JSON.parse(raw)
|
|
110
|
-
return envelope if envelope.is_a?(Hash) && envelope["
|
|
116
|
+
return envelope if envelope.is_a?(Hash) && envelope["path"].is_a?(String) && envelope["payload"].is_a?(Hash)
|
|
117
|
+
|
|
118
|
+
if envelope.is_a?(Hash) && envelope["notice"].is_a?(Hash)
|
|
119
|
+
envelope["version"] = 2
|
|
120
|
+
envelope["path"] = "v1/notices"
|
|
121
|
+
envelope["payload"] = envelope.delete("notice")
|
|
122
|
+
return envelope
|
|
123
|
+
end
|
|
111
124
|
|
|
112
125
|
raise JSON::ParserError, "invalid envelope"
|
|
113
126
|
rescue JSON::ParserError, TypeError => e
|
data/lib/raises/subscriber.rb
CHANGED
|
@@ -5,6 +5,8 @@ require "net/http"
|
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
7
|
module Raises
|
|
8
|
+
# Rails.error integration and explicit application notices share one delivery path.
|
|
9
|
+
# rubocop:disable Metrics/ClassLength
|
|
8
10
|
class Subscriber
|
|
9
11
|
def initialize
|
|
10
12
|
@spool = build_spool
|
|
@@ -31,13 +33,45 @@ module Raises
|
|
|
31
33
|
request: request_from(context)
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
@delivery.call(notice)
|
|
36
|
+
@delivery.call(notice, path: "v1/notices")
|
|
35
37
|
rescue StandardError => e
|
|
36
38
|
warn("raises subscriber failed: #{e.class}: #{e.message}")
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def notify(message, level: :info, context: {}, source: nil)
|
|
43
|
+
message = message.to_s.strip
|
|
44
|
+
level = level.to_s
|
|
45
|
+
validate_event!(message, level, context, source)
|
|
46
|
+
return false unless report?
|
|
47
|
+
|
|
48
|
+
start
|
|
49
|
+
event = {
|
|
50
|
+
env: env_name,
|
|
51
|
+
revision: revision,
|
|
52
|
+
level: level,
|
|
53
|
+
message: message,
|
|
54
|
+
source: source,
|
|
55
|
+
context: json_safe(context)
|
|
56
|
+
}
|
|
57
|
+
@delivery.call(event, path: "v1/events")
|
|
58
|
+
rescue ArgumentError
|
|
59
|
+
raise
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
warn("raises notify failed: #{e.class}: #{e.message}")
|
|
62
|
+
false
|
|
37
63
|
end
|
|
38
64
|
|
|
39
65
|
private
|
|
40
66
|
|
|
67
|
+
def validate_event!(message, level, context, source)
|
|
68
|
+
raise ArgumentError, "message is required" if message.empty?
|
|
69
|
+
raise ArgumentError, "message is too long" if message.length > 2_000
|
|
70
|
+
raise ArgumentError, "level must be info, warning, or error" unless %w[info warning error].include?(level)
|
|
71
|
+
raise ArgumentError, "source is too long" if source.to_s.length > 120
|
|
72
|
+
raise ArgumentError, "context must be a hash" unless context.respond_to?(:each_pair)
|
|
73
|
+
end
|
|
74
|
+
|
|
41
75
|
def build_spool
|
|
42
76
|
directory = ENV["RAISES_SPOOL_DIR"].to_s
|
|
43
77
|
return if directory.empty?
|
|
@@ -132,8 +166,8 @@ module Raises
|
|
|
132
166
|
end
|
|
133
167
|
end
|
|
134
168
|
|
|
135
|
-
def post(
|
|
136
|
-
uri = URI.join(url.end_with?("/") ? url : "#{url}/",
|
|
169
|
+
def post(path, payload)
|
|
170
|
+
uri = URI.join(url.end_with?("/") ? url : "#{url}/", path)
|
|
137
171
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
138
172
|
http.use_ssl = uri.scheme == "https"
|
|
139
173
|
http.open_timeout = timeout("RAISES_OPEN_TIMEOUT", 1)
|
|
@@ -142,7 +176,7 @@ module Raises
|
|
|
142
176
|
request["Authorization"] = "Bearer #{token}"
|
|
143
177
|
request["Content-Type"] = "application/json"
|
|
144
178
|
request["User-Agent"] = "raises-ruby/#{Raises::VERSION}"
|
|
145
|
-
request.body = JSON.generate(
|
|
179
|
+
request.body = JSON.generate(payload)
|
|
146
180
|
http.request(request)
|
|
147
181
|
end
|
|
148
182
|
|
|
@@ -153,4 +187,5 @@ module Raises
|
|
|
153
187
|
fallback
|
|
154
188
|
end
|
|
155
189
|
end
|
|
190
|
+
# rubocop:enable Metrics/ClassLength
|
|
156
191
|
end
|
data/lib/raises/version.rb
CHANGED
data/lib/raises.rb
CHANGED
|
@@ -8,4 +8,13 @@ require "raises/subscriber"
|
|
|
8
8
|
require "raises/railtie" if defined?(Rails::Railtie)
|
|
9
9
|
|
|
10
10
|
module Raises
|
|
11
|
+
class << self
|
|
12
|
+
def notify(message, level: :info, context: {}, source: nil)
|
|
13
|
+
subscriber.notify(message, level: level, context: context, source: source)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def subscriber
|
|
17
|
+
@subscriber ||= Subscriber.new.start
|
|
18
|
+
end
|
|
19
|
+
end
|
|
11
20
|
end
|