class DataBuilder

Gathers and builds body payload to be sent to Treblle in json format. Hides sensitive data based on default values and additional ones provided via env variable.

Constants

DEFAULT_SENSITIVE_FIELDS

Attributes

ended_at[RW]
env[RW]
exception[RW]
headers[RW]
json_response[RW]
request[RW]
started_at[RW]
status[RW]

Public Class Methods

new(params) click to toggle source
# File lib/treblle/data_builder.rb, line 25
def initialize(params)
  @ended_at = params[:ended_at]
  @env = params[:env]
  @exception = params[:exception]
  @headers = params[:headers]
  @json_response = params[:json_response]
  @request = params[:request]
  @started_at = params[:started_at]
  @status = params[:status]
end

Public Instance Methods

call() click to toggle source
# File lib/treblle/data_builder.rb, line 36
def call
  time_spent = ended_at - started_at
  user_agent = env['HTTP_USER_AGENT']
  ip = env['action_dispatch.remote_ip'].calculate_ip
  request_method = env['REQUEST_METHOD']
  project_id = ENV.fetch('TREBLLE_PROJECT_ID') { '' }
  request_body = request_method.downcase == 'get' ? request.query_parameters : safe_to_json(request.raw_post)

  data = {
    api_key: ENV.fetch('TREBLLE_API_KEY') { '' },
    project_id: project_id,
    version: Treblle::TREBLLE_VERSION,
    sdk: 'ruby',
    data: {
      server: {
        ip: server_ip,
        timezone: Time.zone.name,
        software: request_headers.try(:[], 'SERVER_SOFTWARE'),
        signature: '',
        protocol: request_headers.try(:[], 'SERVER_PROTOCOL'),
        os: {}
      },
      language: {
        name: 'ruby',
        version: RUBY_VERSION
      },
      request: {
        timestamp: started_at.to_formatted_s(:db),
        ip: ip,
        url: request.original_url,
        user_agent: user_agent,
        method: request_method,
        headers: request_headers,
        body: without_sensitive_attrs(request_body)
      },
      response: {
        headers: headers || {},
        code: status,
        size: json_response&.to_json&.bytesize || 0,
        load_time: time_spent,
        body: without_sensitive_attrs(json_response),
        errors: build_error_object(exception)
      }
    }
  }

  data.to_json
end

Private Instance Methods

build_error_object(exception) click to toggle source
# File lib/treblle/data_builder.rb, line 112
def build_error_object(exception)
  return [] unless exception.present?

  [
    {
      source: 'onError',
      type: exception.class.to_s || 'Unhandled error',
      message: exception.message,
      file: exception.backtrace.try(:first) || ''
    }
  ]
end
request_headers() click to toggle source
# File lib/treblle/data_builder.rb, line 129
def request_headers
  @request_headers ||= request.headers.env.reject { |key| key.to_s.include?('.') }
end
safe_to_json(obj) click to toggle source
# File lib/treblle/data_builder.rb, line 133
def safe_to_json(obj)
  JSON.parse(obj)
rescue StandardError => e
  {}
end
sensitive_attrs() click to toggle source
# File lib/treblle/data_builder.rb, line 103
def sensitive_attrs
  @sensitive_attrs ||= user_sensitive_fields.merge(DEFAULT_SENSITIVE_FIELDS)
end
server_ip() click to toggle source
# File lib/treblle/data_builder.rb, line 125
def server_ip
  Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
end
user_sensitive_fields() click to toggle source
# File lib/treblle/data_builder.rb, line 107
def user_sensitive_fields
  fields = ENV.fetch('TREBLLE_SENSITIVE_FIELDS') { '' }.gsub(/\s+/, '')
  fields.split(',').to_set
end
without_sensitive_attrs(obj) click to toggle source
# File lib/treblle/data_builder.rb, line 87
def without_sensitive_attrs(obj)
  return {} unless obj.present?

  obj.each do |k, v|
    value = v || k
    if value.is_a?(Hash) || value.is_a?(Array)
      without_sensitive_attrs(value)
    elsif sensitive_attrs.include?(k.to_s)
      obj[k] = '*' * v.to_s.length
    end
  end
  obj
rescue StandardError => e
  Rails.logger.error e.message
end