scout_apm_logging 0.0.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 +7 -0
- data/.github/workflows/test.yml +37 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +65 -0
- data/Dockerfile +18 -0
- data/Gemfile +5 -0
- data/README.md +58 -0
- data/Rakefile +35 -0
- data/bin/scout_apm_logging_monitor +5 -0
- data/gems/rails.gemfile +3 -0
- data/lib/scout_apm/logging/config.rb +265 -0
- data/lib/scout_apm/logging/context.rb +58 -0
- data/lib/scout_apm/logging/logger.rb +26 -0
- data/lib/scout_apm/logging/loggers/capture.rb +46 -0
- data/lib/scout_apm/logging/loggers/formatter.rb +86 -0
- data/lib/scout_apm/logging/loggers/logger.rb +82 -0
- data/lib/scout_apm/logging/loggers/proxy.rb +39 -0
- data/lib/scout_apm/logging/loggers/swap.rb +82 -0
- data/lib/scout_apm/logging/monitor/collector/checksum.rb +51 -0
- data/lib/scout_apm/logging/monitor/collector/configuration.rb +148 -0
- data/lib/scout_apm/logging/monitor/collector/downloader.rb +78 -0
- data/lib/scout_apm/logging/monitor/collector/extractor.rb +37 -0
- data/lib/scout_apm/logging/monitor/collector/manager.rb +57 -0
- data/lib/scout_apm/logging/monitor/monitor.rb +214 -0
- data/lib/scout_apm/logging/monitor_manager/manager.rb +150 -0
- data/lib/scout_apm/logging/state.rb +70 -0
- data/lib/scout_apm/logging/utils.rb +86 -0
- data/lib/scout_apm/logging/version.rb +7 -0
- data/lib/scout_apm_logging.rb +35 -0
- data/scout_apm_logging.gemspec +27 -0
- data/spec/data/config_test_1.yml +27 -0
- data/spec/data/empty_logs_config.yml +0 -0
- data/spec/data/logs_config.yml +3 -0
- data/spec/data/mock_config.yml +29 -0
- data/spec/data/state_file.json +3 -0
- data/spec/integration/loggers/capture_spec.rb +78 -0
- data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +47 -0
- data/spec/integration/monitor/collector_healthcheck_spec.rb +27 -0
- data/spec/integration/monitor/continuous_state_collector_spec.rb +29 -0
- data/spec/integration/monitor/previous_collector_setup_spec.rb +42 -0
- data/spec/integration/monitor_manager/disable_agent_spec.rb +28 -0
- data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +36 -0
- data/spec/integration/monitor_manager/single_monitor_spec.rb +53 -0
- data/spec/integration/rails/lifecycle_spec.rb +29 -0
- data/spec/spec_helper.rb +65 -0
- data/spec/unit/config_spec.rb +25 -0
- data/spec/unit/loggers/capture_spec.rb +64 -0
- data/spec/unit/monitor/collector/configuration_spec.rb +64 -0
- data/spec/unit/state_spec.rb +20 -0
- data/tooling/checksums.rb +106 -0
- metadata +167 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Loggers::Capture do
|
6
|
+
it 'should find the logger, capture the log destination, and rotate collector configs' do
|
7
|
+
ENV['SCOUT_MONITOR_INTERVAL'] = '10'
|
8
|
+
ENV['SCOUT_MONITOR_INTERVAL_DELAY'] = '10'
|
9
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
10
|
+
|
11
|
+
context = ScoutApm::Logging::MonitorManager.instance.context
|
12
|
+
|
13
|
+
state_file_location = context.config.value('monitor_state_file')
|
14
|
+
collector_pid_location = context.config.value('collector_pid_file')
|
15
|
+
ScoutApm::Logging::Utils.ensure_directory_exists(state_file_location)
|
16
|
+
|
17
|
+
first_logger = ScoutTestLogger.new('/tmp/first_file.log')
|
18
|
+
first_logger_basename = File.basename(first_logger.instance_variable_get(:@logdev).filename.to_s)
|
19
|
+
first_logger_updated_path = File.join(context.config.value('logs_proxy_log_dir'), first_logger_basename)
|
20
|
+
|
21
|
+
# While we only use the ObjectSpace for the test logger, we need to wait for it to be captured.
|
22
|
+
wait_for_logger
|
23
|
+
|
24
|
+
similuate_railtie
|
25
|
+
|
26
|
+
# Give the process time to initialize, download the collector, and start it
|
27
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
28
|
+
|
29
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
30
|
+
collector_pid = File.read(collector_pid_location)
|
31
|
+
|
32
|
+
content = File.read(state_file_location)
|
33
|
+
data = JSON.parse(content)
|
34
|
+
expect(data['logs_monitored']).to eq([first_logger_updated_path])
|
35
|
+
|
36
|
+
second_logger = ScoutTestLogger.new('/tmp/second_file.log')
|
37
|
+
second_logger_basename = File.basename(second_logger.instance_variable_get(:@logdev).filename.to_s)
|
38
|
+
second_logger_updated_path = File.join(context.config.value('logs_proxy_log_dir'), second_logger_basename)
|
39
|
+
|
40
|
+
similuate_railtie
|
41
|
+
|
42
|
+
content = File.read(state_file_location)
|
43
|
+
data = JSON.parse(content)
|
44
|
+
|
45
|
+
expect(data['logs_monitored'].sort).to eq([first_logger_updated_path, second_logger_updated_path])
|
46
|
+
|
47
|
+
# Need to wait for the delay first health check, next monitor interval to restart the collector, and then for
|
48
|
+
# the collector to restart
|
49
|
+
sleep 25
|
50
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
51
|
+
|
52
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
53
|
+
new_collector_pid = File.read(collector_pid_location)
|
54
|
+
|
55
|
+
# Should have restarted the collector based on the change
|
56
|
+
expect(new_collector_pid).not_to eq(collector_pid)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def similuate_railtie
|
62
|
+
context = ScoutApm::Logging::MonitorManager.instance.context
|
63
|
+
|
64
|
+
ScoutApm::Logging::Loggers::Capture.new(context).capture_log_locations!
|
65
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
66
|
+
end
|
67
|
+
|
68
|
+
def wait_for_logger
|
69
|
+
start_time = Time.now
|
70
|
+
loop do
|
71
|
+
break if ObjectSpace.each_object(ScoutTestLogger).count.positive?
|
72
|
+
|
73
|
+
raise 'Timed out while waiting for logger in ObjectSpace' if Time.now - start_time > 10
|
74
|
+
|
75
|
+
sleep 0.1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../../../../../lib/scout_apm/logging/monitor/collector/downloader'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Collector::Downloader do
|
6
|
+
it 'should validate checksum, and correct download if neccessary' do
|
7
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
8
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
9
|
+
|
10
|
+
otelcol_contrib_path = '/tmp/scout_apm/otelcol-contrib'
|
11
|
+
ScoutApm::Logging::Utils.ensure_directory_exists(otelcol_contrib_path)
|
12
|
+
|
13
|
+
File.write(otelcol_contrib_path, 'fake content')
|
14
|
+
|
15
|
+
ScoutApm::Logging::MonitorManager.instance.setup!
|
16
|
+
|
17
|
+
# Give the process time to initialize, download the collector, and start it
|
18
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
19
|
+
|
20
|
+
download_time = File.mtime(otelcol_contrib_path)
|
21
|
+
|
22
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
23
|
+
|
24
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'false'
|
25
|
+
|
26
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
27
|
+
|
28
|
+
sleep 5 # Give the process time to exit
|
29
|
+
|
30
|
+
expect(File.exist?(ScoutApm::Logging::MonitorManager.instance.context.config.value('monitor_pid_file'))).to be_falsey
|
31
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).to be_empty
|
32
|
+
expect(`pgrep scout_apm_log_monitor --runstates D,R,S`).to be_empty
|
33
|
+
|
34
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
35
|
+
|
36
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
37
|
+
|
38
|
+
# Give the process time to exit, and for the healthcheck to restart it
|
39
|
+
wait_for_process_with_timeout!('otelcol-contrib', 30)
|
40
|
+
|
41
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
42
|
+
|
43
|
+
recheck_time = File.mtime(otelcol_contrib_path)
|
44
|
+
|
45
|
+
expect(download_time).to eq(recheck_time)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../../../lib/scout_apm/logging/monitor/monitor'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Monitor do
|
6
|
+
it 'should recreate collector process on healthcheck if it has exited' do
|
7
|
+
ENV['SCOUT_MONITOR_INTERVAL'] = '10'
|
8
|
+
ENV['SCOUT_MONITOR_INTERVAL_DELAY'] = '10'
|
9
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
10
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
11
|
+
|
12
|
+
ScoutApm::Logging::Utils.ensure_directory_exists('/tmp/scout_apm/scout_apm_log_monitor.pid')
|
13
|
+
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.setup!
|
15
|
+
|
16
|
+
# Give the process time to initialize, download the collector, and start it
|
17
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
18
|
+
|
19
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
20
|
+
|
21
|
+
# Bypass gracefull shutdown
|
22
|
+
`pkill -9 otelcol-contrib`
|
23
|
+
|
24
|
+
# Give the process time to exit, and for the healthcheck to restart it
|
25
|
+
wait_for_process_with_timeout!('otelcol-contrib', 30)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../../../lib/scout_apm/logging/monitor/monitor'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Monitor do
|
6
|
+
it "Should not restart the collector if the state hasn't changed" do
|
7
|
+
ENV['SCOUT_MONITOR_INTERVAL'] = '10'
|
8
|
+
ENV['SCOUT_MONITOR_INTERVAL_DELAY'] = '10'
|
9
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
10
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
11
|
+
|
12
|
+
context = ScoutApm::Logging::MonitorManager.instance.context
|
13
|
+
collector_pid_location = context.config.value('collector_pid_file')
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.setup!
|
15
|
+
# Give the process time to initialize, download the collector, and start it
|
16
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
17
|
+
|
18
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
19
|
+
collector_pid = File.read(collector_pid_location)
|
20
|
+
|
21
|
+
# Give time for the monitor interval to run.
|
22
|
+
sleep 30
|
23
|
+
|
24
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).not_to be_empty
|
25
|
+
second_read_pid = File.read(collector_pid_location)
|
26
|
+
|
27
|
+
expect(second_read_pid).to eq(collector_pid)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../../../lib/scout_apm/logging/monitor/monitor'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Monitor do
|
6
|
+
it 'should use previous collector setup if monitor daemon exits' do
|
7
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
8
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
9
|
+
|
10
|
+
monitor_pid_location = ScoutApm::Logging::MonitorManager.instance.context.config.value('monitor_pid_file')
|
11
|
+
collector_pid_location = ScoutApm::Logging::MonitorManager.instance.context.config.value('collector_pid_file')
|
12
|
+
ScoutApm::Logging::Utils.ensure_directory_exists(monitor_pid_location)
|
13
|
+
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.setup!
|
15
|
+
# Give the process time to initialize, download the collector, and start it
|
16
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
17
|
+
|
18
|
+
monitor_pid = File.read(monitor_pid_location)
|
19
|
+
|
20
|
+
otelcol_pid = `pgrep otelcol-contrib --runstates D,R,S`.strip!
|
21
|
+
stored_otelcol_pid = File.read(collector_pid_location)
|
22
|
+
expect(otelcol_pid).to eq(stored_otelcol_pid)
|
23
|
+
|
24
|
+
`kill -9 #{monitor_pid}`
|
25
|
+
|
26
|
+
# Create a separate monitor manager instance, or else we won't reload
|
27
|
+
# the configuraiton state.
|
28
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
29
|
+
|
30
|
+
sleep 5
|
31
|
+
|
32
|
+
expect(`pgrep -f /app/bin/scout_apm_logging_monitor --runstates D,R,S`).not_to be_empty
|
33
|
+
|
34
|
+
new_monitor_pid = File.read(monitor_pid_location)
|
35
|
+
expect(new_monitor_pid).not_to eq(monitor_pid)
|
36
|
+
|
37
|
+
should_be_same_otelcol_pid = `pgrep otelcol-contrib --runstates D,R,S`.strip!
|
38
|
+
should_be_same_stored_otelcol_pid = File.read(collector_pid_location)
|
39
|
+
expect(should_be_same_otelcol_pid).to eq(otelcol_pid)
|
40
|
+
expect(should_be_same_stored_otelcol_pid).to eq(stored_otelcol_pid)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../../../lib/scout_apm/logging/monitor/collector/manager'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Collector::Manager do
|
6
|
+
it 'If monitor is false, it should remove the daemon and collector process if they are present' do
|
7
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
8
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
9
|
+
|
10
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).to be_empty
|
11
|
+
|
12
|
+
ScoutApm::Logging::MonitorManager.instance.setup!
|
13
|
+
|
14
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
15
|
+
|
16
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'false'
|
17
|
+
|
18
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
19
|
+
|
20
|
+
sleep 5 # Give the process time to exit
|
21
|
+
|
22
|
+
expect(File.exist?(ScoutApm::Logging::MonitorManager.instance.context.config.value('monitor_pid_file'))).to be_falsey
|
23
|
+
expect(`pgrep otelcol-contrib --runstates D,R,S`).to be_empty
|
24
|
+
expect(`pgrep scout_apm_log_monitor --runstates D,R,S`).to be_empty
|
25
|
+
|
26
|
+
ENV.delete('SCOUT_LOGS_MONITOR')
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require_relative '../../../lib/scout_apm/logging/monitor/collector/manager'
|
4
|
+
|
5
|
+
describe ScoutApm::Logging::Collector::Manager do
|
6
|
+
it 'should recreate the monitor process if monitor.pid file is errant' do
|
7
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
8
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
9
|
+
|
10
|
+
ScoutApm::Logging::Utils.ensure_directory_exists('/tmp/scout_apm/scout_apm_log_monitor.pid')
|
11
|
+
|
12
|
+
# A high enough number to not be a PID of a running process
|
13
|
+
pid_file_path = '/tmp/scout_apm/scout_apm_log_monitor.pid'
|
14
|
+
File.open(pid_file_path, 'w') do |file|
|
15
|
+
file.write('123456')
|
16
|
+
end
|
17
|
+
|
18
|
+
ScoutApm::Logging::MonitorManager.instance.setup!
|
19
|
+
|
20
|
+
# Give the process time to initialize, download the collector, and start it
|
21
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
22
|
+
|
23
|
+
new_pid = File.read(pid_file_path).to_i
|
24
|
+
|
25
|
+
expect(new_pid).not_to eq(12_345)
|
26
|
+
|
27
|
+
# Check if the process with the stored PID is running
|
28
|
+
expect(Process.kill(0, new_pid)).to be_truthy
|
29
|
+
ENV.delete('SCOUT_LOGS_MONITOR')
|
30
|
+
|
31
|
+
# Kill the process and ensure PID file clean up
|
32
|
+
Process.kill('TERM', new_pid)
|
33
|
+
Process.kill('TERM', `pgrep otelcol-contrib --runstates D,R,S`.to_i)
|
34
|
+
sleep 1 # Give the process time to exit
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# This is really meant to simulate multiple processes calling the MonitorManager setup.
|
2
|
+
# Trying to create **multiple fake** rails environments is a bit challening
|
3
|
+
# (such as a rails app and a couple rails runners).
|
4
|
+
|
5
|
+
# See: https://github.com/rails/rails/blob/6d126e03dbbf5d30fa97b580f7dee46343537b7b/railties/test/isolation/abstract_unit.rb#L339
|
6
|
+
|
7
|
+
# We simulate the ultimate outcome here -- ie Railtie invoking the setup.
|
8
|
+
require 'spec_helper'
|
9
|
+
|
10
|
+
describe ScoutApm::Logging do
|
11
|
+
it 'Should only create a single monitor daemon if manager is called multiple times' do
|
12
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
13
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
14
|
+
|
15
|
+
pid_file = ScoutApm::Logging::MonitorManager.instance.context.config.value('monitor_pid_file')
|
16
|
+
expect(File.exist?(pid_file)).to be_falsey
|
17
|
+
|
18
|
+
context = ScoutApm::Logging::MonitorManager.instance.context
|
19
|
+
|
20
|
+
pids = 3.times.map do
|
21
|
+
fork do
|
22
|
+
puts 'fork attempting to gain lock'
|
23
|
+
ScoutApm::Logging::Utils.attempt_exclusive_lock(context) do
|
24
|
+
puts 'obtained lock'
|
25
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
pids.each { |pid| Process.wait(pid) }
|
31
|
+
|
32
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
33
|
+
|
34
|
+
# The file lock really should be gone at this point.
|
35
|
+
expect(File.exist?(context.config.value('manager_lock_file'))).to be_falsey
|
36
|
+
expect(File.exist?(pid_file)).to be_truthy
|
37
|
+
|
38
|
+
original_pid = File.read(pid_file).to_i
|
39
|
+
expect(ScoutApm::Logging::Utils.check_process_liveliness(original_pid, 'scout_apm_logging_monitor')).to be_truthy
|
40
|
+
|
41
|
+
# Another process comes in and tries to start it again
|
42
|
+
ScoutApm::Logging::Utils.attempt_exclusive_lock(context) do
|
43
|
+
puts 'obtained lock later on'
|
44
|
+
ScoutApm::Logging::MonitorManager.new.setup!
|
45
|
+
end
|
46
|
+
|
47
|
+
expect(File.exist?(context.config.value('manager_lock_file'))).to be_falsey
|
48
|
+
expect(File.exist?(pid_file)).to be_truthy
|
49
|
+
updated_pid = File.read(pid_file).to_i
|
50
|
+
expect(updated_pid).to eq(original_pid)
|
51
|
+
expect(ScoutApm::Logging::Utils.check_process_liveliness(original_pid, 'scout_apm_logging_monitor')).to be_truthy
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScoutApm::Logging do
|
4
|
+
it 'checks the Rails lifecycle for creating the daemon and collector processes' do
|
5
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
6
|
+
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
7
|
+
|
8
|
+
pid_file = ScoutApm::Logging::MonitorManager.instance.context.config.value('monitor_pid_file')
|
9
|
+
expect(File.exist?(pid_file)).to be_falsey
|
10
|
+
|
11
|
+
make_basic_app
|
12
|
+
|
13
|
+
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
14
|
+
|
15
|
+
# Check if the monitor PID file exists
|
16
|
+
expect(File.exist?(pid_file)).to be_truthy
|
17
|
+
|
18
|
+
# Read the PID from the PID file
|
19
|
+
pid = File.read(pid_file).to_i
|
20
|
+
|
21
|
+
# Check if the process with the stored PID is running
|
22
|
+
expect(ScoutApm::Logging::Utils.check_process_liveliness(pid, 'scout_apm_logging_monitor')).to be_truthy
|
23
|
+
|
24
|
+
# Kill the process and ensure PID file clean up
|
25
|
+
Process.kill('TERM', pid)
|
26
|
+
sleep 1 # Give the process time to exit
|
27
|
+
expect(File.exist?(pid_file)).to be_falsey
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
require 'scout_apm'
|
6
|
+
|
7
|
+
require "rails"
|
8
|
+
require "action_controller/railtie"
|
9
|
+
require "action_view/railtie"
|
10
|
+
|
11
|
+
# Require after rails, to ensure the railtie is ran
|
12
|
+
require 'scout_apm_logging'
|
13
|
+
|
14
|
+
class TestLoggerWrapper
|
15
|
+
class << self
|
16
|
+
attr_accessor :logger
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ScoutTestLogger < ::Logger
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
ENV["SCOUT_LOG_LEVEL"] = "debug"
|
25
|
+
ENV["SCOUT_COLLECTOR_LOG_LEVEL"] = "debug"
|
26
|
+
|
27
|
+
config.after(:each) do
|
28
|
+
RSpec::Mocks.space.reset_all
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Taken from:
|
33
|
+
# https://github.com/rails/rails/blob/v7.1.3.2/railties/test/isolation/abstract_unit.rb#L252
|
34
|
+
def make_basic_app
|
35
|
+
@app = Class.new(Rails::Application) do
|
36
|
+
def self.name; "RailtiesTestApp"; end
|
37
|
+
end
|
38
|
+
@app.config.hosts << proc { true }
|
39
|
+
@app.config.eager_load = false
|
40
|
+
@app.config.session_store :cookie_store, key: "_myapp_session"
|
41
|
+
@app.config.active_support.deprecation = :log
|
42
|
+
@app.config.log_level = :info
|
43
|
+
@app.config.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
|
44
|
+
|
45
|
+
yield @app if block_given?
|
46
|
+
@app.initialize!
|
47
|
+
|
48
|
+
@app.routes.draw do
|
49
|
+
get "/" => "omg#index"
|
50
|
+
end
|
51
|
+
|
52
|
+
require "rack/test"
|
53
|
+
extend ::Rack::Test::Methods
|
54
|
+
end
|
55
|
+
|
56
|
+
def wait_for_process_with_timeout!(name, timeout_time)
|
57
|
+
Timeout::timeout(timeout_time) do
|
58
|
+
loop do
|
59
|
+
break if `pgrep #{name} --runstates D,R,S`.strip != ""
|
60
|
+
sleep 0.1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
sleep 1
|
65
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScoutApm::Logging::Config do
|
4
|
+
it 'loads the config file' do
|
5
|
+
context = ScoutApm::Logging::Context.new
|
6
|
+
conf_file = File.expand_path('../data/config_test_1.yml', __dir__)
|
7
|
+
conf = ScoutApm::Logging::Config.with_file(context, conf_file)
|
8
|
+
|
9
|
+
expect(conf.value('log_level')).to eq('debug')
|
10
|
+
expect(conf.value('logs_monitor')).to eq(true)
|
11
|
+
expect(conf.value('monitor_pid_file')).to eq('/tmp/scout_apm/scout_apm_log_monitor.pid')
|
12
|
+
expect(conf.value('logs_ingest_key')).to eq('00001000010000abc')
|
13
|
+
expect(conf.value('logs_monitored')).to eq(['/tmp/fake_log_file.log'])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'loads the state file into the config' do
|
17
|
+
ENV['SCOUT_MONITOR_STATE_FILE'] = File.expand_path('../data/state_file.json', __dir__)
|
18
|
+
|
19
|
+
context = ScoutApm::Logging::Context.new
|
20
|
+
conf_file = File.expand_path('../data/config_test_1.yml', __dir__)
|
21
|
+
conf = ScoutApm::Logging::Config.with_file(context, conf_file)
|
22
|
+
|
23
|
+
expect(conf.value('health_check_port')).to eq(1234)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
|
6
|
+
require_relative '../../../lib/scout_apm/logging/loggers/capture'
|
7
|
+
require_relative '../../../lib/scout_apm/logging/loggers/proxy'
|
8
|
+
|
9
|
+
def capture_stdout
|
10
|
+
old_stdout = $stdout
|
11
|
+
$stdout = StringIO.new
|
12
|
+
yield
|
13
|
+
$stdout.string
|
14
|
+
ensure
|
15
|
+
$stdout = old_stdout
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ScoutApm::Logging::Loggers::Capture do
|
19
|
+
it 'should swap the STDOUT logger and create a proxy logger' do
|
20
|
+
ENV['SCOUT_MONITOR_INTERVAL'] = '10'
|
21
|
+
ENV['SCOUT_MONITOR_INTERVAL_DELAY'] = '10'
|
22
|
+
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
23
|
+
|
24
|
+
output_from_log = capture_stdout do
|
25
|
+
context = ScoutApm::Logging::Context.new
|
26
|
+
conf_file = File.expand_path('../data/config_test_1.yml', __dir__)
|
27
|
+
conf = ScoutApm::Logging::Config.with_file(context, conf_file)
|
28
|
+
context.config = conf
|
29
|
+
|
30
|
+
TestLoggerWrapper.logger = ScoutTestLogger.new($stdout)
|
31
|
+
|
32
|
+
# While we only use the ObjectSpace for the test logger, we need to wait for it to be captured.
|
33
|
+
wait_for_logger
|
34
|
+
|
35
|
+
capture = ScoutApm::Logging::Loggers::Capture.new(context)
|
36
|
+
capture.capture_log_locations!
|
37
|
+
|
38
|
+
expect(TestLoggerWrapper.logger.class).to eq(ScoutApm::Logging::Loggers::Proxy)
|
39
|
+
|
40
|
+
TestLoggerWrapper.logger.info('TEST')
|
41
|
+
|
42
|
+
log_path = File.join(context.config.value('logs_proxy_log_dir'), 'test.log')
|
43
|
+
content = File.read(log_path)
|
44
|
+
expect(content).to include('TEST')
|
45
|
+
|
46
|
+
state_file = File.read(context.config.value('monitor_state_file'))
|
47
|
+
state_data = JSON.parse(state_file)
|
48
|
+
expect(state_data['logs_monitored']).to eq([log_path])
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(output_from_log).to include('TEST')
|
52
|
+
end
|
53
|
+
|
54
|
+
def wait_for_logger
|
55
|
+
start_time = Time.now
|
56
|
+
loop do
|
57
|
+
break if ObjectSpace.each_object(::ScoutTestLogger).count.positive?
|
58
|
+
|
59
|
+
raise 'Timed out while waiting for logger in ObjectSpace' if Time.now - start_time > 10
|
60
|
+
|
61
|
+
sleep 0.1
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require_relative '../../../../lib/scout_apm/logging/monitor/collector/configuration'
|
6
|
+
|
7
|
+
describe ScoutApm::Logging::Collector::Configuration do
|
8
|
+
it 'creates the correct configuration for the otelcol' do
|
9
|
+
context = ScoutApm::Logging::Context.new
|
10
|
+
setup_collector_config!(context)
|
11
|
+
|
12
|
+
expect(File.exist?(context.config.value('collector_config_file'))).to be_truthy
|
13
|
+
config = YAML.load_file(context.config.value('collector_config_file'))
|
14
|
+
|
15
|
+
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.telemetryhub.com:4317')
|
16
|
+
expect(config['exporters']['otlp']['headers']['x-telemetryhub-key']).to eq('00001000010000abc')
|
17
|
+
expect(config['receivers']['filelog']['include']).to eq(['/tmp/fake_log_file.log'])
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'merges in a logs config correctly' do
|
21
|
+
ENV['SCOUT_LOGS_CONFIG'] = File.expand_path('../../../data/logs_config.yml', __dir__)
|
22
|
+
|
23
|
+
context = ScoutApm::Logging::Context.new
|
24
|
+
setup_collector_config!(context)
|
25
|
+
|
26
|
+
expect(File.exist?(context.config.value('collector_config_file'))).to be_truthy
|
27
|
+
config = YAML.load_file(context.config.value('collector_config_file'))
|
28
|
+
|
29
|
+
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.telemetryhub.com:4317')
|
30
|
+
expect(config['exporters']['otlp']['headers']['x-telemetryhub-key']).to eq('00001000010000abc')
|
31
|
+
expect(config['receivers']['filelog']['include']).to eq(['/tmp/fake_log_file.log'])
|
32
|
+
|
33
|
+
# Verify merge and consistent keys
|
34
|
+
expect(config['extensions']['file_storage/otc']['directory']).to eq('/dev/null')
|
35
|
+
expect(config['extensions']['file_storage/otc']['timeout']).to eq('10s')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'handles an empty logs config file well' do
|
39
|
+
ENV['SCOUT_LOGS_CONFIG'] = File.expand_path('../../../data/empty_logs_config.yml', __dir__)
|
40
|
+
|
41
|
+
context = ScoutApm::Logging::Context.new
|
42
|
+
setup_collector_config!(context)
|
43
|
+
|
44
|
+
expect(File.exist?(context.config.value('collector_config_file'))).to be_truthy
|
45
|
+
config = YAML.load_file(context.config.value('collector_config_file'))
|
46
|
+
|
47
|
+
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.telemetryhub.com:4317')
|
48
|
+
expect(config['exporters']['otlp']['headers']['x-telemetryhub-key']).to eq('00001000010000abc')
|
49
|
+
expect(config['receivers']['filelog']['include']).to eq(['/tmp/fake_log_file.log'])
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def setup_collector_config!(context)
|
55
|
+
ENV['SCOUT_MONITOR_STATE_FILE'] = File.expand_path('../../../data/state_file.json', __dir__)
|
56
|
+
conf_file = File.expand_path('../../../data/mock_config.yml', __dir__)
|
57
|
+
context.config = ScoutApm::Logging::Config.with_file(context, conf_file)
|
58
|
+
|
59
|
+
ScoutApm::Logging::Utils.ensure_directory_exists(context.config.value('collector_config_file'))
|
60
|
+
|
61
|
+
collector_config = ScoutApm::Logging::Collector::Configuration.new(context)
|
62
|
+
collector_config.setup!
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ScoutApm::Logging::Config do
|
4
|
+
it 'flushes and loads the state' do
|
5
|
+
context = ScoutApm::Logging::Context.new
|
6
|
+
conf_file = File.expand_path('../data/config_test_1.yml', __dir__)
|
7
|
+
conf = ScoutApm::Logging::Config.with_file(context, conf_file)
|
8
|
+
|
9
|
+
context.config = conf
|
10
|
+
|
11
|
+
ScoutApm::Logging::Config::ConfigDynamic.set_value('health_check_port', 1234)
|
12
|
+
|
13
|
+
context.config.state.flush_state!
|
14
|
+
|
15
|
+
data = ScoutApm::Logging::Config::State.new(context).load_state_from_file
|
16
|
+
|
17
|
+
expect(data['health_check_port']).to eq(context.config.value('health_check_port'))
|
18
|
+
expect(data['logs_monitored']).to eq(context.config.value('logs_monitored'))
|
19
|
+
end
|
20
|
+
end
|