scout_apm_logging 0.0.2 → 0.0.4
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/scout_apm/logging/config.rb +3 -0
- data/lib/scout_apm/logging/loggers/capture.rb +31 -7
- data/lib/scout_apm/logging/loggers/formatter.rb +8 -9
- data/lib/scout_apm/logging/loggers/logger.rb +8 -9
- data/lib/scout_apm/logging/loggers/patches/tagged_logging.rb +93 -0
- data/lib/scout_apm/logging/loggers/proxy.rb +2 -3
- data/lib/scout_apm/logging/loggers/swaps/rails.rb +6 -0
- data/lib/scout_apm/logging/monitor/monitor.rb +2 -0
- data/lib/scout_apm/logging/monitor_manager/manager.rb +8 -2
- data/lib/scout_apm/logging/state.rb +13 -14
- data/lib/scout_apm/logging/version.rb +1 -1
- data/lib/scout_apm_logging.rb +1 -1
- data/spec/integration/loggers/capture_spec.rb +1 -1
- data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +2 -0
- data/spec/integration/monitor/collector_healthcheck_spec.rb +2 -0
- data/spec/integration/monitor/continuous_state_collector_spec.rb +2 -0
- data/spec/integration/monitor/previous_collector_setup_spec.rb +3 -0
- data/spec/integration/monitor_manager/disable_agent_spec.rb +2 -0
- data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +2 -0
- data/spec/integration/rails/lifecycle_spec.rb +37 -3
- data/spec/rails/app.rb +39 -0
- data/spec/spec_helper.rb +1 -24
- data/spec/unit/loggers/capture_spec.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a99a84cd9f8dd023b8717c81e5d80b17c99f5afb8c7e7edb6152193ef7fecdf
|
4
|
+
data.tar.gz: b9d2e44b40adcb5f9acc13f6ef6d2176e79ed6b056f6e6d1e8f9f0359857ebf6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f73329ada6fde049cf1ffe18ad2d43c0fa09158cffcc10e51c58f897d9cd40f1aa2636ee645e2b26d7a36204e082ca318c5281e8e32759aaac2b685a4ff5f32
|
7
|
+
data.tar.gz: 664471524c4802ad23cfeee6299a56259d07e7a9a9d2fb241393202b4116946052f695982ada22c3060a39fca25f351ce6a90487e329283bc264749e6ee56738
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
## 0.0.4
|
2
|
+
* Fix memoizing of log attributes, which could lead to persistent attributes.
|
3
|
+
|
4
|
+
## 0.0.3
|
5
|
+
* **Feature**: Add support for TaggedLogging.
|
6
|
+
* Add ability to customize file logger size. Increase default size to 10MiB.
|
7
|
+
* Fix an issue with removing of the monitor process when Rails workers exited, which was only intended for when the main process exits.
|
8
|
+
* Fix an issue with the known monitored logs state being removed on port flushing.
|
@@ -54,6 +54,7 @@ module ScoutApm
|
|
54
54
|
logs_config
|
55
55
|
logs_reporting_endpoint
|
56
56
|
logs_proxy_log_dir
|
57
|
+
logs_log_file_size
|
57
58
|
manager_lock_file
|
58
59
|
monitor_pid_file
|
59
60
|
monitor_state_file
|
@@ -72,6 +73,7 @@ module ScoutApm
|
|
72
73
|
SETTING_COERCIONS = {
|
73
74
|
'logs_monitor' => BooleanCoercion.new,
|
74
75
|
'logs_monitored' => JsonCoercion.new,
|
76
|
+
'logs_log_file_size' => IntegerCoercion.new,
|
75
77
|
'monitor_interval' => IntegerCoercion.new,
|
76
78
|
'monitor_interval_delay' => IntegerCoercion.new,
|
77
79
|
'health_check_port' => IntegerCoercion.new
|
@@ -229,6 +231,7 @@ module ScoutApm
|
|
229
231
|
'logs_capture_level' => 'debug',
|
230
232
|
'logs_reporting_endpoint' => 'https://otlp.scoutotel.com:4317',
|
231
233
|
'logs_proxy_log_dir' => '/tmp/scout_apm/logs/',
|
234
|
+
'logs_log_file_size' => 1024 * 1024 * 10,
|
232
235
|
'manager_lock_file' => '/tmp/scout_apm/monitor_lock_file.lock',
|
233
236
|
'monitor_pid_file' => '/tmp/scout_apm/scout_apm_log_monitor.pid',
|
234
237
|
'monitor_state_file' => '/tmp/scout_apm/scout_apm_log_monitor_state.json',
|
@@ -8,6 +8,7 @@ require_relative './proxy'
|
|
8
8
|
require_relative './swaps/rails'
|
9
9
|
require_relative './swaps/sidekiq'
|
10
10
|
require_relative './swaps/scout'
|
11
|
+
require_relative './patches/tagged_logging'
|
11
12
|
|
12
13
|
module ScoutApm
|
13
14
|
module Logging
|
@@ -26,11 +27,40 @@ module ScoutApm
|
|
26
27
|
@context = context
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
+
def setup!
|
30
31
|
return unless context.config.value('logs_monitor')
|
31
32
|
|
32
33
|
create_proxy_log_dir!
|
33
34
|
|
35
|
+
add_logging_patches!
|
36
|
+
capture_and_swap_log_locations!
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def create_proxy_log_dir!
|
42
|
+
Utils.ensure_directory_exists(context.config.value('logs_proxy_log_dir'))
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_logging_patches!
|
46
|
+
# We can't swap out the logger similar to that of Rails and Sidekiq, as
|
47
|
+
# the TaggedLogging logger is dynamically generated.
|
48
|
+
return unless Rails.logger.respond_to?(:tagged)
|
49
|
+
|
50
|
+
::ActiveSupport::TaggedLogging.prepend(Patches::TaggedLogging)
|
51
|
+
|
52
|
+
# Re-extend TaggedLogging to verify the patch is be applied.
|
53
|
+
# This appears to be an issue in Ruby 2.7 with the broadcast logger.
|
54
|
+
ruby_version = Gem::Version.new(RUBY_VERSION)
|
55
|
+
isruby27 = (ruby_version >= Gem::Version.new('2.7') && ruby_version < Gem::Version.new('3.0'))
|
56
|
+
return unless isruby27 && Rails.logger.respond_to?(:broadcasts)
|
57
|
+
|
58
|
+
Rails.logger.broadcasts.each do |logger|
|
59
|
+
logger.extend ::ActiveSupport::TaggedLogging
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def capture_and_swap_log_locations!
|
34
64
|
# We can move this to filter_map when our lagging version is Ruby 2.7
|
35
65
|
updated_log_locations = KNOWN_LOGGERS.map do |logger|
|
36
66
|
logger.new(context).update_logger! if logger.present?
|
@@ -39,12 +69,6 @@ module ScoutApm
|
|
39
69
|
|
40
70
|
context.config.state.add_log_locations!(updated_log_locations)
|
41
71
|
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def create_proxy_log_dir!
|
46
|
-
Utils.ensure_directory_exists(context.config.value('logs_proxy_log_dir'))
|
47
|
-
end
|
48
72
|
end
|
49
73
|
end
|
50
74
|
end
|
@@ -12,12 +12,15 @@ module ScoutApm
|
|
12
12
|
class Formatter < ::Logger::Formatter
|
13
13
|
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%LZ'
|
14
14
|
|
15
|
-
def call(severity, time, progname, msg)
|
16
|
-
attributes_to_log
|
17
|
-
|
15
|
+
def call(severity, time, progname, msg)
|
16
|
+
attributes_to_log = {
|
17
|
+
severity: severity,
|
18
|
+
time: format_datetime(time),
|
19
|
+
pid: Process.pid.to_s,
|
20
|
+
msg: msg2str(msg)
|
21
|
+
}
|
22
|
+
|
18
23
|
attributes_to_log[:progname] = progname if progname
|
19
|
-
attributes_to_log[:pid] = Process.pid.to_s
|
20
|
-
attributes_to_log[:msg] = msg2str(msg)
|
21
24
|
attributes_to_log['service.name'] = service_name
|
22
25
|
|
23
26
|
attributes_to_log.merge!(scout_layer)
|
@@ -30,10 +33,6 @@ module ScoutApm
|
|
30
33
|
|
31
34
|
private
|
32
35
|
|
33
|
-
def attributes_to_log
|
34
|
-
@attributes_to_log ||= {}
|
35
|
-
end
|
36
|
-
|
37
36
|
def format_datetime(time)
|
38
37
|
time.utc.strftime(DATETIME_FORMAT)
|
39
38
|
end
|
@@ -10,8 +10,6 @@ module ScoutApm
|
|
10
10
|
class Logger
|
11
11
|
attr_reader :context, :log_instance
|
12
12
|
|
13
|
-
# 1 MiB
|
14
|
-
LOG_SIZE = 1024 * 1024
|
15
13
|
# 1 log file
|
16
14
|
LOG_AGE = 1
|
17
15
|
|
@@ -21,15 +19,16 @@ module ScoutApm
|
|
21
19
|
end
|
22
20
|
|
23
21
|
def create_logger!
|
24
|
-
# Defaults are 7 files with 10 MiB.
|
25
22
|
# We create the file in order to prevent a creation header log.
|
26
23
|
File.new(determine_file_path, 'w+') unless File.exist?(determine_file_path)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
log_size = context.config.value('logs_log_file_size')
|
25
|
+
|
26
|
+
FileLogger.new(determine_file_path, LOG_AGE, log_size).tap do |logger|
|
27
|
+
# Ruby's Logger handles a lot of the coercion itself.
|
28
|
+
logger.level = context.config.value('logs_capture_level')
|
29
|
+
# Add our custom formatter to the logger.
|
30
|
+
logger.formatter = Formatter.new
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
34
|
def determine_file_path # rubocop:disable Metrics/AbcSize
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../logger'
|
4
|
+
require_relative '../formatter'
|
5
|
+
require_relative '../proxy'
|
6
|
+
|
7
|
+
module ScoutApm
|
8
|
+
module Logging
|
9
|
+
module Loggers
|
10
|
+
module Patches
|
11
|
+
# Patches TaggedLogging to work with our loggers.
|
12
|
+
module TaggedLogging
|
13
|
+
def tagged(*tags) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
14
|
+
super(*tags) unless (self == ::Rails.logger && is_a?(ScoutApm::Logging::Loggers::Proxy)) ||
|
15
|
+
(::Rails.logger.respond_to?(:broadcasts) && ::Rails.logger.broadcasts.include?(self))
|
16
|
+
|
17
|
+
if is_a?(ScoutApm::Logging::Loggers::Proxy)
|
18
|
+
if block_given?
|
19
|
+
# We skip the first logger to prevent double tagging when calling formatter.tagged
|
20
|
+
loggers = @loggers[1..]
|
21
|
+
pushed_counts = extend_and_push_tags(loggers, *tags)
|
22
|
+
|
23
|
+
formatter.tagged(*tags) { yield self }.tap do
|
24
|
+
logger_pop_tags(loggers, pushed_counts)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
loggers = instance_variable_get(:@loggers)
|
28
|
+
|
29
|
+
new_loggers = create_cloned_extended_loggers(loggers, nil, *tags)
|
30
|
+
|
31
|
+
self.clone.tap do |cp| # rubocop:disable Style/RedundantSelf
|
32
|
+
cp.instance_variable_set(:@loggers, new_loggers)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
elsif block_given?
|
36
|
+
# We skip the first logger to prevent double tagging when calling formatter.tagged
|
37
|
+
loggers = ::Rails.logger.broadcasts[1..]
|
38
|
+
pushed_counts = extend_and_push_tags(loggers, *tags)
|
39
|
+
|
40
|
+
formatter.tagged(*tags) { yield self }.tap do
|
41
|
+
logger_pop_tags(loggers, pushed_counts)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
broadcasts = ::Rails.logger.broadcasts
|
45
|
+
|
46
|
+
tagged_loggers = broadcasts.select { |logger| logger.respond_to?(:tagged) }
|
47
|
+
file_logger = broadcasts.find { |logger| logger.is_a?(Loggers::FileLogger) }
|
48
|
+
loggers = tagged_loggers << file_logger
|
49
|
+
|
50
|
+
current_tags = tagged_loggers.first.formatter.current_tags
|
51
|
+
|
52
|
+
new_loggers = create_cloned_extended_loggers(loggers, current_tags, *tags)
|
53
|
+
Proxy.create_with_loggers(*new_loggers)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_cloned_extended_loggers(loggers, current_tags = nil, *tags)
|
58
|
+
loggers.map do |logger|
|
59
|
+
logger_current_tags = if current_tags
|
60
|
+
current_tags
|
61
|
+
elsif logger.formatter.respond_to?(:current_tags)
|
62
|
+
logger.formatter.current_tags
|
63
|
+
else
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
|
67
|
+
::ActiveSupport::TaggedLogging.new(logger).tap do |new_logger|
|
68
|
+
if defined?(::ActiveSupport::TaggedLogging::LocalTagStorage)
|
69
|
+
new_logger.formatter.extend ::ActiveSupport::TaggedLogging::LocalTagStorage
|
70
|
+
end
|
71
|
+
new_logger.push_tags(*logger_current_tags, *tags)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def extend_and_push_tags(loggers, *tags)
|
77
|
+
loggers.map do |logger|
|
78
|
+
logger.formatter.extend ::ActiveSupport::TaggedLogging::Formatter unless logger.formatter.respond_to?(:tagged)
|
79
|
+
|
80
|
+
logger.formatter.push_tags(tags).size
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def logger_pop_tags(loggers, pushed_counts)
|
85
|
+
loggers.map.with_index do |logger, index|
|
86
|
+
logger.formatter.pop_tags(pushed_counts[index])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -5,10 +5,9 @@ module ScoutApm
|
|
5
5
|
module Loggers
|
6
6
|
# Holds both the original application logger and the new one. Relays commands to both.
|
7
7
|
class Proxy
|
8
|
-
def self.create_with_loggers(
|
8
|
+
def self.create_with_loggers(*loggers)
|
9
9
|
new.tap do |proxy_logger|
|
10
|
-
proxy_logger.add(
|
11
|
-
proxy_logger.add(new_file_logger)
|
10
|
+
loggers.each { |logger| proxy_logger.add(logger) }
|
12
11
|
end
|
13
12
|
end
|
14
13
|
|
@@ -61,8 +61,14 @@ module ScoutApm
|
|
61
61
|
def swap_in_proxy_logger!
|
62
62
|
# First logger needs to be the original logger for the return value of relayed calls.
|
63
63
|
proxy_logger = Proxy.create_with_loggers(original_logger, new_file_logger)
|
64
|
+
proxy_logger.extend ::ActiveSupport::TaggedLogging if log_instance.respond_to?(:tagged)
|
64
65
|
|
65
66
|
::Rails.logger = proxy_logger
|
67
|
+
|
68
|
+
# We also need to swap some of the Rails railtie loggers.
|
69
|
+
::ActiveRecord::Base.logger = proxy_logger if defined?(::ActiveRecord::Base)
|
70
|
+
::ActionController::Base.logger = proxy_logger if defined?(::ActionController::Base)
|
71
|
+
::ActiveJob::Base.logger = proxy_logger if defined?(::ActiveJob::Base)
|
66
72
|
end
|
67
73
|
end
|
68
74
|
end
|
@@ -29,6 +29,7 @@ module ScoutApm
|
|
29
29
|
|
30
30
|
def initialize
|
31
31
|
@context = Context.new
|
32
|
+
context.logger.debug('Monitor instance created')
|
32
33
|
|
33
34
|
context.application_root = $stdin.gets&.chomp
|
34
35
|
|
@@ -83,6 +84,7 @@ module ScoutApm
|
|
83
84
|
exit if fork
|
84
85
|
$stdin.reopen '/dev/null'
|
85
86
|
|
87
|
+
context.logger.debug("Monitor process daemonized, PID: #{Process.pid}")
|
86
88
|
File.write(context.config.value('monitor_pid_file'), Process.pid)
|
87
89
|
end
|
88
90
|
|
@@ -47,10 +47,13 @@ module ScoutApm
|
|
47
47
|
# treated as the same file as before.
|
48
48
|
# If logs get rotated, the fingerprint changes, and the collector automatically detects this.
|
49
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
|
50
53
|
at_exit do
|
51
54
|
# Only remove/restart the monitor and collector if we are exiting from an app_server process.
|
52
55
|
# We need to wait on this check, as the process command line changes at some point.
|
53
|
-
if Utils.current_process_is_app_server?
|
56
|
+
if Utils.current_process_is_app_server? && Process.pid == initialized_process_id
|
54
57
|
context.logger.debug('Exiting from app server process. Removing monitor and collector processes.')
|
55
58
|
remove_processes
|
56
59
|
end
|
@@ -87,7 +90,10 @@ module ScoutApm
|
|
87
90
|
|
88
91
|
# Naive benchmarks show this taking ~0.01 seconds.
|
89
92
|
loop do
|
90
|
-
|
93
|
+
if File.exist?(context.config.value('monitor_pid_file'))
|
94
|
+
context.logger.debug('Monitor PID file exists. Releasing lock.')
|
95
|
+
break
|
96
|
+
end
|
91
97
|
|
92
98
|
if Time.now - start_time > timeout_seconds
|
93
99
|
context.logger.warn('Unable to verify monitor PID file write. Releasing lock.')
|
@@ -28,18 +28,16 @@ module ScoutApm
|
|
28
28
|
memo[key] = context.config.value(key)
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
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
|
33
38
|
|
34
|
-
|
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
|
39
|
+
data['logs_monitored'] =
|
40
|
+
merge_and_dedup_log_locations(updated_log_locations, old_log_state_files, data['logs_monitored'])
|
43
41
|
|
44
42
|
file.rewind # Move cursor to beginning of the file
|
45
43
|
file.truncate(0) # Truncate existing content
|
@@ -55,9 +53,10 @@ module ScoutApm
|
|
55
53
|
|
56
54
|
# Should we add better detection for similar basenames but different paths?
|
57
55
|
# May be a bit tricky with tools like capistrano and releases paths differentiated by time.
|
58
|
-
def merge_and_dedup_log_locations(
|
59
|
-
# Take the new logs if duplication
|
60
|
-
|
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|
|
61
60
|
base_name = File.basename(log_path)
|
62
61
|
hash[base_name] ||= log_path
|
63
62
|
end
|
data/lib/scout_apm_logging.rb
CHANGED
@@ -21,7 +21,7 @@ module ScoutApm
|
|
21
21
|
initializer 'scout_apm_logging.monitor' do
|
22
22
|
context = ScoutApm::Logging::MonitorManager.instance.context
|
23
23
|
|
24
|
-
Loggers::Capture.new(context).
|
24
|
+
Loggers::Capture.new(context).setup!
|
25
25
|
|
26
26
|
unless Utils.skip_setup?
|
27
27
|
Utils.attempt_exclusive_lock(context) do
|
@@ -62,7 +62,7 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
62
62
|
def similuate_railtie
|
63
63
|
context = ScoutApm::Logging::MonitorManager.instance.context
|
64
64
|
|
65
|
-
ScoutApm::Logging::Loggers::Capture.new(context).
|
65
|
+
ScoutApm::Logging::Loggers::Capture.new(context).setup!
|
66
66
|
ScoutApm::Logging::MonitorManager.new.setup!
|
67
67
|
end
|
68
68
|
end
|
@@ -12,7 +12,9 @@ describe ScoutApm::Logging::Collector::Downloader do
|
|
12
12
|
|
13
13
|
File.write(otelcol_contrib_path, 'fake content')
|
14
14
|
|
15
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time start: #{Time.now}"
|
15
16
|
ScoutApm::Logging::MonitorManager.instance.setup!
|
17
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time after setup: #{Time.now}"
|
16
18
|
|
17
19
|
# Give the process time to initialize, download the collector, and start it
|
18
20
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
@@ -11,7 +11,9 @@ describe ScoutApm::Logging::Monitor do
|
|
11
11
|
|
12
12
|
ScoutApm::Logging::Utils.ensure_directory_exists('/tmp/scout_apm/scout_apm_log_monitor.pid')
|
13
13
|
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time start: #{Time.now}"
|
14
15
|
ScoutApm::Logging::MonitorManager.instance.setup!
|
16
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time after setup: #{Time.now}"
|
15
17
|
|
16
18
|
# Give the process time to initialize, download the collector, and start it
|
17
19
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
@@ -11,7 +11,9 @@ describe ScoutApm::Logging::Monitor do
|
|
11
11
|
|
12
12
|
context = ScoutApm::Logging::MonitorManager.instance.context
|
13
13
|
collector_pid_location = context.config.value('collector_pid_file')
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time start: #{Time.now}"
|
14
15
|
ScoutApm::Logging::MonitorManager.instance.setup!
|
16
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time after setup: #{Time.now}"
|
15
17
|
# Give the process time to initialize, download the collector, and start it
|
16
18
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
17
19
|
|
@@ -11,7 +11,10 @@ describe ScoutApm::Logging::Monitor do
|
|
11
11
|
collector_pid_location = ScoutApm::Logging::MonitorManager.instance.context.config.value('collector_pid_file')
|
12
12
|
ScoutApm::Logging::Utils.ensure_directory_exists(monitor_pid_location)
|
13
13
|
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time start: #{Time.now}"
|
14
15
|
ScoutApm::Logging::MonitorManager.instance.setup!
|
16
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time after setup: #{Time.now}"
|
17
|
+
|
15
18
|
# Give the process time to initialize, download the collector, and start it
|
16
19
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
17
20
|
|
@@ -9,7 +9,9 @@ describe ScoutApm::Logging::Collector::Manager do
|
|
9
9
|
|
10
10
|
expect(`pgrep otelcol-contrib --runstates D,R,S`).to be_empty
|
11
11
|
|
12
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time start: #{Time.now}"
|
12
13
|
ScoutApm::Logging::MonitorManager.instance.setup!
|
14
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time after setup: #{Time.now}"
|
13
15
|
|
14
16
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
15
17
|
|
@@ -15,7 +15,9 @@ describe ScoutApm::Logging::Collector::Manager do
|
|
15
15
|
file.write('123456')
|
16
16
|
end
|
17
17
|
|
18
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time start: #{Time.now}"
|
18
19
|
ScoutApm::Logging::MonitorManager.instance.setup!
|
20
|
+
ScoutApm::Logging::MonitorManager.instance.context.logger.info "Time after setup: #{Time.now}"
|
19
21
|
|
20
22
|
# Give the process time to initialize, download the collector, and start it
|
21
23
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
@@ -1,14 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require_relative '../../rails/app'
|
2
3
|
|
3
4
|
describe ScoutApm::Logging do
|
4
5
|
it 'checks the Rails lifecycle for creating the daemon and collector processes' do
|
5
6
|
ENV['SCOUT_LOGS_MONITOR'] = 'true'
|
6
|
-
ENV['SCOUT_LOGS_MONITORED'] = '["/tmp/test.log"]'
|
7
7
|
|
8
|
-
|
8
|
+
context = ScoutApm::Logging::MonitorManager.instance.context
|
9
|
+
pid_file = context.config.value('monitor_pid_file')
|
9
10
|
expect(File.exist?(pid_file)).to be_falsey
|
10
11
|
|
11
|
-
|
12
|
+
rails_pid = fork do
|
13
|
+
initialize_app
|
14
|
+
end
|
12
15
|
|
13
16
|
wait_for_process_with_timeout!('otelcol-contrib', 20)
|
14
17
|
|
@@ -21,6 +24,37 @@ describe ScoutApm::Logging do
|
|
21
24
|
# Check if the process with the stored PID is running
|
22
25
|
expect(ScoutApm::Logging::Utils.check_process_liveliness(pid, 'scout_apm_logging_monitor')).to be_truthy
|
23
26
|
|
27
|
+
# Call the app to generate the logs
|
28
|
+
`curl localhost:8080`
|
29
|
+
|
30
|
+
proxy_dir = context.config.value('logs_proxy_log_dir')
|
31
|
+
files = Dir.entries(proxy_dir) - ['.', '..']
|
32
|
+
log_file = File.join(proxy_dir, files[0])
|
33
|
+
|
34
|
+
lines = []
|
35
|
+
File.open(log_file, 'r') do |file|
|
36
|
+
file.each_line do |line|
|
37
|
+
# Parse each line as JSON
|
38
|
+
lines << JSON.parse(line)
|
39
|
+
rescue JSON::ParserError => e
|
40
|
+
puts e
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
messages = lines.map { |item| item['msg'] }
|
45
|
+
|
46
|
+
# Verify we have all the logs
|
47
|
+
expect(messages.count('[TEST] Some log')).to eq(1)
|
48
|
+
expect(messages.count('[YIELD] Yield Test')).to eq(1)
|
49
|
+
expect(messages.count('Another Log')).to eq(1)
|
50
|
+
|
51
|
+
log_locations = lines.map { |item| item['log_location'] }.compact
|
52
|
+
|
53
|
+
# Verify that log attributes aren't persisted
|
54
|
+
expect(log_locations.size).to eq(1)
|
55
|
+
|
56
|
+
# Kill the rails process. We use kill as using any other signal throws a long log line.
|
57
|
+
Process.kill('KILL', rails_pid)
|
24
58
|
# Kill the process and ensure PID file clean up
|
25
59
|
Process.kill('TERM', pid)
|
26
60
|
sleep 1 # Give the process time to exit
|
data/spec/rails/app.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# https://github.com/rack/rack/pull/1937
|
2
|
+
begin
|
3
|
+
require 'rackup'
|
4
|
+
rescue LoadError # rubocop:disable Lint/SuppressedException
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'action_controller/railtie'
|
8
|
+
require 'logger'
|
9
|
+
require 'scout_apm_logging'
|
10
|
+
|
11
|
+
Rails.logger = ActiveSupport::TaggedLogging.new(Logger.new($stdout))
|
12
|
+
|
13
|
+
class App < ::Rails::Application
|
14
|
+
config.eager_load = false
|
15
|
+
|
16
|
+
routes.append do
|
17
|
+
root to: 'root#index'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class RootController < ActionController::Base
|
22
|
+
def index
|
23
|
+
Rails.logger.warn('Add location log attributes')
|
24
|
+
Rails.logger.tagged('TEST').info('Some log')
|
25
|
+
Rails.logger.tagged('YIELD') { logger.info('Yield Test') }
|
26
|
+
Rails.logger.info('Another Log')
|
27
|
+
render plain: Rails.version
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize_app
|
32
|
+
App.initialize!
|
33
|
+
|
34
|
+
if defined?(Rack::Server)
|
35
|
+
Rack::Server.start(app: App)
|
36
|
+
else
|
37
|
+
Rackup::Server.start(app: App)
|
38
|
+
end
|
39
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -21,6 +21,7 @@ class ScoutTestLogger < ::Logger
|
|
21
21
|
end
|
22
22
|
|
23
23
|
RSpec.configure do |config|
|
24
|
+
ENV["SCOUT_LOG_FILE_PATH"] = "STDOUT"
|
24
25
|
ENV["SCOUT_LOG_LEVEL"] = "debug"
|
25
26
|
ENV["SCOUT_COLLECTOR_LOG_LEVEL"] = "info"
|
26
27
|
|
@@ -29,30 +30,6 @@ RSpec.configure do |config|
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
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
33
|
def wait_for_process_with_timeout!(name, timeout_time)
|
57
34
|
Timeout::timeout(timeout_time) do
|
58
35
|
loop do
|
@@ -29,7 +29,7 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
29
29
|
TestLoggerWrapper.logger = ScoutTestLogger.new($stdout)
|
30
30
|
|
31
31
|
capture = ScoutApm::Logging::Loggers::Capture.new(context)
|
32
|
-
capture.
|
32
|
+
capture.setup!
|
33
33
|
|
34
34
|
expect(TestLoggerWrapper.logger.class).to eq(ScoutApm::Logging::Loggers::Proxy)
|
35
35
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scout_apm_logging
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scout APM
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: scout_apm
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- ".github/workflows/test.yml"
|
77
77
|
- ".gitignore"
|
78
78
|
- ".rubocop.yml"
|
79
|
+
- CHANGELOG.md
|
79
80
|
- Dockerfile
|
80
81
|
- Gemfile
|
81
82
|
- README.md
|
@@ -88,6 +89,7 @@ files:
|
|
88
89
|
- lib/scout_apm/logging/loggers/capture.rb
|
89
90
|
- lib/scout_apm/logging/loggers/formatter.rb
|
90
91
|
- lib/scout_apm/logging/loggers/logger.rb
|
92
|
+
- lib/scout_apm/logging/loggers/patches/tagged_logging.rb
|
91
93
|
- lib/scout_apm/logging/loggers/proxy.rb
|
92
94
|
- lib/scout_apm/logging/loggers/swaps/rails.rb
|
93
95
|
- lib/scout_apm/logging/loggers/swaps/scout.rb
|
@@ -118,6 +120,7 @@ files:
|
|
118
120
|
- spec/integration/monitor_manager/monitor_pid_file_spec.rb
|
119
121
|
- spec/integration/monitor_manager/single_monitor_spec.rb
|
120
122
|
- spec/integration/rails/lifecycle_spec.rb
|
123
|
+
- spec/rails/app.rb
|
121
124
|
- spec/spec_helper.rb
|
122
125
|
- spec/unit/config_spec.rb
|
123
126
|
- spec/unit/loggers/capture_spec.rb
|
@@ -162,6 +165,7 @@ test_files:
|
|
162
165
|
- spec/integration/monitor_manager/monitor_pid_file_spec.rb
|
163
166
|
- spec/integration/monitor_manager/single_monitor_spec.rb
|
164
167
|
- spec/integration/rails/lifecycle_spec.rb
|
168
|
+
- spec/rails/app.rb
|
165
169
|
- spec/spec_helper.rb
|
166
170
|
- spec/unit/config_spec.rb
|
167
171
|
- spec/unit/loggers/capture_spec.rb
|