soar_auditing_provider 0.7.0 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +19 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +1156 -0
- data/.ruby-version +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +146 -59
- data/lib/soar_auditing_provider/auditing_overflow_error.rb +4 -0
- data/lib/soar_auditing_provider/auditing_provider.rb +150 -0
- data/lib/soar_auditing_provider/auditing_worker.rb +153 -0
- data/lib/soar_auditing_provider/version.rb +1 -1
- data/lib/soar_auditing_provider.rb +3 -2
- data/sanity/.ruby-version +1 -1
- data/sanity/Gemfile +2 -1
- data/sanity/sanity.rb +50 -13
- data/soar_auditing_provider.gemspec +20 -12
- metadata +116 -13
- data/.travis.yml +0 -4
- data/lib/soar_auditing_provider/auditing_provider_api.rb +0 -61
- data/lib/soar_auditing_provider/nfr_match_error.rb +0 -4
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.3.0
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
# SoarAuditingProvider
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/soar_auditing_provider.png)](https://badge.fury.io/rb/soar_auditing_provider) [<img src="http://soar-ci.dev.auto-h.net:8080/job/soar-auditing-provider/badge/icon">](http://soar-ci.dev.auto-h.net:8080/job/soar-auditing-provider)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
The API provides default delegation behaviour that calls the API methods verbatim on the auditor. NFRs can be anything you want. You can ask the auditing provider to select an auditor based on a set of NFRs. The default behaviour is that the first auditor which matches all NFRs exactly is returned. You can override this behaviour by overriding the select(nfrs) method.
|
8
|
-
|
9
|
-
If you'd simply like the auditing provider to use the first (perhaps only?) auditor that it knows about, you can tell it to select DEFAULT so:
|
10
|
-
|
11
|
-
auditor = auditing_provider.select(SoarAuditingProvider::AuditingProviderAPI::DEFAULT)
|
5
|
+
This gem provides an auditing provider for the SOAR architecture.
|
12
6
|
|
13
7
|
## Installation
|
14
8
|
|
@@ -18,6 +12,16 @@ Add this line to your application's Gemfile:
|
|
18
12
|
gem 'soar_auditing_provider'
|
19
13
|
```
|
20
14
|
|
15
|
+
Note that the auditing provider will only be useful when configured with an auditor that extends from soar_auditor_api. Recommend using log4r_auditor for auditing to a local file and standard stream. Alternatively use logstash_auditor for auditing to a centralized system.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'log4r_auditor'
|
19
|
+
```
|
20
|
+
and/or
|
21
|
+
```ruby
|
22
|
+
gem 'logstash_auditor'
|
23
|
+
```
|
24
|
+
|
21
25
|
And then execute:
|
22
26
|
|
23
27
|
$ bundle
|
@@ -26,81 +30,143 @@ Or install it yourself as:
|
|
26
30
|
|
27
31
|
$ gem install soar_auditing_provider
|
28
32
|
|
29
|
-
## Usage
|
30
33
|
|
31
|
-
|
34
|
+
## Testing
|
32
35
|
|
33
|
-
|
34
|
-
class MyAuditingProvider < SoarAuditingProvider::AuditingProviderAPI
|
35
|
-
end
|
36
|
-
```
|
36
|
+
Run the rspec test tests:
|
37
37
|
|
38
|
-
|
38
|
+
$ bundle exec rspec -cfd spec
|
39
39
|
|
40
|
-
```
|
41
|
-
auditor_provider = SoarAuditingProvider::AuditingProviderAPI.new(auditors)
|
42
|
-
```
|
43
40
|
|
44
|
-
|
41
|
+
## Usage
|
45
42
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
43
|
+
Require the gems responsible for various aspects of the auditing
|
44
|
+
```ruby
|
45
|
+
require 'soar_auditing_provider'
|
46
|
+
require 'log4r_auditor'
|
47
|
+
require 'soar_flow'
|
50
48
|
```
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
Initialize and configure the provider.
|
51
|
+
```ruby
|
52
|
+
AUDITING_CONFIGURATION = {
|
53
|
+
'auditing' => {
|
54
|
+
'level' => 'debug',
|
55
|
+
'queue_worker' => {
|
56
|
+
'queue_size' => 1000,
|
57
|
+
'initial_back_off_in_seconds' => 1,
|
58
|
+
'back_off_multiplier' => 2,
|
59
|
+
'back_off_attempts' => 5
|
60
|
+
},
|
61
|
+
'default_nfrs' => {
|
62
|
+
'accessibility' => 'local',
|
63
|
+
'privacy' => 'not encrypted',
|
64
|
+
'reliability' => 'instance',
|
65
|
+
'performance' => 'high'
|
66
|
+
},
|
67
|
+
'auditors' => {
|
68
|
+
'log4r' => {
|
69
|
+
'adaptor' => 'Log4rAuditor::Log4rAuditor',
|
70
|
+
'file_name' => 'soar_sc.log',
|
71
|
+
'standard_stream' => 'stdout',
|
72
|
+
'nfrs' => {
|
73
|
+
'accessibility' => 'local',
|
74
|
+
'privacy' => 'not encrypted',
|
75
|
+
'reliability' => 'instance',
|
76
|
+
'performance' => 'high'
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
myauditing = SoarAuditingProvider::AuditingProvider.new( AUDITING_CONFIGURATION['auditing'] )
|
62
83
|
```
|
63
84
|
|
64
|
-
|
65
|
-
|
85
|
+
In order to associate all startup and shutdown related audit events with each other it is useful to set an instance flow identifier. Generate an unique flow identifier and pass to the auditing provider. Optional but very useful.
|
86
|
+
```ruby
|
87
|
+
myauditing.instance_flow_identifier = SoarFlow::ID::generate_flow_id
|
66
88
|
```
|
67
|
-
auditor_A = @iut.select( {'my nfr 1' => 'criteria 1' })
|
68
|
-
auditor_B = @iut.select( {'my nfr 2' => 'criteria 2' })
|
69
|
-
|
70
|
-
auditor_A.debug('debug')
|
71
|
-
auditor_B.warn('warn')
|
72
89
|
|
73
|
-
|
90
|
+
When auditing to a local file there is no need to identify each audit event with a specific service since each service probably has its own audit file. However, when merging audit events to a centralized system it is vital to associate each audit event with a specific service and instance thereof. Set an unique service identifer that will form part of each audit event as follow:
|
91
|
+
```ruby
|
92
|
+
myauditing.service_identifier = 'my-test-service.com'
|
74
93
|
```
|
75
94
|
|
76
|
-
|
77
|
-
|
95
|
+
Configure the audit level of the auditing provider either inside the configuration or by calling the set_audit_level method. This will result in audit events of a lower level being ignored.
|
96
|
+
```ruby
|
97
|
+
myauditing.set_audit_level(:info)
|
78
98
|
```
|
79
|
-
|
99
|
+
|
100
|
+
Generate audit events by passing in anything that implements the to_s method. The set and order of audit levels are [debug,info,warn,error,fatal]. Note that passing in the flow identifier is optional but recommended for each audit event.
|
101
|
+
```ruby
|
102
|
+
some_debug_object = 123
|
103
|
+
myauditing.info("This is info",flow_id)
|
104
|
+
myauditing.debug(some_debug_object,flow_id)
|
105
|
+
dropped = 95
|
106
|
+
myauditing.warn("Statistics show that dropped packets have increased to #{dropped}%",flow_id)
|
107
|
+
myauditing.error("Could not resend some dropped packets. They have been lost. All is still OK, I could compensate",flow_id)
|
108
|
+
myauditing.fatal("Unable to perform action, too many dropped packets. Functional degradation.",flow_id)
|
109
|
+
myauditing << 'Rack::CommonLogger requires this'
|
80
110
|
```
|
81
111
|
|
82
112
|
## Detailed example
|
83
113
|
|
84
|
-
```
|
85
|
-
require 'log4r'
|
114
|
+
```ruby
|
86
115
|
require 'soar_auditing_provider'
|
116
|
+
require 'log4r_auditor'
|
117
|
+
require 'soar_flow'
|
87
118
|
|
88
119
|
class Main
|
89
|
-
|
90
|
-
|
120
|
+
|
121
|
+
AUDITING_CONFIGURATION = {
|
122
|
+
'auditing' => {
|
123
|
+
'level' => 'debug',
|
124
|
+
'queue_worker' => {
|
125
|
+
'queue_size' => 1000,
|
126
|
+
'initial_back_off_in_seconds' => 1,
|
127
|
+
'back_off_multiplier' => 2,
|
128
|
+
'back_off_attempts' => 5
|
129
|
+
},
|
130
|
+
'default_nfrs' => {
|
131
|
+
'accessibility' => 'local',
|
132
|
+
'privacy' => 'not encrypted',
|
133
|
+
'reliability' => 'instance',
|
134
|
+
'performance' => 'high'
|
135
|
+
},
|
136
|
+
'auditors' => {
|
137
|
+
'log4r' => {
|
138
|
+
'adaptor' => 'Log4rAuditor::Log4rAuditor',
|
139
|
+
'file_name' => 'soar_sc.log',
|
140
|
+
'standard_stream' => 'stdout',
|
141
|
+
'nfrs' => {
|
142
|
+
'accessibility' => 'local',
|
143
|
+
'privacy' => 'not encrypted',
|
144
|
+
'reliability' => 'instance',
|
145
|
+
'performance' => 'high'
|
146
|
+
}
|
147
|
+
}
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
91
152
|
def test_sanity
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
153
|
+
#create and configure auditing instance
|
154
|
+
myauditing = SoarAuditingProvider::AuditingProvider.new( AUDITING_CONFIGURATION['auditing'] )
|
155
|
+
myauditing.instance_flow_identifier = SoarFlow::ID::generate_flow_id
|
156
|
+
myauditing.service_identifier = 'my-test-service.com'
|
157
|
+
|
158
|
+
#associate a set of auditing entries with a flow by generating a flow identifiers
|
159
|
+
flow_id = SoarFlow::ID::generate_flow_id
|
160
|
+
|
161
|
+
#generate audit events
|
96
162
|
some_debug_object = 123
|
97
|
-
|
98
|
-
|
163
|
+
myauditing.info("This is info",flow_id)
|
164
|
+
myauditing.debug(some_debug_object,flow_id)
|
99
165
|
dropped = 95
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
166
|
+
myauditing.warn("Statistics show that dropped packets have increased to #{dropped}%",flow_id)
|
167
|
+
myauditing.error("Could not resend some dropped packets. They have been lost. All is still OK, I could compensate",flow_id)
|
168
|
+
myauditing.fatal("Unable to perform action, too many dropped packets. Functional degradation.",flow_id)
|
169
|
+
myauditing << 'Rack::CommonLogger requires this'
|
104
170
|
end
|
105
171
|
end
|
106
172
|
|
@@ -108,6 +174,28 @@ main = Main.new
|
|
108
174
|
main.test_sanity
|
109
175
|
```
|
110
176
|
|
177
|
+
## Out of Band Auditing
|
178
|
+
|
179
|
+
In order prevent auditing from affecting normal execution the audit events are buffered and published to the auditor using a separate thread.
|
180
|
+
|
181
|
+
The buffer size can be configured. When full, new entries are discarded in favor of old entries and the overflow counter incremented. The separate thread will use the auditor to publish the buffered audit events in order irrespective of level. Failures are retried using a configurable exponential back off scheme.
|
182
|
+
|
183
|
+
## At_exit Hook
|
184
|
+
|
185
|
+
The auditing provider automatically chains a hook into the Kernel at_exit method in order to flush the audit entries at shutdown. It will generate a final audit entry (info level) stating "Application exit" with the flow identifier set using the instance_flow_id= method. Thereafter it attempts to flush the remaining entries to the selected auditor. Failing that, it will flush the entries to the standard error stream and exit.
|
186
|
+
|
187
|
+
## Status
|
188
|
+
|
189
|
+
Provision has been made for out-of-band status/statistics gathering inside the auditing provider. The hash containing the status/statistics is accessible using the status method call:
|
190
|
+
```ruby
|
191
|
+
myauditing.get_status
|
192
|
+
```
|
193
|
+
|
194
|
+
At present only the buffer overflow count is avialable:
|
195
|
+
```ruby
|
196
|
+
{ 'audit_buffer_overflows' => 123 }
|
197
|
+
```
|
198
|
+
|
111
199
|
## Contributing
|
112
200
|
|
113
201
|
Bug reports and feature requests are welcome by email to ernst dot van dot graan at hetzner dot co dot za. This gem is sponsored by Hetzner (Pty) Ltd (http://hetzner.co.za)
|
@@ -119,4 +207,3 @@ Though out of scope for the provider, auditors should take into account encoding
|
|
119
207
|
## License
|
120
208
|
|
121
209
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
122
|
-
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'soar_auditing_provider_api'
|
2
|
+
require 'soar_auditing_format'
|
3
|
+
require 'soar_configured_factory'
|
4
|
+
require 'time'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
module SoarAuditingProvider
|
8
|
+
class AuditingProvider < SoarAuditingProviderAPI::AuditingProviderAPI
|
9
|
+
private
|
10
|
+
|
11
|
+
#Aliases for bypassing overridden methods when accessing underlying super class api
|
12
|
+
alias :super_debug :debug
|
13
|
+
alias :super_info :info
|
14
|
+
alias :super_warn :warn
|
15
|
+
alias :super_error :error
|
16
|
+
alias :super_fatal :fatal
|
17
|
+
|
18
|
+
public
|
19
|
+
|
20
|
+
attr_accessor :instance_flow_identifier
|
21
|
+
attr_accessor :service_identifier
|
22
|
+
attr_reader :configuration
|
23
|
+
|
24
|
+
def initialize(configuration)
|
25
|
+
validate_provider_configuration(configuration)
|
26
|
+
@configuration = configuration
|
27
|
+
super(create_auditors(configuration))
|
28
|
+
select_auditor(configuration['default_nfrs'])
|
29
|
+
create_auditing_worker
|
30
|
+
@buffer_overflow_count = 0
|
31
|
+
install_at_exit_handler
|
32
|
+
end
|
33
|
+
|
34
|
+
def select_auditor(nfrs)
|
35
|
+
select(nfrs)
|
36
|
+
set_audit_level(@configuration['level'].to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_audit_level(level)
|
40
|
+
@auditor.set_audit_level(level)
|
41
|
+
rescue ArgumentError
|
42
|
+
$stderr.puts 'Invalid auditing level'
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
|
46
|
+
def debug(data, flow_identifier = nil)
|
47
|
+
enqueue(:debug, format(:debug, data, flow_identifier))
|
48
|
+
end
|
49
|
+
|
50
|
+
def info(data, flow_identifier = nil)
|
51
|
+
enqueue(:info, format(:info, data, flow_identifier))
|
52
|
+
end
|
53
|
+
|
54
|
+
def warn(data, flow_identifier = nil)
|
55
|
+
enqueue(:warn, format(:warn, data, flow_identifier))
|
56
|
+
end
|
57
|
+
|
58
|
+
def error(data, flow_identifier = nil)
|
59
|
+
enqueue(:error, format(:error, data, flow_identifier))
|
60
|
+
end
|
61
|
+
|
62
|
+
def fatal(data, flow_identifier = nil)
|
63
|
+
enqueue(:fatal, format(:fatal, data, flow_identifier))
|
64
|
+
end
|
65
|
+
|
66
|
+
def <<(data, flow_identifier = nil)
|
67
|
+
enqueue(:info, format(:info, data, flow_identifier))
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_status
|
71
|
+
{ 'audit_buffer_overflows' => @buffer_overflow_count }
|
72
|
+
end
|
73
|
+
|
74
|
+
def flush
|
75
|
+
@worker.flush
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def install_at_exit_handler
|
81
|
+
if @configuration['install_exit_handler'] == 'true'
|
82
|
+
Kernel.at_exit do
|
83
|
+
exit_cleanup
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def exit_cleanup(exception = nil)
|
89
|
+
audit_exception_message(exception) if exception
|
90
|
+
info("Application exit",@instance_flow_identifier)
|
91
|
+
flush
|
92
|
+
end
|
93
|
+
|
94
|
+
def audit_exception_message(exception)
|
95
|
+
exception_message = "#{exception.class}: #{exception.message}"
|
96
|
+
exception_message = exception_message + ":\n\t" + exception.backtrace.join("\n\t") if ENV['RACK_ENV'] == 'development'
|
97
|
+
fatal(exception_message,@instance_flow_identifier)
|
98
|
+
end
|
99
|
+
|
100
|
+
def enqueue(level, data)
|
101
|
+
@worker.enqueue(level, data)
|
102
|
+
rescue AuditingOverflowError
|
103
|
+
increase_buffer_overflow_count
|
104
|
+
$stderr.puts "Audit buffer full, unable to audit event : #{level} : #{data}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def increase_buffer_overflow_count
|
108
|
+
@buffer_overflow_count += 1
|
109
|
+
end
|
110
|
+
|
111
|
+
def validate_provider_configuration(configuration)
|
112
|
+
raise 'Missing auditing level' if configuration['level'].nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
def format(level, data, flow_identifier)
|
116
|
+
SoarAuditingFormatter::Formatter.format(level,@service_identifier,flow_identifier,Time.now.utc.iso8601(3),data)
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_auditing_worker
|
120
|
+
@worker = AuditingWorker.new
|
121
|
+
@worker.configure(queue_worker_configuration: @configuration['queue_worker'], auditor_audit_method: method(:super_class_caller))
|
122
|
+
@worker.start
|
123
|
+
end
|
124
|
+
|
125
|
+
def super_class_caller(level, data)
|
126
|
+
send("super_#{level}",data)
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_auditors(configuration)
|
130
|
+
auditor_factory = SoarConfiguredFactory::ConfiguredFactory.new(configuration['auditors'])
|
131
|
+
auditors = {}
|
132
|
+
configuration['auditors'].each do |auditor_name, auditor_configuration|
|
133
|
+
raise 'Missing auditor configuration' if auditor_configuration.nil?
|
134
|
+
auditor = create_auditor(auditor_factory,auditor_name)
|
135
|
+
auditors[auditor] = { 'name' => auditor_name, 'nfrs' => auditor_configuration['nfrs'] }
|
136
|
+
end
|
137
|
+
auditors
|
138
|
+
rescue
|
139
|
+
$stderr.puts 'Failure initializing auditor'
|
140
|
+
raise
|
141
|
+
end
|
142
|
+
|
143
|
+
def create_auditor(auditor_factory,auditor_name)
|
144
|
+
auditor_factory.create(auditor_name)
|
145
|
+
rescue
|
146
|
+
$stderr.puts 'Invalid auditor configuration'
|
147
|
+
raise
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'soar_thread_worker/thread_worker'
|
2
|
+
|
3
|
+
module SoarAuditingProvider
|
4
|
+
class AuditingWorker < SoarThreadWorker::ThreadWorker
|
5
|
+
def initialize
|
6
|
+
@queue = Queue.new
|
7
|
+
@start_mutex = Mutex.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def configure(queue_worker_configuration: ,auditor_audit_method: )
|
11
|
+
validate_configuration(queue_worker_configuration)
|
12
|
+
@maximum_queue_size = queue_worker_configuration['queue_size'].to_i
|
13
|
+
@initial_back_off_in_seconds = queue_worker_configuration['initial_back_off_in_seconds'].to_i
|
14
|
+
@back_off_multiplier = queue_worker_configuration['back_off_multiplier'].to_i
|
15
|
+
@maximum_back_off_attempts = queue_worker_configuration['back_off_attempts'].to_i
|
16
|
+
@auditor_audit_method = auditor_audit_method
|
17
|
+
end
|
18
|
+
|
19
|
+
def enqueue(level, data)
|
20
|
+
if @queue.size < @maximum_queue_size then
|
21
|
+
@queue.push({:level => level, :data => data})
|
22
|
+
else
|
23
|
+
raise AuditingOverflowError
|
24
|
+
end
|
25
|
+
ensure_worker_is_running
|
26
|
+
end
|
27
|
+
|
28
|
+
def start(verbose: false)
|
29
|
+
@start_mutex.synchronize {
|
30
|
+
if not running? then
|
31
|
+
super()
|
32
|
+
$stderr.puts("Auditing worker was not running and respawned") if verbose
|
33
|
+
end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute
|
38
|
+
audit_event = @queue.pop
|
39
|
+
failed_before = false
|
40
|
+
begin
|
41
|
+
if @stopping
|
42
|
+
@queue.push(audit_event) if audit_event #push the event back into the queue so that fallback flush mechanism can deal with this audit event
|
43
|
+
return true #indicates to thread worder that we are done executing since we are in the process of stopping
|
44
|
+
end
|
45
|
+
exponential_back_off(start_at_last_attempt: failed_before) {
|
46
|
+
@auditor_audit_method.call(audit_event[:level],audit_event[:data])
|
47
|
+
}
|
48
|
+
rescue Exception => e
|
49
|
+
print_exception_with_message_to_stderr(nil,e)
|
50
|
+
failed_before = true
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
return false #indicates to thread worder that we are not done executing
|
54
|
+
end
|
55
|
+
|
56
|
+
def flush(timeout = 1)
|
57
|
+
ensure_worker_is_running
|
58
|
+
wait_for_worker_to_clear_queue(timeout)
|
59
|
+
fallback_flush_to_stderr if @queue.size > 0
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def wait_for_worker_to_clear_queue(timeout = 1)
|
65
|
+
start_time = Time.now
|
66
|
+
until ((@queue.size == 0) or ((Time.now - start_time) >= timeout)) do
|
67
|
+
sleep(0.1)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def fallback_flush_to_stderr(timeout = 1)
|
72
|
+
$stderr.puts 'Unable to flush audit entries to auditor, stopping worker and flushing to stderr'
|
73
|
+
ensure_worker_is_stopped
|
74
|
+
start_time = Time.now
|
75
|
+
until ((@queue.size == 0) or ((Time.now - start_time) >= timeout)) do
|
76
|
+
audit_event = @queue.pop
|
77
|
+
$stderr.puts audit_event[:data].to_s
|
78
|
+
end
|
79
|
+
rescue Exception => e
|
80
|
+
print_exception_with_message_to_stderr('Failure during fallback attempt to flush audit entries to stderr',e)
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
|
84
|
+
def print_exception_with_message_to_stderr(notification,exception)
|
85
|
+
message = "#{exception.class}: #{exception.message}"
|
86
|
+
message = message + ":\n\t" + exception.backtrace.join("\n\t") if ENV['RACK_ENV'] == 'development'
|
87
|
+
$stderr.puts "#{notification}: #{message}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def ensure_worker_is_running
|
91
|
+
start(verbose: true)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ensure_worker_is_stopped
|
95
|
+
attempt_graceful_stop
|
96
|
+
sleep_while_still_running(5)
|
97
|
+
force_stop
|
98
|
+
end
|
99
|
+
|
100
|
+
def attempt_graceful_stop
|
101
|
+
@stopping = true
|
102
|
+
end
|
103
|
+
|
104
|
+
def force_stop
|
105
|
+
stop
|
106
|
+
end
|
107
|
+
|
108
|
+
def validate_configuration(queue_worker_configuration)
|
109
|
+
raise ArgumentError.new("Invalid queue size (#{queue_worker_configuration['queue_size'].to_i})") if queue_worker_configuration['queue_size'].to_i < 1
|
110
|
+
raise ArgumentError.new("Invalid number of back off attempts (#{queue_worker_configuration['back_off_attempts'].to_i})") if queue_worker_configuration['back_off_attempts'].to_i < 1
|
111
|
+
end
|
112
|
+
|
113
|
+
def exponential_back_off(start_at_last_attempt: false)
|
114
|
+
attempt = 1
|
115
|
+
if start_at_last_attempt
|
116
|
+
attempt = @maximum_back_off_attempts
|
117
|
+
sleep_unless_stopping(calculate_back_off_delay(@maximum_back_off_attempts))
|
118
|
+
end
|
119
|
+
begin
|
120
|
+
yield
|
121
|
+
rescue StandardError
|
122
|
+
# Any exception derived from StandardError is assumed to be a failure and
|
123
|
+
# attempted again until it completes without an exception or an exception
|
124
|
+
# not derived from StandardError
|
125
|
+
if ((attempt <= @maximum_back_off_attempts) and (not @stopping)) then
|
126
|
+
sleep_unless_stopping(calculate_back_off_delay(attempt))
|
127
|
+
attempt = attempt + 1
|
128
|
+
retry
|
129
|
+
else
|
130
|
+
raise
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def calculate_back_off_delay(attempt)
|
136
|
+
@initial_back_off_in_seconds * (@back_off_multiplier ** (attempt-1))
|
137
|
+
end
|
138
|
+
|
139
|
+
def sleep_unless_stopping(desired_delay)
|
140
|
+
start_time = Time.now
|
141
|
+
until (@stopping or ((Time.now - start_time) >= desired_delay)) do
|
142
|
+
sleep(0.1)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def sleep_while_still_running(desired_delay)
|
147
|
+
start_time = Time.now
|
148
|
+
until ((false == @running) or ((Time.now - start_time) >= desired_delay)) do
|
149
|
+
sleep(0.1)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|