goodsorcery 0.1.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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/LICENSE +21 -0
  4. data/README.md +299 -0
  5. data/lib/generators/goodsorcery/install/credentials_api_key.rb +64 -0
  6. data/lib/generators/goodsorcery/install/install_generator.rb +64 -0
  7. data/lib/generators/goodsorcery/install/templates/initializer.rb +54 -0
  8. data/lib/goodsorcery/backtrace/code_context.rb +27 -0
  9. data/lib/goodsorcery/backtrace/line_cache.rb +57 -0
  10. data/lib/goodsorcery/backtrace/parser.rb +38 -0
  11. data/lib/goodsorcery/backtrace/paths.rb +42 -0
  12. data/lib/goodsorcery/bundler_plugin.rb +117 -0
  13. data/lib/goodsorcery/client.rb +180 -0
  14. data/lib/goodsorcery/configuration/validation.rb +59 -0
  15. data/lib/goodsorcery/configuration.rb +204 -0
  16. data/lib/goodsorcery/defaults.rb +40 -0
  17. data/lib/goodsorcery/delivery/active_job_queue.rb +71 -0
  18. data/lib/goodsorcery/delivery/async.rb +122 -0
  19. data/lib/goodsorcery/delivery/backoff.rb +20 -0
  20. data/lib/goodsorcery/delivery/custom.rb +27 -0
  21. data/lib/goodsorcery/delivery/event_queue.rb +124 -0
  22. data/lib/goodsorcery/delivery/sync.rb +27 -0
  23. data/lib/goodsorcery/delivery/test.rb +30 -0
  24. data/lib/goodsorcery/delivery/worker.rb +71 -0
  25. data/lib/goodsorcery/delivery.rb +39 -0
  26. data/lib/goodsorcery/delivery_job.rb +26 -0
  27. data/lib/goodsorcery/env_flag.rb +24 -0
  28. data/lib/goodsorcery/environment_gate.rb +61 -0
  29. data/lib/goodsorcery/errors.rb +14 -0
  30. data/lib/goodsorcery/event/breadcrumb.rb +32 -0
  31. data/lib/goodsorcery/event/builder.rb +77 -0
  32. data/lib/goodsorcery/event/cause_chain.rb +39 -0
  33. data/lib/goodsorcery/event/exception_section.rb +68 -0
  34. data/lib/goodsorcery/event/fields.rb +61 -0
  35. data/lib/goodsorcery/event/job_section.rb +34 -0
  36. data/lib/goodsorcery/event/request_section.rb +41 -0
  37. data/lib/goodsorcery/event/runtime_context.rb +52 -0
  38. data/lib/goodsorcery/event/tag_set.rb +41 -0
  39. data/lib/goodsorcery/event/user_section.rb +36 -0
  40. data/lib/goodsorcery/event_filter.rb +54 -0
  41. data/lib/goodsorcery/event_pipeline.rb +35 -0
  42. data/lib/goodsorcery/event_serializer.rb +25 -0
  43. data/lib/goodsorcery/exception_coercion.rb +18 -0
  44. data/lib/goodsorcery/extra_filter_probe.rb +32 -0
  45. data/lib/goodsorcery/ignore_list.rb +15 -0
  46. data/lib/goodsorcery/install_check.rb +58 -0
  47. data/lib/goodsorcery/integrations/action_cable.rb +75 -0
  48. data/lib/goodsorcery/integrations/active_job/job_details.rb +90 -0
  49. data/lib/goodsorcery/integrations/active_job.rb +39 -0
  50. data/lib/goodsorcery/integrations/rails/controller_action.rb +40 -0
  51. data/lib/goodsorcery/integrations/rails/controller_enricher.rb +27 -0
  52. data/lib/goodsorcery/integrations/rails/error_subscriber.rb +40 -0
  53. data/lib/goodsorcery/integrations/rails/notification_breadcrumbs.rb +82 -0
  54. data/lib/goodsorcery/integrations/rails/sql_statement.rb +31 -0
  55. data/lib/goodsorcery/integrations/rails/user_lookup.rb +39 -0
  56. data/lib/goodsorcery/integrations/rails.rb +56 -0
  57. data/lib/goodsorcery/integrations/rake.rb +68 -0
  58. data/lib/goodsorcery/integrations/sidekiq/job_details.rb +54 -0
  59. data/lib/goodsorcery/integrations/sidekiq.rb +55 -0
  60. data/lib/goodsorcery/integrations.rb +27 -0
  61. data/lib/goodsorcery/job_arguments.rb +77 -0
  62. data/lib/goodsorcery/key_filter.rb +99 -0
  63. data/lib/goodsorcery/log.rb +55 -0
  64. data/lib/goodsorcery/masked_key.rb +29 -0
  65. data/lib/goodsorcery/rack/middleware.rb +57 -0
  66. data/lib/goodsorcery/rack/request_details.rb +120 -0
  67. data/lib/goodsorcery/rack/request_enricher.rb +20 -0
  68. data/lib/goodsorcery/rails_filter_parameters.rb +24 -0
  69. data/lib/goodsorcery/railtie.rb +29 -0
  70. data/lib/goodsorcery/release_detector.rb +42 -0
  71. data/lib/goodsorcery/reported_mark.rb +19 -0
  72. data/lib/goodsorcery/sanitizer.rb +71 -0
  73. data/lib/goodsorcery/scope.rb +106 -0
  74. data/lib/goodsorcery/status_report.rb +50 -0
  75. data/lib/goodsorcery/tasks.rb +46 -0
  76. data/lib/goodsorcery/test_event.rb +46 -0
  77. data/lib/goodsorcery/text.rb +35 -0
  78. data/lib/goodsorcery/transport/batch_chunks.rb +40 -0
  79. data/lib/goodsorcery/transport/connection.rb +87 -0
  80. data/lib/goodsorcery/transport/result.rb +103 -0
  81. data/lib/goodsorcery/transport.rb +172 -0
  82. data/lib/goodsorcery/verification.rb +47 -0
  83. data/lib/goodsorcery/version.rb +6 -0
  84. data/lib/goodsorcery.rb +176 -0
  85. data/plugins.rb +6 -0
  86. metadata +130 -0
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transport/result"
4
+ require_relative "transport/connection"
5
+ require_relative "transport/batch_chunks"
6
+
7
+ module GoodSorcery
8
+ # Talks to the ingest API over HTTP(S): keep-alive, gzip, Bearer auth.
9
+ # Takes serialized events (JSON strings) and returns Transport::Result
10
+ # objects. Logs statuses and counts only, never bodies or the full key.
11
+ class Transport
12
+ EVENTS_PATH = "/api/v1/events"
13
+ BATCH_PATH = "/api/v1/events/batch"
14
+ VERIFY_PATH = "/api/v1/verify"
15
+ GZIP_MIN_BYTES = 1_024
16
+
17
+ def initialize(endpoint:, api_key:, open_timeout:, read_timeout:, log:)
18
+ @uri = URI(endpoint)
19
+ @api_key = api_key.to_s
20
+ @timeouts = { open_timeout: open_timeout, read_timeout: read_timeout }
21
+ @log = log
22
+ @connection = Connection.new(@uri, **@timeouts)
23
+ @connection_lock = Mutex.new
24
+ @batch_supported = true
25
+ warn_about_plain_http
26
+ end
27
+
28
+ def post_event(json)
29
+ result = send_request(post(EVENTS_PATH, json))
30
+ log_result(result, 1)
31
+ result
32
+ end
33
+
34
+ # Sends events in as few requests as possible. On a 429 or a retryable
35
+ # failure it stops and returns that result with the unsent events in
36
+ # +pending+. Falls back to one request per event if the server has no
37
+ # batch endpoint (404), and remembers that. A batch the server (or a
38
+ # proxy in front of it) refuses as too large (413) is split in halves.
39
+ def post_batch(jsons)
40
+ return post_each(jsons) unless @batch_supported
41
+
42
+ chunks = BatchChunks.split(jsons)
43
+ chunks.each_with_index do |chunk, index|
44
+ result = post_chunk(chunk)
45
+ return result.with_pending(unsent(result, chunk, chunks[(index + 1)..])) if result.retry_later?
46
+ return post_each(chunks[index..].flatten) if result.not_found? && batch_unsupported!
47
+ end
48
+ Result.new(:success)
49
+ end
50
+
51
+ def verify
52
+ send_request(Net::HTTP::Get.new(path(VERIFY_PATH), headers))
53
+ end
54
+
55
+ def close
56
+ @connection_lock.synchronize { @connection.close }
57
+ end
58
+
59
+ def batch_supported?
60
+ @batch_supported
61
+ end
62
+
63
+ # Shows the host and the masked key only.
64
+ def inspect
65
+ "#<#{self.class.name} #{@uri.scheme}://#{@uri.host}:#{@uri.port} api_key=#{MaskedKey.new(@api_key)}>"
66
+ end
67
+
68
+ private
69
+
70
+ def post_chunk(chunk)
71
+ result = send_request(post(BATCH_PATH, BatchChunks.body(chunk)))
72
+ return post_halves(chunk) if result.status == 413 && chunk.size > 1
73
+
74
+ log_result(result, chunk.size) unless result.not_found?
75
+ log_partial_rejections(result) if result.success?
76
+ result
77
+ end
78
+
79
+ def post_halves(chunk)
80
+ halves = chunk.each_slice((chunk.size + 1) / 2).to_a
81
+ halves.each_with_index do |half, index|
82
+ result = post_chunk(half)
83
+ return result.with_pending(unsent(result, half, halves[(index + 1)..])) if result.retry_later?
84
+ end
85
+ Result.new(:success)
86
+ end
87
+
88
+ # What is left after a 429 or retryable result for +chunk+.
89
+ def unsent(result, chunk, later_chunks)
90
+ (result.pending.empty? ? chunk : result.pending) + later_chunks.flatten
91
+ end
92
+
93
+ def post_each(jsons)
94
+ jsons.each_with_index do |json, index|
95
+ result = post_event(json)
96
+ return result.with_pending(jsons[index..]) if result.retry_later?
97
+ end
98
+ Result.new(:success)
99
+ end
100
+
101
+ def batch_unsupported!
102
+ @log.info("the server has no batch endpoint; sending events one per request")
103
+ @batch_supported = false
104
+ true
105
+ end
106
+
107
+ def send_request(request)
108
+ Result.from_response(perform(request))
109
+ rescue StandardError => e
110
+ Result.from_error(e)
111
+ end
112
+
113
+ # The persistent connection serves one thread at a time; a concurrent
114
+ # caller (the :sync delivery in a busy app) gets a short-lived one.
115
+ def perform(request)
116
+ return Connection.new(@uri, **@timeouts).request_once(request) unless @connection_lock.try_lock
117
+
118
+ begin
119
+ @connection.request(request)
120
+ ensure
121
+ @connection_lock.unlock
122
+ end
123
+ end
124
+
125
+ def post(path_name, body)
126
+ request = Net::HTTP::Post.new(path(path_name), headers)
127
+ if body.bytesize >= GZIP_MIN_BYTES
128
+ request["Content-Encoding"] = "gzip"
129
+ body = Zlib.gzip(body, level: Zlib::BEST_SPEED)
130
+ end
131
+ request.body = body
132
+ request
133
+ end
134
+
135
+ def headers
136
+ {
137
+ "Authorization" => "Bearer #{@api_key}",
138
+ "Content-Type" => "application/json",
139
+ "Accept" => "application/json",
140
+ "User-Agent" => "#{NOTIFIER_NAME}/#{VERSION}"
141
+ }
142
+ end
143
+
144
+ def path(name)
145
+ "#{@uri.path.chomp("/")}#{name}"
146
+ end
147
+
148
+ def log_result(result, count)
149
+ return log_invalid_key if result.status == 401
150
+ return if result.success? || result.retry_later?
151
+
152
+ @log.warn_every(:"rejected_#{result.status}", 60, "the server rejected #{count} event(s) (#{result.description})")
153
+ end
154
+
155
+ def log_invalid_key
156
+ @log.warn_once(:invalid_api_key, "invalid API key #{MaskedKey.new(@api_key)} (HTTP 401); events are dropped")
157
+ end
158
+
159
+ def log_partial_rejections(result)
160
+ rejected = Array(result.body["rejected"])
161
+ return if rejected.empty?
162
+
163
+ @log.warn_every(:batch_rejections, 60, "the server rejected #{rejected.size} event(s) of a batch as invalid")
164
+ end
165
+
166
+ def warn_about_plain_http
167
+ return unless @uri.scheme == "http" && !%w[localhost 127.0.0.1 ::1].include?(@uri.host)
168
+
169
+ @log.warn_once(:plain_http, "endpoint #{@uri.host} uses plain HTTP; the API key is sent unencrypted")
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodSorcery
4
+ # The outcome of GoodSorcery.verify: can this endpoint be reached, and does
5
+ # it accept the API key? Messages show the masked key only.
6
+ class Verification
7
+ attr_reader :message, :environment
8
+
9
+ NO_KEY = "no API key configured (set GOODSORCERY_API_KEY or config.api_key)"
10
+
11
+ def self.run(config, transport)
12
+ return new(false, NO_KEY) if config.api_key.to_s.empty?
13
+
14
+ from_result(transport.verify, config)
15
+ end
16
+
17
+ def self.from_result(result, config)
18
+ return success(result, config) if result.success?
19
+ return new(false, "the server rejected API key #{config.masked_api_key} (HTTP 401)") if result.status == 401
20
+ return new(false, "#{config.endpoint_label} has no verify endpoint (HTTP 404)") if result.not_found?
21
+ return new(false, "rate limited (HTTP 429); try again in #{result.retry_after} s") if result.rate_limited?
22
+
23
+ new(false, "could not reach #{config.endpoint_label} (#{result.description})")
24
+ end
25
+
26
+ def self.success(result, config)
27
+ environment = result.body["environment"]
28
+ label = environment ? " for environment #{environment.to_s.inspect}" : ""
29
+ message = "API key #{config.masked_api_key} is valid#{label} at #{config.endpoint_label}"
30
+ new(true, message, environment: environment)
31
+ end
32
+
33
+ def initialize(succeeded, message, environment: nil)
34
+ @ok = succeeded
35
+ @message = message
36
+ @environment = environment
37
+ end
38
+
39
+ def ok?
40
+ @ok
41
+ end
42
+
43
+ def to_s
44
+ message
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodSorcery
4
+ VERSION = "0.1.0"
5
+ NOTIFIER_NAME = "goodsorcery-ruby"
6
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "logger"
5
+ require "monitor"
6
+ require "net/http"
7
+ require "securerandom"
8
+ require "socket"
9
+ require "time"
10
+ require "uri"
11
+ require "zlib"
12
+
13
+ require_relative "goodsorcery/version"
14
+ require_relative "goodsorcery/errors"
15
+ require_relative "goodsorcery/log"
16
+ require_relative "goodsorcery/defaults"
17
+ require_relative "goodsorcery/env_flag"
18
+ require_relative "goodsorcery/masked_key"
19
+ require_relative "goodsorcery/release_detector"
20
+ require_relative "goodsorcery/configuration"
21
+ require_relative "goodsorcery/environment_gate"
22
+ require_relative "goodsorcery/text"
23
+ require_relative "goodsorcery/sanitizer"
24
+ require_relative "goodsorcery/key_filter"
25
+ require_relative "goodsorcery/extra_filter_probe"
26
+ require_relative "goodsorcery/event_filter"
27
+ require_relative "goodsorcery/rails_filter_parameters"
28
+ require_relative "goodsorcery/job_arguments"
29
+ require_relative "goodsorcery/scope"
30
+ require_relative "goodsorcery/backtrace/parser"
31
+ require_relative "goodsorcery/backtrace/paths"
32
+ require_relative "goodsorcery/backtrace/line_cache"
33
+ require_relative "goodsorcery/backtrace/code_context"
34
+ require_relative "goodsorcery/event/fields"
35
+ require_relative "goodsorcery/event/exception_section"
36
+ require_relative "goodsorcery/event/cause_chain"
37
+ require_relative "goodsorcery/event/runtime_context"
38
+ require_relative "goodsorcery/event/user_section"
39
+ require_relative "goodsorcery/event/tag_set"
40
+ require_relative "goodsorcery/event/request_section"
41
+ require_relative "goodsorcery/event/job_section"
42
+ require_relative "goodsorcery/event/breadcrumb"
43
+ require_relative "goodsorcery/event/builder"
44
+ require_relative "goodsorcery/event_pipeline"
45
+ require_relative "goodsorcery/event_serializer"
46
+ require_relative "goodsorcery/reported_mark"
47
+ require_relative "goodsorcery/ignore_list"
48
+ require_relative "goodsorcery/exception_coercion"
49
+ require_relative "goodsorcery/transport"
50
+ require_relative "goodsorcery/delivery"
51
+ require_relative "goodsorcery/verification"
52
+ require_relative "goodsorcery/test_event"
53
+ require_relative "goodsorcery/client"
54
+ require_relative "goodsorcery/rack/middleware"
55
+
56
+ # Reports exceptions to a Good Sorcery error tracker. See README.md.
57
+ module GoodSorcery
58
+ LOCK = Mutex.new
59
+ @log = Log.new { configuration.logger }
60
+
61
+ class << self
62
+ def configuration
63
+ @configuration || LOCK.synchronize { @configuration ||= Configuration.new }
64
+ end
65
+
66
+ # Yields the configuration. Rebuilds the delivery when a delivery-related
67
+ # setting changed, and recompiles filters.
68
+ def configure
69
+ before = configuration.delivery_settings
70
+ yield configuration
71
+ client.reconfigure(rebuild_delivery: before != configuration.delivery_settings)
72
+ configuration
73
+ end
74
+
75
+ def client
76
+ return @client if @client
77
+
78
+ config = configuration
79
+ LOCK.synchronize { @client ||= Client.new(config, log) }
80
+ end
81
+
82
+ # The gem's own logger (prefixed, rate-limited; see GoodSorcery::Log).
83
+ attr_reader :log
84
+
85
+ # Reports an exception (or a message). => event id, or nil. Never raises.
86
+ def notify(exception_or_message, context: nil, tags: nil, user: nil, level: nil,
87
+ fingerprint: nil, transaction: nil)
88
+ options = { context: context, tags: tags, user: user, level: level,
89
+ fingerprint: fingerprint, transaction: transaction }.compact
90
+ client.notify(exception_or_message, **options)
91
+ rescue StandardError
92
+ nil
93
+ end
94
+
95
+ def enabled?
96
+ client.enabled?
97
+ end
98
+
99
+ def scope
100
+ Scope.current
101
+ end
102
+
103
+ def clear_scope
104
+ Scope.clear
105
+ end
106
+
107
+ # Runs the block with a fresh scope (one request or job), then restores
108
+ # the previous one.
109
+ def with_scope
110
+ previous = Thread.current[Scope::STORAGE_KEY]
111
+ Scope.current = Scope.new(unit_of_work: true)
112
+ yield Scope.current
113
+ ensure
114
+ Scope.current = previous
115
+ end
116
+
117
+ def with_context(hash, &)
118
+ scope.with_context(hash, &)
119
+ end
120
+
121
+ def context(hash)
122
+ scope.merge_context(hash)
123
+ end
124
+
125
+ def user=(user)
126
+ scope.user = user
127
+ end
128
+
129
+ def tags(hash)
130
+ scope.merge_tags(hash)
131
+ end
132
+
133
+ def add_breadcrumb(message, category: "custom", level: "info", data: nil)
134
+ client.add_breadcrumb(message, category: category, level: level, data: data)
135
+ end
136
+
137
+ def flush(timeout = 5)
138
+ client.flush(timeout)
139
+ end
140
+
141
+ def shutdown(timeout = nil)
142
+ @client&.shutdown(timeout || configuration.shutdown_timeout)
143
+ rescue StandardError
144
+ nil
145
+ end
146
+
147
+ def verify
148
+ client.verify
149
+ end
150
+
151
+ def test_events
152
+ client.test_events
153
+ end
154
+
155
+ def clear_test_events
156
+ client.clear_test_events
157
+ end
158
+
159
+ # Forgets configuration, client and the current scope. For tests.
160
+ def reset!
161
+ shutdown(0.5)
162
+ LOCK.synchronize do
163
+ @client = nil
164
+ @configuration = nil
165
+ end
166
+ clear_scope
167
+ end
168
+ end
169
+ end
170
+
171
+ require_relative "goodsorcery/railtie" if defined?(Rails::Railtie)
172
+
173
+ if defined?(Sidekiq)
174
+ require_relative "goodsorcery/integrations/sidekiq"
175
+ GoodSorcery::Integrations::Sidekiq.install
176
+ end
data/plugins.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Bundler plugin entry point (`plugin "goodsorcery"` in a Gemfile).
4
+ require_relative "lib/goodsorcery/bundler_plugin"
5
+
6
+ GoodSorcery::BundlerPlugin.register
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: goodsorcery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Philip Q Nguyen
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Reports exceptions from Ruby and Rails apps to Good Sorcery. Filters
13
+ sensitive data in-process and delivers events in batches from a background thread,
14
+ without blocking the host app.
15
+ email:
16
+ - 5519675+philipqnguyen@users.noreply.github.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE
23
+ - README.md
24
+ - lib/generators/goodsorcery/install/credentials_api_key.rb
25
+ - lib/generators/goodsorcery/install/install_generator.rb
26
+ - lib/generators/goodsorcery/install/templates/initializer.rb
27
+ - lib/goodsorcery.rb
28
+ - lib/goodsorcery/backtrace/code_context.rb
29
+ - lib/goodsorcery/backtrace/line_cache.rb
30
+ - lib/goodsorcery/backtrace/parser.rb
31
+ - lib/goodsorcery/backtrace/paths.rb
32
+ - lib/goodsorcery/bundler_plugin.rb
33
+ - lib/goodsorcery/client.rb
34
+ - lib/goodsorcery/configuration.rb
35
+ - lib/goodsorcery/configuration/validation.rb
36
+ - lib/goodsorcery/defaults.rb
37
+ - lib/goodsorcery/delivery.rb
38
+ - lib/goodsorcery/delivery/active_job_queue.rb
39
+ - lib/goodsorcery/delivery/async.rb
40
+ - lib/goodsorcery/delivery/backoff.rb
41
+ - lib/goodsorcery/delivery/custom.rb
42
+ - lib/goodsorcery/delivery/event_queue.rb
43
+ - lib/goodsorcery/delivery/sync.rb
44
+ - lib/goodsorcery/delivery/test.rb
45
+ - lib/goodsorcery/delivery/worker.rb
46
+ - lib/goodsorcery/delivery_job.rb
47
+ - lib/goodsorcery/env_flag.rb
48
+ - lib/goodsorcery/environment_gate.rb
49
+ - lib/goodsorcery/errors.rb
50
+ - lib/goodsorcery/event/breadcrumb.rb
51
+ - lib/goodsorcery/event/builder.rb
52
+ - lib/goodsorcery/event/cause_chain.rb
53
+ - lib/goodsorcery/event/exception_section.rb
54
+ - lib/goodsorcery/event/fields.rb
55
+ - lib/goodsorcery/event/job_section.rb
56
+ - lib/goodsorcery/event/request_section.rb
57
+ - lib/goodsorcery/event/runtime_context.rb
58
+ - lib/goodsorcery/event/tag_set.rb
59
+ - lib/goodsorcery/event/user_section.rb
60
+ - lib/goodsorcery/event_filter.rb
61
+ - lib/goodsorcery/event_pipeline.rb
62
+ - lib/goodsorcery/event_serializer.rb
63
+ - lib/goodsorcery/exception_coercion.rb
64
+ - lib/goodsorcery/extra_filter_probe.rb
65
+ - lib/goodsorcery/ignore_list.rb
66
+ - lib/goodsorcery/install_check.rb
67
+ - lib/goodsorcery/integrations.rb
68
+ - lib/goodsorcery/integrations/action_cable.rb
69
+ - lib/goodsorcery/integrations/active_job.rb
70
+ - lib/goodsorcery/integrations/active_job/job_details.rb
71
+ - lib/goodsorcery/integrations/rails.rb
72
+ - lib/goodsorcery/integrations/rails/controller_action.rb
73
+ - lib/goodsorcery/integrations/rails/controller_enricher.rb
74
+ - lib/goodsorcery/integrations/rails/error_subscriber.rb
75
+ - lib/goodsorcery/integrations/rails/notification_breadcrumbs.rb
76
+ - lib/goodsorcery/integrations/rails/sql_statement.rb
77
+ - lib/goodsorcery/integrations/rails/user_lookup.rb
78
+ - lib/goodsorcery/integrations/rake.rb
79
+ - lib/goodsorcery/integrations/sidekiq.rb
80
+ - lib/goodsorcery/integrations/sidekiq/job_details.rb
81
+ - lib/goodsorcery/job_arguments.rb
82
+ - lib/goodsorcery/key_filter.rb
83
+ - lib/goodsorcery/log.rb
84
+ - lib/goodsorcery/masked_key.rb
85
+ - lib/goodsorcery/rack/middleware.rb
86
+ - lib/goodsorcery/rack/request_details.rb
87
+ - lib/goodsorcery/rack/request_enricher.rb
88
+ - lib/goodsorcery/rails_filter_parameters.rb
89
+ - lib/goodsorcery/railtie.rb
90
+ - lib/goodsorcery/release_detector.rb
91
+ - lib/goodsorcery/reported_mark.rb
92
+ - lib/goodsorcery/sanitizer.rb
93
+ - lib/goodsorcery/scope.rb
94
+ - lib/goodsorcery/status_report.rb
95
+ - lib/goodsorcery/tasks.rb
96
+ - lib/goodsorcery/test_event.rb
97
+ - lib/goodsorcery/text.rb
98
+ - lib/goodsorcery/transport.rb
99
+ - lib/goodsorcery/transport/batch_chunks.rb
100
+ - lib/goodsorcery/transport/connection.rb
101
+ - lib/goodsorcery/transport/result.rb
102
+ - lib/goodsorcery/verification.rb
103
+ - lib/goodsorcery/version.rb
104
+ - plugins.rb
105
+ homepage: https://app.goodsorcery.com
106
+ licenses:
107
+ - MIT
108
+ metadata:
109
+ rubygems_mfa_required: 'true'
110
+ source_code_uri: https://github.com/creativesorcery/goodsorcery-ruby
111
+ changelog_uri: https://github.com/creativesorcery/goodsorcery-ruby/blob/main/CHANGELOG.md
112
+ bug_tracker_uri: https://github.com/creativesorcery/goodsorcery-ruby/issues
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '3.2'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 4.0.16
128
+ specification_version: 4
129
+ summary: Error reporting client for the Good Sorcery error tracker.
130
+ test_files: []