scout_apm_logging 0.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +37 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +65 -0
  5. data/Dockerfile +18 -0
  6. data/Gemfile +5 -0
  7. data/README.md +58 -0
  8. data/Rakefile +35 -0
  9. data/bin/scout_apm_logging_monitor +5 -0
  10. data/gems/rails.gemfile +3 -0
  11. data/lib/scout_apm/logging/config.rb +265 -0
  12. data/lib/scout_apm/logging/context.rb +58 -0
  13. data/lib/scout_apm/logging/logger.rb +26 -0
  14. data/lib/scout_apm/logging/loggers/capture.rb +46 -0
  15. data/lib/scout_apm/logging/loggers/formatter.rb +86 -0
  16. data/lib/scout_apm/logging/loggers/logger.rb +82 -0
  17. data/lib/scout_apm/logging/loggers/proxy.rb +39 -0
  18. data/lib/scout_apm/logging/loggers/swap.rb +82 -0
  19. data/lib/scout_apm/logging/monitor/collector/checksum.rb +51 -0
  20. data/lib/scout_apm/logging/monitor/collector/configuration.rb +148 -0
  21. data/lib/scout_apm/logging/monitor/collector/downloader.rb +78 -0
  22. data/lib/scout_apm/logging/monitor/collector/extractor.rb +37 -0
  23. data/lib/scout_apm/logging/monitor/collector/manager.rb +57 -0
  24. data/lib/scout_apm/logging/monitor/monitor.rb +214 -0
  25. data/lib/scout_apm/logging/monitor_manager/manager.rb +150 -0
  26. data/lib/scout_apm/logging/state.rb +70 -0
  27. data/lib/scout_apm/logging/utils.rb +86 -0
  28. data/lib/scout_apm/logging/version.rb +7 -0
  29. data/lib/scout_apm_logging.rb +35 -0
  30. data/scout_apm_logging.gemspec +27 -0
  31. data/spec/data/config_test_1.yml +27 -0
  32. data/spec/data/empty_logs_config.yml +0 -0
  33. data/spec/data/logs_config.yml +3 -0
  34. data/spec/data/mock_config.yml +29 -0
  35. data/spec/data/state_file.json +3 -0
  36. data/spec/integration/loggers/capture_spec.rb +78 -0
  37. data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +47 -0
  38. data/spec/integration/monitor/collector_healthcheck_spec.rb +27 -0
  39. data/spec/integration/monitor/continuous_state_collector_spec.rb +29 -0
  40. data/spec/integration/monitor/previous_collector_setup_spec.rb +42 -0
  41. data/spec/integration/monitor_manager/disable_agent_spec.rb +28 -0
  42. data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +36 -0
  43. data/spec/integration/monitor_manager/single_monitor_spec.rb +53 -0
  44. data/spec/integration/rails/lifecycle_spec.rb +29 -0
  45. data/spec/spec_helper.rb +65 -0
  46. data/spec/unit/config_spec.rb +25 -0
  47. data/spec/unit/loggers/capture_spec.rb +64 -0
  48. data/spec/unit/monitor/collector/configuration_spec.rb +64 -0
  49. data/spec/unit/state_spec.rb +20 -0
  50. data/tooling/checksums.rb +106 -0
  51. 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
@@ -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