scout_apm_logging 0.0.12 → 1.0.0

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -4
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +8 -0
  5. data/NOTICE +4 -0
  6. data/lib/scout_apm/logging/config.rb +5 -131
  7. data/lib/scout_apm/logging/loggers/capture.rb +8 -6
  8. data/lib/scout_apm/logging/loggers/formatter.rb +21 -2
  9. data/lib/scout_apm/logging/loggers/opentelemetry/LICENSE +201 -0
  10. data/lib/scout_apm/logging/loggers/opentelemetry/NOTICE +9 -0
  11. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/log_record.rb +18 -0
  12. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger.rb +64 -0
  13. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger_provider.rb +31 -0
  14. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/severity_number.rb +43 -0
  15. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/version.rb +18 -0
  16. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs.rb +28 -0
  17. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +389 -0
  18. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/version.rb +20 -0
  19. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/collector/logs/v1/logs_service_pb.rb +43 -0
  20. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/common/v1/common_pb.rb +58 -0
  21. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/logs/v1/logs_pb.rb +91 -0
  22. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/resource/v1/resource_pb.rb +33 -0
  23. data/lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb +62 -0
  24. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +225 -0
  25. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb +64 -0
  26. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export.rb +34 -0
  27. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +115 -0
  28. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +31 -0
  29. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +53 -0
  30. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +94 -0
  31. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +158 -0
  32. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +20 -0
  33. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +28 -0
  34. data/lib/scout_apm/logging/loggers/patches/rails_logger.rb +17 -0
  35. data/lib/scout_apm/logging/loggers/proxy.rb +22 -5
  36. data/lib/scout_apm/logging/loggers/swaps/rails.rb +4 -12
  37. data/lib/scout_apm/logging/loggers/swaps/scout.rb +2 -10
  38. data/lib/scout_apm/logging/loggers/swaps/sidekiq.rb +2 -6
  39. data/lib/scout_apm/logging/utils.rb +0 -69
  40. data/lib/scout_apm/logging/version.rb +1 -1
  41. data/lib/scout_apm_logging.rb +3 -12
  42. data/scout_apm_logging.gemspec +7 -0
  43. data/spec/data/config_test_1.yml +0 -1
  44. data/spec/data/mock_config.yml +0 -3
  45. data/spec/integration/rails/lifecycle_spec.rb +57 -23
  46. data/spec/spec_helper.rb +0 -12
  47. data/spec/unit/config_spec.rb +0 -12
  48. data/spec/unit/loggers/capture_spec.rb +0 -6
  49. metadata +127 -39
  50. data/bin/scout_apm_logging_monitor +0 -6
  51. data/lib/scout_apm/logging/monitor/_rails.rb +0 -22
  52. data/lib/scout_apm/logging/monitor/collector/checksum.rb +0 -51
  53. data/lib/scout_apm/logging/monitor/collector/configuration.rb +0 -150
  54. data/lib/scout_apm/logging/monitor/collector/downloader.rb +0 -78
  55. data/lib/scout_apm/logging/monitor/collector/extractor.rb +0 -37
  56. data/lib/scout_apm/logging/monitor/collector/manager.rb +0 -57
  57. data/lib/scout_apm/logging/monitor/monitor.rb +0 -216
  58. data/lib/scout_apm/logging/monitor_manager/manager.rb +0 -162
  59. data/lib/scout_apm/logging/state.rb +0 -69
  60. data/spec/data/empty_logs_config.yml +0 -0
  61. data/spec/data/logs_config.yml +0 -3
  62. data/spec/data/state_file.json +0 -3
  63. data/spec/integration/loggers/capture_spec.rb +0 -68
  64. data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +0 -49
  65. data/spec/integration/monitor/collector_healthcheck_spec.rb +0 -29
  66. data/spec/integration/monitor/continuous_state_collector_spec.rb +0 -31
  67. data/spec/integration/monitor/previous_collector_setup_spec.rb +0 -45
  68. data/spec/integration/monitor_manager/disable_agent_spec.rb +0 -30
  69. data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +0 -38
  70. data/spec/integration/monitor_manager/single_monitor_spec.rb +0 -53
  71. data/spec/unit/monitor/collector/configuration_spec.rb +0 -64
  72. data/spec/unit/state_spec.rb +0 -20
  73. data/tooling/checksums.rb +0 -106
@@ -1,78 +0,0 @@
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
@@ -1,37 +0,0 @@
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
@@ -1,57 +0,0 @@
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
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ##
4
- # Launched as a daemon process by the monitor manager at Rails startup.
5
- ##
6
- require 'json'
7
-
8
- require 'scout_apm'
9
-
10
- require_relative '../logger'
11
- require_relative '../context'
12
- require_relative '../config'
13
- require_relative '../utils'
14
- require_relative '../state'
15
- require_relative './collector/manager'
16
-
17
- module ScoutApm
18
- module Logging
19
- # Entry point for the monitor daemon process.
20
- class Monitor
21
- attr_reader :context
22
- attr_accessor :latest_state_sha
23
-
24
- @@instance = nil
25
-
26
- def self.instance
27
- @@instance ||= new
28
- end
29
-
30
- def initialize
31
- @context = Context.new
32
- context.logger.debug('Monitor instance created')
33
-
34
- context.application_root = $stdin.gets&.chomp
35
-
36
- # Load in the dynamic and state based config settings.
37
- context.config = Config.with_file(context, determine_scout_config_filepath)
38
-
39
- daemonize_process!
40
- end
41
-
42
- def setup!
43
- context.config.logger.info('Monitor daemon process started')
44
-
45
- add_exit_handler!
46
-
47
- unless has_logs_to_monitor?
48
- context.config.logger.warn('No logs are set to be monitored. Please set the `logs_monitored` config setting. Exiting.')
49
- return
50
- end
51
-
52
- initiate_collector_setup! unless has_previous_collector_setup?
53
-
54
- @latest_state_sha = get_state_file_sha
55
-
56
- run!
57
- end
58
-
59
- def run!
60
- # Prevent the monitor from checking the collector health before it's fully started.
61
- # Having this be configurable is useful for testing.
62
- sleep context.config.value('monitor_interval_delay')
63
-
64
- loop do
65
- sleep context.config.value('monitor_interval')
66
-
67
- check_collector_health
68
-
69
- check_state_change
70
- end
71
- end
72
-
73
- # Only useful for testing.
74
- def config=(config)
75
- context.config = config
76
- end
77
-
78
- private
79
-
80
- def daemonize_process!
81
- # Similar to that of Process.daemon, but we want to keep the dir, STDOUT and STDERR.
82
- exit if fork
83
- Process.setsid
84
- exit if fork
85
- $stdin.reopen '/dev/null'
86
-
87
- context.logger.debug("Monitor process daemonized, PID: #{Process.pid}")
88
- File.write(context.config.value('monitor_pid_file'), Process.pid)
89
- end
90
-
91
- def has_logs_to_monitor?
92
- context.config.value('logs_monitored').any?
93
- end
94
-
95
- def has_previous_collector_setup?
96
- return false unless context.config.value('health_check_port') != 0
97
-
98
- healthy_response = request_health_check_port("http://localhost:#{context.config.value('health_check_port')}/")
99
-
100
- if healthy_response
101
- context.logger.info("Collector already setup on port #{context.config.value('health_check_port')}")
102
- else
103
- context.logger.info('Setting up new collector')
104
- end
105
-
106
- healthy_response
107
- end
108
-
109
- def initiate_collector_setup!
110
- set_health_check_port!
111
-
112
- Collector::Manager.new(context).setup!
113
- end
114
-
115
- def is_port_available?(port)
116
- socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
117
- remote_address = Socket.sockaddr_in(port, '127.0.0.1')
118
-
119
- begin
120
- socket.connect_nonblock(remote_address)
121
- rescue Errno::EINPROGRESS
122
- IO.select(nil, [socket])
123
- retry
124
- rescue Errno::EISCONN, Errno::ECONNRESET
125
- false
126
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
127
- true
128
- ensure
129
- socket.close if socket && !socket.closed?
130
- end
131
- end
132
-
133
- def set_health_check_port!
134
- health_check_port = 13_133
135
- until is_port_available?(health_check_port)
136
- sleep 0.1
137
- health_check_port += 1
138
- end
139
-
140
- Config::ConfigDynamic.set_value('health_check_port', health_check_port)
141
- context.config.state.flush_state!
142
- end
143
-
144
- def request_health_check_port(endpoint)
145
- uri = URI(endpoint)
146
-
147
- begin
148
- response = Net::HTTP.get_response(uri)
149
-
150
- unless response.is_a?(Net::HTTPSuccess)
151
- context.logger.error("Error occurred while checking collector health: #{response.message}")
152
- return false
153
- end
154
- rescue StandardError => e
155
- context.logger.error("Error occurred while checking collector health: #{e.message}")
156
- return false
157
- end
158
-
159
- true
160
- end
161
-
162
- def check_collector_health
163
- context.logger.debug('Checking collector health')
164
- collector_health_endpoint = "http://localhost:#{context.config.value('health_check_port')}/"
165
-
166
- healthy_response = request_health_check_port(collector_health_endpoint)
167
-
168
- initiate_collector_setup! unless healthy_response
169
- end
170
-
171
- def remove_collector_process # rubocop:disable Metrics/AbcSize
172
- return unless File.exist? context.config.value('collector_pid_file')
173
-
174
- process_id = File.read(context.config.value('collector_pid_file'))
175
- return if process_id.empty?
176
-
177
- begin
178
- Process.kill('TERM', process_id.to_i)
179
- rescue Errno::ENOENT, Errno::ESRCH => e
180
- context.logger.error("Error occurred while removing collector process from monitor: #{e.message}")
181
- ensure
182
- File.delete(context.config.value('collector_pid_file'))
183
- end
184
- end
185
-
186
- def check_state_change
187
- current_sha = get_state_file_sha
188
-
189
- return if current_sha == latest_state_sha
190
-
191
- remove_collector_process
192
- initiate_collector_setup!
193
-
194
- # File SHA can change due to port mappings on collector setup.
195
- @latest_state_sha = get_state_file_sha
196
- end
197
-
198
- def add_exit_handler!
199
- at_exit do
200
- # There may not be a file to delete, as the monitor manager ensures cleaning it up when monitoring is disabled.
201
- File.delete(context.config.value('monitor_pid_file')) if File.exist?(context.config.value('monitor_pid_file'))
202
- end
203
- end
204
-
205
- def get_state_file_sha
206
- return nil unless File.exist?(context.config.value('monitor_state_file'))
207
-
208
- `sha256sum #{context.config.value('monitor_state_file')}`.split(' ').first
209
- end
210
-
211
- def determine_scout_config_filepath
212
- "#{context.application_root}/config/scout_apm.yml"
213
- end
214
- end
215
- end
216
- end
@@ -1,162 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ScoutApm
4
- module Logging
5
- # Manages the creation of the daemon monitor process.
6
- class MonitorManager
7
- attr_reader :context
8
-
9
- @@instance = nil
10
-
11
- def self.instance
12
- @@instance ||= new
13
- end
14
-
15
- def initialize
16
- @context = Context.new
17
- context.config = Config.with_file(context, context.config.value('config_file'))
18
- end
19
-
20
- def setup!
21
- context.config.log_settings(context.logger)
22
- context.logger.info('Setting up monitor daemon process')
23
-
24
- add_exit_handler!
25
-
26
- determine_configuration_state
27
- end
28
-
29
- def determine_configuration_state
30
- monitoring_enabled = context.config.value('logs_monitor')
31
-
32
- if monitoring_enabled
33
- context.logger.info('Log monitoring enabled')
34
- create_process
35
-
36
- # Continue to hold the lock until we have written the PID file.
37
- ensure_monitor_pid_file_exists
38
- else
39
- context.logger.info('Log monitoring disabled')
40
- remove_processes
41
- end
42
- end
43
-
44
- # With the use of fileoffsets in the collector, and the persistent queue of already collected logs,
45
- # we can safely restart the collector. Due to the way fingerprinting of the files works, if the
46
- # file path switches, but the beginning contents of the file remain the same, the file will be
47
- # treated as the same file as before.
48
- # If logs get rotated, the fingerprint changes, and the collector automatically detects this.
49
- def add_exit_handler!
50
- # With the use of unicorn and puma worker killer, we want to ensure we only restart (exit and
51
- # eventually start) the monitor and collector when the main process exits, and not the workers.
52
- initialized_process_id = Process.pid
53
- at_exit do
54
- # Only remove/restart the monitor and collector if we are exiting from an app_server process.
55
- # We need to wait on this check, as the process command line changes at some point.
56
- if Utils.current_process_is_app_server? && Process.pid == initialized_process_id
57
- context.logger.debug('Exiting from app server process. Removing monitor and collector processes.')
58
- remove_processes
59
- end
60
- end
61
- end
62
-
63
- def create_process
64
- return if process_exists?
65
-
66
- Utils.ensure_directory_exists(context.config.value('monitor_pid_file'))
67
-
68
- reader, writer = IO.pipe
69
-
70
- gem_directory = File.expand_path('../../../..', __dir__)
71
-
72
- # As we daemonize the process, we will write to the pid file within the process.
73
- pid = Process.spawn("ruby #{gem_directory}/bin/scout_apm_logging_monitor", in: reader)
74
-
75
- reader.close
76
- # TODO: Add support for Sinatra.
77
- writer.puts Rails.root if defined?(Rails)
78
- writer.close
79
- # Block until we have spawned the process and forked. This is to ensure
80
- # we keep the exclusive lock until the process has written the PID file.
81
- Process.wait(pid)
82
- end
83
-
84
- private
85
-
86
- def ensure_monitor_pid_file_exists
87
- start_time = Time.now
88
- # We don't want to hold up the initial Rails boot time for very long.
89
- timeout_seconds = 0.1
90
-
91
- # Naive benchmarks show this taking ~0.01 seconds.
92
- loop do
93
- if File.exist?(context.config.value('monitor_pid_file'))
94
- context.logger.debug('Monitor PID file exists. Releasing lock.')
95
- break
96
- end
97
-
98
- if Time.now - start_time > timeout_seconds
99
- context.logger.warn('Unable to verify monitor PID file write. Releasing lock.')
100
- break
101
- end
102
-
103
- sleep 0.01
104
- end
105
- end
106
-
107
- def process_exists?
108
- return false unless File.exist? context.config.value('monitor_pid_file')
109
-
110
- process_id = File.read(context.config.value('monitor_pid_file'))
111
- return false if process_id.empty?
112
-
113
- process_exists = Utils.check_process_liveliness(process_id.to_i, 'scout_apm_logging_monitor')
114
- File.delete(context.config.value('monitor_pid_file')) unless process_exists
115
-
116
- process_exists
117
- end
118
-
119
- def remove_monitor_process # rubocop:disable Metrics/AbcSize
120
- return unless File.exist? context.config.value('monitor_pid_file')
121
-
122
- process_id = File.read(context.config.value('monitor_pid_file'))
123
- return if process_id.empty?
124
-
125
- begin
126
- Process.kill('TERM', process_id.to_i)
127
- rescue Errno::ENOENT, Errno::ESRCH => e
128
- context.logger.error("Error occurred while removing monitor process: #{e.message}")
129
- File.delete(context.config.value('monitor_pid_file'))
130
- end
131
- end
132
-
133
- def remove_collector_process # rubocop:disable Metrics/AbcSize
134
- return unless File.exist? context.config.value('collector_pid_file')
135
-
136
- process_id = File.read(context.config.value('collector_pid_file'))
137
- return if process_id.empty?
138
-
139
- begin
140
- Process.kill('TERM', process_id.to_i)
141
- rescue Errno::ENOENT, Errno::ESRCH => e
142
- context.logger.error("Error occurred while removing collector process from manager: #{e.message}")
143
- ensure
144
- File.delete(context.config.value('collector_pid_file'))
145
- end
146
- end
147
-
148
- def remove_data_file
149
- return unless File.exist? context.config.value('monitor_state_file')
150
-
151
- File.delete(context.config.value('monitor_state_file'))
152
- end
153
-
154
- # Remove both the monitor and collector processes that we have spawned.
155
- def remove_processes
156
- remove_monitor_process
157
- remove_collector_process
158
- remove_data_file
159
- end
160
- end
161
- end
162
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ScoutApm
4
- module Logging
5
- class Config
6
- # Responsibling for ensuring safe interprocess persitance around configuration state.
7
- class State
8
- attr_reader :context
9
-
10
- def initialize(context)
11
- @context = context
12
- end
13
-
14
- def load_state_from_file
15
- return unless File.exist?(context.config.value('monitor_state_file'))
16
-
17
- file_contents = File.read(context.config.value('monitor_state_file'))
18
- JSON.parse(file_contents)
19
- end
20
-
21
- def flush_to_file!(updated_log_locations = []) # rubocop:disable Metrics/AbcSize
22
- Utils.ensure_directory_exists(context.config.value('monitor_state_file'))
23
-
24
- File.open(context.config.value('monitor_state_file'), (File::RDWR | File::CREAT), 0o644) do |file|
25
- file.flock(File::LOCK_EX)
26
-
27
- data = Config::ConfigState.get_values_to_set.each_with_object({}) do |key, memo|
28
- memo[key] = context.config.value(key)
29
- end
30
-
31
- contents = file.read
32
- old_log_state_files = if contents.empty?
33
- []
34
- else
35
- current_data = JSON.parse(contents)
36
- current_data['logs_monitored']
37
- end
38
-
39
- data['logs_monitored'] =
40
- merge_and_dedup_log_locations(updated_log_locations, old_log_state_files, data['logs_monitored'])
41
-
42
- file.rewind # Move cursor to beginning of the file
43
- file.truncate(0) # Truncate existing content
44
- file.write(JSON.pretty_generate(data))
45
- rescue StandardError => e
46
- context.logger.error("Error occurred while flushing state to file: #{e.message}. Unlocking.")
47
- ensure
48
- file.flock(File::LOCK_UN)
49
- end
50
- end
51
-
52
- private
53
-
54
- # Should we add better detection for similar basenames but different paths?
55
- # May be a bit tricky with tools like capistrano and releases paths differentiated by time.
56
- def merge_and_dedup_log_locations(*log_locations)
57
- # Take the new logs if duplication (those first passed in the args) as we could be in a newer release.
58
- logs = log_locations.reduce([], :concat)
59
- merged = logs.each_with_object({}) do |log_path, hash|
60
- base_name = File.basename(log_path)
61
- hash[base_name] ||= log_path
62
- end
63
-
64
- merged.values
65
- end
66
- end
67
- end
68
- end
69
- end
File without changes
@@ -1,3 +0,0 @@
1
- extensions:
2
- file_storage/otc:
3
- directory: /dev/null
@@ -1,3 +0,0 @@
1
- {
2
- "health_check_port": 1234
3
- }