injest-client 0.1.2 → 0.1.3

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: 8b596088bca86e19140c5a6896579a1157c93e6f7892ec690697a1cee3e9d731
4
+ data.tar.gz: bccef6f66027afa03d3ce6d241b0e548a83bd1a2402ba36a1a66b5f8c04099d1
5
5
  SHA512:
6
- metadata.gz: 4002ab02de4c80a77c9359d030ae93ca73dd90a89dd138966b0d847b1fc12e1931f48b8b1274ed70b28addb0fa222fa69c789ec7e5c77421688b6bfcbdba85bf
7
- data.tar.gz: 463a06c6a30c442323149bb33e5cfaf2cc0d164c63939a33fd4509cb844c90d51128d5128b444b4531add4ef47970fcf7b6feb9927f86b3042779116151ce049
6
+ metadata.gz: f490690c6e154ce55a3d166729c5683a449750824a33aa873dff73f7a152af9c383731ec585c43516fa9884212f53a0ba9a7f42cf72fe0108eb037196b2f0752
7
+ data.tar.gz: 78ba835c11aff60fcab737943b8e74b8fd51dbd77f58b50c624fa77b91b38a4bf27bbace1d1d2aacda4f2c8a9465324d3a60de737117bb8726121171c8972007
data/lib/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Injest Client
2
+
3
+ ENV vars
4
+
5
+ - `INJEST_STRATEGY`: stdout by default
6
+
7
+ Allowed values: Values: stdout | null | http | push
8
+
9
+ `http` and `push` have the same behavior.
10
+
11
+ - `INJEST_ROOT`: a URL required with strategy `http` or `push`
12
+ - `INJEST_JWT`: a JWT required with strategy `http` or `push`
13
+ - `INJEST_CLIENT`: (optional) a string to bind to a specific client
14
+
15
+ ## TODO:
16
+
17
+ - Customization with procs
18
+ - 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,189 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ class Injest::HttpClient
5
+ attr_reader :root_url,
6
+ :push_url,
7
+ :search_url,
8
+ :jwt,
9
+ :client_code
10
+ def initialize(configuration)
11
+ @root_url = configuration.injest_root
12
+ @client_code = configuration.injest_client
13
+ if client_code.nil? || client_code == ''
14
+ @push_url = [root_url, 'api/v1/logs'].join('/')
15
+ else
16
+ @push_url = [root_url, 'api/v1/logs', client_code].join('/')
17
+ end
18
+ @search_url = [root_url, 'api/v1/logs/search'].join('/')
19
+ @jwt = configuration.injest_token
20
+ end
21
+
22
+ # Push an audit log
23
+ def audit(data)
24
+ push data.merge(log_type: 'audit')
25
+ end
26
+
27
+ def search(page: 1, per: 20, **other)
28
+ payload = {
29
+ page: page,
30
+ per: per,
31
+ logs_search_form: {}
32
+ }
33
+ other.each do |k, v|
34
+ payload[:logs_search_form][k] = v
35
+ end
36
+
37
+ post_http_request search_url, payload
38
+ end
39
+
40
+ # Push a log
41
+ def push(data)
42
+ data = data_as_hash(data)
43
+ create_log(data)
44
+ end
45
+
46
+ def json(time:, severity:, data:,
47
+ correlation_id: nil,
48
+ request_id: nil,
49
+ partition_key: nil,
50
+ owner: nil,
51
+ delta: nil)
52
+ request_id ||= SecureRandom.uuid
53
+ data = {
54
+ request_id: request_id,
55
+ correlation_id: correlation_id,
56
+ time: time,
57
+ human_time: Time.at(time).to_s,
58
+ log_type: 'json',
59
+ severity: severity,
60
+ partition_key: partition_key,
61
+ owner: owner,
62
+ delta: delta,
63
+ data: data_as_hash(data)
64
+ }
65
+ create_log data
66
+ end
67
+
68
+ def text(time:, severity:, message:,
69
+ correlation_id: nil,
70
+ request_id: nil,
71
+ partition_key: nil,
72
+ owner: nil,
73
+ delta: nil)
74
+ request_id ||= SecureRandom.uuid
75
+ data = {
76
+ request_id: request_id,
77
+ correlation_id: correlation_id,
78
+ time: time,
79
+ human_time: Time.at(time).to_s,
80
+ log_type: 'text',
81
+ severity: severity,
82
+ partition_key: partition_key,
83
+ owner: owner,
84
+ delta: delta,
85
+ text: message
86
+ }
87
+ create_log data
88
+ end
89
+
90
+ def process_response!(response)
91
+ case response.code
92
+ when '200', '201'
93
+ unless response.body.empty?
94
+ JSON.parse(response.body)
95
+ else
96
+ {}
97
+ end
98
+ else
99
+ data = JSON.parse(response.body) unless response.body.blank?
100
+ puts "Endpoint replied with #{response.code} - #{data['error_code']}", data: data
101
+ raise "Endpoint replied with #{response.code} - #{data['error_code']}", data: data
102
+ end
103
+ end
104
+
105
+ private
106
+ def create_log(data)
107
+ return if ENV['INJEST_STRATEGY'] == 'null'
108
+
109
+ if ENV['INJEST_STRATEGY'] == 'stdout'
110
+ puts "== Injest STDOUT data =="
111
+ # puts data.inspect
112
+ print_log(data)
113
+ return
114
+ end
115
+
116
+ post_http_request push_url, data
117
+ # uri = URI.parse push_url
118
+ # request = Net::HTTP::Post.new(uri)
119
+ # request["Content-Type"] = "application/json"
120
+ # request["Accept"] = "application/json"
121
+ # request['Authorization'] = "Bearer #{jwt}"
122
+ # case data
123
+ # when String
124
+ # request.body = data
125
+ # when Hash
126
+ # request.body = data.to_json
127
+ # end
128
+ # req_options = { use_ssl: uri.scheme == "https" }
129
+ # response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
130
+ # http.request(request)
131
+ # end
132
+ #
133
+ # process_response!(response)
134
+ end
135
+
136
+ def post_http_request(url, data)
137
+ uri = URI.parse url
138
+ request = Net::HTTP::Post.new(uri)
139
+ request["Content-Type"] = "application/json"
140
+ request["Accept"] = "application/json"
141
+ request['Authorization'] = "Bearer #{jwt}"
142
+ case data
143
+ when String
144
+ request.body = data
145
+ when Hash
146
+ request.body = data.to_json
147
+ end
148
+ req_options = { use_ssl: uri.scheme == "https" }
149
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
150
+ http.request(request)
151
+ end
152
+
153
+ process_response!(response)
154
+ end
155
+
156
+ def data_as_hash(data)
157
+ case data
158
+ when String
159
+ JSON.parse(data)
160
+ when Hash
161
+ data
162
+ end
163
+ end
164
+
165
+ def print_log(data)
166
+ puts "LOGTYPE '#{data[:log_type]}'"
167
+ if data[:log_type] != 'audit'
168
+ puts data.inspect
169
+ return
170
+ end
171
+
172
+ puts "#{data[:human_time]} - Request #{data[:request_id]}"
173
+ puts "#{data[:request][:method]} #{data[:request][:path]}"
174
+ puts ''
175
+ puts "Subject"
176
+ puts data[:request][:subject].inspect
177
+ puts ''
178
+ puts 'Context'
179
+ puts data[:request][:context].inspect
180
+ puts ''
181
+ puts "Headers"
182
+ data[:request][:headers].each do |k, v|
183
+ puts "#{k}: #{v}"
184
+ end
185
+ puts ''
186
+ puts "Response: #{data[:response][:status]}"
187
+ puts data[:response][:body].inspect
188
+ end
189
+ 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,7 +1,7 @@
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.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefano Lampis
@@ -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