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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'logger'
5
+
6
+ require 'scout_apm'
7
+
8
+ module ScoutApm
9
+ module Logging
10
+ module Loggers
11
+ # A simple JSON formatter which we can add a couple attributes to.
12
+ class Formatter < ::Logger::Formatter
13
+ DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%LZ'
14
+
15
+ def call(severity, time, progname, msg) # rubocop:disable Metrics/AbcSize
16
+ attributes_to_log[:severity] = severity
17
+ attributes_to_log[:time] = format_datetime(time)
18
+ attributes_to_log[:progname] = progname if progname
19
+ attributes_to_log[:pid] = Process.pid.to_s
20
+ attributes_to_log[:msg] = msg2str(msg)
21
+ attributes_to_log['service.name'] = service_name
22
+
23
+ attributes_to_log.merge!(scout_layer)
24
+ attributes_to_log.merge!(scout_context)
25
+ # Naive local benchmarks show this takes around 200 microseconds. As such, we only apply it to WARN and above.
26
+ attributes_to_log.merge!(local_log_location) if ::Logger::Severity.const_get(severity) >= ::Logger::Severity::WARN
27
+
28
+ "#{attributes_to_log.to_json}\n"
29
+ end
30
+
31
+ private
32
+
33
+ def attributes_to_log
34
+ @attributes_to_log ||= {}
35
+ end
36
+
37
+ def format_datetime(time)
38
+ time.utc.strftime(DATETIME_FORMAT)
39
+ end
40
+
41
+ def scout_layer # rubocop:disable Metrics/AbcSize
42
+ req = ScoutApm::RequestManager.lookup
43
+ layer = req.instance_variable_get('@layers').find { |lay| lay.type == 'Controller' || lay.type == 'Job' }
44
+
45
+ return {} unless layer
46
+
47
+ name, action = layer.name.split('/')
48
+
49
+ return {} unless name && action
50
+
51
+ updated_name = name.split('_').map(&:capitalize).join
52
+
53
+ derived_key = "#{layer.type.downcase}_entrypoint".to_sym
54
+ derived_value_of_scout_name = "#{updated_name}#{layer.type.capitalize}##{action.capitalize}"
55
+
56
+ { derived_key => derived_value_of_scout_name }
57
+ end
58
+
59
+ def scout_context
60
+ req = ScoutApm::RequestManager.lookup
61
+ extra_context = req.context.instance_variable_get('@extra')
62
+ user_context = req.context.instance_variable_get('@user')
63
+ # We may want to make this a configuration option in the future, as well as capturing
64
+ # the URI from the request annotations, but this may include PII.
65
+ user_context.delete(:ip)
66
+
67
+ user_context.transform_keys { |key| "user.#{key}" }.merge(extra_context)
68
+ end
69
+
70
+ def local_log_location
71
+ # Should give us the last local stack which called the log within just the last couple frames.
72
+ last_local_location = caller[0..15].find { |path| path.include?(Rails.root.to_s) }
73
+
74
+ return {} unless last_local_location
75
+
76
+ { 'log_location' => last_local_location }
77
+ end
78
+
79
+ # We may need to clean this up a bit.
80
+ def service_name
81
+ $PROGRAM_NAME
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module ScoutApm
6
+ module Logging
7
+ module Loggers
8
+ class FileLogger < ::Logger
9
+ end
10
+
11
+ # The newly created logger which we can configure, and will log to a filepath.
12
+ class Logger
13
+ attr_reader :context, :log_instance
14
+
15
+ # 1 MiB
16
+ LOG_SIZE = 1024 * 1024
17
+ # 1 log file
18
+ LOG_AGE = 1
19
+
20
+ def initialize(context, log_instance)
21
+ @context = context
22
+ @log_instance = log_instance
23
+ end
24
+
25
+ def create_logger!
26
+ # Defaults are 7 files with 10 MiB.
27
+ # We create the file in order to prevent a creation header log.
28
+ File.new(determine_file_path, 'w+') unless File.exist?(determine_file_path)
29
+ new_logger = FileLogger.new(determine_file_path, LOG_AGE, LOG_SIZE)
30
+ # Ruby's Logger handles a lot of the coercion itself.
31
+ new_logger.level = context.config.value('logs_capture_level')
32
+ new_logger
33
+ end
34
+
35
+ def determine_file_path # rubocop:disable Metrics/AbcSize
36
+ log_directory = context.config.value('logs_proxy_log_dir')
37
+
38
+ original_basename = File.basename(log_destination) if log_destination.is_a?(String)
39
+
40
+ file_basename = if original_basename
41
+ original_basename
42
+ elsif defined?(::ActiveSupport::Logger) && log_instance.is_a?(::ActiveSupport::Logger)
43
+ 'rails.log'
44
+ elsif defined?(::ActiveSupport::BroadcastLogger) && log_instance.is_a?(::ActiveSupport::BroadcastLogger)
45
+ 'rails.log'
46
+ elsif defined?(::Sidekiq::Logger) && log_instance.is_a?(::Sidekiq::Logger)
47
+ 'sidekiq.log'
48
+ elsif defined?(::ScoutTestLogger) && log_instance.is_a?(::ScoutTestLogger)
49
+ 'test.log'
50
+ else
51
+ 'mix.log'
52
+ end
53
+
54
+ File.join(log_directory, file_basename)
55
+ end
56
+
57
+ private
58
+
59
+ def find_log_destination(logdev)
60
+ dev = try(logdev, :filename) || try(logdev, :dev)
61
+ if dev.is_a?(String)
62
+ dev
63
+ elsif dev.respond_to?(:path)
64
+ dev.path
65
+ elsif dev.respond_to?(:filename) || dev.respond_to?(:dev)
66
+ find_log_destination(dev)
67
+ else
68
+ dev
69
+ end
70
+ end
71
+
72
+ def log_destination
73
+ @log_destination ||= find_log_destination(log_instance.instance_variable_get(:@logdev))
74
+ end
75
+
76
+ def try(obj, method)
77
+ obj.respond_to?(method) ? obj.send(method) : nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module ScoutApm
6
+ module Logging
7
+ module Loggers
8
+ # Holds both the original application logger and the new one. Relays commands to both.
9
+ class Proxy
10
+ def initialize
11
+ @loggers = []
12
+ end
13
+
14
+ def add(logger)
15
+ @loggers << logger
16
+ end
17
+
18
+ def remove(logger)
19
+ @loggers.reject! { |inst_log| inst_log == logger }
20
+
21
+ @loggers
22
+ end
23
+
24
+ def method_missing(name, *args, &block)
25
+ # Some libraries will do stuff like Library.logger.formatter = Rails.logger.formatter
26
+ # As such, we should return the first logger's (the original logger) return value.
27
+ return_value = @loggers.first.send(name, *args, &block)
28
+ @loggers[1..].each { |logger| logger.send(name, *args, &block) }
29
+
30
+ return_value
31
+ end
32
+
33
+ def respond_to_missing?(name, *args)
34
+ @loggers.first.respond_to?(name, *args) || super
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative './formatter'
6
+ require_relative './logger'
7
+
8
+ module ScoutApm
9
+ module Logging
10
+ module Loggers
11
+ # Swaps in our logger for the application's logger.
12
+ class Swap
13
+ attr_reader :context, :log_instance, :new_file_logger
14
+
15
+ def initialize(context, log_instance)
16
+ @context = context
17
+ @log_instance = log_instance
18
+ end
19
+
20
+ def update_logger!
21
+ create_proxy_log_dir!
22
+
23
+ # In Rails 7.1, broadcast logger was added which allows sinking to multiple IO devices.
24
+ if defined?(::ActiveSupport::BroadcastLogger) && log_instance.is_a?(::ActiveSupport::BroadcastLogger)
25
+ add_logger_to_broadcast!
26
+ else
27
+ swap_in_proxy_logger!
28
+ end
29
+ end
30
+
31
+ def log_location
32
+ new_file_logger.instance_variable_get(:@logdev).filename
33
+ end
34
+
35
+ private
36
+
37
+ def add_logger_to_broadcast!
38
+ @new_file_logger = create_file_logger
39
+ @new_file_logger.formatter = Loggers::Formatter.new
40
+
41
+ log_instance.broadcast_to(new_file_logger)
42
+ end
43
+
44
+ def swap_in_proxy_logger! # rubocop:disable Metrics/AbcSize
45
+ proxy_logger = Proxy.new
46
+ # We can use the previous logdev. log_device will continuously call write
47
+ # through the devices until the logdev (@dev) is an IO device other than logdev:
48
+ # https://github.com/ruby/ruby/blob/master/lib/logger/log_device.rb#L42
49
+ # Log device holds the configurations around shifting too.
50
+ original_logdevice = log_instance.instance_variable_get(:@logdev)
51
+ updated_original_logger = ::Logger.new(original_logdevice)
52
+ updated_original_logger.formatter = log_instance.formatter
53
+
54
+ @new_file_logger = create_file_logger
55
+ @new_file_logger.formatter = Loggers::Formatter.new
56
+
57
+ # First logger needs to be the original logger for the return value of relayed calls.
58
+ proxy_logger.add(updated_original_logger)
59
+ proxy_logger.add(new_file_logger)
60
+
61
+ if defined?(::ActiveSupport::Logger) && log_instance.is_a?(::ActiveSupport::Logger)
62
+ Rails.logger = proxy_logger
63
+ elsif defined?(::Sidekiq::Logger) && log_instance.is_a?(::Sidekiq::Logger)
64
+ Sidekiq.configure_server do |config|
65
+ config.logger = proxy_logger
66
+ end
67
+ elsif defined?(::ScoutTestLogger) && log_instance.is_a?(::ScoutTestLogger)
68
+ TestLoggerWrapper.logger = proxy_logger
69
+ end
70
+ end
71
+
72
+ def create_file_logger
73
+ Loggers::Logger.new(context, log_instance).create_logger!
74
+ end
75
+
76
+ def create_proxy_log_dir!
77
+ Utils.ensure_directory_exists(context.config.value('logs_proxy_log_dir'))
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ module Collector
6
+ # Contains logic around verifying the checksum of the otelcol-contrib binary.
7
+ class Checksum
8
+ attr_reader :context
9
+
10
+ KNOWN_CHECKSUMS = {
11
+ 'darwin_amd64' => '5456734e124221e7ff775c52bd3693d05b3fac43ebe06b22aa5f220f1962ed8c',
12
+ 'darwin_arm64' => 'f9564560798ac5c099885903f303fcda97b7ea649ec299e075b72f3805873879',
13
+ 'linux_amd64' => '326772622016f7ff7e966a7ae8a0f439dc49a3d80b6d79a82b62608af447e851',
14
+ 'linux_arm64' => '73d797817540363a37f27e32270f98053ed17b1df36df2d30db1715ce40f4cff'
15
+ }.freeze
16
+
17
+ def initialize(context)
18
+ @context = context
19
+ end
20
+
21
+ def verified_checksum?(should_log_failures: false)
22
+ return false unless File.exist?(collector_tar_path)
23
+
24
+ checksum = `sha256sum #{collector_tar_path}`.split(' ').first
25
+ same_checksum_result = checksum == KNOWN_CHECKSUMS[double]
26
+
27
+ log_failed_checksum if !same_checksum_result && should_log_failures
28
+ same_checksum_result
29
+ end
30
+
31
+ def log_failed_checksum
32
+ if KNOWN_CHECKSUMS.key?(double)
33
+ context.logger.error('Checksum verification failed for otelcol.tar.gz.')
34
+ else
35
+ context.logger.error("Checksum verification failed for otelcol.tar.gz. Unknown architecture: #{double}")
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def double
42
+ "#{Utils.get_host_os}_#{Utils.get_architecture}"
43
+ end
44
+
45
+ def collector_tar_path
46
+ "#{context.config.value('collector_download_dir')}/otelcol.tar.gz"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ module Collector
6
+ # Adds a method to Hash similar to that of the Rails deep_merge.
7
+ module HashDeepMerge
8
+ refine Hash do
9
+ def deep_merge(second)
10
+ merger = proc { |_, v1, v2|
11
+ if v1.is_a?(Hash) && v2.is_a?(Hash)
12
+ v1.merge(v2, &merger)
13
+ elsif v1.is_a?(Array) && v2.is_a?(Array)
14
+ v1 | v2
15
+ else
16
+ [:undefined, nil, :nil].include?(v2) ? v1 : v2
17
+ end
18
+ }
19
+ merge(second.to_h, &merger)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Creates the configuration to be used when launching the collector.
25
+ class Configuration
26
+ using HashDeepMerge
27
+
28
+ attr_reader :context
29
+
30
+ def initialize(context)
31
+ @context = context
32
+ end
33
+
34
+ def setup!
35
+ create_storage_directories
36
+
37
+ create_config_file
38
+ end
39
+
40
+ def create_config_file
41
+ contents = YAML.dump(combined_contents)
42
+ File.write(config_file, contents)
43
+ end
44
+
45
+ private
46
+
47
+ def create_storage_directories
48
+ # Sending queue storage directory
49
+ Utils.ensure_directory_exists(context.config.value('collector_sending_queue_storage_dir'))
50
+ # Offset storage directory
51
+ Utils.ensure_directory_exists(context.config.value('collector_offset_storage_dir'))
52
+ end
53
+
54
+ def combined_contents
55
+ default_contents = YAML.safe_load(config_contents)
56
+
57
+ default_contents.deep_merge(loaded_config_contents)
58
+ end
59
+
60
+ def loaded_config_contents
61
+ config_path = context.config.value('logs_config')
62
+
63
+ if config_path && File.exist?(config_path)
64
+ YAML.load_file(config_path) || {}
65
+ elsif File.exist?(assumed_config_file_path)
66
+ YAML.load_file(assumed_config_file_path) || {}
67
+ else
68
+ {}
69
+ end
70
+ end
71
+
72
+ def config_file
73
+ context.config.value('collector_config_file')
74
+ end
75
+
76
+ def config_contents # rubocop:disable Metrics/AbcSize
77
+ <<~CONFIG
78
+ receivers:
79
+ filelog:
80
+ include: [#{context.config.value('logs_monitored').join(',')}]
81
+ storage: file_storage/filelogreceiver
82
+ operators:
83
+ - type: json_parser
84
+ severity:
85
+ parse_from: attributes.severity
86
+ timestamp:
87
+ parse_from: attributes.time
88
+ layout: "%Y-%m-%dT%H:%M:%S.%LZ"
89
+ processors:
90
+ transform:
91
+ log_statements:
92
+ - context: log
93
+ statements:
94
+ # Copy original body to raw_bytes attribute.
95
+ - 'set(attributes["raw_bytes"], body)'
96
+ # Replace the body with the log message.
97
+ - 'set(body, attributes["msg"])'
98
+ # Move service.name attribute to resource attribute.
99
+ - 'set(resource.attributes["service.name"], attributes["service.name"])'
100
+ batch:
101
+ exporters:
102
+ otlp:
103
+ endpoint: #{context.config.value('logs_reporting_endpoint')}
104
+ headers:
105
+ x-telemetryhub-key: #{context.config.value('logs_ingest_key')}
106
+ sending_queue:
107
+ storage: file_storage/otc
108
+ extensions:
109
+ health_check:
110
+ endpoint: #{health_check_endpoint}
111
+ file_storage/filelogreceiver:
112
+ directory: #{context.config.value('collector_offset_storage_dir')}
113
+ file_storage/otc:
114
+ directory: #{context.config.value('collector_sending_queue_storage_dir')}
115
+ timeout: 10s
116
+ service:
117
+ extensions:
118
+ - health_check
119
+ - file_storage/filelogreceiver
120
+ - file_storage/otc
121
+ pipelines:
122
+ logs:
123
+ receivers:
124
+ - filelog
125
+ processors:
126
+ - transform
127
+ - batch
128
+ exporters:
129
+ - otlp
130
+ telemetry:
131
+ metrics:
132
+ level: none
133
+ logs:
134
+ level: #{context.config.value('collector_log_level')}
135
+ CONFIG
136
+ end
137
+
138
+ def health_check_endpoint
139
+ "localhost:#{context.config.value('health_check_port')}"
140
+ end
141
+
142
+ def assumed_config_file_path
143
+ "#{context.application_root}/config/scout_logs_config.yml"
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ module Collector
6
+ # Downloads the collector-contrib binary from the OpenTelemetry project.
7
+ class Downloader
8
+ attr_accessor :failed_count
9
+ attr_reader :context, :checksum
10
+
11
+ def initialize(context)
12
+ @context = context
13
+ @checksum = Checksum.new(context)
14
+ end
15
+
16
+ def download!
17
+ # Already downloaded the collector. Noop.
18
+ return if checksum.verified_checksum?
19
+
20
+ # Account for issues such as failed extractions or download corruptions.
21
+ download_collector
22
+ verify_checksum
23
+ rescue StandardError => e
24
+ # Bypass Rubcop useless asignment rule.
25
+ failed_count ||= 0
26
+
27
+ if failed_count < 3
28
+ context.logger.error("Failed to download or extract otelcol-contrib: #{e}. Retrying...")
29
+ failed_count += 1
30
+ retry
31
+ end
32
+ end
33
+
34
+ def download_collector(url = nil, redirect: false) # rubocop:disable Metrics/AbcSize
35
+ # Prevent double logging.
36
+ unless redirect
37
+ context.logger.debug("Downloading otelcol-contrib for version #{context.config.value('collector_version')}")
38
+ end
39
+
40
+ url_to_download = url || collector_url
41
+ uri = URI(url_to_download)
42
+
43
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
44
+ request = Net::HTTP::Get.new(uri)
45
+ http.request(request) do |response|
46
+ return download_collector(response['location'], redirect: true) if response.code == '302'
47
+
48
+ File.open(destination, 'wb') do |file|
49
+ response.read_body do |chunk|
50
+ file.write(chunk)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def verify_checksum
60
+ raise 'Invalid checksum on download.' unless checksum.verified_checksum?
61
+ end
62
+
63
+ def collector_url
64
+ collector_version = context.config.value('collector_version')
65
+ architecture = Utils.get_architecture
66
+ host_os = Utils.get_host_os
67
+
68
+ # https://opentelemetry.io/docs/collector/installation/#manual-linux-installation
69
+ "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v#{collector_version}/otelcol-contrib_#{collector_version}_#{host_os}_#{architecture}.tar.gz"
70
+ end
71
+
72
+ def destination
73
+ "#{context.config.value('collector_download_dir')}/otelcol.tar.gz"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ module Collector
6
+ # Extracts the contents of the collector tar file.
7
+ class Extractor
8
+ attr_reader :context
9
+
10
+ def initialize(context)
11
+ @context = context
12
+ end
13
+
14
+ def extract!
15
+ # Already extracted. Noop.
16
+ return if has_been_extracted?
17
+
18
+ system("tar -xzf #{tar_path} -C #{context.config.value('collector_download_dir')}")
19
+ end
20
+
21
+ def has_been_extracted?
22
+ File.exist?(binary_path)
23
+ end
24
+
25
+ private
26
+
27
+ def tar_path
28
+ "#{context.config.value('collector_download_dir')}/otelcol.tar.gz"
29
+ end
30
+
31
+ def binary_path
32
+ "#{context.config.value('collector_download_dir')}/otelcol-contrib"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './checksum'
4
+ require_relative './configuration'
5
+ require_relative './downloader'
6
+ require_relative './extractor'
7
+
8
+ module ScoutApm
9
+ module Logging
10
+ module Collector
11
+ # Manager class for the downloading, configuring, and starting of the collector.
12
+ class Manager
13
+ attr_reader :context
14
+
15
+ def initialize(context)
16
+ @context = context
17
+
18
+ @checksum = Checksum.new(@context)
19
+ @configuration = Configuration.new(@context)
20
+ @downloader = Downloader.new(@context)
21
+ @extractor = Extractor.new(@context)
22
+ end
23
+
24
+ def setup!
25
+ @configuration.setup!
26
+ @downloader.download!
27
+ @extractor.extract!
28
+
29
+ start_collector if verified_checksum_and_extracted?
30
+ end
31
+
32
+ def start_collector
33
+ context.logger.info('Starting otelcol-contrib')
34
+ collector_process = Process.spawn("#{extracted_collector_path}/otelcol-contrib --config #{config_file}")
35
+ File.write(context.config.value('collector_pid_file'), collector_process)
36
+ end
37
+
38
+ private
39
+
40
+ def verified_checksum_and_extracted?
41
+ has_verfied_checksum = @checksum.verified_checksum?(should_log_failures: true)
42
+ has_extracted_content = @extractor.has_been_extracted?
43
+
44
+ has_verfied_checksum && has_extracted_content
45
+ end
46
+
47
+ def extracted_collector_path
48
+ context.config.value('collector_download_dir')
49
+ end
50
+
51
+ def config_file
52
+ context.config.value('collector_config_file')
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end