Skip to content

Commit

Permalink
fix reporting errors and sanitizing
Browse files Browse the repository at this point in the history
  • Loading branch information
KapustaB committed Mar 20, 2024
1 parent 7c51b17 commit 6facfb9
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 26 deletions.
1 change: 1 addition & 0 deletions lib/treblle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'treblle/middleware'
require 'treblle/configuration'
require 'treblle/rails/railtie'

# Treblle middleware for request interception and gathering.
module Treblle
Expand Down
26 changes: 20 additions & 6 deletions lib/treblle/generate_payload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class GeneratePayload
SDK_LANG = 'ruby'
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'

def initialize(request:, response:, started_at:, exception: nil, configuration: Treblle.configuration)
def initialize(request:, response:, started_at:, exception: false, configuration: Treblle.configuration)
@request = request
@response = response
@started_at = started_at
Expand Down Expand Up @@ -73,24 +73,38 @@ def payload
code: response.status,
size: response.size,
load_time: load_time,
body: sanitize(response.body),
body: response.body,
errors: build_error_object
}
}
}
end

def build_error_object
return [] if exception.blank?
return [] if exception == false

trace = response.body.dig("traces", "Application Trace")&.first&.[]("trace")
file_path, line_number = get_exception_path_and_line(trace)

[
{
source: 'onError',
type: exception.class.to_s || 'Unhandled error',
message: exception.message,
file: exception.backtrace
type: response.body["error"] || response.body["errors"] || 'Unhandled error',
message: response.body["exception"] || response.body["error"] || response.body["errors"],
file: file_path,
line: line_number
}
]
end

def get_exception_path_and_line(trace)
return [nil, nil] if trace.nil?

match_data = trace.match(/^(.*):(\d+):in `.*'$/)
file_path = match_data[1]
line_number = match_data[2]

[file_path, line_number]
end
end
end
15 changes: 5 additions & 10 deletions lib/treblle/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,20 @@ def call(env)
def call_with_treblle_monitoring(env)
started_at = Time.now

begin
response = @app.call(env)
rescue Exception => e
handle_monitoring(env, response, started_at, e)
raise e
end
response = @app.call(env)
status, _headers, _rack_response = response

handle_monitoring(env, response, started_at)
handle_monitoring(env, response, started_at) if status < 400

response
end

def handle_monitoring(env, rack_response, started_at, exception: nil)
def handle_monitoring(env, rack_response, started_at)
configuration.validate_credentials!

request = RequestBuilder.new(env).build
response = ResponseBuilder.new(rack_response).build
payload = GeneratePayload.new(request: request, response: response, started_at: started_at,
exception: exception).call
payload = GeneratePayload.new(request: request, response: response, started_at: started_at).call

Dispatcher.new(payload: payload).call
rescue StandardError => e
Expand Down
48 changes: 48 additions & 0 deletions lib/treblle/rails/capture_exceptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

require 'treblle/dispatcher'
require 'treblle/request_builder'
require 'treblle/response_builder'
require 'treblle/generate_payload'
require 'treblle/logging'
require 'treblle'

module Treblle
module Rails
class CaptureExceptions
include Logging

def initialize(app, configuration: Treblle.configuration)
@app = app
@configuration = configuration
end

def call(env)
started_at = Time.now
response = @app.call(env)
status, _headers, _rack_response = response

handle_monitoring(env, response, started_at) if status >= 400

response
end

private

attr_reader :configuration

def handle_monitoring(env, rack_response, started_at)
configuration.validate_credentials!

request = RequestBuilder.new(env).build
response = ResponseBuilder.new(rack_response).build
payload = GeneratePayload.new(request: request, response: response, started_at: started_at,
exception: true).call

Dispatcher.new(payload: payload).call
rescue StandardError => e
log_error(e.message)
end
end
end
end
14 changes: 14 additions & 0 deletions lib/treblle/rails/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require 'rails'
require 'treblle/rails/capture_exceptions'

module Treblle
module Init
module Rails
class Railtie < ::Rails::Railtie
initializer 'treblle.install_middleware' do |app|
app.config.middleware.insert_after ActionDispatch::ShowExceptions, Treblle::Rails::CaptureExceptions
end
end
end
end
end
14 changes: 12 additions & 2 deletions lib/treblle/response_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ def build

private

attr_reader :rack_response
attr_reader :rack_response, :handle_errors

def apply_to_response(response)
status, headers, response_data = rack_response || [500, [], nil]

response.status = status
response.headers = headers
response.body = parse_body(response_data)
response.body = parse_body(response_data) || parse_error_body(response_data)
response.size = calculate_size(response.body, response.headers)
response
end
Expand All @@ -40,6 +40,16 @@ def parse_body(response_data)
return nil unless response_data.respond_to?(:body)

JSON.parse(response_data.body)
rescue JSON::ParserError
response_data.body
end

def parse_error_body(response_data)
return nil unless response_data.is_a?(Array) && !response_data.empty?

JSON.parse(response_data.first)
rescue JSON::ParserError
response_data.body
end
end
end
28 changes: 23 additions & 5 deletions lib/treblle/utils/hash_sanitizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,36 @@ module Treblle
module Utils
class HashSanitizer
class << self
def sanitize(hash, sensitive_attrs)
return {} if hash.nil? || hash.empty?
return hash unless hash.is_a?(Hash) || hash.is_a?(Array)
def sanitize(body, sensitive_attrs)
return {} if body.nil? || body.empty?
return hash unless body.is_a?(Hash) || body.is_a?(Array)

if body.is_a?(Hash)
sanitize_hash(body, sensitive_attrs)
elsif body.is_a?(Array)
sanitize_array(body, sensitive_attrs)
end
end

private

def sanitize_hash(hash, sensitive_attrs)
hash.each_with_object({}) do |(key, value), result|
result[key] = if value.is_a?(Hash) || value.is_a?(Array)
sanitize_hash(value, sensitive_attrs)
sanitize(value, sensitive_attrs)
else
sensitive_attrs.include?(key.to_s) ? '*' * value.to_s.length : value
sanitize_value(key, value, sensitive_attrs)
end
end
end

def sanitize_array(array, sensitive_attrs)
array.map { |item| sanitize(item, sensitive_attrs) }
end

def sanitize_value(key, value, sensitive_attrs)
sensitive_attrs.include?(key.to_s) ? '*' * value.to_s.length : value
end
end
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/lib/request_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@

result = subject.new(env).build

expect(result.body).to eq '{"something":"everything"}'
expect(result.body.encoding).to eq Encoding::UTF_8
expect(result.body.valid_encoding?).to be true
expect(result.body).to eq({ "something" => "everything" })
expect(result.body.to_json.encoding).to eq Encoding::UTF_8
expect(result.body.to_json.valid_encoding?).to be true
end
end
end
Expand Down

0 comments on commit 6facfb9

Please sign in to comment.