errorgap 0.8.0 → 0.10.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 +70 -0
- data/README.md +56 -1
- data/exe/errorgap +17 -1
- data/lib/errorgap/configuration.rb +49 -2
- data/lib/errorgap/delivery.rb +248 -0
- data/lib/errorgap/integrations/active_job.rb +58 -0
- data/lib/errorgap/integrations/delayed_job.rb +75 -0
- data/lib/errorgap/integrations/net_http.rb +50 -0
- data/lib/errorgap/integrations/rake.rb +44 -0
- data/lib/errorgap/integrations/resque.rb +45 -0
- data/lib/errorgap/integrations/sidekiq.rb +56 -0
- data/lib/errorgap/log_delivery.rb +3 -25
- data/lib/errorgap/notice.rb +10 -21
- data/lib/errorgap/notifier.rb +30 -21
- data/lib/errorgap/rack_middleware.rb +48 -5
- data/lib/errorgap/rails/railtie.rb +15 -0
- data/lib/errorgap/release_detector.rb +69 -0
- data/lib/errorgap/remote_settings.rb +99 -0
- data/lib/errorgap/sanitizer.rb +91 -0
- data/lib/errorgap/sign_ins.rb +2 -19
- data/lib/errorgap/transacter.rb +3 -24
- data/lib/errorgap/version.rb +1 -1
- data/lib/errorgap.rb +119 -6
- metadata +12 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Errorgap
|
|
8
|
+
# The project's remote settings: what errorgap wants this app to send.
|
|
9
|
+
# Someone can turn errors, performance data or logs off for a project in
|
|
10
|
+
# errorgap (Project settings › What SDKs send) and running apps follow
|
|
11
|
+
# within a poll interval, with no deploy.
|
|
12
|
+
#
|
|
13
|
+
# Read from `GET /api/projects/:slug/config` on a background thread, never
|
|
14
|
+
# on the caller's. Until the first answer, and whenever errorgap cannot be
|
|
15
|
+
# reached, everything stays on.
|
|
16
|
+
class RemoteSettings
|
|
17
|
+
DEFAULT_INTERVAL = 600
|
|
18
|
+
MIN_INTERVAL = 60
|
|
19
|
+
KINDS = { errors: "errors_enabled", apm: "apm_enabled", logs: "logs_enabled" }.freeze
|
|
20
|
+
|
|
21
|
+
def initialize(configuration)
|
|
22
|
+
@configuration = configuration
|
|
23
|
+
@mutex = Mutex.new
|
|
24
|
+
@settings = {}
|
|
25
|
+
@interval = DEFAULT_INTERVAL
|
|
26
|
+
@thread = nil
|
|
27
|
+
@pid = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def configure(configuration)
|
|
31
|
+
@configuration = configuration
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# False only when errorgap has said to stop sending this kind of data.
|
|
35
|
+
def enabled?(kind)
|
|
36
|
+
return true unless @configuration.remote_settings
|
|
37
|
+
|
|
38
|
+
ensure_polling
|
|
39
|
+
@settings[KINDS.fetch(kind)] != false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Fetch now, on this thread. Returns the settings, or nil when they
|
|
43
|
+
# could not be read.
|
|
44
|
+
def refresh
|
|
45
|
+
uri = URI.join(base_url, "api/projects/#{@configuration.project_slug}/config")
|
|
46
|
+
request = Net::HTTP::Get.new(uri)
|
|
47
|
+
request["User-Agent"] = "errorgap-ruby/#{Errorgap::VERSION}"
|
|
48
|
+
request["X-Errorgap-Project-Key"] = @configuration.api_key.to_s unless @configuration.api_key.to_s.empty?
|
|
49
|
+
timeout = @configuration.timeout.to_f.positive? ? @configuration.timeout.to_f : 5
|
|
50
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https",
|
|
51
|
+
open_timeout: timeout, read_timeout: timeout) do |http|
|
|
52
|
+
http.request(request)
|
|
53
|
+
end
|
|
54
|
+
return nil unless response.code.to_i == 200
|
|
55
|
+
|
|
56
|
+
apply(JSON.parse(response.body))
|
|
57
|
+
rescue StandardError
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def apply(body)
|
|
64
|
+
return nil unless body.is_a?(Hash)
|
|
65
|
+
|
|
66
|
+
@settings = body.slice(*KINDS.values)
|
|
67
|
+
seconds = body["poll_interval_seconds"].to_i
|
|
68
|
+
@interval = seconds >= MIN_INTERVAL ? seconds : DEFAULT_INTERVAL
|
|
69
|
+
@settings
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A forked child (Puma workers, Sidekiq) has the settings but not the
|
|
73
|
+
# thread: start its own.
|
|
74
|
+
def ensure_polling
|
|
75
|
+
return if @pid == Process.pid && @thread&.alive?
|
|
76
|
+
return if @configuration.project_slug.to_s.strip.empty?
|
|
77
|
+
|
|
78
|
+
@mutex.synchronize do
|
|
79
|
+
return if @pid == Process.pid && @thread&.alive?
|
|
80
|
+
|
|
81
|
+
@pid = Process.pid
|
|
82
|
+
@thread = Thread.new do
|
|
83
|
+
loop do
|
|
84
|
+
Thread.current[:errorgap_sending] = true
|
|
85
|
+
refresh
|
|
86
|
+
sleep(@interval)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
@thread.name = "errorgap-settings" if @thread.respond_to?(:name=)
|
|
90
|
+
@thread.report_on_exception = false if @thread.respond_to?(:report_on_exception=)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def base_url
|
|
95
|
+
endpoint = @configuration.endpoint.to_s
|
|
96
|
+
endpoint.end_with?("/") ? endpoint : "#{endpoint}/"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Errorgap
|
|
4
|
+
# Makes caller-supplied data safe to serialize: masks sensitive keys at any
|
|
5
|
+
# depth, cuts long strings and large or deeply nested collections, replaces
|
|
6
|
+
# invalid UTF-8 and breaks reference cycles. Without it one binary param or
|
|
7
|
+
# a self-referencing hash loses the whole notice.
|
|
8
|
+
class Sanitizer
|
|
9
|
+
FILTERED = "[FILTERED]"
|
|
10
|
+
TRUNCATED = "[TRUNCATED]"
|
|
11
|
+
CIRCULAR = "[CIRCULAR]"
|
|
12
|
+
MAX_STRING = 4096
|
|
13
|
+
MAX_ITEMS = 128
|
|
14
|
+
MAX_DEPTH = 8
|
|
15
|
+
|
|
16
|
+
# `filter_keys` entries are substrings (case-insensitive) or Regexps.
|
|
17
|
+
def initialize(filter_keys = [])
|
|
18
|
+
@filter_keys = Array(filter_keys).map { |key| key.is_a?(Regexp) ? key : key.to_s.downcase }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Truncate only.
|
|
22
|
+
def clean(value)
|
|
23
|
+
walk(value, false, 0, {})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Truncate and mask sensitive keys.
|
|
27
|
+
def filter(value)
|
|
28
|
+
walk(value, true, 0, {})
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.string(value, limit = MAX_STRING)
|
|
32
|
+
text = value.to_s
|
|
33
|
+
text = text.dup.force_encoding(Encoding::UTF_8) unless text.encoding == Encoding::UTF_8
|
|
34
|
+
text = text.scrub("?") unless text.valid_encoding?
|
|
35
|
+
text.length > limit ? "#{text[0, limit]}#{TRUNCATED}" : text
|
|
36
|
+
rescue StandardError
|
|
37
|
+
"[UNPRINTABLE]"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def walk(value, masking, depth, seen)
|
|
43
|
+
case value
|
|
44
|
+
when Hash
|
|
45
|
+
nested(value, depth, seen) do
|
|
46
|
+
value.first(MAX_ITEMS).each_with_object({}) do |(key, item), result|
|
|
47
|
+
name = self.class.string(key, 256)
|
|
48
|
+
result[name] = masking && sensitive?(name) ? FILTERED : walk(item, masking, depth + 1, seen)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
when Array
|
|
52
|
+
nested(value, depth, seen) do
|
|
53
|
+
value.first(MAX_ITEMS).map { |item| walk(item, masking, depth + 1, seen) }
|
|
54
|
+
end
|
|
55
|
+
when String, Symbol
|
|
56
|
+
self.class.string(value)
|
|
57
|
+
when Integer, NilClass, TrueClass, FalseClass
|
|
58
|
+
value
|
|
59
|
+
when Float
|
|
60
|
+
value.finite? ? value : value.to_s
|
|
61
|
+
else
|
|
62
|
+
if value.respond_to?(:to_hash)
|
|
63
|
+
walk(value.to_hash, masking, depth, seen)
|
|
64
|
+
else
|
|
65
|
+
self.class.string(value)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
rescue StandardError
|
|
69
|
+
"[UNPRINTABLE]"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def nested(value, depth, seen)
|
|
73
|
+
return TRUNCATED if depth >= MAX_DEPTH
|
|
74
|
+
return CIRCULAR if seen[value.object_id]
|
|
75
|
+
|
|
76
|
+
seen[value.object_id] = true
|
|
77
|
+
begin
|
|
78
|
+
yield
|
|
79
|
+
ensure
|
|
80
|
+
seen.delete(value.object_id)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def sensitive?(key)
|
|
85
|
+
lowered = key.downcase
|
|
86
|
+
@filter_keys.any? do |filter|
|
|
87
|
+
filter.is_a?(Regexp) ? filter.match?(key) : lowered.include?(filter)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/errorgap/sign_ins.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
|
-
require "net/http"
|
|
5
|
-
require "uri"
|
|
6
3
|
require "time"
|
|
7
4
|
|
|
8
5
|
module Errorgap
|
|
@@ -53,27 +50,13 @@ module Errorgap
|
|
|
53
50
|
if sync || !@configuration.async
|
|
54
51
|
deliver(payload)
|
|
55
52
|
else
|
|
56
|
-
Errorgap.
|
|
53
|
+
Errorgap.delivery.enqueue("logins/web", payload)
|
|
57
54
|
nil
|
|
58
55
|
end
|
|
59
56
|
end
|
|
60
57
|
|
|
61
58
|
def deliver(payload)
|
|
62
|
-
|
|
63
|
-
@configuration.endpoint.end_with?("/") ? @configuration.endpoint : "#{@configuration.endpoint}/",
|
|
64
|
-
"api/projects/#{@configuration.project_slug}/logins/web"
|
|
65
|
-
)
|
|
66
|
-
request = Net::HTTP::Post.new(uri)
|
|
67
|
-
request["Content-Type"] = "application/json"
|
|
68
|
-
request["User-Agent"] = "errorgap-ruby/#{Errorgap::VERSION}"
|
|
69
|
-
request["X-Errorgap-Project-Key"] = @configuration.api_key if present?(@configuration.api_key)
|
|
70
|
-
request.body = JSON.generate(payload)
|
|
71
|
-
|
|
72
|
-
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
73
|
-
http.request(request)
|
|
74
|
-
end
|
|
75
|
-
rescue StandardError => exception
|
|
76
|
-
@configuration.logger&.warn("[errorgap] sign-in delivery error: #{exception.class}: #{exception.message}")
|
|
59
|
+
Errorgap.delivery.post("logins/web", payload)
|
|
77
60
|
end
|
|
78
61
|
|
|
79
62
|
private
|
data/lib/errorgap/transacter.rb
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "json"
|
|
4
|
-
require "net/http"
|
|
5
|
-
require "uri"
|
|
6
|
-
|
|
7
3
|
module Errorgap
|
|
8
4
|
class Transacter
|
|
9
5
|
def initialize(configuration)
|
|
@@ -16,27 +12,14 @@ module Errorgap
|
|
|
16
12
|
|
|
17
13
|
def deliver_async(transaction)
|
|
18
14
|
return if @configuration.ignored_environment?
|
|
15
|
+
return unless Errorgap.remote_settings.enabled?(:apm)
|
|
19
16
|
return unless should_sample?
|
|
20
17
|
|
|
21
|
-
Errorgap.
|
|
18
|
+
Errorgap.delivery.enqueue("transactions", transaction.to_h)
|
|
22
19
|
end
|
|
23
20
|
|
|
24
21
|
def deliver(transaction)
|
|
25
|
-
|
|
26
|
-
@configuration.endpoint.end_with?("/") ? @configuration.endpoint : "#{@configuration.endpoint}/",
|
|
27
|
-
"api/projects/#{@configuration.project_slug}/transactions"
|
|
28
|
-
)
|
|
29
|
-
request = Net::HTTP::Post.new(uri)
|
|
30
|
-
request["Content-Type"] = "application/json"
|
|
31
|
-
request["User-Agent"] = "errorgap-ruby/#{Errorgap::VERSION}"
|
|
32
|
-
request["X-Errorgap-Project-Key"] = @configuration.api_key if present?(@configuration.api_key)
|
|
33
|
-
request.body = JSON.generate(transaction.to_h)
|
|
34
|
-
|
|
35
|
-
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
36
|
-
http.request(request)
|
|
37
|
-
end
|
|
38
|
-
rescue StandardError => exception
|
|
39
|
-
@configuration.logger&.warn("[errorgap] APM delivery error: #{exception.class}: #{exception.message}")
|
|
22
|
+
Errorgap.delivery.post("transactions", transaction.to_h)
|
|
40
23
|
end
|
|
41
24
|
|
|
42
25
|
private
|
|
@@ -48,9 +31,5 @@ module Errorgap
|
|
|
48
31
|
|
|
49
32
|
rand < rate
|
|
50
33
|
end
|
|
51
|
-
|
|
52
|
-
def present?(value)
|
|
53
|
-
!value.nil? && !value.to_s.empty?
|
|
54
|
-
end
|
|
55
34
|
end
|
|
56
35
|
end
|
data/lib/errorgap/version.rb
CHANGED
data/lib/errorgap.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
|
|
5
5
|
require_relative "errorgap/configuration"
|
|
6
|
+
require_relative "errorgap/sanitizer"
|
|
7
|
+
require_relative "errorgap/release_detector"
|
|
8
|
+
require_relative "errorgap/delivery"
|
|
9
|
+
require_relative "errorgap/remote_settings"
|
|
6
10
|
require_relative "errorgap/breadcrumbs"
|
|
7
11
|
require_relative "errorgap/notifier"
|
|
8
12
|
require_relative "errorgap/notice"
|
|
@@ -14,12 +18,20 @@ require_relative "errorgap/span_collector"
|
|
|
14
18
|
require_relative "errorgap/span_recorder"
|
|
15
19
|
require_relative "errorgap/transacter"
|
|
16
20
|
require_relative "errorgap/rack_middleware"
|
|
21
|
+
require_relative "errorgap/integrations/sidekiq"
|
|
22
|
+
require_relative "errorgap/integrations/active_job"
|
|
23
|
+
require_relative "errorgap/integrations/net_http"
|
|
24
|
+
require_relative "errorgap/integrations/resque"
|
|
25
|
+
require_relative "errorgap/integrations/delayed_job"
|
|
26
|
+
require_relative "errorgap/integrations/rake"
|
|
17
27
|
require_relative "errorgap/version"
|
|
18
28
|
|
|
19
29
|
module Errorgap
|
|
20
30
|
class << self
|
|
21
31
|
def configure
|
|
22
32
|
yield(configuration)
|
|
33
|
+
delivery.configure(configuration)
|
|
34
|
+
remote_settings.configure(configuration)
|
|
23
35
|
notifier.configure(configuration)
|
|
24
36
|
transacter.configure(configuration)
|
|
25
37
|
log_delivery.configure(configuration)
|
|
@@ -35,6 +47,16 @@ module Errorgap
|
|
|
35
47
|
@notifier ||= Notifier.new(configuration)
|
|
36
48
|
end
|
|
37
49
|
|
|
50
|
+
# What errorgap wants this app to send (the project's remote settings).
|
|
51
|
+
def remote_settings
|
|
52
|
+
@remote_settings ||= RemoteSettings.new(configuration)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The shared sender behind notices, transactions, logs and sign-ins.
|
|
56
|
+
def delivery
|
|
57
|
+
@delivery ||= Delivery.new(configuration)
|
|
58
|
+
end
|
|
59
|
+
|
|
38
60
|
def transacter
|
|
39
61
|
@transacter ||= Transacter.new(configuration)
|
|
40
62
|
end
|
|
@@ -65,6 +87,7 @@ module Errorgap
|
|
|
65
87
|
if (transaction_id = current_transaction_id) && !context.key?(:transaction_id) && !context.key?("transaction_id")
|
|
66
88
|
context = context.merge(transaction_id: transaction_id)
|
|
67
89
|
end
|
|
90
|
+
session = user_session.merge(session || {}) if current_user
|
|
68
91
|
notifier.notify(
|
|
69
92
|
error,
|
|
70
93
|
context: context,
|
|
@@ -76,6 +99,82 @@ module Errorgap
|
|
|
76
99
|
)
|
|
77
100
|
end
|
|
78
101
|
|
|
102
|
+
REPORTED_IVAR = :@__errorgap_reported
|
|
103
|
+
USER_KEY = :errorgap_user
|
|
104
|
+
|
|
105
|
+
# `notify`, unless this exception object was already reported: lets the
|
|
106
|
+
# Rack middleware, a job integration and the app's own rescue all see the
|
|
107
|
+
# same error without sending it more than once.
|
|
108
|
+
def notify_once(error, **options)
|
|
109
|
+
return nil if reported?(error)
|
|
110
|
+
|
|
111
|
+
mark_reported(error)
|
|
112
|
+
notify(error, **options)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def reported?(error)
|
|
116
|
+
error.instance_variable_defined?(REPORTED_IVAR)
|
|
117
|
+
rescue StandardError
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def mark_reported(error)
|
|
122
|
+
error.instance_variable_set(REPORTED_IVAR, true) unless error.frozen?
|
|
123
|
+
rescue StandardError
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Name the user the current request or job is acting for. Errors reported
|
|
128
|
+
# on this thread until `clear_user` carry it as `session.user_id` /
|
|
129
|
+
# `session.user_email`, which errorgap's user filters read.
|
|
130
|
+
def set_user(id: nil, email: nil)
|
|
131
|
+
user = {}
|
|
132
|
+
user[:id] = id.to_s unless id.nil? || id.to_s.empty?
|
|
133
|
+
user[:email] = email.to_s unless email.nil? || email.to_s.empty?
|
|
134
|
+
Thread.current[USER_KEY] = user.empty? ? nil : user
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def clear_user
|
|
138
|
+
Thread.current[USER_KEY] = nil
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def current_user
|
|
142
|
+
Thread.current[USER_KEY]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Record a release with errorgap (Deploys): errors are then attributed to
|
|
146
|
+
# the deploy that shipped them. Defaults come from the configuration.
|
|
147
|
+
def track_release(version: nil, sha: nil, branch: nil, environment: nil, sync: true)
|
|
148
|
+
configuration.validate!
|
|
149
|
+
return if configuration.ignored_environment?
|
|
150
|
+
|
|
151
|
+
payload = {
|
|
152
|
+
version: version || configuration.release,
|
|
153
|
+
sha: sha || configuration.release_sha,
|
|
154
|
+
branch: branch || configuration.release_branch,
|
|
155
|
+
environment: environment || configuration.environment
|
|
156
|
+
}.compact
|
|
157
|
+
return unless payload[:version] || payload[:sha]
|
|
158
|
+
|
|
159
|
+
if sync
|
|
160
|
+
delivery.post("releases", payload)
|
|
161
|
+
else
|
|
162
|
+
delivery.enqueue("releases", payload)
|
|
163
|
+
end
|
|
164
|
+
rescue StandardError => exception
|
|
165
|
+
configuration.logger&.warn("[errorgap] release not recorded: #{exception.class}: #{exception.message}")
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# `config.track_release`: the first notice of a process records the
|
|
170
|
+
# release, so an app that never errors never calls errorgap.
|
|
171
|
+
def record_release_once
|
|
172
|
+
return if @release_recorded
|
|
173
|
+
|
|
174
|
+
@release_recorded = true
|
|
175
|
+
track_release(sync: false)
|
|
176
|
+
end
|
|
177
|
+
|
|
79
178
|
# Record a diagnostic breadcrumb attached to subsequent notices.
|
|
80
179
|
def add_breadcrumb(message, category: nil, metadata: nil)
|
|
81
180
|
breadcrumbs.add(message, category: category, metadata: metadata)
|
|
@@ -121,12 +220,13 @@ module Errorgap
|
|
|
121
220
|
def notify_transaction(transaction, sync: false)
|
|
122
221
|
return unless configuration.apm_enabled
|
|
123
222
|
return if configuration.ignored_environment?
|
|
223
|
+
return unless remote_settings.enabled?(:apm)
|
|
124
224
|
return unless sample_apm?
|
|
125
225
|
|
|
126
226
|
if sync || !configuration.async
|
|
127
227
|
transacter.deliver(transaction)
|
|
128
228
|
else
|
|
129
|
-
|
|
229
|
+
delivery.enqueue("transactions", transaction.to_h)
|
|
130
230
|
end
|
|
131
231
|
end
|
|
132
232
|
|
|
@@ -176,25 +276,38 @@ module Errorgap
|
|
|
176
276
|
end
|
|
177
277
|
end
|
|
178
278
|
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
def flush
|
|
279
|
+
# Wait for queued notices, transactions and logs to be sent. Call before
|
|
280
|
+
# process exit; queued payloads are also flushed from an `at_exit` hook.
|
|
281
|
+
def flush(timeout = Delivery::FLUSH_TIMEOUT)
|
|
282
|
+
delivery.flush(timeout)
|
|
182
283
|
threads = thread_mutex.synchronize do
|
|
183
284
|
pending = delivery_threads.dup
|
|
184
285
|
delivery_threads.clear
|
|
185
286
|
pending
|
|
186
287
|
end
|
|
187
|
-
threads.each { |thread| thread.join(
|
|
288
|
+
threads.each { |thread| thread.join(timeout) }
|
|
188
289
|
nil
|
|
189
290
|
end
|
|
190
291
|
|
|
292
|
+
# Kept for callers that run their own delivery threads.
|
|
191
293
|
def register_thread(thread)
|
|
192
|
-
thread_mutex.synchronize
|
|
294
|
+
thread_mutex.synchronize do
|
|
295
|
+
delivery_threads.select!(&:alive?)
|
|
296
|
+
delivery_threads << thread
|
|
297
|
+
end
|
|
193
298
|
thread
|
|
194
299
|
end
|
|
195
300
|
|
|
196
301
|
private
|
|
197
302
|
|
|
303
|
+
def user_session
|
|
304
|
+
user = current_user || {}
|
|
305
|
+
session = {}
|
|
306
|
+
session["user_id"] = user[:id] if user[:id]
|
|
307
|
+
session["user_email"] = user[:email] if user[:email]
|
|
308
|
+
session
|
|
309
|
+
end
|
|
310
|
+
|
|
198
311
|
def sample_apm?
|
|
199
312
|
rate = configuration.apm_sample_rate.to_f
|
|
200
313
|
return true if rate >= 1.0
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: errorgap
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Errorgap
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -67,11 +67,21 @@ files:
|
|
|
67
67
|
- lib/errorgap.rb
|
|
68
68
|
- lib/errorgap/breadcrumbs.rb
|
|
69
69
|
- lib/errorgap/configuration.rb
|
|
70
|
+
- lib/errorgap/delivery.rb
|
|
71
|
+
- lib/errorgap/integrations/active_job.rb
|
|
72
|
+
- lib/errorgap/integrations/delayed_job.rb
|
|
73
|
+
- lib/errorgap/integrations/net_http.rb
|
|
74
|
+
- lib/errorgap/integrations/rake.rb
|
|
75
|
+
- lib/errorgap/integrations/resque.rb
|
|
76
|
+
- lib/errorgap/integrations/sidekiq.rb
|
|
70
77
|
- lib/errorgap/log_delivery.rb
|
|
71
78
|
- lib/errorgap/notice.rb
|
|
72
79
|
- lib/errorgap/notifier.rb
|
|
73
80
|
- lib/errorgap/rack_middleware.rb
|
|
74
81
|
- lib/errorgap/rails/railtie.rb
|
|
82
|
+
- lib/errorgap/release_detector.rb
|
|
83
|
+
- lib/errorgap/remote_settings.rb
|
|
84
|
+
- lib/errorgap/sanitizer.rb
|
|
75
85
|
- lib/errorgap/sign_ins.rb
|
|
76
86
|
- lib/errorgap/span_collector.rb
|
|
77
87
|
- lib/errorgap/span_recorder.rb
|