treblle 1.0.2 → 2.0.2
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/MIT-LICENSE +20 -0
- data/README.md +168 -0
- data/SECURITY.md +12 -0
- data/lib/treblle/configuration.rb +75 -0
- data/lib/treblle/dispatcher.rb +70 -0
- data/lib/treblle/errors/configuration_errors.rb +21 -0
- data/lib/treblle/generate_payload.rb +94 -0
- data/lib/treblle/logging.rb +19 -0
- data/lib/treblle/middleware.rb +54 -0
- data/lib/treblle/models/request/client.rb +17 -0
- data/lib/treblle/models/request/server.rb +51 -0
- data/lib/treblle/models/request.rb +9 -0
- data/lib/treblle/models/response/exception.rb +47 -0
- data/lib/treblle/models/response.rb +9 -0
- data/lib/treblle/rails/railtie.rb +12 -0
- data/lib/treblle/request_builder.rb +74 -0
- data/lib/treblle/response_builder.rb +58 -0
- data/lib/treblle/utils/hash_sanitizer.rb +40 -0
- data/lib/treblle/version.rb +6 -0
- data/lib/treblle.rb +10 -84
- metadata +43 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f42bc2d08d4eb451f07f8924e5ba73719b4c2e92c5b10de40ee7b28565750167
|
|
4
|
+
data.tar.gz: 16576e8b2c202f02c6d484ace4aa8fce5e1125b7feddd5f203953fc2238d5850
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4807e68c43c329e0c770a30194724de8f49a231d171301ec6463d2894ccfeb4bc659ec72cc7ce077ac74e2a334c37def9a0fcbc70a6e91266d9b61e119236ae8
|
|
7
|
+
data.tar.gz: 6044a7c79673530ca87a84a5367e5dc102c2011e5ada7fd66e5aa7f4f6811e3c12606ee86e0dc15fc56df184655d9843993ebacbeb51a354eba1e87f8d277269
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2024 Treblle
|
|
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
|
|
7
|
+
deal in the Software without restriction, including without limitation the
|
|
8
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
9
|
+
sell 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
|
|
13
|
+
all 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
|
|
18
|
+
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://treblle-github.s3.us-east-1.amazonaws.com/github-header.jpg"/>
|
|
3
|
+
</div>
|
|
4
|
+
<div align="center">
|
|
5
|
+
|
|
6
|
+
# Treblle
|
|
7
|
+
|
|
8
|
+
<a href="https://docs.treblle.com/en/integrations" target="_blank">Integrations</a>
|
|
9
|
+
<span> • </span>
|
|
10
|
+
<a href="http://treblle.com/" target="_blank">Website</a>
|
|
11
|
+
<span> • </span>
|
|
12
|
+
<a href="https://docs.treblle.com" target="_blank">Docs</a>
|
|
13
|
+
<span> • </span>
|
|
14
|
+
<a href="https://blog.treblle.com" target="_blank">Blog</a>
|
|
15
|
+
<span> • </span>
|
|
16
|
+
<a href="https://twitter.com/treblleapi" target="_blank">Twitter</a>
|
|
17
|
+
<span> • </span>
|
|
18
|
+
<a href="https://treblle.com/chat" target="_blank">Discord</a>
|
|
19
|
+
<br />
|
|
20
|
+
|
|
21
|
+
<hr />
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
Treblle is a lightweight SDK that helps Engineering and Product teams build, ship & maintain REST based APIs faster.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
<div align="center">
|
|
29
|
+
<br />
|
|
30
|
+
<img src="https://treblle-github.s3.amazonaws.com/features.png"/>
|
|
31
|
+
<br />
|
|
32
|
+
<br />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
- [API Monitoring & Observability](https://www.treblle.com/features/api-monitoring-observability)
|
|
36
|
+
- [Auto-generated API Docs](https://www.treblle.com/features/auto-generated-api-docs)
|
|
37
|
+
- [API analytics](https://www.treblle.com/features/api-analytics)
|
|
38
|
+
- [Treblle API Score](https://www.treblle.com/features/api-quality-score)
|
|
39
|
+
- [API Lifecycle Collaboration](https://www.treblle.com/features/api-lifecycle)
|
|
40
|
+
- [Native Treblle Apps](https://www.treblle.com/features/native-apps)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## How Treblle Works
|
|
44
|
+
Once you’ve integrated a Treblle SDK in your codebase, this SDK will send requests and response data to your Treblle Dashboard.
|
|
45
|
+
|
|
46
|
+
In your Treblle Dashboard you get to see real-time requests to your API, auto-generated API docs, API analytics like how fast the response was for an endpoint, the load size of the response, etc.
|
|
47
|
+
|
|
48
|
+
Treblle also uses the requests sent to your Dashboard to calculate your API score which is a quality score that’s calculated based on the performance, quality, and security best practices for your API.
|
|
49
|
+
|
|
50
|
+
> Visit [https://docs.treblle.com](http://docs.treblle.com) for the complete documentation.
|
|
51
|
+
|
|
52
|
+
## Security
|
|
53
|
+
|
|
54
|
+
### Masking fields
|
|
55
|
+
Masking fields ensure certain sensitive data are removed before being sent to Treblle.
|
|
56
|
+
|
|
57
|
+
To make sure masking is done before any data leaves your server [we built it into all our SDKs](https://docs.treblle.com/en/security/masked-fields#fields-masked-by-default).
|
|
58
|
+
|
|
59
|
+
This means data masking is super fast and happens on a programming level before the API request is sent to Treblle. You can [customize](https://docs.treblle.com/en/security/masked-fields#custom-masked-fields) exactly which fields are masked when you’re integrating the SDK.
|
|
60
|
+
|
|
61
|
+
> Visit the [Masked fields](https://docs.treblle.com/en/security/masked-fields) section of the [docs](https://docs.sailscasts.com) for the complete documentation.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Get Started
|
|
65
|
+
|
|
66
|
+
1. Sign in to [Treblle](https://app.treblle.com).
|
|
67
|
+
2. [Create a Treblle project](https://docs.treblle.com/en/dashboard/projects#creating-a-project).
|
|
68
|
+
3. [Setup the SDK](#install-the-SDK) for your platform.
|
|
69
|
+
|
|
70
|
+
#### SDK Requirements
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
gem "rails", ">= 6.1"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Install the SDK
|
|
77
|
+
|
|
78
|
+
Add the gem to your Gemfile:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# Gemfile
|
|
82
|
+
gem "treblle", "~> 2.0"
|
|
83
|
+
```
|
|
84
|
+
Configure Treblle:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# config/initializers/treblle.rb
|
|
88
|
+
|
|
89
|
+
Treblle.configure do |config|
|
|
90
|
+
config.api_key = ENV.fetch('TREBLLE_API_KEY')
|
|
91
|
+
config.project_id = ENV.fetch('TREBLLE_PROJECT_ID')
|
|
92
|
+
config.enabled_environments = %w[staging development]
|
|
93
|
+
config.sensitive_attrs = %w[top_secret_custom_attribute password_digest]
|
|
94
|
+
config.restricted_endpoints = %w[/api/users /api/v2/*]
|
|
95
|
+
config.whitelisted_endpoints = %w[/api/]
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- `config.api_key` (required)
|
|
100
|
+
- `config.project_id` (required)
|
|
101
|
+
- `config.enabled_environments` (required) - enables monitoring on specified environments
|
|
102
|
+
- `config.sensitive_attrs` (optional) - mask additional attributes
|
|
103
|
+
- `config.restricted_endpoints` (optional) - routes that you don't want to monitor
|
|
104
|
+
- `config.whitelisted_endpoints` (optional) - routes that you want to monitor, by default `/api/`
|
|
105
|
+
|
|
106
|
+
> See the [docs](https://docs.treblle.com/en/integrations/ruby) for this SDK to learn more.
|
|
107
|
+
|
|
108
|
+
## Available SDKs
|
|
109
|
+
|
|
110
|
+
Treblle provides [open-source SDKs](https://docs.treblle.com/en/integrations) that let you seamlessly integrate Treblle with your REST-based APIs.
|
|
111
|
+
|
|
112
|
+
- [`treblle-laravel`](https://github.com/Treblle/treblle-laravel): SDK for Laravel
|
|
113
|
+
- [`treblle-php`](https://github.com/Treblle/treblle-php): SDK for PHP
|
|
114
|
+
- [`treblle-symfony`](https://github.com/Treblle/treblle-symfony): SDK for Symfony
|
|
115
|
+
- [`treblle-lumen`](https://github.com/Treblle/treblle-lumen): SDK for Lumen
|
|
116
|
+
- [`treblle-sails`](https://github.com/Treblle/treblle-sails): SDK for Sails
|
|
117
|
+
- [`treblle-adonisjs`](https://github.com/Treblle/treblle-adonisjs): SDK for AdonisJS
|
|
118
|
+
- [`treblle-fastify`](https://github.com/Treblle/treblle-fastify): SDK for Fastify
|
|
119
|
+
- [`treblle-directus`](https://github.com/Treblle/treblle-directus): SDK for Directus
|
|
120
|
+
- [`treblle-strapi`](https://github.com/Treblle/treblle-strapi): SDK for Strapi
|
|
121
|
+
- [`treblle-express`](https://github.com/Treblle/treblle-express): SDK for Express
|
|
122
|
+
- [`treblle-koa`](https://github.com/Treblle/treblle-koa): SDK for Koa
|
|
123
|
+
- [`treblle-go`](https://github.com/Treblle/treblle-go): SDK for Go
|
|
124
|
+
- [`treblle-ruby`](https://github.com/Treblle/treblle-ruby): SDK for Ruby on Rails
|
|
125
|
+
- [`treblle-python`](https://github.com/Treblle/treblle-python): SDK for Python/Django
|
|
126
|
+
|
|
127
|
+
> See the [docs](https://docs.treblle.com/en/integrations) for more on SDKs and Integrations.
|
|
128
|
+
|
|
129
|
+
## Other Packages
|
|
130
|
+
|
|
131
|
+
Besides the SDKs, we also provide helpers and configuration used for SDK
|
|
132
|
+
development. If you're thinking about contributing to or creating a SDK, have a look at the resources
|
|
133
|
+
below:
|
|
134
|
+
|
|
135
|
+
- [`treblle-utils`](https://github.com/Treblle/treblle-utils): A set of helpers and
|
|
136
|
+
utility functions useful for the JavaScript SDKs.
|
|
137
|
+
- [`php-utils`](https://github.com/Treblle/php-utils): A set of helpers and
|
|
138
|
+
utility functions useful for the PHP SDKs.
|
|
139
|
+
|
|
140
|
+
## Community 💙
|
|
141
|
+
|
|
142
|
+
First and foremost: **Star and watch this repository** to stay up-to-date.
|
|
143
|
+
|
|
144
|
+
Also, follow our [Blog](https://blog.treblle.com), and on [Twitter](https://twitter.com/treblleapi).
|
|
145
|
+
|
|
146
|
+
You can chat with the team and other members on [Discord](https://treblle.com/chat) and follow our tutorials and other video material at [YouTube](https://youtube.com/@treblle).
|
|
147
|
+
|
|
148
|
+
[](https://treblle.com/chat)
|
|
149
|
+
|
|
150
|
+
[](https://youtube.com/@treblle)
|
|
151
|
+
|
|
152
|
+
[](https://twitter.com/treblleapi)
|
|
153
|
+
|
|
154
|
+
### How to contribute
|
|
155
|
+
|
|
156
|
+
Here are some ways of contributing to making Treblle better:
|
|
157
|
+
|
|
158
|
+
- **[Try out Treblle](https://docs.treblle.com/en/introduction#getting-started)**, and let us know ways to make Treblle better for you. Let us know here on [Discord](https://treblle.com/chat).
|
|
159
|
+
- Join our [Discord](https://treblle.com/chat) and connect with other members to share and learn from.
|
|
160
|
+
- Send a pull request to any of our [open source repositories](https://github.com/Treblle) on Github. Check the contribution guide on the repo you want to contribute to for more details about how to contribute. We're looking forward to your contribution!
|
|
161
|
+
|
|
162
|
+
### Contributors
|
|
163
|
+
<!-- Replace link with the link of the SDK contributors-->
|
|
164
|
+
<a href="https://github.com/Treblle/treblle-ruby/graphs/contributors">
|
|
165
|
+
<p align="center">
|
|
166
|
+
<img src="https://contrib.rocks/image?repo=Treblle/treblle-ruby" alt="A table of avatars from the project's contributors" />
|
|
167
|
+
</p>
|
|
168
|
+
</a>
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
The current release series and the next most recent one (by major-minor version) will receive patches and new versions in case of a security issue.
|
|
6
|
+
|
|
7
|
+
### Unsupported Release Series
|
|
8
|
+
|
|
9
|
+
When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. If you are not comfortable maintaining your own versions, you should upgrade to a supported version.
|
|
10
|
+
|
|
11
|
+
## Reporting a security issue
|
|
12
|
+
Contact the Treblle support at treblle.com.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'errors/configuration_errors'
|
|
4
|
+
|
|
5
|
+
module Treblle
|
|
6
|
+
class Configuration
|
|
7
|
+
DEFAULT_SENSITIVE_ATTRS = %w[
|
|
8
|
+
card_number
|
|
9
|
+
cardNumber
|
|
10
|
+
cc
|
|
11
|
+
ccv
|
|
12
|
+
credit_score
|
|
13
|
+
creditScore
|
|
14
|
+
password
|
|
15
|
+
password_confirmation
|
|
16
|
+
passwordConfirmation
|
|
17
|
+
pwd
|
|
18
|
+
secretn
|
|
19
|
+
ssn
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
attr_accessor :restricted_endpoints, :whitelisted_endpoints,
|
|
23
|
+
:api_key, :project_id, :app_version
|
|
24
|
+
attr_reader :sensitive_attrs, :enabled_environments
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@restricted_endpoints = []
|
|
28
|
+
@whitelisted_endpoints = '/api/'
|
|
29
|
+
@app_version = nil
|
|
30
|
+
@sensitive_attrs = DEFAULT_SENSITIVE_ATTRS
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def monitoring_enabled?(request_url)
|
|
34
|
+
whitelisted_endpoint?(request_url) && !restricted_endpoint?(request_url)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def sensitive_attrs=(attributes)
|
|
38
|
+
@sensitive_attrs = attributes ? DEFAULT_SENSITIVE_ATTRS + attributes : DEFAULT_SENSITIVE_ATTRS
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def enabled_environments=(value)
|
|
42
|
+
@enabled_environments = Array(value)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def enabled_environment?
|
|
46
|
+
return false if enabled_environments.empty?
|
|
47
|
+
|
|
48
|
+
enabled_environments.include?(environment)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_credentials!
|
|
52
|
+
raise Errors::MissingApiKeyError if api_key.to_s.empty?
|
|
53
|
+
raise Errors::MissingProjectIdError if project_id.to_s.empty?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def environment
|
|
59
|
+
@environment || ::Rails.env
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def whitelisted_endpoint?(request_url)
|
|
63
|
+
Array(whitelisted_endpoints).any? do |endpoint|
|
|
64
|
+
request_url.start_with?(endpoint)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def restricted_endpoint?(request_url)
|
|
69
|
+
restricted_endpoints.any? do |endpoint|
|
|
70
|
+
pattern = Regexp.escape(endpoint).gsub('\*', '.*')
|
|
71
|
+
Regexp.new("^#{pattern}$") =~ request_url
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'treblle/logging'
|
|
7
|
+
|
|
8
|
+
module Treblle
|
|
9
|
+
class Dispatcher
|
|
10
|
+
include Logging
|
|
11
|
+
|
|
12
|
+
TREBLLE_URIS = %w[
|
|
13
|
+
https://rocknrolla.treblle.com
|
|
14
|
+
https://punisher.treblle.com
|
|
15
|
+
https://sicario.treblle.com
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
def initialize(payload:, configuration: Treblle.configuration)
|
|
19
|
+
@payload = payload
|
|
20
|
+
@uri = get_uri
|
|
21
|
+
@configuration = configuration
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call
|
|
25
|
+
send_payload_to_treblle
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :payload, :uri, :configuration
|
|
31
|
+
|
|
32
|
+
def get_uri
|
|
33
|
+
URI(TREBLLE_URIS.sample)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def send_payload_to_treblle
|
|
37
|
+
Thread.new do
|
|
38
|
+
response = make_http_request
|
|
39
|
+
|
|
40
|
+
if response.code.to_i >= 400
|
|
41
|
+
log_error(response.body)
|
|
42
|
+
else
|
|
43
|
+
log_success(response.body)
|
|
44
|
+
end
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
log_error(e.message)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def make_http_request
|
|
51
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
|
52
|
+
http.use_ssl = true
|
|
53
|
+
http.read_timeout = 2
|
|
54
|
+
http.open_timeout = 2
|
|
55
|
+
|
|
56
|
+
http.start do |http_instance|
|
|
57
|
+
http_instance.request(build_request)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def build_request
|
|
62
|
+
req = Net::HTTP::Post.new(uri)
|
|
63
|
+
req['Content-Type'] = 'application/json'
|
|
64
|
+
req['x-api-key'] = configuration.api_key
|
|
65
|
+
req['x-treblle-trace-id'] = SecureRandom.uuid
|
|
66
|
+
req.body = payload
|
|
67
|
+
req
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Treblle
|
|
2
|
+
module Errors
|
|
3
|
+
class ConfigurationError < StandardError
|
|
4
|
+
def initialize(msg = "Configuration error")
|
|
5
|
+
super(msg)
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class MissingApiKeyError < ConfigurationError
|
|
10
|
+
def initialize(msg = "API key is missing")
|
|
11
|
+
super(msg)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class MissingProjectIdError < ConfigurationError
|
|
16
|
+
def initialize(msg = "Project ID is missing")
|
|
17
|
+
super(msg)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'treblle/version'
|
|
4
|
+
require 'treblle/utils/hash_sanitizer'
|
|
5
|
+
|
|
6
|
+
module Treblle
|
|
7
|
+
class GeneratePayload
|
|
8
|
+
SDK_LANG = 'ruby'
|
|
9
|
+
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
10
|
+
|
|
11
|
+
def initialize(request:, response:, started_at:, configuration: Treblle.configuration)
|
|
12
|
+
@request = request
|
|
13
|
+
@response = response
|
|
14
|
+
@started_at = started_at
|
|
15
|
+
@configuration = configuration
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
payload.to_json
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
attr_reader :request, :response, :started_at, :configuration
|
|
25
|
+
|
|
26
|
+
def sanitize(body)
|
|
27
|
+
Utils::HashSanitizer.sanitize(body, configuration.sensitive_attrs)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def timestamp
|
|
31
|
+
started_at.strftime(TIME_FORMAT)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def load_time
|
|
35
|
+
Time.now - started_at
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def payload
|
|
39
|
+
{
|
|
40
|
+
api_key: configuration.api_key,
|
|
41
|
+
project_id: configuration.project_id,
|
|
42
|
+
version: Treblle::API_VERSION,
|
|
43
|
+
sdk: SDK_LANG,
|
|
44
|
+
data: {
|
|
45
|
+
server: {
|
|
46
|
+
ip: request.server.remote_addr,
|
|
47
|
+
timezone: request.server.timezone,
|
|
48
|
+
software: request.server.software,
|
|
49
|
+
signature: '',
|
|
50
|
+
protocol: request.server.protocol,
|
|
51
|
+
os: {
|
|
52
|
+
name: request.server.os_name,
|
|
53
|
+
release: '',
|
|
54
|
+
architecture: request.server.os_architecture
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
language: {
|
|
58
|
+
name: SDK_LANG,
|
|
59
|
+
version: RUBY_VERSION
|
|
60
|
+
},
|
|
61
|
+
request: {
|
|
62
|
+
timestamp: timestamp,
|
|
63
|
+
ip: request.client.ip,
|
|
64
|
+
url: request.client.url,
|
|
65
|
+
user_agent: request.client.user_agent,
|
|
66
|
+
method: request.method,
|
|
67
|
+
headers: request.headers,
|
|
68
|
+
body: sanitize(request.body)
|
|
69
|
+
},
|
|
70
|
+
response: {
|
|
71
|
+
headers: response.headers,
|
|
72
|
+
code: response.status,
|
|
73
|
+
size: response.size,
|
|
74
|
+
load_time: load_time,
|
|
75
|
+
body: response.body,
|
|
76
|
+
errors: errors
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def errors
|
|
83
|
+
return [] if response.exception.nil?
|
|
84
|
+
|
|
85
|
+
[{
|
|
86
|
+
source: 'onError',
|
|
87
|
+
type: response.exception.type,
|
|
88
|
+
message: response.exception.message,
|
|
89
|
+
file: response.exception.file_path,
|
|
90
|
+
line: response.exception.line_number
|
|
91
|
+
}]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
module Treblle
|
|
6
|
+
module Logging
|
|
7
|
+
def logger
|
|
8
|
+
@logger ||= Logger.new($stdout)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def log_error(message)
|
|
12
|
+
logger.error("Treblle monitoring failed: #{message}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def log_success(message)
|
|
16
|
+
logger.info("Successfully sent to Treblle: #{message}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'treblle/dispatcher'
|
|
4
|
+
require 'treblle/request_builder'
|
|
5
|
+
require 'treblle/response_builder'
|
|
6
|
+
require 'treblle/generate_payload'
|
|
7
|
+
require 'treblle/logging'
|
|
8
|
+
require 'treblle'
|
|
9
|
+
|
|
10
|
+
module Treblle
|
|
11
|
+
class Middleware
|
|
12
|
+
include Logging
|
|
13
|
+
|
|
14
|
+
def initialize(app, configuration: Treblle.configuration)
|
|
15
|
+
@app = app
|
|
16
|
+
@configuration = configuration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(env)
|
|
20
|
+
if should_monitor?(env)
|
|
21
|
+
call_with_treblle_monitoring(env)
|
|
22
|
+
else
|
|
23
|
+
@app.call(env)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :configuration
|
|
30
|
+
|
|
31
|
+
def call_with_treblle_monitoring(env)
|
|
32
|
+
started_at = Time.now
|
|
33
|
+
|
|
34
|
+
response = @app.call(env)
|
|
35
|
+
handle_monitoring(env, response, started_at)
|
|
36
|
+
|
|
37
|
+
response
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def handle_monitoring(env, rack_response, started_at)
|
|
41
|
+
request = RequestBuilder.new(env).build
|
|
42
|
+
response = ResponseBuilder.new(rack_response).build
|
|
43
|
+
payload = GeneratePayload.new(request: request, response: response, started_at: started_at).call
|
|
44
|
+
|
|
45
|
+
Dispatcher.new(payload: payload).call
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
log_error(e.message)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def should_monitor?(env)
|
|
51
|
+
configuration.enabled_environment? && configuration.monitoring_enabled?(env['PATH_INFO'])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treblle
|
|
4
|
+
module Models
|
|
5
|
+
class Request
|
|
6
|
+
class Client
|
|
7
|
+
def initialize(request)
|
|
8
|
+
@ip = request.ip
|
|
9
|
+
@url = request.original_url
|
|
10
|
+
@user_agent = request.user_agent
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :ip, :url, :user_agent
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Treblle
|
|
6
|
+
module Models
|
|
7
|
+
class Request
|
|
8
|
+
class Server
|
|
9
|
+
def initialize(request)
|
|
10
|
+
@request = request
|
|
11
|
+
@software = get_server_software
|
|
12
|
+
@protocol = get_server_protocol
|
|
13
|
+
@os_architecture = get_platform
|
|
14
|
+
@os_name = get_host_os
|
|
15
|
+
@timezone = get_timezone
|
|
16
|
+
@remote_addr = get_remote_addr
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :software, :protocol, :os_name, :os_architecture, :remote_addr, :timezone
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
attr_reader :request
|
|
24
|
+
|
|
25
|
+
def get_host_os
|
|
26
|
+
RbConfig::CONFIG.fetch('host_os', 'unknown')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_platform
|
|
30
|
+
Gem::Platform.local.os
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_remote_addr
|
|
34
|
+
request.env.fetch('REMOTE_ADDR', '')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_server_software
|
|
38
|
+
request.env.fetch('SERVER_SOFTWARE', '')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_server_protocol
|
|
42
|
+
request.env.fetch('SERVER_PROTOCOL', '')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def get_timezone
|
|
46
|
+
Time.now.zone
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treblle
|
|
4
|
+
module Models
|
|
5
|
+
class Response
|
|
6
|
+
class Exception
|
|
7
|
+
def initialize(response)
|
|
8
|
+
@response = response
|
|
9
|
+
@trace = fetch_trace
|
|
10
|
+
@type = fetch_type
|
|
11
|
+
@message = fetch_message
|
|
12
|
+
@file_path, @line_number = get_exception_path_and_line
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :file_path, :line_number, :message, :type, :trace
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :response
|
|
20
|
+
|
|
21
|
+
def fetch_trace
|
|
22
|
+
response.body.dig("traces", "Application Trace")&.first&.[]("trace")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_type
|
|
26
|
+
response.body["error"] || response.body["errors"] || 'Unhandled error'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fetch_message
|
|
30
|
+
response.body["exception"] || response.body["error"] || response.body["errors"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_exception_path_and_line
|
|
34
|
+
return [nil, nil] if trace.nil?
|
|
35
|
+
|
|
36
|
+
match_data = trace.match(/^(.*):(\d+):in `.*'$/)
|
|
37
|
+
return [nil, nil] if match_data.nil?
|
|
38
|
+
|
|
39
|
+
file_path = match_data[1]
|
|
40
|
+
line_number = match_data[2]
|
|
41
|
+
|
|
42
|
+
[file_path, line_number]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'treblle/middleware'
|
|
2
|
+
require 'rails'
|
|
3
|
+
|
|
4
|
+
module Treblle
|
|
5
|
+
module Rails
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
initializer 'treblle.install_middleware' do |app|
|
|
8
|
+
app.config.middleware.insert_after ActionDispatch::ShowExceptions, Treblle::Middleware
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'treblle/models/request'
|
|
4
|
+
require 'treblle/models/request/server'
|
|
5
|
+
require 'treblle/models/request/client'
|
|
6
|
+
require 'action_dispatch'
|
|
7
|
+
|
|
8
|
+
module Treblle
|
|
9
|
+
class RequestBuilder
|
|
10
|
+
MAX_BODY_LENGTH = 2048
|
|
11
|
+
|
|
12
|
+
HEADER_PREFIXES = %w[
|
|
13
|
+
HTTP AUTHORIZATION QUERY CONTENT REMOTE
|
|
14
|
+
REQUEST SERVER ACCEPT USER HOST X PATH
|
|
15
|
+
VARY REFERRER
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
def initialize(rack_env)
|
|
19
|
+
@rack_env = rack_env
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build
|
|
23
|
+
Models::Request.new.tap do |request|
|
|
24
|
+
apply_to_request(request)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :rack_env
|
|
31
|
+
|
|
32
|
+
def apply_to_request(request)
|
|
33
|
+
request_metadata = ActionDispatch::Request.new(rack_env)
|
|
34
|
+
|
|
35
|
+
request.server = Models::Request::Server.new(request_metadata)
|
|
36
|
+
request.client = Models::Request::Client.new(request_metadata)
|
|
37
|
+
request.method = request_metadata.method
|
|
38
|
+
request.headers = get_headers(request_metadata)
|
|
39
|
+
request.body = get_body(request_metadata)
|
|
40
|
+
|
|
41
|
+
request
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_headers(request_metadata)
|
|
45
|
+
request_metadata.headers.select { |header, _value| header_to_include?(header) }.to_h
|
|
46
|
+
.transform_keys { |header| normalize_header(header) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def normalize_header(header)
|
|
50
|
+
header.delete_prefix('HTTP_').tr('_', '-')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def header_to_include?(header)
|
|
54
|
+
return false if header.start_with?('rack', 'puma')
|
|
55
|
+
|
|
56
|
+
HEADER_PREFIXES.any? do |prefix|
|
|
57
|
+
header.to_s.start_with?(prefix)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get_body(request_metadata)
|
|
62
|
+
case request_metadata.media_type
|
|
63
|
+
when 'application/x-www-form-urlencoded', 'multipart/form-data'
|
|
64
|
+
request_metadata.POST.dup
|
|
65
|
+
else
|
|
66
|
+
body = request_metadata.body.read
|
|
67
|
+
request_metadata.body.rewind
|
|
68
|
+
body.byteslice(0, MAX_BODY_LENGTH).force_encoding('utf-8').scrub
|
|
69
|
+
|
|
70
|
+
request_metadata.media_type == 'application/json' ? JSON.parse(body) : body
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'treblle/models/response'
|
|
4
|
+
require 'treblle/models/response/exception'
|
|
5
|
+
require 'active_support'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Treblle
|
|
9
|
+
class ResponseBuilder
|
|
10
|
+
def initialize(rack_response)
|
|
11
|
+
@rack_response = rack_response
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build
|
|
15
|
+
Models::Response.new.tap do |response|
|
|
16
|
+
apply_to_response(response)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :rack_response, :handle_errors
|
|
23
|
+
|
|
24
|
+
def apply_to_response(response)
|
|
25
|
+
status, headers, response_data = rack_response || [500, [], nil]
|
|
26
|
+
|
|
27
|
+
response.status = status
|
|
28
|
+
response.headers = headers
|
|
29
|
+
response.body = parse_body(response_data) || parse_error_body(response_data)
|
|
30
|
+
response.size = calculate_size(response.body, response.headers)
|
|
31
|
+
response.exception = Models::Response::Exception.new(response) if status >= 400
|
|
32
|
+
|
|
33
|
+
response
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def calculate_size(body, headers)
|
|
37
|
+
return 0 if body.nil?
|
|
38
|
+
|
|
39
|
+
headers.fetch('content-length', nil) || ActiveSupport::JSON.encode(body).size
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parse_body(response_data)
|
|
43
|
+
return nil unless response_data.respond_to?(:body)
|
|
44
|
+
|
|
45
|
+
JSON.parse(response_data.body)
|
|
46
|
+
rescue JSON::ParserError
|
|
47
|
+
response_data.body
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_error_body(response_data)
|
|
51
|
+
return nil unless response_data.is_a?(Array) && !response_data.empty?
|
|
52
|
+
|
|
53
|
+
JSON.parse(response_data.first)
|
|
54
|
+
rescue JSON::ParserError
|
|
55
|
+
response_data.body
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Treblle
|
|
4
|
+
module Utils
|
|
5
|
+
class HashSanitizer
|
|
6
|
+
class << self
|
|
7
|
+
def sanitize(body, sensitive_attrs)
|
|
8
|
+
return {} if body.nil? || body.empty?
|
|
9
|
+
return hash unless body.is_a?(Hash) || body.is_a?(Array)
|
|
10
|
+
|
|
11
|
+
if body.is_a?(Hash)
|
|
12
|
+
sanitize_hash(body, sensitive_attrs)
|
|
13
|
+
elsif body.is_a?(Array)
|
|
14
|
+
sanitize_array(body, sensitive_attrs)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def sanitize_hash(hash, sensitive_attrs)
|
|
21
|
+
hash.each_with_object({}) do |(key, value), result|
|
|
22
|
+
result[key] = if value.is_a?(Hash) || value.is_a?(Array)
|
|
23
|
+
sanitize(value, sensitive_attrs)
|
|
24
|
+
else
|
|
25
|
+
sanitize_value(key, value, sensitive_attrs)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def sanitize_array(array, sensitive_attrs)
|
|
31
|
+
array.map { |item| sanitize(item, sensitive_attrs) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def sanitize_value(key, value, sensitive_attrs)
|
|
35
|
+
sensitive_attrs.include?(key.to_s) ? '*' * value.to_s.length : value
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/treblle.rb
CHANGED
|
@@ -1,94 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
4
|
-
require '
|
|
5
|
-
require 'treblle/
|
|
3
|
+
require 'treblle/middleware'
|
|
4
|
+
require 'treblle/configuration'
|
|
5
|
+
require 'treblle/rails/railtie'
|
|
6
6
|
|
|
7
7
|
# Treblle middleware for request interception and gathering.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def initialize(app)
|
|
13
|
-
@app = app
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def call(env)
|
|
17
|
-
request = ActionDispatch::Request.new(env)
|
|
18
|
-
started_at = Time.now
|
|
19
|
-
|
|
20
|
-
begin
|
|
21
|
-
status, headers, response = @app.call(env)
|
|
22
|
-
to_parse = response.clone
|
|
23
|
-
to_parse = to_parse.try(:body) if to_parse.respond_to?(:body)
|
|
24
|
-
|
|
25
|
-
begin
|
|
26
|
-
json_response = JSON.parse(to_parse)
|
|
27
|
-
rescue JSON::ParserError, TypeError
|
|
28
|
-
# continue with original values when unable to parse reponse to json
|
|
29
|
-
return [status, headers, response]
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
params = {
|
|
33
|
-
ended_at: Time.now,
|
|
34
|
-
env: env,
|
|
35
|
-
headers: headers,
|
|
36
|
-
json_response: json_response,
|
|
37
|
-
request: request,
|
|
38
|
-
started_at: started_at,
|
|
39
|
-
status: status
|
|
40
|
-
}
|
|
41
|
-
capture(params)
|
|
42
|
-
rescue Exception => e
|
|
43
|
-
status = status_code_for_exception(e)
|
|
44
|
-
params = {
|
|
45
|
-
ended_at: Time.now,
|
|
46
|
-
env: env,
|
|
47
|
-
exception: e,
|
|
48
|
-
headers: headers,
|
|
49
|
-
request: request,
|
|
50
|
-
started_at: started_at,
|
|
51
|
-
status: status
|
|
52
|
-
}
|
|
53
|
-
# send error payload to treblle, but raise exception as well
|
|
54
|
-
capture(params)
|
|
55
|
-
raise e
|
|
8
|
+
module Treblle
|
|
9
|
+
class << self
|
|
10
|
+
def configuration
|
|
11
|
+
@configuration ||= Configuration.new
|
|
56
12
|
end
|
|
57
13
|
|
|
58
|
-
|
|
59
|
-
|
|
14
|
+
def configure
|
|
15
|
+
yield(configuration)
|
|
60
16
|
|
|
61
|
-
|
|
62
|
-
# for perfomance reasons.
|
|
63
|
-
# @param [Object] params
|
|
64
|
-
def capture(params)
|
|
65
|
-
data = DataBuilder.new(params).call
|
|
66
|
-
return if data&.bytesize.to_i > 2.megabytes # ignore the capture for unnaturally large requests
|
|
67
|
-
|
|
68
|
-
Thread.new do
|
|
69
|
-
send_to_treblle(data)
|
|
70
|
-
end
|
|
71
|
-
rescue Exception => e
|
|
72
|
-
Rails.logger.error(e.message)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def send_to_treblle(data)
|
|
76
|
-
uri = URI(TREBLLE_URI)
|
|
77
|
-
req = Net::HTTP::Post.new(uri)
|
|
78
|
-
req['Content-Type'] = 'application/json'
|
|
79
|
-
req['x-api-key'] = ENV.fetch('TREBLLE_API_KEY') { '' }
|
|
80
|
-
req.body = data
|
|
81
|
-
puts data
|
|
82
|
-
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
83
|
-
http.request(req)
|
|
17
|
+
configuration.validate_credentials! if configuration.enabled_environment?
|
|
84
18
|
end
|
|
85
|
-
puts res.try(:body)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def status_code_for_exception(exception)
|
|
89
|
-
exception_wrapper = ActionDispatch::ExceptionWrapper.new(nil, exception)
|
|
90
|
-
exception_wrapper.status_code
|
|
91
|
-
rescue
|
|
92
|
-
500
|
|
93
19
|
end
|
|
94
20
|
end
|
metadata
CHANGED
|
@@ -1,22 +1,57 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: treblle
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Borna Kapusta
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
date: 2024-03-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 6.1.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 6.1.0
|
|
27
|
+
description: |
|
|
28
|
+
Treblle is a lightweight SDK that helps Engineering and Product teams
|
|
29
|
+
build, ship & maintain REST based APIs faster.
|
|
30
|
+
email: borna.kapusta@cactus-code.com
|
|
15
31
|
executables: []
|
|
16
32
|
extensions: []
|
|
17
33
|
extra_rdoc_files: []
|
|
18
34
|
files:
|
|
35
|
+
- MIT-LICENSE
|
|
36
|
+
- README.md
|
|
37
|
+
- SECURITY.md
|
|
19
38
|
- lib/treblle.rb
|
|
39
|
+
- lib/treblle/configuration.rb
|
|
40
|
+
- lib/treblle/dispatcher.rb
|
|
41
|
+
- lib/treblle/errors/configuration_errors.rb
|
|
42
|
+
- lib/treblle/generate_payload.rb
|
|
43
|
+
- lib/treblle/logging.rb
|
|
44
|
+
- lib/treblle/middleware.rb
|
|
45
|
+
- lib/treblle/models/request.rb
|
|
46
|
+
- lib/treblle/models/request/client.rb
|
|
47
|
+
- lib/treblle/models/request/server.rb
|
|
48
|
+
- lib/treblle/models/response.rb
|
|
49
|
+
- lib/treblle/models/response/exception.rb
|
|
50
|
+
- lib/treblle/rails/railtie.rb
|
|
51
|
+
- lib/treblle/request_builder.rb
|
|
52
|
+
- lib/treblle/response_builder.rb
|
|
53
|
+
- lib/treblle/utils/hash_sanitizer.rb
|
|
54
|
+
- lib/treblle/version.rb
|
|
20
55
|
homepage: https://rubygems.org/gems/treblle
|
|
21
56
|
licenses:
|
|
22
57
|
- MIT
|
|
@@ -36,8 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
36
71
|
- !ruby/object:Gem::Version
|
|
37
72
|
version: '0'
|
|
38
73
|
requirements: []
|
|
39
|
-
rubygems_version: 3.
|
|
74
|
+
rubygems_version: 3.5.6
|
|
40
75
|
signing_key:
|
|
41
76
|
specification_version: 4
|
|
42
|
-
summary:
|
|
77
|
+
summary: Middleware for monitoring your API endpoints
|
|
43
78
|
test_files: []
|