tensorbuzz-api 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +55 -0
- data/lib/tensorbuzz/api/bug_reporting.rb +166 -0
- data/lib/tensorbuzz/api/configuration.rb +21 -0
- data/lib/tensorbuzz/api/rack_middleware.rb +59 -0
- data/lib/tensorbuzz/api/rails.rb +13 -0
- data/lib/tensorbuzz/api/response.rb +7 -0
- data/lib/tensorbuzz/api/sanitizer.rb +41 -0
- data/lib/tensorbuzz/api/sidekiq.rb +17 -0
- data/lib/tensorbuzz/api/transport.rb +44 -0
- data/lib/tensorbuzz/api/version.rb +7 -0
- data/lib/tensorbuzz/api.rb +13 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e6c56342468d420d2953579a8724215a497335b7d574d2efd99853e88e21aae1
|
|
4
|
+
data.tar.gz: ce6ae673c3562b6fa1119e0ac48f5db086353cbd8268f55ad043581c45133f43
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a7d3a46e7cdde24c0257aaf433235260824e6f15db03a5aa784628b877e5a710160c70bf3b0597e0bf7007f481cb8f740e666763ca4fb48e275e08b95974534b
|
|
7
|
+
data.tar.gz: 253e6d374148290d16534380b8878e39fe12846bec9207bf5b3afd5fbf8299ce40729e0501b13a6f4513a874bb5c662cade7d99222fc5990c6bd37c404079389
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kasper Stöckel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# tensorbuzz-api for Ruby
|
|
2
|
+
|
|
3
|
+
Ruby, Rack, Rails, and Sidekiq error reporting for TensorBuzz.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem "tensorbuzz-api"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require "tensorbuzz/api"
|
|
15
|
+
|
|
16
|
+
TensorBuzz::BugReporting.configure do |config|
|
|
17
|
+
config.auth_token = ENV.fetch("TENSORBUZZ_BUG_REPORT_AUTH_TOKEN")
|
|
18
|
+
config.project_id = ENV.fetch("TENSORBUZZ_BUG_REPORT_PROJECT_ID")
|
|
19
|
+
config.hostname = ENV["TENSORBUZZ_BUG_REPORT_HOSTNAME"]
|
|
20
|
+
config.environment = ENV.fetch("RAILS_ENV", "production")
|
|
21
|
+
config.release = ENV["APP_RELEASE"]
|
|
22
|
+
config.logger = Rails.logger
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The default endpoint is `https://server.tensorbuzz.com/errors/reports`. Set
|
|
27
|
+
`config.post_url` when using another TensorBuzz installation.
|
|
28
|
+
|
|
29
|
+
## Reporting
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
TensorBuzz::BugReporting.report(error, parameters: {order_id: order.id})
|
|
33
|
+
TensorBuzz::BugReporting.report_message("Payment session creation failed")
|
|
34
|
+
|
|
35
|
+
TensorBuzz::BugReporting.with_parameters(user_id: user.id) do
|
|
36
|
+
perform_work
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Scoped parameters are attached to exceptions that leave the block, allowing a
|
|
41
|
+
later Rails or Sidekiq handler to report the original context.
|
|
42
|
+
|
|
43
|
+
## Rails and Sidekiq
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
require "tensorbuzz/api/rails"
|
|
47
|
+
require "tensorbuzz/api/sidekiq"
|
|
48
|
+
|
|
49
|
+
TensorBuzz::Api::Rails.configure
|
|
50
|
+
TensorBuzz::Api::Sidekiq.configure
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The Rack middleware reports uncaught request errors and re-raises the original
|
|
54
|
+
exception. The Sidekiq integration uses Sidekiq's error-handler API. Reporting
|
|
55
|
+
failures are logged and never replace the application exception.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'socket'
|
|
5
|
+
|
|
6
|
+
module TensorBuzz
|
|
7
|
+
module Api
|
|
8
|
+
class BugReporting
|
|
9
|
+
CAPTURED_PARAMETERS_VARIABLE = :@tensorbuzz_captured_parameters
|
|
10
|
+
CONTEXT_KEY = :tensorbuzz_bug_reporting_context
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_reader :current
|
|
14
|
+
|
|
15
|
+
def configure
|
|
16
|
+
configuration = Configuration.new
|
|
17
|
+
yield configuration
|
|
18
|
+
configuration.validate!
|
|
19
|
+
@current = new(configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def report(error, **options)
|
|
23
|
+
current&.report(error, **options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def report_message(message, **options)
|
|
27
|
+
report(RuntimeError.new(message), **options)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def safely_report(error, **options)
|
|
31
|
+
report(error, **options)
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
current&.log_reporting_error(e)
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def with_parameters(parameters)
|
|
38
|
+
return yield unless current
|
|
39
|
+
|
|
40
|
+
context_id = SecureRandom.hex(16)
|
|
41
|
+
context[context_id] = parameters
|
|
42
|
+
yield
|
|
43
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
44
|
+
current&.capture_parameters_for_error(e)
|
|
45
|
+
raise
|
|
46
|
+
ensure
|
|
47
|
+
context.delete(context_id) if context_id
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reset!
|
|
51
|
+
@current = nil
|
|
52
|
+
Thread.current[CONTEXT_KEY] = nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def context
|
|
56
|
+
Thread.current[CONTEXT_KEY] ||= {}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def initialize(configuration, transport: Transport.new(configuration))
|
|
61
|
+
@configuration = configuration
|
|
62
|
+
@transport = transport
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def report(error, environment: nil, http_method: nil, parameters: nil, remote_ip: nil,
|
|
66
|
+
request_body: nil, url: nil, user_agent: nil)
|
|
67
|
+
normalized_error = normalize_error(error)
|
|
68
|
+
stack_trace = Array(normalized_error.backtrace)
|
|
69
|
+
file_path, line_number = location_from(stack_trace)
|
|
70
|
+
merged_environment = compact_hash(
|
|
71
|
+
application_environment: configuration.environment,
|
|
72
|
+
release: configuration.release
|
|
73
|
+
).merge(environment || {})
|
|
74
|
+
payload = {
|
|
75
|
+
authToken: configuration.auth_token,
|
|
76
|
+
projectId: configuration.project_id,
|
|
77
|
+
hostname: configuration.hostname || Socket.gethostname,
|
|
78
|
+
runtimeEnvironment: 'ruby',
|
|
79
|
+
error: {
|
|
80
|
+
backtrace: stack_trace,
|
|
81
|
+
environment: Sanitizer.call(merged_environment),
|
|
82
|
+
error_class: normalized_error.class.name,
|
|
83
|
+
file_path:,
|
|
84
|
+
http_method:,
|
|
85
|
+
line_number:,
|
|
86
|
+
message: normalized_error.message,
|
|
87
|
+
parameters: Sanitizer.call(merged_parameters(normalized_error, parameters)),
|
|
88
|
+
remote_ip:,
|
|
89
|
+
request_body: Sanitizer.call(request_body),
|
|
90
|
+
url:,
|
|
91
|
+
user_agent:
|
|
92
|
+
}.compact
|
|
93
|
+
}
|
|
94
|
+
response = transport.post(payload)
|
|
95
|
+
|
|
96
|
+
Response.new(
|
|
97
|
+
bug_report_id: response['bugReportId'],
|
|
98
|
+
bug_report_instance_id: response['bugReportInstanceId'],
|
|
99
|
+
project_id: response['projectId'],
|
|
100
|
+
project_slug: response['projectSlug'],
|
|
101
|
+
url: response['url']
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def capture_parameters_for_error(error)
|
|
106
|
+
return if error.instance_variable_defined?(CAPTURED_PARAMETERS_VARIABLE)
|
|
107
|
+
|
|
108
|
+
error.instance_variable_set(CAPTURED_PARAMETERS_VARIABLE, merged_parameters(error, nil))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def log_reporting_error(error)
|
|
112
|
+
logger = configuration.logger
|
|
113
|
+
message = "TensorBuzz reporting failed: #{error.class}: #{error.message}"
|
|
114
|
+
|
|
115
|
+
if logger
|
|
116
|
+
logger.error(message)
|
|
117
|
+
else
|
|
118
|
+
warn(message)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
attr_reader :configuration, :transport
|
|
125
|
+
|
|
126
|
+
def compact_hash(**values)
|
|
127
|
+
values.compact
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def deep_merge(left, right)
|
|
131
|
+
left.merge(right) do |_key, left_value, right_value|
|
|
132
|
+
if left_value.is_a?(Hash) && right_value.is_a?(Hash)
|
|
133
|
+
deep_merge(left_value, right_value)
|
|
134
|
+
else
|
|
135
|
+
right_value
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def location_from(backtrace)
|
|
141
|
+
backtrace.each do |line|
|
|
142
|
+
match = line.match(/\A(.+?):(\d+)(?::|\z)/)
|
|
143
|
+
return [match[1], match[2].to_i] if match
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
[nil, nil]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def merged_parameters(error, parameters)
|
|
150
|
+
values = self.class.context.values
|
|
151
|
+
if error.instance_variable_defined?(CAPTURED_PARAMETERS_VARIABLE)
|
|
152
|
+
captured = error.instance_variable_get(CAPTURED_PARAMETERS_VARIABLE)
|
|
153
|
+
end
|
|
154
|
+
values << captured if captured
|
|
155
|
+
values << parameters if parameters
|
|
156
|
+
values.compact.reduce({}) { |result, value| deep_merge(result, value) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def normalize_error(error)
|
|
160
|
+
normalized_error = error.is_a?(Exception) ? error : RuntimeError.new(error.to_s)
|
|
161
|
+
normalized_error.set_backtrace(caller(2)) if normalized_error.backtrace.nil? || normalized_error.backtrace.empty?
|
|
162
|
+
normalized_error
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TensorBuzz
|
|
4
|
+
module Api
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :auth_token, :environment, :hostname, :logger, :open_timeout,
|
|
7
|
+
:post_url, :project_id, :read_timeout, :release
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@open_timeout = 2
|
|
11
|
+
@post_url = 'https://server.tensorbuzz.com/errors/reports'
|
|
12
|
+
@read_timeout = 5
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def validate!
|
|
16
|
+
raise ArgumentError, 'TensorBuzz auth_token is required' if auth_token.to_s.empty?
|
|
17
|
+
raise ArgumentError, 'TensorBuzz project_id is required' if project_id.to_s.empty?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack'
|
|
4
|
+
require 'rack/request'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module TensorBuzz
|
|
8
|
+
module Api
|
|
9
|
+
class RackMiddleware
|
|
10
|
+
def initialize(app)
|
|
11
|
+
@app = app
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(environment)
|
|
15
|
+
app.call(environment)
|
|
16
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
17
|
+
safely_report(e, environment)
|
|
18
|
+
raise
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :app
|
|
24
|
+
|
|
25
|
+
def safely_report(error, environment)
|
|
26
|
+
request = Rack::Request.new(environment)
|
|
27
|
+
BugReporting.safely_report(
|
|
28
|
+
error,
|
|
29
|
+
environment: rack_environment(environment),
|
|
30
|
+
http_method: request.request_method,
|
|
31
|
+
parameters: { query: request.GET },
|
|
32
|
+
remote_ip: request.ip,
|
|
33
|
+
request_body: parsed_request_body(request),
|
|
34
|
+
url: request.url,
|
|
35
|
+
user_agent: request.user_agent
|
|
36
|
+
)
|
|
37
|
+
rescue StandardError => e
|
|
38
|
+
BugReporting.current&.log_reporting_error(e)
|
|
39
|
+
BugReporting.safely_report(error)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parsed_request_body(request)
|
|
43
|
+
return request.POST unless request.media_type == 'application/json'
|
|
44
|
+
|
|
45
|
+
body = request.body.read
|
|
46
|
+
request.body.rewind
|
|
47
|
+
JSON.parse(body)
|
|
48
|
+
rescue StandardError
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def rack_environment(environment)
|
|
53
|
+
environment.each_with_object({}) do |(key, value), result|
|
|
54
|
+
result[key] = value if key.start_with?('SERVER_', 'HTTP_') && value.is_a?(String)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TensorBuzz
|
|
4
|
+
module Api
|
|
5
|
+
class Sanitizer
|
|
6
|
+
MAX_ARRAY_LENGTH = 100
|
|
7
|
+
MAX_DEPTH = 8
|
|
8
|
+
MAX_HASH_KEYS = 100
|
|
9
|
+
MAX_STRING_LENGTH = 4_000
|
|
10
|
+
SENSITIVE_KEY = /(authorization|cookie|password|secret|session|token)/i
|
|
11
|
+
|
|
12
|
+
def self.call(value, depth: 0, key: nil)
|
|
13
|
+
return '[redacted]' if key.to_s.match?(SENSITIVE_KEY)
|
|
14
|
+
return '[maximum depth reached]' if depth >= MAX_DEPTH
|
|
15
|
+
|
|
16
|
+
case value
|
|
17
|
+
when Array
|
|
18
|
+
value.first(MAX_ARRAY_LENGTH).map { |item| call(item, depth: depth + 1) }
|
|
19
|
+
when Hash
|
|
20
|
+
value.first(MAX_HASH_KEYS).to_h do |child_key, child_value|
|
|
21
|
+
[child_key.to_s, call(child_value, depth: depth + 1, key: child_key)]
|
|
22
|
+
end
|
|
23
|
+
when String
|
|
24
|
+
truncate(value)
|
|
25
|
+
when Numeric, TrueClass, FalseClass, NilClass
|
|
26
|
+
value
|
|
27
|
+
else
|
|
28
|
+
truncate(value.to_s)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.truncate(value)
|
|
33
|
+
return value if value.length <= MAX_STRING_LENGTH
|
|
34
|
+
|
|
35
|
+
"#{value[0, MAX_STRING_LENGTH]}[truncated #{value.length - MAX_STRING_LENGTH} characters]"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private_class_method :truncate
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TensorBuzz
|
|
4
|
+
module Api
|
|
5
|
+
module Sidekiq
|
|
6
|
+
def self.configure
|
|
7
|
+
require 'sidekiq'
|
|
8
|
+
|
|
9
|
+
::Sidekiq.configure_server do |config|
|
|
10
|
+
config.error_handlers << proc do |error, context, _configuration = nil|
|
|
11
|
+
BugReporting.safely_report(error, parameters: { sidekiq: context })
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module TensorBuzz
|
|
8
|
+
module Api
|
|
9
|
+
class ReportingError < StandardError; end
|
|
10
|
+
|
|
11
|
+
class Transport
|
|
12
|
+
def initialize(configuration)
|
|
13
|
+
@configuration = configuration
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def post(payload)
|
|
17
|
+
uri = URI(configuration.post_url)
|
|
18
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
19
|
+
http.use_ssl = uri.scheme == 'https'
|
|
20
|
+
http.open_timeout = configuration.open_timeout
|
|
21
|
+
http.read_timeout = configuration.read_timeout
|
|
22
|
+
|
|
23
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
24
|
+
request['Content-Type'] = 'application/json'
|
|
25
|
+
request.body = JSON.generate(payload)
|
|
26
|
+
response = http.request(request)
|
|
27
|
+
|
|
28
|
+
raise ReportingError, "TensorBuzz rejected the report with HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
29
|
+
|
|
30
|
+
JSON.parse(response.body)
|
|
31
|
+
rescue JSON::ParserError => e
|
|
32
|
+
raise ReportingError, "TensorBuzz returned invalid JSON: #{e.message}"
|
|
33
|
+
rescue ReportingError
|
|
34
|
+
raise
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
raise ReportingError, "Could not report to TensorBuzz: #{e.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :configuration
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tensorbuzz/api/version'
|
|
4
|
+
require 'tensorbuzz/api/configuration'
|
|
5
|
+
require 'tensorbuzz/api/response'
|
|
6
|
+
require 'tensorbuzz/api/sanitizer'
|
|
7
|
+
require 'tensorbuzz/api/transport'
|
|
8
|
+
require 'tensorbuzz/api/bug_reporting'
|
|
9
|
+
|
|
10
|
+
module TensorBuzz
|
|
11
|
+
BugReporting = Api::BugReporting
|
|
12
|
+
ReportingError = Api::ReportingError
|
|
13
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tensorbuzz-api
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kasper Stöckel
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Reports Ruby, Rack, Rails, and Sidekiq errors to TensorBuzz.
|
|
13
|
+
email:
|
|
14
|
+
- k@spernj.org
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- LICENSE
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/tensorbuzz/api.rb
|
|
22
|
+
- lib/tensorbuzz/api/bug_reporting.rb
|
|
23
|
+
- lib/tensorbuzz/api/configuration.rb
|
|
24
|
+
- lib/tensorbuzz/api/rack_middleware.rb
|
|
25
|
+
- lib/tensorbuzz/api/rails.rb
|
|
26
|
+
- lib/tensorbuzz/api/response.rb
|
|
27
|
+
- lib/tensorbuzz/api/sanitizer.rb
|
|
28
|
+
- lib/tensorbuzz/api/sidekiq.rb
|
|
29
|
+
- lib/tensorbuzz/api/transport.rb
|
|
30
|
+
- lib/tensorbuzz/api/version.rb
|
|
31
|
+
homepage: https://github.com/kaspernj/tensorbuzz
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
rubygems_mfa_required: 'true'
|
|
36
|
+
source_code_uri: https://github.com/kaspernj/tensorbuzz/tree/master/tensorbuzz-api-ruby
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
require_paths:
|
|
39
|
+
- lib
|
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '3.2'
|
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '0'
|
|
50
|
+
requirements: []
|
|
51
|
+
rubygems_version: 3.6.9
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Ruby client for TensorBuzz bug reporting.
|
|
54
|
+
test_files: []
|