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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbe47505a0defc4e795ece37c30380707600c95708954f2aa99b86e2eff5aefb
4
- data.tar.gz: 7aa1c9af19eb96358fbc5ceb037b901cc2dd4a01ab87cc6d737f9b0205fa77cf
3
+ metadata.gz: 7a99a84cd9f8dd023b8717c81e5d80b17c99f5afb8c7e7edb6152193ef7fecdf
4
+ data.tar.gz: b9d2e44b40adcb5f9acc13f6ef6d2176e79ed6b056f6e6d1e8f9f0359857ebf6
5
5
  SHA512:
6
- metadata.gz: a1d8ee77dd245e14a26a40298c5c77d61af4b1869aa4bc8d53ae1856ae336456148a7a3d2ad865b1e920ef4184fe14120cb07dc453e7b6a8e25738495123a7f1
7
- data.tar.gz: 25380fc37e48a7a6a6f901246faf3fec03884c4665a853d6decb3646a566a8b637aaf1851e86f76865429838fb631aaa5d0c245dd9ff7d453824f80a854f2ce3
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 capture_and_swap_log_locations!
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) # rubocop:disable Metrics/AbcSize
16
- attributes_to_log[:severity] = severity
17
- attributes_to_log[:time] = format_datetime(time)
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
- new_logger = FileLogger.new(determine_file_path, LOG_AGE, LOG_SIZE)
28
- # Ruby's Logger handles a lot of the coercion itself.
29
- new_logger.level = context.config.value('logs_capture_level')
30
- # Add our custom formatter to the logger.
31
- new_logger.formatter = Formatter.new
32
- new_logger
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(original_logger, new_file_logger)
8
+ def self.create_with_loggers(*loggers)
9
9
  new.tap do |proxy_logger|
10
- proxy_logger.add(original_logger)
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
- break if File.exist?(context.config.value('monitor_pid_file'))
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
- unless updated_log_locations.empty?
32
- contents = file.read
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
- 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
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(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|
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ScoutApm
4
4
  module Logging
5
- VERSION = '0.0.2'
5
+ VERSION = '0.0.4'
6
6
  end
7
7
  end
@@ -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).capture_and_swap_log_locations!
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).capture_and_swap_log_locations!
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
- pid_file = ScoutApm::Logging::MonitorManager.instance.context.config.value('monitor_pid_file')
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
- make_basic_app
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.capture_and_swap_log_locations!
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.2
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-07-23 00:00:00.000000000 Z
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