scout_apm_logging 0.0.0.1 → 0.0.3
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 +5 -0
- data/Rakefile +2 -0
- data/lib/scout_apm/logging/config.rb +4 -1
- data/lib/scout_apm/logging/loggers/capture.rb +45 -16
- 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 +6 -2
- data/lib/scout_apm/logging/loggers/swaps/rails.rb +77 -0
- data/lib/scout_apm/logging/loggers/swaps/scout.rb +62 -0
- data/lib/scout_apm/logging/loggers/swaps/sidekiq.rb +64 -0
- data/lib/scout_apm/logging/monitor/monitor.rb +2 -0
- data/lib/scout_apm/logging/monitor_manager/manager.rb +18 -6
- 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/scout_apm_logging.gemspec +0 -2
- data/spec/integration/loggers/capture_spec.rb +5 -15
- 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 +32 -3
- data/spec/rails/app.rb +38 -0
- data/spec/spec_helper.rb +2 -25
- data/spec/unit/loggers/capture_spec.rb +1 -16
- data/spec/unit/monitor/collector/configuration_spec.rb +3 -3
- metadata +9 -3
- data/lib/scout_apm/logging/loggers/swap.rb +0 -82
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb4dc1a11fe63b4c983636958676d8ca7e76bb04c5281bdabd272373deaf83e5
|
|
4
|
+
data.tar.gz: 70809502cbbfb93a9cc73e26329f4e52b57008f51f9e34b3769fdfb17b2d46f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3a17d497a14d952294dd23ce5eac8605f7cee5da807ba53cc312acafbe314c33a303d75d97ac828f3ea1aba40e733da1104aa655866121e7f3c149afc9935cb
|
|
7
|
+
data.tar.gz: 3515ffc8593bc564318859e0fb7340f82c2c066509a82411091b8e5e21bb4abcb223e1318d83b76a12ac11986db22bf4abaec5f511d6c07bdcaa5cc32742b7fd
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
## 0.0.3
|
|
2
|
+
* **Feature**: Add support for TaggedLogging.
|
|
3
|
+
* Add ability to customize file logger size. Increase default size to 10MiB.
|
|
4
|
+
* Fix an issue with removing of the monitor process when Rails workers exited, which was only intended for when the main process exits.
|
|
5
|
+
* Fix an issue with the known monitored logs state being removed on port flushing.
|
data/Rakefile
CHANGED
|
@@ -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
|
|
@@ -227,8 +229,9 @@ module ScoutApm
|
|
|
227
229
|
'log_level' => 'info',
|
|
228
230
|
'logs_monitored' => [],
|
|
229
231
|
'logs_capture_level' => 'debug',
|
|
230
|
-
'logs_reporting_endpoint' => 'https://otlp.
|
|
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',
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require 'logger'
|
|
4
4
|
|
|
5
|
-
require_relative './
|
|
5
|
+
require_relative './formatter'
|
|
6
|
+
require_relative './logger'
|
|
6
7
|
require_relative './proxy'
|
|
8
|
+
require_relative './swaps/rails'
|
|
9
|
+
require_relative './swaps/sidekiq'
|
|
10
|
+
require_relative './swaps/scout'
|
|
11
|
+
require_relative './patches/tagged_logging'
|
|
7
12
|
|
|
8
13
|
module ScoutApm
|
|
9
14
|
module Logging
|
|
@@ -12,33 +17,57 @@ module ScoutApm
|
|
|
12
17
|
class Capture
|
|
13
18
|
attr_reader :context
|
|
14
19
|
|
|
20
|
+
KNOWN_LOGGERS = [
|
|
21
|
+
Swaps::Rails,
|
|
22
|
+
Swaps::Sidekiq,
|
|
23
|
+
Swaps::Scout
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
15
26
|
def initialize(context)
|
|
16
27
|
@context = context
|
|
17
28
|
end
|
|
18
29
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
logger_instances << Sidekiq.logger if defined?(Sidekiq)
|
|
22
|
-
logger_instances << ObjectSpace.each_object(::ScoutTestLogger).to_a if defined?(::ScoutTestLogger)
|
|
30
|
+
def setup!
|
|
31
|
+
return unless context.config.value('logs_monitor')
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
updated_log_locations = logger_instances.compact.flatten.map do |logger|
|
|
26
|
-
swapped_in_location(logger)
|
|
27
|
-
end
|
|
33
|
+
create_proxy_log_dir!
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
add_logging_patches!
|
|
36
|
+
capture_and_swap_log_locations!
|
|
30
37
|
end
|
|
31
38
|
|
|
32
39
|
private
|
|
33
40
|
|
|
34
|
-
def
|
|
35
|
-
|
|
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
|
|
36
61
|
end
|
|
37
62
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
def capture_and_swap_log_locations!
|
|
64
|
+
# We can move this to filter_map when our lagging version is Ruby 2.7
|
|
65
|
+
updated_log_locations = KNOWN_LOGGERS.map do |logger|
|
|
66
|
+
logger.new(context).update_logger! if logger.present?
|
|
67
|
+
end
|
|
68
|
+
updated_log_locations.compact!
|
|
69
|
+
|
|
70
|
+
context.config.state.add_log_locations!(updated_log_locations)
|
|
42
71
|
end
|
|
43
72
|
end
|
|
44
73
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'logger'
|
|
4
|
-
|
|
5
3
|
module ScoutApm
|
|
6
4
|
module Logging
|
|
7
5
|
module Loggers
|
|
@@ -12,8 +10,6 @@ module ScoutApm
|
|
|
12
10
|
class Logger
|
|
13
11
|
attr_reader :context, :log_instance
|
|
14
12
|
|
|
15
|
-
# 1 MiB
|
|
16
|
-
LOG_SIZE = 1024 * 1024
|
|
17
13
|
# 1 log file
|
|
18
14
|
LOG_AGE = 1
|
|
19
15
|
|
|
@@ -23,13 +19,16 @@ module ScoutApm
|
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
def create_logger!
|
|
26
|
-
# Defaults are 7 files with 10 MiB.
|
|
27
22
|
# We create the file in order to prevent a creation header log.
|
|
28
23
|
File.new(determine_file_path, 'w+') unless File.exist?(determine_file_path)
|
|
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
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'logger'
|
|
4
|
-
|
|
5
3
|
module ScoutApm
|
|
6
4
|
module Logging
|
|
7
5
|
module Loggers
|
|
8
6
|
# Holds both the original application logger and the new one. Relays commands to both.
|
|
9
7
|
class Proxy
|
|
8
|
+
def self.create_with_loggers(*loggers)
|
|
9
|
+
new.tap do |proxy_logger|
|
|
10
|
+
loggers.each { |logger| proxy_logger.add(logger) }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
def initialize
|
|
11
15
|
@loggers = []
|
|
12
16
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ScoutApm
|
|
4
|
+
module Logging
|
|
5
|
+
module Loggers
|
|
6
|
+
module Swaps
|
|
7
|
+
# Swaps in our logger for the Rails logger.
|
|
8
|
+
class Rails
|
|
9
|
+
attr_reader :context
|
|
10
|
+
|
|
11
|
+
def self.present?
|
|
12
|
+
defined?(::Rails) && ::Rails.logger
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(context)
|
|
16
|
+
@context = context
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update_logger!
|
|
20
|
+
# In Rails 7.1, broadcast logger was added which allows sinking to multiple IO devices.
|
|
21
|
+
if defined?(::ActiveSupport::BroadcastLogger) && log_instance.is_a?(::ActiveSupport::BroadcastLogger)
|
|
22
|
+
add_logger_to_broadcast!
|
|
23
|
+
else
|
|
24
|
+
swap_in_proxy_logger!
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
new_log_location
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def log_instance
|
|
33
|
+
::Rails.logger
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def new_file_logger
|
|
37
|
+
@new_file_logger ||= Loggers::Logger.new(context, log_instance).create_logger!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Eseentially creates the original logger.
|
|
41
|
+
def original_logger
|
|
42
|
+
# We can use the previous logdev. log_device will continuously call write
|
|
43
|
+
# through the devices until the logdev (@dev) is an IO device other than logdev:
|
|
44
|
+
# https://github.com/ruby/ruby/blob/master/lib/logger/log_device.rb#L42
|
|
45
|
+
# Log device holds the configurations around shifting too.
|
|
46
|
+
original_logdevice = log_instance.instance_variable_get(:@logdev)
|
|
47
|
+
|
|
48
|
+
::Logger.new(original_logdevice).tap do |logger|
|
|
49
|
+
logger.formatter = log_instance.formatter
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def new_log_location
|
|
54
|
+
new_file_logger.instance_variable_get(:@logdev).filename
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def add_logger_to_broadcast!
|
|
58
|
+
log_instance.broadcast_to(new_file_logger)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def swap_in_proxy_logger!
|
|
62
|
+
# First logger needs to be the original logger for the return value of relayed calls.
|
|
63
|
+
proxy_logger = Proxy.create_with_loggers(original_logger, new_file_logger)
|
|
64
|
+
proxy_logger.extend ::ActiveSupport::TaggedLogging if log_instance.respond_to?(:tagged)
|
|
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)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ScoutApm
|
|
4
|
+
module Logging
|
|
5
|
+
module Loggers
|
|
6
|
+
module Swaps
|
|
7
|
+
# Swaps in our logger for the test Scout logger.
|
|
8
|
+
class Scout
|
|
9
|
+
attr_reader :context
|
|
10
|
+
|
|
11
|
+
def self.present?
|
|
12
|
+
defined?(::TestLoggerWrapper) && ::TestLoggerWrapper.logger
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(context)
|
|
16
|
+
@context = context
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update_logger!
|
|
20
|
+
swap_in_proxy_logger!
|
|
21
|
+
|
|
22
|
+
new_log_location
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def log_instance
|
|
28
|
+
::TestLoggerWrapper.logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def new_file_logger
|
|
32
|
+
@new_file_logger ||= Loggers::Logger.new(context, log_instance).create_logger!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Eseentially creates the original logger.
|
|
36
|
+
def original_logger
|
|
37
|
+
# We can use the previous logdev. log_device will continuously call write
|
|
38
|
+
# through the devices until the logdev (@dev) is an IO device other than logdev:
|
|
39
|
+
# https://github.com/ruby/ruby/blob/master/lib/logger/log_device.rb#L42
|
|
40
|
+
# Log device holds the configurations around shifting too.
|
|
41
|
+
original_logdevice = log_instance.instance_variable_get(:@logdev)
|
|
42
|
+
|
|
43
|
+
::Logger.new(original_logdevice).tap do |logger|
|
|
44
|
+
logger.formatter = log_instance.formatter
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def new_log_location
|
|
49
|
+
new_file_logger.instance_variable_get(:@logdev).filename
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def swap_in_proxy_logger!
|
|
53
|
+
# First logger needs to be the original logger for the return value of relayed calls.
|
|
54
|
+
proxy_logger = Proxy.create_with_loggers(original_logger, new_file_logger)
|
|
55
|
+
|
|
56
|
+
::TestLoggerWrapper.logger = proxy_logger
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ScoutApm
|
|
4
|
+
module Logging
|
|
5
|
+
module Loggers
|
|
6
|
+
module Swaps
|
|
7
|
+
# Swaps in our logger for the Sidekiq logger.
|
|
8
|
+
class Sidekiq
|
|
9
|
+
attr_reader :context
|
|
10
|
+
|
|
11
|
+
def self.present?
|
|
12
|
+
defined?(::Sidekiq) && ::Sidekiq.logger
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(context)
|
|
16
|
+
@context = context
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update_logger!
|
|
20
|
+
swap_in_proxy_logger!
|
|
21
|
+
|
|
22
|
+
new_log_location
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def log_instance
|
|
28
|
+
::Sidekiq.logger
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def new_file_logger
|
|
32
|
+
@new_file_logger ||= Loggers::Logger.new(context, log_instance).create_logger!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Eseentially creates the original logger.
|
|
36
|
+
def original_logger
|
|
37
|
+
# We can use the previous logdev. log_device will continuously call write
|
|
38
|
+
# through the devices until the logdev (@dev) is an IO device other than logdev:
|
|
39
|
+
# https://github.com/ruby/ruby/blob/master/lib/logger/log_device.rb#L42
|
|
40
|
+
# Log device holds the configurations around shifting too.
|
|
41
|
+
original_logdevice = log_instance.instance_variable_get(:@logdev)
|
|
42
|
+
|
|
43
|
+
::Logger.new(original_logdevice).tap do |logger|
|
|
44
|
+
logger.formatter = log_instance.formatter
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def new_log_location
|
|
49
|
+
new_file_logger.instance_variable_get(:@logdev).filename
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def swap_in_proxy_logger!
|
|
53
|
+
# First logger needs to be the original logger for the return value of relayed calls.
|
|
54
|
+
proxy_logger = Proxy.create_with_loggers(original_logger, new_file_logger)
|
|
55
|
+
|
|
56
|
+
::Sidekiq.configure_server do |config|
|
|
57
|
+
config.logger = proxy_logger
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
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
|
|
|
@@ -24,9 +24,6 @@ module ScoutApm
|
|
|
24
24
|
add_exit_handler!
|
|
25
25
|
|
|
26
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
27
|
end
|
|
31
28
|
|
|
32
29
|
def determine_configuration_state
|
|
@@ -35,6 +32,9 @@ module ScoutApm
|
|
|
35
32
|
if monitoring_enabled
|
|
36
33
|
context.logger.info('Log monitoring enabled')
|
|
37
34
|
create_process
|
|
35
|
+
|
|
36
|
+
# Continue to hold the lock until we have written the PID file.
|
|
37
|
+
ensure_monitor_pid_file_exists
|
|
38
38
|
else
|
|
39
39
|
context.logger.info('Log monitoring disabled')
|
|
40
40
|
remove_processes
|
|
@@ -47,10 +47,16 @@ 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
|
-
|
|
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
|
|
54
60
|
end
|
|
55
61
|
end
|
|
56
62
|
|
|
@@ -64,12 +70,15 @@ module ScoutApm
|
|
|
64
70
|
gem_directory = File.expand_path('../../../..', __dir__)
|
|
65
71
|
|
|
66
72
|
# 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)
|
|
73
|
+
pid = Process.spawn("ruby #{gem_directory}/bin/scout_apm_logging_monitor", in: reader)
|
|
68
74
|
|
|
69
75
|
reader.close
|
|
70
76
|
# TODO: Add support for Sinatra.
|
|
71
77
|
writer.puts Rails.root if defined?(Rails)
|
|
72
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)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
private
|
|
@@ -81,7 +90,10 @@ module ScoutApm
|
|
|
81
90
|
|
|
82
91
|
# Naive benchmarks show this taking ~0.01 seconds.
|
|
83
92
|
loop do
|
|
84
|
-
|
|
93
|
+
if File.exist?(context.config.value('monitor_pid_file'))
|
|
94
|
+
context.logger.debug('Monitor PID file exists. Releasing lock.')
|
|
95
|
+
break
|
|
96
|
+
end
|
|
85
97
|
|
|
86
98
|
if Time.now - start_time > timeout_seconds
|
|
87
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
|
data/scout_apm_logging.gemspec
CHANGED
|
@@ -2,6 +2,8 @@ require 'logger'
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
+
require_relative '../../../lib/scout_apm/logging/loggers/capture'
|
|
6
|
+
|
|
5
7
|
describe ScoutApm::Logging::Loggers::Capture do
|
|
6
8
|
it 'should find the logger, capture the log destination, and rotate collector configs' do
|
|
7
9
|
ENV['SCOUT_MONITOR_INTERVAL'] = '10'
|
|
@@ -17,9 +19,7 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
|
17
19
|
first_logger = ScoutTestLogger.new('/tmp/first_file.log')
|
|
18
20
|
first_logger_basename = File.basename(first_logger.instance_variable_get(:@logdev).filename.to_s)
|
|
19
21
|
first_logger_updated_path = File.join(context.config.value('logs_proxy_log_dir'), first_logger_basename)
|
|
20
|
-
|
|
21
|
-
# While we only use the ObjectSpace for the test logger, we need to wait for it to be captured.
|
|
22
|
-
wait_for_logger
|
|
22
|
+
TestLoggerWrapper.logger = first_logger
|
|
23
23
|
|
|
24
24
|
similuate_railtie
|
|
25
25
|
|
|
@@ -36,6 +36,7 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
|
36
36
|
second_logger = ScoutTestLogger.new('/tmp/second_file.log')
|
|
37
37
|
second_logger_basename = File.basename(second_logger.instance_variable_get(:@logdev).filename.to_s)
|
|
38
38
|
second_logger_updated_path = File.join(context.config.value('logs_proxy_log_dir'), second_logger_basename)
|
|
39
|
+
TestLoggerWrapper.logger = second_logger
|
|
39
40
|
|
|
40
41
|
similuate_railtie
|
|
41
42
|
|
|
@@ -61,18 +62,7 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
|
61
62
|
def similuate_railtie
|
|
62
63
|
context = ScoutApm::Logging::MonitorManager.instance.context
|
|
63
64
|
|
|
64
|
-
ScoutApm::Logging::Loggers::Capture.new(context).
|
|
65
|
+
ScoutApm::Logging::Loggers::Capture.new(context).setup!
|
|
65
66
|
ScoutApm::Logging::MonitorManager.new.setup!
|
|
66
67
|
end
|
|
67
|
-
|
|
68
|
-
def wait_for_logger
|
|
69
|
-
start_time = Time.now
|
|
70
|
-
loop do
|
|
71
|
-
break if ObjectSpace.each_object(ScoutTestLogger).count.positive?
|
|
72
|
-
|
|
73
|
-
raise 'Timed out while waiting for logger in ObjectSpace' if Time.now - start_time > 10
|
|
74
|
-
|
|
75
|
-
sleep 0.1
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
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,32 @@ 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
|
+
# Kill the rails process. We use kill as using any other signal throws a long log line.
|
|
52
|
+
Process.kill('KILL', rails_pid)
|
|
24
53
|
# Kill the process and ensure PID file clean up
|
|
25
54
|
Process.kill('TERM', pid)
|
|
26
55
|
sleep 1 # Give the process time to exit
|
data/spec/rails/app.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
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.tagged('TEST').info('Some log')
|
|
24
|
+
Rails.logger.tagged('YIELD') { logger.info('Yield Test') }
|
|
25
|
+
Rails.logger.info('Another Log')
|
|
26
|
+
render plain: Rails.version
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize_app
|
|
31
|
+
App.initialize!
|
|
32
|
+
|
|
33
|
+
if defined?(Rack::Server)
|
|
34
|
+
Rack::Server.start(app: App)
|
|
35
|
+
else
|
|
36
|
+
Rackup::Server.start(app: App)
|
|
37
|
+
end
|
|
38
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -21,38 +21,15 @@ 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
|
-
ENV["SCOUT_COLLECTOR_LOG_LEVEL"] = "
|
|
26
|
+
ENV["SCOUT_COLLECTOR_LOG_LEVEL"] = "info"
|
|
26
27
|
|
|
27
28
|
config.after(:each) do
|
|
28
29
|
RSpec::Mocks.space.reset_all
|
|
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
|
|
@@ -4,7 +4,6 @@ require 'stringio'
|
|
|
4
4
|
require 'spec_helper'
|
|
5
5
|
|
|
6
6
|
require_relative '../../../lib/scout_apm/logging/loggers/capture'
|
|
7
|
-
require_relative '../../../lib/scout_apm/logging/loggers/proxy'
|
|
8
7
|
|
|
9
8
|
def capture_stdout
|
|
10
9
|
old_stdout = $stdout
|
|
@@ -29,11 +28,8 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
|
29
28
|
|
|
30
29
|
TestLoggerWrapper.logger = ScoutTestLogger.new($stdout)
|
|
31
30
|
|
|
32
|
-
# While we only use the ObjectSpace for the test logger, we need to wait for it to be captured.
|
|
33
|
-
wait_for_logger
|
|
34
|
-
|
|
35
31
|
capture = ScoutApm::Logging::Loggers::Capture.new(context)
|
|
36
|
-
capture.
|
|
32
|
+
capture.setup!
|
|
37
33
|
|
|
38
34
|
expect(TestLoggerWrapper.logger.class).to eq(ScoutApm::Logging::Loggers::Proxy)
|
|
39
35
|
|
|
@@ -50,15 +46,4 @@ describe ScoutApm::Logging::Loggers::Capture do
|
|
|
50
46
|
|
|
51
47
|
expect(output_from_log).to include('TEST')
|
|
52
48
|
end
|
|
53
|
-
|
|
54
|
-
def wait_for_logger
|
|
55
|
-
start_time = Time.now
|
|
56
|
-
loop do
|
|
57
|
-
break if ObjectSpace.each_object(::ScoutTestLogger).count.positive?
|
|
58
|
-
|
|
59
|
-
raise 'Timed out while waiting for logger in ObjectSpace' if Time.now - start_time > 10
|
|
60
|
-
|
|
61
|
-
sleep 0.1
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
49
|
end
|
|
@@ -12,7 +12,7 @@ describe ScoutApm::Logging::Collector::Configuration do
|
|
|
12
12
|
expect(File.exist?(context.config.value('collector_config_file'))).to be_truthy
|
|
13
13
|
config = YAML.load_file(context.config.value('collector_config_file'))
|
|
14
14
|
|
|
15
|
-
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.
|
|
15
|
+
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.scoutotel.com:4317')
|
|
16
16
|
expect(config['exporters']['otlp']['headers']['x-telemetryhub-key']).to eq('00001000010000abc')
|
|
17
17
|
expect(config['receivers']['filelog']['include']).to eq(['/tmp/fake_log_file.log'])
|
|
18
18
|
end
|
|
@@ -26,7 +26,7 @@ describe ScoutApm::Logging::Collector::Configuration do
|
|
|
26
26
|
expect(File.exist?(context.config.value('collector_config_file'))).to be_truthy
|
|
27
27
|
config = YAML.load_file(context.config.value('collector_config_file'))
|
|
28
28
|
|
|
29
|
-
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.
|
|
29
|
+
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.scoutotel.com:4317')
|
|
30
30
|
expect(config['exporters']['otlp']['headers']['x-telemetryhub-key']).to eq('00001000010000abc')
|
|
31
31
|
expect(config['receivers']['filelog']['include']).to eq(['/tmp/fake_log_file.log'])
|
|
32
32
|
|
|
@@ -44,7 +44,7 @@ describe ScoutApm::Logging::Collector::Configuration do
|
|
|
44
44
|
expect(File.exist?(context.config.value('collector_config_file'))).to be_truthy
|
|
45
45
|
config = YAML.load_file(context.config.value('collector_config_file'))
|
|
46
46
|
|
|
47
|
-
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.
|
|
47
|
+
expect(config['exporters']['otlp']['endpoint']).to eq('https://otlp.scoutotel.com:4317')
|
|
48
48
|
expect(config['exporters']['otlp']['headers']['x-telemetryhub-key']).to eq('00001000010000abc')
|
|
49
49
|
expect(config['receivers']['filelog']['include']).to eq(['/tmp/fake_log_file.log'])
|
|
50
50
|
end
|
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.3
|
|
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-08 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,8 +89,11 @@ 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
|
-
- lib/scout_apm/logging/loggers/
|
|
94
|
+
- lib/scout_apm/logging/loggers/swaps/rails.rb
|
|
95
|
+
- lib/scout_apm/logging/loggers/swaps/scout.rb
|
|
96
|
+
- lib/scout_apm/logging/loggers/swaps/sidekiq.rb
|
|
93
97
|
- lib/scout_apm/logging/monitor/collector/checksum.rb
|
|
94
98
|
- lib/scout_apm/logging/monitor/collector/configuration.rb
|
|
95
99
|
- lib/scout_apm/logging/monitor/collector/downloader.rb
|
|
@@ -116,6 +120,7 @@ files:
|
|
|
116
120
|
- spec/integration/monitor_manager/monitor_pid_file_spec.rb
|
|
117
121
|
- spec/integration/monitor_manager/single_monitor_spec.rb
|
|
118
122
|
- spec/integration/rails/lifecycle_spec.rb
|
|
123
|
+
- spec/rails/app.rb
|
|
119
124
|
- spec/spec_helper.rb
|
|
120
125
|
- spec/unit/config_spec.rb
|
|
121
126
|
- spec/unit/loggers/capture_spec.rb
|
|
@@ -160,6 +165,7 @@ test_files:
|
|
|
160
165
|
- spec/integration/monitor_manager/monitor_pid_file_spec.rb
|
|
161
166
|
- spec/integration/monitor_manager/single_monitor_spec.rb
|
|
162
167
|
- spec/integration/rails/lifecycle_spec.rb
|
|
168
|
+
- spec/rails/app.rb
|
|
163
169
|
- spec/spec_helper.rb
|
|
164
170
|
- spec/unit/config_spec.rb
|
|
165
171
|
- spec/unit/loggers/capture_spec.rb
|
|
@@ -1,82 +0,0 @@
|
|
|
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
|