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,214 @@
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
+
33
+ context.application_root = $stdin.gets&.chomp
34
+
35
+ # Load in the dynamic and state based config settings.
36
+ context.config = Config.with_file(context, determine_scout_config_filepath)
37
+
38
+ daemonize_process!
39
+ end
40
+
41
+ def setup!
42
+ context.config.logger.info('Monitor daemon process started')
43
+
44
+ add_exit_handler!
45
+
46
+ unless has_logs_to_monitor?
47
+ context.config.logger.warn('No logs are set to be monitored. Please set the `logs_monitored` config setting. Exiting.')
48
+ return
49
+ end
50
+
51
+ initiate_collector_setup! unless has_previous_collector_setup?
52
+
53
+ @latest_state_sha = get_state_file_sha
54
+
55
+ run!
56
+ end
57
+
58
+ def run!
59
+ # Prevent the monitor from checking the collector health before it's fully started.
60
+ # Having this be configurable is useful for testing.
61
+ sleep context.config.value('monitor_interval_delay')
62
+
63
+ loop do
64
+ sleep context.config.value('monitor_interval')
65
+
66
+ check_collector_health
67
+
68
+ check_state_change
69
+ end
70
+ end
71
+
72
+ # Only useful for testing.
73
+ def config=(config)
74
+ context.config = config
75
+ end
76
+
77
+ private
78
+
79
+ def daemonize_process!
80
+ # Similar to that of Process.daemon, but we want to keep the dir, STDOUT and STDERR.
81
+ exit if fork
82
+ Process.setsid
83
+ exit if fork
84
+ $stdin.reopen '/dev/null'
85
+
86
+ File.write(context.config.value('monitor_pid_file'), Process.pid)
87
+ end
88
+
89
+ def has_logs_to_monitor?
90
+ context.config.value('logs_monitored').any?
91
+ end
92
+
93
+ def has_previous_collector_setup?
94
+ return false unless context.config.value('health_check_port') != 0
95
+
96
+ healthy_response = request_health_check_port("http://localhost:#{context.config.value('health_check_port')}/")
97
+
98
+ if healthy_response
99
+ context.logger.info("Collector already setup on port #{context.config.value('health_check_port')}")
100
+ else
101
+ context.logger.info('Setting up new collector')
102
+ end
103
+
104
+ healthy_response
105
+ end
106
+
107
+ def initiate_collector_setup!
108
+ set_health_check_port!
109
+
110
+ Collector::Manager.new(context).setup!
111
+ end
112
+
113
+ def is_port_available?(port)
114
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
115
+ remote_address = Socket.sockaddr_in(port, '127.0.0.1')
116
+
117
+ begin
118
+ socket.connect_nonblock(remote_address)
119
+ rescue Errno::EINPROGRESS
120
+ IO.select(nil, [socket])
121
+ retry
122
+ rescue Errno::EISCONN, Errno::ECONNRESET
123
+ false
124
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
125
+ true
126
+ ensure
127
+ socket.close if socket && !socket.closed?
128
+ end
129
+ end
130
+
131
+ def set_health_check_port!
132
+ health_check_port = 13_133
133
+ until is_port_available?(health_check_port)
134
+ sleep 0.1
135
+ health_check_port += 1
136
+ end
137
+
138
+ Config::ConfigDynamic.set_value('health_check_port', health_check_port)
139
+ context.config.state.flush_state!
140
+ end
141
+
142
+ def request_health_check_port(endpoint)
143
+ uri = URI(endpoint)
144
+
145
+ begin
146
+ response = Net::HTTP.get_response(uri)
147
+
148
+ unless response.is_a?(Net::HTTPSuccess)
149
+ context.logger.error("Error occurred while checking collector health: #{response.message}")
150
+ return false
151
+ end
152
+ rescue StandardError => e
153
+ context.logger.error("Error occurred while checking collector health: #{e.message}")
154
+ return false
155
+ end
156
+
157
+ true
158
+ end
159
+
160
+ def check_collector_health
161
+ context.logger.debug('Checking collector health')
162
+ collector_health_endpoint = "http://localhost:#{context.config.value('health_check_port')}/"
163
+
164
+ healthy_response = request_health_check_port(collector_health_endpoint)
165
+
166
+ initiate_collector_setup! unless healthy_response
167
+ end
168
+
169
+ def remove_collector_process # rubocop:disable Metrics/AbcSize
170
+ return unless File.exist? context.config.value('collector_pid_file')
171
+
172
+ process_id = File.read(context.config.value('collector_pid_file'))
173
+ return if process_id.empty?
174
+
175
+ begin
176
+ Process.kill('TERM', process_id.to_i)
177
+ rescue Errno::ENOENT, Errno::ESRCH => e
178
+ context.logger.error("Error occurred while removing collector process from monitor: #{e.message}")
179
+ ensure
180
+ File.delete(context.config.value('collector_pid_file'))
181
+ end
182
+ end
183
+
184
+ def check_state_change
185
+ current_sha = get_state_file_sha
186
+
187
+ return if current_sha == latest_state_sha
188
+
189
+ remove_collector_process
190
+ initiate_collector_setup!
191
+
192
+ # File SHA can change due to port mappings on collector setup.
193
+ @latest_state_sha = get_state_file_sha
194
+ end
195
+
196
+ def add_exit_handler!
197
+ at_exit do
198
+ # There may not be a file to delete, as the monitor manager ensures cleaning it up when monitoring is disabled.
199
+ File.delete(context.config.value('monitor_pid_file')) if File.exist?(context.config.value('monitor_pid_file'))
200
+ end
201
+ end
202
+
203
+ def get_state_file_sha
204
+ return nil unless File.exist?(context.config.value('monitor_state_file'))
205
+
206
+ `sha256sum #{context.config.value('monitor_state_file')}`.split(' ').first
207
+ end
208
+
209
+ def determine_scout_config_filepath
210
+ "#{context.application_root}/config/scout_apm.yml"
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,150 @@
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
+
28
+ # Continue to hold the lock until we have written the PID file.
29
+ ensure_monitor_pid_file_exists
30
+ end
31
+
32
+ def determine_configuration_state
33
+ monitoring_enabled = context.config.value('logs_monitor')
34
+
35
+ if monitoring_enabled
36
+ context.logger.info('Log monitoring enabled')
37
+ create_process
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
+ at_exit do
51
+ # Only remove/restart the monitor and collector if we are exiting from an app_server process.
52
+ # We need to wait on this check, as the process command line changes at some point.
53
+ remove_processes if Utils.current_process_is_app_server?
54
+ end
55
+ end
56
+
57
+ def create_process
58
+ return if process_exists?
59
+
60
+ Utils.ensure_directory_exists(context.config.value('monitor_pid_file'))
61
+
62
+ reader, writer = IO.pipe
63
+
64
+ gem_directory = File.expand_path('../../../..', __dir__)
65
+
66
+ # As we daemonize the process, we will write to the pid file within the process.
67
+ Process.spawn("ruby #{gem_directory}/bin/scout_apm_logging_monitor", in: reader)
68
+
69
+ reader.close
70
+ # TODO: Add support for Sinatra.
71
+ writer.puts Rails.root if defined?(Rails)
72
+ writer.close
73
+ end
74
+
75
+ private
76
+
77
+ def ensure_monitor_pid_file_exists
78
+ start_time = Time.now
79
+ # We don't want to hold up the initial Rails boot time for very long.
80
+ timeout_seconds = 0.1
81
+
82
+ # Naive benchmarks show this taking ~0.01 seconds.
83
+ loop do
84
+ break if File.exist?(context.config.value('monitor_pid_file'))
85
+
86
+ if Time.now - start_time > timeout_seconds
87
+ context.logger.warn('Unable to verify monitor PID file write. Releasing lock.')
88
+ break
89
+ end
90
+
91
+ sleep 0.01
92
+ end
93
+ end
94
+
95
+ def process_exists?
96
+ return false unless File.exist? context.config.value('monitor_pid_file')
97
+
98
+ process_id = File.read(context.config.value('monitor_pid_file'))
99
+ return false if process_id.empty?
100
+
101
+ process_exists = Utils.check_process_liveliness(process_id.to_i, 'scout_apm_logging_monitor')
102
+ File.delete(context.config.value('monitor_pid_file')) unless process_exists
103
+
104
+ process_exists
105
+ end
106
+
107
+ def remove_monitor_process # rubocop:disable Metrics/AbcSize
108
+ return unless File.exist? context.config.value('monitor_pid_file')
109
+
110
+ process_id = File.read(context.config.value('monitor_pid_file'))
111
+ return if process_id.empty?
112
+
113
+ begin
114
+ Process.kill('TERM', process_id.to_i)
115
+ rescue Errno::ENOENT, Errno::ESRCH => e
116
+ context.logger.error("Error occurred while removing monitor process: #{e.message}")
117
+ File.delete(context.config.value('monitor_pid_file'))
118
+ end
119
+ end
120
+
121
+ def remove_collector_process # rubocop:disable Metrics/AbcSize
122
+ return unless File.exist? context.config.value('collector_pid_file')
123
+
124
+ process_id = File.read(context.config.value('collector_pid_file'))
125
+ return if process_id.empty?
126
+
127
+ begin
128
+ Process.kill('TERM', process_id.to_i)
129
+ rescue Errno::ENOENT, Errno::ESRCH => e
130
+ context.logger.error("Error occurred while removing collector process from manager: #{e.message}")
131
+ ensure
132
+ File.delete(context.config.value('collector_pid_file'))
133
+ end
134
+ end
135
+
136
+ def remove_data_file
137
+ return unless File.exist? context.config.value('monitor_state_file')
138
+
139
+ File.delete(context.config.value('monitor_state_file'))
140
+ end
141
+
142
+ # Remove both the monitor and collector processes that we have spawned.
143
+ def remove_processes
144
+ remove_monitor_process
145
+ remove_collector_process
146
+ remove_data_file
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,70 @@
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
+ unless updated_log_locations.empty?
32
+ contents = file.read
33
+
34
+ olds_log_files = if contents.empty?
35
+ []
36
+ else
37
+ current_data = JSON.parse(contents)
38
+ current_data['logs_monitored']
39
+ end
40
+
41
+ data['logs_monitored'] = merge_and_dedup_log_locations(updated_log_locations, olds_log_files)
42
+ end
43
+
44
+ file.rewind # Move cursor to beginning of the file
45
+ file.truncate(0) # Truncate existing content
46
+ file.write(JSON.pretty_generate(data))
47
+ rescue StandardError => e
48
+ context.logger.error("Error occurred while flushing state to file: #{e.message}. Unlocking.")
49
+ ensure
50
+ file.flock(File::LOCK_UN)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # Should we add better detection for similar basenames but different paths?
57
+ # May be a bit tricky with tools like capistrano and releases paths differentiated by time.
58
+ def merge_and_dedup_log_locations(new_logs, old_logs)
59
+ # Take the new logs if duplication, as we could be in a newer release.
60
+ merged = (new_logs + old_logs).each_with_object({}) do |log_path, hash|
61
+ base_name = File.basename(log_path)
62
+ hash[base_name] ||= log_path
63
+ end
64
+
65
+ merged.values
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module ScoutApm
6
+ module Logging
7
+ # Miscellaneous utilities for the logging module.
8
+ module Utils
9
+ # Takes a complete file path, and ensures that the directory structure exists.
10
+ def self.ensure_directory_exists(file_path)
11
+ file_path = File.dirname(file_path) unless file_path[-1] == '/'
12
+
13
+ FileUtils.mkdir_p(file_path) unless File.directory?(file_path)
14
+ end
15
+
16
+ # TODO: Add support for other platforms
17
+ def self.get_architecture
18
+ if /arm/ =~ RbConfig::CONFIG['arch']
19
+ 'arm64'
20
+ else
21
+ 'amd64'
22
+ end
23
+ end
24
+
25
+ def self.get_host_os
26
+ if /darwin|mac os/ =~ RbConfig::CONFIG['host_os']
27
+ 'darwin'
28
+ else
29
+ 'linux'
30
+ end
31
+ end
32
+
33
+ def self.check_process_liveliness(pid, name)
34
+ # Pipe to cat to prevent truncation of the output
35
+ process_information = `ps -p #{pid} -o pid=,stat=,command= | cat`
36
+ return false if process_information.empty?
37
+
38
+ process_information_parts = process_information.split(' ')
39
+ process_information_status = process_information_parts[1]
40
+
41
+ return false if process_information_status == 'Z'
42
+ return false unless process_information.include?(name)
43
+
44
+ true
45
+ end
46
+
47
+ def self.current_process_is_app_server?
48
+ # TODO: Add more app servers.
49
+ process_command = `ps -p #{Process.pid} -o command= | cat`.downcase
50
+ [
51
+ process_command.include?('puma'),
52
+ process_command.include?('unicorn'),
53
+ process_command.include?('passenger')
54
+ ].any?
55
+ end
56
+
57
+ def self.skip_setup?
58
+ [
59
+ ARGV.include?('assets:precompile'),
60
+ ARGV.include?('assets:clean'),
61
+ (defined?(::Rails::Console) && $stdout.isatty && $stdin.isatty)
62
+ ].any?
63
+ end
64
+
65
+ def self.attempt_exclusive_lock(context)
66
+ lock_file = context.config.value('manager_lock_file')
67
+ ensure_directory_exists(lock_file)
68
+
69
+ begin
70
+ file = File.open(lock_file, File::RDWR | File::CREAT | File::EXCL)
71
+ rescue Errno::EEXIST
72
+ context.logger.info('Manager lock file held, continuing.')
73
+ return
74
+ end
75
+
76
+ # Ensure the lock file is deleted when the block completes
77
+ begin
78
+ yield
79
+ ensure
80
+ file.close
81
+ File.delete(lock_file) if File.exist?(lock_file)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ VERSION = '0.0.0.1'
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'scout_apm'
4
+
5
+ require 'scout_apm/logging/config'
6
+ require 'scout_apm/logging/logger'
7
+ require 'scout_apm/logging/context'
8
+ require 'scout_apm/logging/utils'
9
+ require 'scout_apm/logging/state'
10
+
11
+ require 'scout_apm/logging/loggers/capture'
12
+
13
+ require 'scout_apm/logging/monitor_manager/manager'
14
+
15
+ module ScoutApm
16
+ ## This module is responsible for setting up monitoring of the application's logs.
17
+ module Logging
18
+ if defined?(Rails) && defined?(Rails::Railtie)
19
+ # If we are in a Rails environment, setup the monitor daemon manager.
20
+ class RailTie < ::Rails::Railtie
21
+ initializer 'scout_apm_logging.monitor' do
22
+ context = ScoutApm::Logging::MonitorManager.instance.context
23
+
24
+ Loggers::Capture.new(context).capture_log_locations!
25
+
26
+ unless Utils.skip_setup?
27
+ Utils.attempt_exclusive_lock(context) do
28
+ ScoutApm::Logging::MonitorManager.instance.setup!
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'scout_apm/logging/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'scout_apm_logging'
8
+ s.version = ScoutApm::Logging::VERSION
9
+ s.authors = 'Scout APM'
10
+ s.email = ['support@scoutapp.com']
11
+ s.homepage = 'https://github.com/scoutapp/scout_apm_ruby_logging'
12
+ s.summary = 'Ruby Logging Support'
13
+ s.description = 'Sets up log monitoring for Scout APM Ruby clients.'
14
+ s.license = 'MIT'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.require_paths = ['lib']
19
+
20
+ s.required_ruby_version = '>= 2.6'
21
+
22
+ s.add_dependency 'scout_apm'
23
+
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'rubocop', '1.50.2'
26
+ s.add_development_dependency 'rubocop-ast', '1.30.0'
27
+ end
@@ -0,0 +1,27 @@
1
+ common: &defaults
2
+ name: Scout APM
3
+ key: 000011110000
4
+ log_level: debug
5
+ monitor: true
6
+
7
+ ###
8
+ # Logging
9
+ ###
10
+ logs_monitor: true
11
+ logs_ingest_key: "00001000010000abc"
12
+ logs_monitored: ["/tmp/fake_log_file.log"]
13
+
14
+ production:
15
+ <<: *defaults
16
+ name: APM Test Conf (Production)
17
+
18
+ development:
19
+ <<: *defaults
20
+ name: APM Test Conf (Development)
21
+ host: http://localhost:3000
22
+ monitor: true
23
+
24
+ test:
25
+ <<: *defaults
26
+ name: APM Test Conf (Test)
27
+ monitor: false
File without changes
@@ -0,0 +1,3 @@
1
+ extensions:
2
+ file_storage/otc:
3
+ directory: /dev/null
@@ -0,0 +1,29 @@
1
+ common: &defaults
2
+ name: Scout APM
3
+ key: 000011110000
4
+ log_level: debug
5
+ monitor: true
6
+
7
+ ###
8
+ # Logging
9
+ ###
10
+ logs_monitor: true
11
+ logs_ingest_key: "00001000010000abc"
12
+ logs_monitored: ["/tmp/fake_log_file.log"]
13
+ # Need to give a high enough number for the original health check to pass
14
+ monitor_interval: 10
15
+
16
+ production:
17
+ <<: *defaults
18
+ name: APM Test Conf (Production)
19
+
20
+ development:
21
+ <<: *defaults
22
+ name: APM Test Conf (Development)
23
+ host: http://localhost:3000
24
+ monitor: true
25
+
26
+ test:
27
+ <<: *defaults
28
+ name: APM Test Conf (Test)
29
+ monitor: false
@@ -0,0 +1,3 @@
1
+ {
2
+ "health_check_port": 1234
3
+ }