treblle 1.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ec91ab95442d3471d51d7b7c23baf9c3129e89baa265af101b95db188e1c323b
4
+ data.tar.gz: 44076ace1dc079742312999cfe5e3b73eaee7a2960beb6211dbf6b1e73789445
5
+ SHA512:
6
+ metadata.gz: bab469e6487bab98fa3e3a92af832e22c58aff4dd8c6ddf02f3d0b55d6c7c51959933426fa84b97f05200f8d2cfc673d3fe868fbb12aad49243914fb9da9c266
7
+ data.tar.gz: d6caa192c3a881d974f993c7e492e63f5c87b6f7966d8ba99b2a737cbb7d2f58c7798c366b6d920bff06d1d2255da9cc602b267d3072744644128a39b6707a48
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+
5
+ # Gathers and builds body payload to be sent to Treblle in json format.
6
+ # Hides sensitive data based on default values and additional ones provided via env variable.
7
+ class DataBuilder
8
+ DEFAULT_SENSITIVE_FIELDS = Set[
9
+ 'card_number',
10
+ 'cardNumber',
11
+ 'cc',
12
+ 'ccv',
13
+ 'credit_score',
14
+ 'creditScore',
15
+ 'password',
16
+ 'password_confirmation',
17
+ 'passwordConfirmation',
18
+ 'pwd',
19
+ 'secret',
20
+ 'ssn'
21
+ ].freeze
22
+
23
+ attr_accessor :env, :status, :started_at, :ended_at, :request, :headers, :json_response, :exception
24
+
25
+ def initialize(params)
26
+ @ended_at = params[:ended_at]
27
+ @env = params[:env]
28
+ @exception = params[:exception]
29
+ @headers = params[:headers]
30
+ @json_response = params[:json_response]
31
+ @request = params[:request]
32
+ @started_at = params[:started_at]
33
+ @status = params[:status]
34
+ end
35
+
36
+ def call
37
+ time_spent = ended_at - started_at
38
+ user_agent = env['HTTP_USER_AGENT']
39
+ ip = env['action_dispatch.remote_ip'].calculate_ip
40
+ request_method = env['REQUEST_METHOD']
41
+ project_id = ENV.fetch('TREBLLE_PROJECT_ID') { '' }
42
+ request_body = request_method.downcase == 'get' ? request.query_parameters : safe_to_json(request.raw_post)
43
+
44
+ data = {
45
+ api_key: ENV.fetch('TREBLLE_API_KEY') { '' },
46
+ project_id: project_id,
47
+ version: Treblle::TREBLLE_VERSION,
48
+ sdk: 'ruby',
49
+ data: {
50
+ server: {
51
+ ip: server_ip,
52
+ timezone: Time.zone.name,
53
+ software: request_headers.try(:[], 'SERVER_SOFTWARE'),
54
+ signature: '',
55
+ protocol: request_headers.try(:[], 'SERVER_PROTOCOL'),
56
+ os: {}
57
+ },
58
+ language: {
59
+ name: 'ruby',
60
+ version: RUBY_VERSION
61
+ },
62
+ request: {
63
+ timestamp: started_at.to_formatted_s(:db),
64
+ ip: ip,
65
+ url: request.original_url,
66
+ user_agent: user_agent,
67
+ method: request_method,
68
+ headers: request_headers,
69
+ body: without_sensitive_attrs(request_body)
70
+ },
71
+ response: {
72
+ headers: headers || {},
73
+ code: status,
74
+ size: json_response&.to_json&.bytesize || 0,
75
+ load_time: time_spent,
76
+ body: without_sensitive_attrs(json_response),
77
+ errors: build_error_object(exception)
78
+ }
79
+ }
80
+ }
81
+
82
+ data.to_json
83
+ end
84
+
85
+ private
86
+
87
+ def without_sensitive_attrs(obj)
88
+ return {} unless obj.present?
89
+
90
+ obj.each do |k, v|
91
+ value = v || k
92
+ if value.is_a?(Hash) || value.is_a?(Array)
93
+ without_sensitive_attrs(value)
94
+ elsif sensitive_attrs.include?(k.to_s)
95
+ obj[k] = '*' * v.to_s.length
96
+ end
97
+ end
98
+ obj
99
+ rescue StandardError => e
100
+ Rails.logger.error e.message
101
+ end
102
+
103
+ def sensitive_attrs
104
+ @sensitive_attrs ||= user_sensitive_fields.merge(DEFAULT_SENSITIVE_FIELDS)
105
+ end
106
+
107
+ def user_sensitive_fields
108
+ fields = ENV.fetch('TREBLLE_SENSITIVE_FIELDS') { '' }.gsub(/\s+/, '')
109
+ fields.split(',').to_set
110
+ end
111
+
112
+ def build_error_object(exception)
113
+ return [] unless exception.present?
114
+
115
+ [
116
+ {
117
+ source: 'onError',
118
+ type: exception.class.to_s || 'Unhandled error',
119
+ message: exception.message,
120
+ file: exception.backtrace.try(:first) || ''
121
+ }
122
+ ]
123
+ end
124
+
125
+ def server_ip
126
+ Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
127
+ end
128
+
129
+ def request_headers
130
+ @request_headers ||= request.headers.env.reject { |key| key.to_s.include?('.') }
131
+ end
132
+
133
+ def safe_to_json(obj)
134
+ JSON.parse(obj)
135
+ rescue StandardError => e
136
+ {}
137
+ end
138
+ end
data/lib/treblle.rb ADDED
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'treblle/data_builder'
6
+
7
+ # Treblle middleware for request interception and gathering.
8
+ class Treblle
9
+ TREBLLE_URI = 'https://rocknrolla.treblle.com'
10
+ TREBLLE_VERSION = '0.6'
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
56
+ end
57
+
58
+ [status, headers, response]
59
+ end
60
+
61
+ # Creates data to send in a format that Treblle expects and does the sending in a new thread
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)
84
+ end
85
+ 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
+ end
94
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: treblle
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Hrvoje Grdic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-09-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: TreblleSDK for ruby
14
+ email: hrvoje.grdic@bitlab.hr
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/treblle.rb
20
+ - lib/treblle/data_builder.rb
21
+ homepage: https://rubygems.org/gems/treblle
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubygems_version: 3.1.6
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: TreblleSDK
44
+ test_files: []