end_point_blank 0.2.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/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/README.md +64 -0
- data/Rakefile +12 -0
- data/build.sh +5 -0
- data/docs/superpowers/plans/2026-07-08-framework-agnostic-core.md +68 -0
- data/docs/superpowers/specs/2026-07-08-framework-agnostic-core-design.md +58 -0
- data/end_point_blank.gemspec +45 -0
- data/lib/end_point_blank/access_tokens.rb +72 -0
- data/lib/end_point_blank/authorization.rb +36 -0
- data/lib/end_point_blank/commands/authentication_cache.rb +95 -0
- data/lib/end_point_blank/commands/basic_authenticate.rb +47 -0
- data/lib/end_point_blank/commands/bearer_generate.rb +33 -0
- data/lib/end_point_blank/commands/endpoint_authorize.rb +71 -0
- data/lib/end_point_blank/commands/endpoint_update.rb +129 -0
- data/lib/end_point_blank/commands/generate_access_token.rb +46 -0
- data/lib/end_point_blank/commands/http.rb +45 -0
- data/lib/end_point_blank/commands/route_pattern_finder.rb +21 -0
- data/lib/end_point_blank/commands/version_finder.rb +70 -0
- data/lib/end_point_blank/configuration.rb +107 -0
- data/lib/end_point_blank/fast_json_truncator.rb +49 -0
- data/lib/end_point_blank/log_entry.rb +16 -0
- data/lib/end_point_blank/loggers/logger.rb +30 -0
- data/lib/end_point_blank/masking.rb +282 -0
- data/lib/end_point_blank/middleware/rack/report_interaction.rb +54 -0
- data/lib/end_point_blank/rack/env_store.rb +34 -0
- data/lib/end_point_blank/rack/headers.rb +11 -0
- data/lib/end_point_blank/rails/authenticated.rb +21 -0
- data/lib/end_point_blank/rails/authorized.rb +33 -0
- data/lib/end_point_blank/rails/railtie.rb +18 -0
- data/lib/end_point_blank/rails/versioned.rb +64 -0
- data/lib/end_point_blank/session_configuration.rb +44 -0
- data/lib/end_point_blank/string_truncator.rb +21 -0
- data/lib/end_point_blank/unauthorized_error.rb +10 -0
- data/lib/end_point_blank/version.rb +5 -0
- data/lib/end_point_blank/writers/delayed_writer.rb +110 -0
- data/lib/end_point_blank/writers/direct_writer.rb +18 -0
- data/lib/end_point_blank/writers/exception_writer.rb +50 -0
- data/lib/end_point_blank/writers/log_writer.rb +69 -0
- data/lib/end_point_blank/writers/request_writer.rb +61 -0
- data/lib/end_point_blank/writers/response_writer.rb +70 -0
- data/lib/end_point_blank/writers/shared.rb +39 -0
- data/lib/end_point_blank/xml_truncator.rb +89 -0
- data/lib/end_point_blank.rb +57 -0
- data/sig/end_point_blank_rack.rbs +4 -0
- data/test.sh +5 -0
- metadata +163 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'excon'
|
|
4
|
+
require_relative 'http'
|
|
5
|
+
|
|
6
|
+
module EndPointBlank
|
|
7
|
+
module Commands
|
|
8
|
+
# Collects and sends application endpoint information to a remote registry service.
|
|
9
|
+
# Scans all Rails routes and extracts details including path, HTTP verb, API version,
|
|
10
|
+
# and deprecation status from controller versioning concerns. Sends this data along
|
|
11
|
+
# with application metadata (name, hostname, environment) to the configured endpoint_update_url
|
|
12
|
+
# for centralized endpoint tracking and documentation.
|
|
13
|
+
class EndpointUpdate
|
|
14
|
+
def initialize
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configuration
|
|
18
|
+
EndPointBlank::Configuration.instance
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def auth
|
|
22
|
+
Authorization.header
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write(data)
|
|
26
|
+
EndPointBlank.logger.info "[EndPointBlank] Sending application update: " \
|
|
27
|
+
"application=#{data[:application]} environment=#{data[:environment]} " \
|
|
28
|
+
"app_version=#{data[:app_version]}"
|
|
29
|
+
|
|
30
|
+
response = Excon.post(configuration.endpoint_update_url,
|
|
31
|
+
headers: {'Authorization' => auth, 'Content-Type' => 'application/json'},
|
|
32
|
+
body: data.to_json,
|
|
33
|
+
**EndPointBlank::Commands::Http::TIMEOUT_OPTIONS
|
|
34
|
+
)
|
|
35
|
+
if response.status > 299
|
|
36
|
+
EndPointBlank.logger.error "Failed to update endpoint: #{response.status} - #{response.body}"
|
|
37
|
+
else
|
|
38
|
+
EndPointBlank.logger.info "Endpoint updated successfully: #{response.status}"
|
|
39
|
+
end
|
|
40
|
+
rescue Excon::Error => e
|
|
41
|
+
# Was `rescue Excon::Error::Socket, Excon::Error::Connection` -
|
|
42
|
+
# Excon::Error::Connection does not exist in this excon version, so
|
|
43
|
+
# that clause raised NameError instead of catching anything, and a
|
|
44
|
+
# timeout (Excon::Error::Timeout) would have crashed the caller.
|
|
45
|
+
# Rescuing Excon::Error catches every transport failure, including
|
|
46
|
+
# timeouts, without letting this fire-and-forget call ever raise.
|
|
47
|
+
EndPointBlank.logger.warn "EndPointBlank: could not reach intake to update endpoints (#{e.message})"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.update
|
|
51
|
+
new.update
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update
|
|
55
|
+
write(application_info)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def application_info
|
|
59
|
+
{
|
|
60
|
+
application: application_name,
|
|
61
|
+
hostname: hostname,
|
|
62
|
+
lib_version: EndPointBlank::VERSION,
|
|
63
|
+
environment: environment,
|
|
64
|
+
endpoints: ,
|
|
65
|
+
app_version: ,
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def app_version
|
|
72
|
+
return configuration.application_version if configuration.respond_to?(:application_version) && configuration.application_version.present?
|
|
73
|
+
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def application_name
|
|
78
|
+
::Rails.application.class.module_parent_name.underscore
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def hostname
|
|
82
|
+
# Try to get hostname from various sources
|
|
83
|
+
if defined?(::Rails) && ::Rails.application.config.respond_to?(:force_ssl) && ::Rails.application.config.force_ssl
|
|
84
|
+
protocol = 'https'
|
|
85
|
+
else
|
|
86
|
+
protocol = 'http'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
host = if ::Rails.application.config.respond_to?(:host)
|
|
90
|
+
::Rails.application.config.host
|
|
91
|
+
elsif defined?(ActionMailer) && ActionMailer::Base.default_url_options[:host]
|
|
92
|
+
ActionMailer::Base.default_url_options[:host]
|
|
93
|
+
else
|
|
94
|
+
'localhost'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
port = ::Rails.application.config.respond_to?(:port) ? ::Rails.application.config.port : nil
|
|
98
|
+
port_suffix = port && port != 80 && port != 443 ? ":#{port}" : ""
|
|
99
|
+
|
|
100
|
+
"#{host}#{port_suffix}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def environment
|
|
104
|
+
::Rails.env
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def endpoints
|
|
108
|
+
::Rails.application.routes.routes.map do |route|
|
|
109
|
+
if route.defaults[:controller].nil? || route.defaults[:action].nil?
|
|
110
|
+
nil
|
|
111
|
+
else
|
|
112
|
+
controller_class = "#{route.defaults[:controller].camelize}Controller".constantize
|
|
113
|
+
versions = controller_class.respond_to?(:versions) ? controller_class.versions(route.defaults[:action].to_sym) : {}
|
|
114
|
+
{
|
|
115
|
+
path: route.path.spec.to_s.gsub(/\([^)]*\)/, ''),
|
|
116
|
+
http_method: route.verb,
|
|
117
|
+
endpoint_versions: versions,
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
end.compact.uniq.filter { |e| e[:path].present? && !(e[:path].start_with?('/rails/') || e[:path] == '/assets' || e[:endpoint_versions].empty?)}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def extract_version_from_path(path)
|
|
124
|
+
match = path.match(/\/v(\d+)\//)
|
|
125
|
+
match ? "v#{match[1]}" : nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'excon'
|
|
4
|
+
require "json"
|
|
5
|
+
require_relative 'http'
|
|
6
|
+
|
|
7
|
+
module EndPointBlank
|
|
8
|
+
module Commands
|
|
9
|
+
module GenerateAccessTokenMethods
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def configuration
|
|
12
|
+
EndPointBlank::Configuration.instance
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def token(hostname)
|
|
16
|
+
body = {hostname: hostname}
|
|
17
|
+
if configuration.token_ttl
|
|
18
|
+
body[:token_ttl] = configuration.token_ttl
|
|
19
|
+
end
|
|
20
|
+
auth = Authorization.header
|
|
21
|
+
response = Excon.post(configuration.access_token_url,
|
|
22
|
+
headers: {'Authorization' => auth, 'Content-Type' => 'application/json'},
|
|
23
|
+
body: body.to_json,
|
|
24
|
+
**EndPointBlank::Commands::Http::TIMEOUT_OPTIONS
|
|
25
|
+
)
|
|
26
|
+
EndPointBlank.logger.info "Authentication response: #{response.status} - #{response.body}"
|
|
27
|
+
parsed_body = response.body.is_a?(String) ? JSON.parse(response.body) : response.body
|
|
28
|
+
parsed_body.transform_keys(&:to_sym)
|
|
29
|
+
rescue => e
|
|
30
|
+
EndPointBlank.logger.error "Error occurred during authentication: #{e.message}\n #{e.backtrace.join("\n")}"
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.included(base)
|
|
36
|
+
base.extend(ClassMethods)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Generates an access token by sending a request to a remote authorization service.
|
|
41
|
+
# Returns the access token from the authorization service or nil if an error occurs.
|
|
42
|
+
class GenerateAccessToken
|
|
43
|
+
include GenerateAccessTokenMethods
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'excon'
|
|
2
|
+
|
|
3
|
+
module EndPointBlank
|
|
4
|
+
module Commands
|
|
5
|
+
module Http
|
|
6
|
+
MAX_ATTEMPTS = 3
|
|
7
|
+
RETRY_DELAY = 0.2
|
|
8
|
+
|
|
9
|
+
# Per-attempt timeouts (seconds). Kept short because every call site in
|
|
10
|
+
# this lib is fire-and-forget telemetry: a slow/unreachable intake must
|
|
11
|
+
# never block the host application's request thread.
|
|
12
|
+
CONNECT_TIMEOUT = 3
|
|
13
|
+
READ_TIMEOUT = 5
|
|
14
|
+
|
|
15
|
+
# Shared Excon options so the timeout values live in exactly one place.
|
|
16
|
+
# Merge this into any `Excon.post`/`Excon.new` call in the lib.
|
|
17
|
+
TIMEOUT_OPTIONS = { connect_timeout: CONNECT_TIMEOUT, read_timeout: READ_TIMEOUT }.freeze
|
|
18
|
+
|
|
19
|
+
def self.post(url, auth, body)
|
|
20
|
+
attempt = 0
|
|
21
|
+
begin
|
|
22
|
+
attempt += 1
|
|
23
|
+
Excon.post(
|
|
24
|
+
url,
|
|
25
|
+
headers: { 'Authorization' => auth, 'Content-Type' => 'application/json' },
|
|
26
|
+
body: body.to_json,
|
|
27
|
+
**TIMEOUT_OPTIONS
|
|
28
|
+
)
|
|
29
|
+
rescue Excon::Error => e
|
|
30
|
+
# Excon::Error::Timeout (raised for both connect_timeout and
|
|
31
|
+
# read_timeout) is a subclass of Excon::Error, so it is handled
|
|
32
|
+
# here the same as any other transport error: retried, then
|
|
33
|
+
# swallowed with a log line rather than raised to the caller.
|
|
34
|
+
if attempt < MAX_ATTEMPTS
|
|
35
|
+
sleep RETRY_DELAY
|
|
36
|
+
retry
|
|
37
|
+
end
|
|
38
|
+
EndPointBlank.logger.error \
|
|
39
|
+
"[EndPointBlank] HTTP POST to #{url} failed after #{MAX_ATTEMPTS} attempts: #{e.message}"
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EndPointBlank
|
|
4
|
+
module Commands
|
|
5
|
+
class RoutePatternFinder
|
|
6
|
+
def self.find(request)
|
|
7
|
+
return nil unless defined?(::Rails)
|
|
8
|
+
|
|
9
|
+
matched = nil
|
|
10
|
+
::Rails.application.routes.router.recognize(request) do |route, _params|
|
|
11
|
+
matched = route.path.spec.to_s
|
|
12
|
+
break
|
|
13
|
+
end
|
|
14
|
+
matched
|
|
15
|
+
rescue StandardError => e
|
|
16
|
+
EndPointBlank.logger.debug("Error finding route pattern: #{e.message}")
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module EndPointBlank
|
|
2
|
+
module Commands
|
|
3
|
+
# Finds the API version from an incoming request by checking multiple sources:
|
|
4
|
+
# - Custom version finder configured in Configuration.instance.version_finder
|
|
5
|
+
# - Accept header (e.g., application/vnd.api.v1+json)
|
|
6
|
+
# - X-Api-Version header (e.g., v1)
|
|
7
|
+
# - Content-Type header (e.g., application/vnd.api.v1+json)
|
|
8
|
+
# - Query parameter 'version' (e.g., ?version=v1)
|
|
9
|
+
# - URL path segment (e.g., /v1/resource)
|
|
10
|
+
# - Controller versioning configuration (via Versioned concern)
|
|
11
|
+
# Returns the version number as a string (e.g., "1") or nil if no version is found.
|
|
12
|
+
class VersionFinder
|
|
13
|
+
def headers
|
|
14
|
+
@headers ||= ::EndPointBlank::Rack::Headers.extract
|
|
15
|
+
end
|
|
16
|
+
def find(request)
|
|
17
|
+
return Configuration.instance.version_finder.call(request) if Configuration.instance.version_finder
|
|
18
|
+
|
|
19
|
+
return version if respond_to?(:version)
|
|
20
|
+
|
|
21
|
+
# Logic to determine version from request
|
|
22
|
+
# This could involve checking headers, query parameters, etc.
|
|
23
|
+
headers["Accept"]&.match(%r{application/vnd\.\w+\.v(\d+)}) do |m|
|
|
24
|
+
return m[1]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
headers["X-Api-Version"]&.match(/v(\d+)/) do |m|
|
|
28
|
+
return m[1]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
headers["Content-Type"]&.match(%r{application/vnd\.\w+\.v(\d+)}) do |m|
|
|
32
|
+
return m[1]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
request.params["version"]&.match(/v(\d+)/) do |m|
|
|
36
|
+
return m[1]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
request.path.match(%r{/v(\d+)/}) do |m|
|
|
40
|
+
return m[1]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Try to find version from controller versioning configuration
|
|
44
|
+
version_from_controller(request)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def version_from_controller(request)
|
|
50
|
+
return nil unless defined?(::Rails)
|
|
51
|
+
return nil unless request.respond_to?(:path_parameters)
|
|
52
|
+
|
|
53
|
+
controller_name = request.path_parameters[:controller]
|
|
54
|
+
action_name = request.path_parameters[:action]
|
|
55
|
+
|
|
56
|
+
return nil if controller_name.nil? || action_name.nil?
|
|
57
|
+
|
|
58
|
+
controller_class = "#{controller_name.camelize}Controller".constantize
|
|
59
|
+
return nil unless controller_class.respond_to?(:versions)
|
|
60
|
+
|
|
61
|
+
versions = controller_class.versions(action_name.to_sym)
|
|
62
|
+
return nil if versions.empty?
|
|
63
|
+
|
|
64
|
+
versions.values.flatten.first&.match(/v?(\d+)/)&.[](1)
|
|
65
|
+
rescue NameError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
module EndPointBlank
|
|
6
|
+
# Singleton configuration object for the EndPointBlank client.
|
|
7
|
+
#
|
|
8
|
+
# Values may be set explicitly via {EndPointBlank.configure}. Several
|
|
9
|
+
# settings also fall back to ENDPOINTBLANK_* environment variables when
|
|
10
|
+
# not explicitly configured, in order to support Rails-free deployments.
|
|
11
|
+
class Configuration
|
|
12
|
+
include Singleton
|
|
13
|
+
|
|
14
|
+
attr_writer :client_id, :client_secret, :base_url, :log_base_url, :app_name, :env_name
|
|
15
|
+
|
|
16
|
+
attr_accessor :environment, :worker_count, :log_mode,
|
|
17
|
+
:version_finder, :application_version, :token_ttl, :cache_ttl,
|
|
18
|
+
:masking_rules, :mask_hook, :logger
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@worker_count = 4
|
|
22
|
+
@token_ttl = nil
|
|
23
|
+
@cache_ttl = 300
|
|
24
|
+
@masking_rules = []
|
|
25
|
+
@mask_hook = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the configured client id, falling back to the
|
|
29
|
+
# ENDPOINTBLANK_CLIENT_ID environment variable when not explicitly set.
|
|
30
|
+
def client_id
|
|
31
|
+
@client_id || ENV["ENDPOINTBLANK_CLIENT_ID"]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the configured client secret, falling back to the
|
|
35
|
+
# ENDPOINTBLANK_CLIENT_SECRET environment variable when not explicitly set.
|
|
36
|
+
def client_secret
|
|
37
|
+
@client_secret || ENV["ENDPOINTBLANK_CLIENT_SECRET"]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the configured base URL, falling back to the
|
|
41
|
+
# ENDPOINTBLANK_BASE_URL environment variable, then a built-in default.
|
|
42
|
+
def base_url
|
|
43
|
+
@base_url || ENV["ENDPOINTBLANK_BASE_URL"] || "https://in.endpointblank.com"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the configured log base URL, falling back to the
|
|
47
|
+
# ENDPOINTBLANK_LOG_BASE_URL environment variable, then a built-in default.
|
|
48
|
+
def log_base_url
|
|
49
|
+
@log_base_url || ENV["ENDPOINTBLANK_LOG_BASE_URL"] || "https://log.endpointblank.com"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def endpoint_update_url
|
|
53
|
+
"#{base_url}/api/application_updates"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def access_token_url
|
|
57
|
+
"#{base_url}/api/access_token"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def authorize_url
|
|
61
|
+
"#{base_url}/api/authorize"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def errors_url
|
|
65
|
+
"#{log_base_url}/api/application_errors"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def requests_url
|
|
69
|
+
"#{log_base_url}/api/application_requests"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def responses_url
|
|
73
|
+
"#{log_base_url}/api/application_responses"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def logs_url
|
|
77
|
+
"#{log_base_url}/api/application_logs"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns the name of the application.
|
|
81
|
+
#
|
|
82
|
+
# If {#app_name=} is called, then that value is returned.
|
|
83
|
+
#
|
|
84
|
+
# Otherwise, falls back to the ENDPOINTBLANK_APP_NAME environment
|
|
85
|
+
# variable.
|
|
86
|
+
#
|
|
87
|
+
# Otherwise, if the application is a Rails application, then
|
|
88
|
+
# {::Rails.application.name} is returned.
|
|
89
|
+
#
|
|
90
|
+
# Otherwise, nil is returned.
|
|
91
|
+
def app_name
|
|
92
|
+
if @app_name
|
|
93
|
+
@app_name
|
|
94
|
+
elsif ENV["ENDPOINTBLANK_APP_NAME"]
|
|
95
|
+
ENV["ENDPOINTBLANK_APP_NAME"]
|
|
96
|
+
elsif defined?(::Rails)
|
|
97
|
+
::Rails.application.name.underscore
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the configured environment name, falling back to the
|
|
102
|
+
# ENDPOINTBLANK_ENV environment variable when not explicitly set.
|
|
103
|
+
def env_name
|
|
104
|
+
@env_name || ENV["ENDPOINTBLANK_ENV"]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
class FastJsonTruncator
|
|
4
|
+
MAX_BYTES = 10000
|
|
5
|
+
MAX_DEPTH = 5
|
|
6
|
+
MAX_LIST = 20
|
|
7
|
+
MAX_STRING = 200
|
|
8
|
+
MAX_KEYS = 20
|
|
9
|
+
|
|
10
|
+
def self.truncate(data, limit = MAX_BYTES)
|
|
11
|
+
pruned = prune(data, 0)
|
|
12
|
+
json = JSON.generate(pruned)
|
|
13
|
+
ensure_limit(json, limit)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.prune(value, depth)
|
|
17
|
+
return "[truncated]" if depth > MAX_DEPTH
|
|
18
|
+
|
|
19
|
+
case value
|
|
20
|
+
when Hash
|
|
21
|
+
result = {}
|
|
22
|
+
value.each_with_index do |(k, v), i|
|
|
23
|
+
break if i >= MAX_KEYS
|
|
24
|
+
result[k] = prune(v, depth + 1)
|
|
25
|
+
end
|
|
26
|
+
result
|
|
27
|
+
|
|
28
|
+
when Array
|
|
29
|
+
value.first(MAX_LIST).map { |v| prune(v, depth + 1) }
|
|
30
|
+
|
|
31
|
+
when String
|
|
32
|
+
if value.bytesize > MAX_STRING
|
|
33
|
+
value.byteslice(0, MAX_STRING) + "..."
|
|
34
|
+
else
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
else
|
|
39
|
+
value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.ensure_limit(json, limit)
|
|
44
|
+
return json if json.bytesize <= limit
|
|
45
|
+
|
|
46
|
+
truncated = json.byteslice(0, limit - 20)
|
|
47
|
+
truncated + '...,"truncated":true}'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module EndPointBlank
|
|
2
|
+
class LogEntry
|
|
3
|
+
attr_accessor :message, :stacktrace, :app, :status, :headers, :body, :env, :sent_at
|
|
4
|
+
|
|
5
|
+
def initialize(message:, env: , stacktrace:, app:, status:, headers:, body:, sent_at: Time.now)
|
|
6
|
+
@message = message
|
|
7
|
+
@env = env
|
|
8
|
+
@stacktrace = stacktrace
|
|
9
|
+
@app = app
|
|
10
|
+
@status = status
|
|
11
|
+
@headers = headers
|
|
12
|
+
@body = body
|
|
13
|
+
@sent_at = sent_at
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module EndPointBlank
|
|
2
|
+
module Loggers
|
|
3
|
+
class Logger
|
|
4
|
+
|
|
5
|
+
def self.info(message)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.debug(message)
|
|
9
|
+
Writers::Writer.new(:debug).write(message: message)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.error(message)
|
|
13
|
+
EndPointBlank.logger.error(message)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.warn(message)
|
|
17
|
+
EndPointBlank.logger.warn(message)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.fatal(message)
|
|
21
|
+
EndPointBlank.logger.fatal(message)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def self.write(message:, level: )
|
|
26
|
+
Writers::Writer.new(:info).write(message: message)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|