injest-client 0.1.2 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a527ee5b2ec9e083f3d841a8a01bbfd62ac72d5dd5772455b10d8cde4ce1e015
4
- data.tar.gz: e0ac5772bbac8b9e9bafa24ff78327fd20fa1f904df65a2bc7129f66f5f16fa2
3
+ metadata.gz: 5b3054fcd879d5b947d3babca7db973a464f569b32b6de529f41dcdba731e4e3
4
+ data.tar.gz: 72abf9a2575b870373be452eb8f52bfb05d57979badafed40b726843a806489c
5
5
  SHA512:
6
- metadata.gz: 4002ab02de4c80a77c9359d030ae93ca73dd90a89dd138966b0d847b1fc12e1931f48b8b1274ed70b28addb0fa222fa69c789ec7e5c77421688b6bfcbdba85bf
7
- data.tar.gz: 463a06c6a30c442323149bb33e5cfaf2cc0d164c63939a33fd4509cb844c90d51128d5128b444b4531add4ef47970fcf7b6feb9927f86b3042779116151ce049
6
+ metadata.gz: 329b5be30aa1f679e790e1406a46ef3c0337278e5b2e3e5ff6bb428154350e77ee067d4825283e45ed24df226ba4f90935d6e7e2f3630b79ba75563be4c1a54f
7
+ data.tar.gz: f01211a252f24a637df6641f693830410864253655d51395b601b850249168190e4550e22bdff976705f1d2738e061aa52b839eedc01add61276ba8e90acbce7
data/lib/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Injest Client
2
+
3
+ ## Changelog
4
+
5
+ ### 0.1.4
6
+
7
+ - add raw_search to Injest::HttpClient
8
+
9
+ ENV vars
10
+
11
+ - `INJEST_STRATEGY`: stdout by default
12
+
13
+ Allowed values: Values: stdout | null | http | push
14
+
15
+ `http` and `push` have the same behavior.
16
+
17
+ - `INJEST_ROOT`: a URL required with strategy `http` or `push`
18
+ - `INJEST_JWT`: a JWT required with strategy `http` or `push`
19
+ - `INJEST_CLIENT`: (optional) a string to bind to a specific client
20
+
21
+ ## TODO:
22
+
23
+ - Customization with procs
24
+ - Tests: https://guides.rubygems.org/make-your-own-gem/#writing-tests
@@ -0,0 +1,18 @@
1
+ class Injest::Configuration
2
+ include Singleton
3
+
4
+ attr_reader :strategy,
5
+ :injest_root, :injest_token, :injest_client
6
+
7
+ private
8
+ def initialize
9
+ @strategy = ENV.fetch('INJEST_STRATEGY', 'stdout')
10
+
11
+ if strategy == 'http' || strategy == 'push'
12
+ @injest_root = ENV.fetch('INJEST_ROOT')
13
+ @injest_token = ENV.fetch('INJEST_JWT')
14
+ end
15
+
16
+ @injest_client = ENV.fetch('INJEST_CLIENT', nil)
17
+ end
18
+ end
@@ -0,0 +1,198 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ class Injest::HttpClient
5
+ attr_reader :root_url,
6
+ :push_url,
7
+ :search_url,
8
+ :raw_search_url,
9
+ :jwt,
10
+ :client_code
11
+ def initialize(configuration)
12
+ @root_url = configuration.injest_root
13
+ @client_code = configuration.injest_client
14
+ if client_code.nil? || client_code == ''
15
+ @push_url = [root_url, 'api/v1/logs'].join('/')
16
+ else
17
+ @push_url = [root_url, 'api/v1/logs', client_code].join('/')
18
+ end
19
+ @search_url = [root_url, 'api/v1/logs/search'].join('/')
20
+ @raw_search_url = [root_url, 'api/v1/logs/raw_search'].join('/')
21
+ @jwt = configuration.injest_token
22
+ end
23
+
24
+ # Push an audit log
25
+ def audit(data)
26
+ push data.merge(log_type: 'audit')
27
+ end
28
+
29
+ def search(page: 1, per: 20, **other)
30
+ payload = {
31
+ page: page,
32
+ per: per,
33
+ logs_search_form: {}
34
+ }
35
+ other.each do |k, v|
36
+ payload[:logs_search_form][k] = v
37
+ end
38
+
39
+ post_http_request search_url, payload
40
+ end
41
+
42
+ def raw_search(body:, index_postfix: nil)
43
+ payload = { body: body }
44
+ payload[:index_postfix] = index_postfix unless index_postfix.nil?
45
+
46
+ post_http_request raw_search_url, payload
47
+ end
48
+
49
+ # Push a log
50
+ def push(data)
51
+ data = data_as_hash(data)
52
+ create_log(data)
53
+ end
54
+
55
+ def json(time:, severity:, data:,
56
+ correlation_id: nil,
57
+ request_id: nil,
58
+ partition_key: nil,
59
+ owner: nil,
60
+ delta: nil)
61
+ request_id ||= SecureRandom.uuid
62
+ data = {
63
+ request_id: request_id,
64
+ correlation_id: correlation_id,
65
+ time: time,
66
+ human_time: Time.at(time).to_s,
67
+ log_type: 'json',
68
+ severity: severity,
69
+ partition_key: partition_key,
70
+ owner: owner,
71
+ delta: delta,
72
+ data: data_as_hash(data)
73
+ }
74
+ create_log data
75
+ end
76
+
77
+ def text(time:, severity:, message:,
78
+ correlation_id: nil,
79
+ request_id: nil,
80
+ partition_key: nil,
81
+ owner: nil,
82
+ delta: nil)
83
+ request_id ||= SecureRandom.uuid
84
+ data = {
85
+ request_id: request_id,
86
+ correlation_id: correlation_id,
87
+ time: time,
88
+ human_time: Time.at(time).to_s,
89
+ log_type: 'text',
90
+ severity: severity,
91
+ partition_key: partition_key,
92
+ owner: owner,
93
+ delta: delta,
94
+ text: message
95
+ }
96
+ create_log data
97
+ end
98
+
99
+ def process_response!(response)
100
+ case response.code
101
+ when '200', '201'
102
+ unless response.body.empty?
103
+ JSON.parse(response.body)
104
+ else
105
+ {}
106
+ end
107
+ else
108
+ data = JSON.parse(response.body) unless response.body.blank?
109
+ puts "Endpoint replied with #{response.code} - #{data['error_code']}", data: data
110
+ raise "Endpoint replied with #{response.code} - #{data['error_code']}", data: data
111
+ end
112
+ end
113
+
114
+ private
115
+ def create_log(data)
116
+ return if ENV['INJEST_STRATEGY'] == 'null'
117
+
118
+ if ENV['INJEST_STRATEGY'] == 'stdout'
119
+ puts "== Injest STDOUT data =="
120
+ # puts data.inspect
121
+ print_log(data)
122
+ return
123
+ end
124
+
125
+ post_http_request push_url, data
126
+ # uri = URI.parse push_url
127
+ # request = Net::HTTP::Post.new(uri)
128
+ # request["Content-Type"] = "application/json"
129
+ # request["Accept"] = "application/json"
130
+ # request['Authorization'] = "Bearer #{jwt}"
131
+ # case data
132
+ # when String
133
+ # request.body = data
134
+ # when Hash
135
+ # request.body = data.to_json
136
+ # end
137
+ # req_options = { use_ssl: uri.scheme == "https" }
138
+ # response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
139
+ # http.request(request)
140
+ # end
141
+ #
142
+ # process_response!(response)
143
+ end
144
+
145
+ def post_http_request(url, data)
146
+ uri = URI.parse url
147
+ request = Net::HTTP::Post.new(uri)
148
+ request["Content-Type"] = "application/json"
149
+ request["Accept"] = "application/json"
150
+ request['Authorization'] = "Bearer #{jwt}"
151
+ case data
152
+ when String
153
+ request.body = data
154
+ when Hash
155
+ request.body = data.to_json
156
+ end
157
+ req_options = { use_ssl: uri.scheme == "https" }
158
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
159
+ http.request(request)
160
+ end
161
+
162
+ process_response!(response)
163
+ end
164
+
165
+ def data_as_hash(data)
166
+ case data
167
+ when String
168
+ JSON.parse(data)
169
+ when Hash
170
+ data
171
+ end
172
+ end
173
+
174
+ def print_log(data)
175
+ puts "LOGTYPE '#{data[:log_type]}'"
176
+ if data[:log_type] != 'audit'
177
+ puts data.inspect
178
+ return
179
+ end
180
+
181
+ puts "#{data[:human_time]} - Request #{data[:request_id]}"
182
+ puts "#{data[:request][:method]} #{data[:request][:path]}"
183
+ puts ''
184
+ puts "Subject"
185
+ puts data[:request][:subject].inspect
186
+ puts ''
187
+ puts 'Context'
188
+ puts data[:request][:context].inspect
189
+ puts ''
190
+ puts "Headers"
191
+ data[:request][:headers].each do |k, v|
192
+ puts "#{k}: #{v}"
193
+ end
194
+ puts ''
195
+ puts "Response: #{data[:response][:status]}"
196
+ puts data[:response][:body].inspect
197
+ end
198
+ end
@@ -0,0 +1,173 @@
1
+ class Injest::Middleware
2
+ HEADERS_BLACKLIST = %w[HTTP_COOKIE].freeze
3
+
4
+ attr_reader :configuration
5
+ attr_reader :request_type, :env, :consumer, :subject
6
+
7
+ def initialize(app, processor: nil)
8
+ @app = app
9
+ init_configuration
10
+
11
+ @processor = processor
12
+
13
+ @consumer = nil
14
+ @subject = nil
15
+ end
16
+
17
+ def call(env)
18
+ dup._call(env)
19
+ end
20
+
21
+ def _call(env)
22
+ @env = env
23
+ set_request_type
24
+ return @app.call(env) unless trackable?
25
+
26
+ @request_started_on = Time.now
27
+ @status, @headers, @response = @app.call(env)
28
+ @request_ended_on = Time.now
29
+
30
+ set_subject
31
+ set_consumer
32
+
33
+ data = build_data
34
+ data = @processor.call(env, data) unless @processor.nil?
35
+
36
+ append(data)
37
+
38
+ [@status, @headers, @response]
39
+ end
40
+
41
+ def init_configuration
42
+ @configuration = Injest::Configuration.instance
43
+ end
44
+
45
+ private
46
+
47
+ def append(data)
48
+ Injest::Writer.instance.append(data)
49
+ end
50
+
51
+ def build_data
52
+ {
53
+ request_id: env["action_dispatch.request_id"],
54
+ time: @request_started_on.to_i,
55
+ human_time: @request_started_on,
56
+ type: request_type,
57
+ request: build_request,
58
+ response: {
59
+ status: @status,
60
+ body: response_body(@response),
61
+ headers: @headers
62
+ },
63
+ delta: @request_ended_on - @request_started_on,
64
+ log_type: 'audit'
65
+ }
66
+ end
67
+
68
+ def build_request
69
+ if env.key?('rack.session')
70
+ auth_scheme = env["rack.session"].fetch('auth_scheme', [])
71
+ else
72
+ auth_scheme = []
73
+ end
74
+
75
+ {
76
+ method: env['REQUEST_METHOD'],
77
+ server_name: env['SERVER_NAME'],
78
+ port: env['SERVER_PORT'],
79
+ query_string: env['QUERY_STRING'],
80
+ path: env['PATH_INFO'],
81
+ scheme: env["rack.url_scheme"],
82
+ remote_addr: env['REMOTE_ADDR'],
83
+ remote_ip: env['action_dispatch.remote_ip'].to_s,
84
+ formats: (env["action_dispatch.request.formats"] || []).map(&:to_s),
85
+ headers: get_request_headers,
86
+ subject: subject,
87
+ context: get_context(env['action_dispatch.request.parameters'], env["action_dispatch.request.request_parameters"]),
88
+ auth_scheme: auth_scheme
89
+ }
90
+ end
91
+
92
+ def get_context(data, params)
93
+ data ||= {}
94
+ {
95
+ controller: data['controller'],
96
+ action: data['action'],
97
+ params: params,
98
+ version: Gem.loaded_specs["injest-client"].version.to_s,
99
+ consumer: consumer
100
+ }
101
+ end
102
+
103
+ def get_request_headers
104
+ result = {}
105
+ env.each do |k, v|
106
+ if (k.to_s.start_with?('HTTP_') || k.to_s.start_with?('X-')) && !HEADERS_BLACKLIST.include?(k) && !v.blank?
107
+ result[k] = v
108
+ end
109
+ end
110
+ result
111
+ end
112
+
113
+ def response_body(response)
114
+ if @headers['Content-Type'] == 'text/csv' ||
115
+ @headers['Content-Type'] == 'application/pdf'
116
+ return { raw: "* * * RESPONSE NOT SHOWN FOR #{@headers['Content-Type']} * * *" }
117
+ end
118
+
119
+ if request_type == 'api'
120
+ if response.respond_to?(:body)
121
+ JSON.parse(response.body) rescue { raw: "* * * Unable to parse JSON response for #{@headers['Content-Type']} * * *" }
122
+ else
123
+ response
124
+ end
125
+ else
126
+ if @headers['Content-Type']&.downcase&.include?('application/json')
127
+ if response.respond_to?(:body)
128
+ JSON.parse(response.body) rescue { raw: "* * * Unable to parse JSON response for #{@headers['Content-Type']} * * *" }
129
+ else
130
+ { raw: "* * * RESPONSE NOT SHOWN * * *" }
131
+ end
132
+ else
133
+ { raw: "* * * RESPONSE NOT SHOWN FOR #{@headers['Content-Type']} * * *" }
134
+ end
135
+ end
136
+ end
137
+
138
+ def set_consumer
139
+ if !subject.nil?
140
+ @consumer = [subject[:type].downcase, subject[:id]].join(':')
141
+ elsif env.key?("action_dispatch.request.parameters")
142
+ @consumer = env["action_dispatch.request.parameters"].fetch('_consumer', 'unknown')
143
+ end
144
+ end
145
+
146
+ def set_request_type
147
+ # TODO: maybe we should check some header instead
148
+ if env['PATH_INFO'].match?(/.*\.(?:png|css|jpeg|ico|gif)/)
149
+ @request_type = 'static_asset'
150
+ elsif env['PATH_INFO'].match?(/^\/api\/v[0-9]+/)
151
+ @request_type = 'api'
152
+ elsif env['PATH_INFO'].include?('/sidekiq')
153
+ @request_type = 'sidekiq'
154
+ elsif env['PATH_INFO'].include?('/rails')
155
+ @request_type = 'rails'
156
+ else
157
+ @request_type = 'browser'
158
+ end
159
+ end
160
+
161
+ def set_subject
162
+ return if env['rack.session'].nil?
163
+
164
+ @subject = env["rack.session"].fetch('current_subject', {}).with_indifferent_access
165
+ if @subject.nil?
166
+ @subject = env["rack.session"].fetch(:current_subject, {}).with_indifferent_access
167
+ end
168
+ end
169
+
170
+ def trackable?
171
+ !%w[static_asset sidekiq rails].include?(request_type)
172
+ end
173
+ end
@@ -0,0 +1,6 @@
1
+ class Injest::Worker
2
+ include Sidekiq::Worker
3
+ def perform(data)
4
+ Injest::Writer.instance.append(data, sync: true)
5
+ end
6
+ end
@@ -0,0 +1,63 @@
1
+ class Injest::Writer
2
+ include Singleton
3
+
4
+ attr_reader :configuration, :http_client
5
+
6
+ def append(data, sync: false)
7
+ case configuration.strategy
8
+ when 'push', 'http'
9
+ if sync
10
+ http_client.push(data)
11
+ else
12
+ Injest::Worker.perform_async(data)
13
+ end
14
+
15
+ when 'stdout'
16
+ print(data)
17
+
18
+ when 'null'
19
+ # Do nothing
20
+ else
21
+ # Do nothing
22
+ end
23
+ end
24
+
25
+ private
26
+ def initialize
27
+ @configuration = Injest::Configuration.instance
28
+ @http_client = Injest::HttpClient.new(configuration)
29
+ end
30
+
31
+ def print(data)
32
+ puts "=== Injest output ==="
33
+ if data[:log_type] != 'audit'
34
+ data.each do |k, v|
35
+ if v.is_a?(Hash)
36
+ puts " - #{k}:"
37
+ v.each do |k1, v1|
38
+ puts " - #{k1}: #{v1}"
39
+ end
40
+ else
41
+ puts " - #{k}: #{v}"
42
+ end
43
+ end
44
+ return
45
+ end
46
+
47
+ puts "#{data[:request][:method]} #{data[:request][:path]} @ #{data[:human_time]} -- Request #{data[:request_id]}"
48
+ puts ''
49
+ puts "Subject"
50
+ puts " " + data[:request][:subject].inspect
51
+ puts ''
52
+ puts 'Context'
53
+ puts " " + data[:request][:context].inspect
54
+ puts ''
55
+ puts "Headers"
56
+ data[:request][:headers].each do |k, v|
57
+ puts " - #{k}: #{v}"
58
+ end
59
+ puts ''
60
+ puts "Response: #{data[:response][:status]}"
61
+ puts data[:response][:body].inspect
62
+ end
63
+ end
data/lib/injest.rb CHANGED
@@ -5,9 +5,9 @@ end
5
5
  require 'singleton'
6
6
  require 'sidekiq'
7
7
 
8
- require_relative 'injest/configuration'
9
- require_relative 'injest/writer'
10
- require_relative 'injest/http_client'
11
- require_relative 'injest/worker'
8
+ require 'injest/configuration'
9
+ require 'injest/writer'
10
+ require 'injest/http_client'
11
+ require 'injest/worker'
12
12
 
13
- require_relative 'injest/middleware'
13
+ require 'injest/middleware'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: injest-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefano Lampis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-09 00:00:00.000000000 Z
11
+ date: 2023-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -58,7 +58,13 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
+ - lib/README.md
61
62
  - lib/injest.rb
63
+ - lib/injest/configuration.rb
64
+ - lib/injest/http_client.rb
65
+ - lib/injest/middleware.rb
66
+ - lib/injest/worker.rb
67
+ - lib/injest/writer.rb
62
68
  homepage: https://rubygems.org/gems/slampis-injest-client
63
69
  licenses:
64
70
  - MIT