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
         
     |