scout_apm_logging 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7064d80f9892529f81a77b0e46f9e517e8ea96f8e9e53880ee2eefd6c5abbf2
4
- data.tar.gz: 3a3ec083c05435e99d23b719bbc31f38d7ce4a07ddcf25ef47fed544147ea21c
3
+ metadata.gz: 3a472edfe7271717a5eb8b5c76f1dda7a84dc1b8ad2f0da4c6223173f87c44db
4
+ data.tar.gz: 1eaf6f5de049078460287b0b02e09583cad2db3fc31c3b54a7d83930d5fca787
5
5
  SHA512:
6
- metadata.gz: d7ea291371355544a94b69d3ab7ad75ee7407c84d450bb3e9a44ce6aabfa6bb2c27ce5813276111fb6584268eaba59875d346bd6b36b83c4db0d153478f7ba9f
7
- data.tar.gz: ff9ee96494248654b146c2cc9d45cc7db960196781661cc94e3f79ebbabde83930b4c264e67c0270cd24e1510027242b62e307cda061b26e1b2789d1af5f7941
6
+ metadata.gz: f74a53847327e6068ad671b8f2363516971b6ee04f9aea1f2c47d5ea069eae3ba70ea5b36be17c3bd57e2d378cd3c3835c6a229d1ea825793c8456585630c4a1
7
+ data.tar.gz: b3de4ed980c0129c615574b6e55e1bb245df9f85d515a3dba0fad8ebc2fe692cd99c9aeff2b96a47b4efb24fbe34fbbc540d78fcc96a048b25fab84ef6363b29
@@ -12,7 +12,7 @@ jobs:
12
12
  env:
13
13
  DOCKER_RUBY_VERSION: ${{ matrix.ruby }}
14
14
  BUNDLE_GEMFILE: gems/rails.gemfile
15
- runs-on: ubuntu-20.04
15
+ runs-on: ubuntu-latest
16
16
  steps:
17
17
  - uses: actions/checkout@v2
18
18
  - name: Extract Library Version
data/.rubocop.yml CHANGED
@@ -12,6 +12,7 @@ AllCops:
12
12
 
13
13
  Metrics/BlockLength:
14
14
  Exclude:
15
+ - "scout_apm_logging.gemspec"
15
16
  - "spec/**/*"
16
17
 
17
18
  Metrics/ClassLength:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 2.1.0
2
+ * Add ability to capture log line with logs.
3
+ * `logs_capture_log_line: true`
4
+ * Add ability to capture stack trace with logs.
5
+ * `logs_capture_call_stack: true`
6
+ * See [#98](https://github.com/scoutapp/scout_apm_ruby_logging/pull/98) for benchmarks.
7
+ * Add ability to disable warn message on method_missing.
8
+ * `logs_method_missing_warning: false`
9
+ * Add ability to log stack trace on method_missing.
10
+ * `logs_method_missing_call_stack: true`
11
+ * Add tests for ActionCable.
12
+
1
13
  ## 2.0.0
2
14
  * Remove vendored opentelmetry SDK.
3
15
  * Add support for Ruby 3.4.
@@ -19,19 +19,12 @@
19
19
  # logs_config - a hash of configuration options for merging into the collector's config
20
20
  # logs_reporting_endpoint - the endpoint to send logs to
21
21
  # logs_proxy_log_dir - the directory to store logs in for monitoring
22
- # manager_lock_file - the location for obtaining an exclusive lock for running monitor manager
23
- # monitor_pid_file - the location of the pid file for the monitor
24
- # monitor_state_file - the location of the state file for the monitor
25
- # monitor_interval - the interval to check the collector healtcheck and for new state logs
26
- # monitor_interval_delay - the delay to wait before running the first monitor interval
27
- # collector_log_level - the log level for the collector
28
- # collector_sending_queue_storage_dir - the directory to store queue files
29
- # collector_offset_storage_dir - the directory to store offset files
30
- # collector_pid_file - the location of the pid file for the collector
31
- # collector_download_dir - the directory to store downloaded collector files
32
- # collector_config_file - the location of the config file for the collector
33
- # collector_version - the version of the collector to download
34
- # health_check_port - the port to use for the collector health check. Default is dynamically derived based on port availability
22
+ # logs_capture_call_stack - true or false. If true, capture the call stack for each log message
23
+ # logs_capture_log_line - true or false. If true, capture the log line for each log message
24
+ # logs_call_stack_search_depth - the number of frames to search in the call stack
25
+ # logs_call_stack_capture_depth - the number of frames to capture in the call stack
26
+ # logs_method_missing_warning - true or false. If true, log a warning when method_missing is called
27
+ # logs_method_missing_call_stack - true or false. If true, capture the call stack when method_missing is called
35
28
  #
36
29
  # Any of these config settings can be set with an environment variable prefixed
37
30
  # by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
@@ -55,11 +48,23 @@ module ScoutApm
55
48
  logs_reporting_endpoint_http
56
49
  logs_proxy_log_dir
57
50
  logs_log_file_size
51
+ logs_capture_call_stack
52
+ logs_capture_log_line
53
+ logs_call_stack_search_depth
54
+ logs_call_stack_capture_depth
55
+ logs_method_missing_warning
56
+ logs_method_missing_call_stack
58
57
  ].freeze
59
58
 
60
59
  SETTING_COERCIONS = {
61
60
  'logs_monitor' => BooleanCoercion.new,
62
- 'logs_log_file_size' => IntegerCoercion.new
61
+ 'logs_capture_call_stack' => BooleanCoercion.new,
62
+ 'logs_capture_log_line' => BooleanCoercion.new,
63
+ 'logs_call_stack_search_depth' => IntegerCoercion.new,
64
+ 'logs_call_stack_capture_depth' => IntegerCoercion.new,
65
+ 'logs_log_file_size' => IntegerCoercion.new,
66
+ 'logs_method_missing_warning' => BooleanCoercion.new,
67
+ 'logs_method_missing_call_stack' => BooleanCoercion.new
63
68
  }.freeze
64
69
 
65
70
  # The bootstrapped, and initial config that we attach to the context. Will be swapped out by
@@ -117,7 +122,13 @@ module ScoutApm
117
122
  'logs_reporting_endpoint' => 'https://otlp.scoutotel.com:4317',
118
123
  'logs_reporting_endpoint_http' => 'https://otlp.scoutotel.com:4318/v1/logs',
119
124
  'logs_proxy_log_dir' => '/tmp/scout_apm/logs/',
120
- 'logs_log_file_size' => 1024 * 1024 * 10
125
+ 'logs_log_file_size' => 1024 * 1024 * 10,
126
+ 'logs_capture_call_stack' => false,
127
+ 'logs_capture_log_line' => false,
128
+ 'logs_call_stack_search_depth' => 15,
129
+ 'logs_call_stack_capture_depth' => 2,
130
+ 'logs_method_missing_warning' => true,
131
+ 'logs_method_missing_call_stack' => false
121
132
  }.freeze
122
133
 
123
134
  def value(key)
@@ -7,6 +7,14 @@ module ScoutApm
7
7
  # The root of the application.
8
8
  attr_accessor :application_root
9
9
 
10
+ # Use this as the entrypoint.
11
+ def self.instance
12
+ @@instance ||= new.tap do |instance|
13
+ instance.config = ScoutApm::Logging::Config.with_file(instance, instance.config.value('config_file'))
14
+ instance.config.log_settings(instance.logger)
15
+ end
16
+ end
17
+
10
18
  # Initially start up without attempting to load a configuration file. We
11
19
  # need to be able to lookup configuration options like "application_root"
12
20
  # which would then in turn influence where the yaml configuration file is
@@ -56,7 +56,7 @@ module ScoutApm
56
56
  # Re-extend TaggedLogging to verify the patch is be applied.
57
57
  # This appears to be an issue in Ruby 2.7 with the broadcast logger.
58
58
  ruby_version = Gem::Version.new(RUBY_VERSION)
59
- isruby27 = (ruby_version >= Gem::Version.new('2.7') && ruby_version < Gem::Version.new('3.0'))
59
+ isruby27 = ruby_version >= Gem::Version.new('2.7') && ruby_version < Gem::Version.new('3.0')
60
60
  return unless isruby27 && ::Rails.logger.respond_to?(:broadcasts)
61
61
 
62
62
  ::Rails.logger.broadcasts.each do |logger|
@@ -12,24 +12,31 @@ 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
15
+ def call(severity, time, progname, msg)
16
16
  attributes_to_log = {
17
17
  severity:,
18
18
  time: format_datetime(time),
19
19
  msg: msg2str(msg)
20
20
  }
21
21
 
22
+ log_location = Thread.current[:scout_log_location]
23
+
22
24
  attributes_to_log[:progname] = progname if progname
23
25
  attributes_to_log['service.name'] = service_name
26
+ attributes_to_log['log_location'] = log_location if log_location
24
27
 
25
28
  attributes_to_log.merge!(scout_transaction_id)
26
29
  attributes_to_log.merge!(scout_layer)
27
30
  attributes_to_log.merge!(scout_context)
28
- # Naive local benchmarks show this takes around 200 microseconds. As such, we only apply it to WARN and above.
29
- attributes_to_log.merge!(local_log_location) if ::Logger::Severity.const_get(severity) >= ::Logger::Severity::WARN
30
31
 
31
- message = "#{attributes_to_log.to_json}\n"
32
+ emit_log(msg, severity, time, attributes_to_log)
33
+
34
+ "#{attributes_to_log.to_json}\n"
35
+ end
36
+
37
+ private
32
38
 
39
+ def emit_log(msg, severity, time, attributes_to_log)
33
40
  ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider.logger(
34
41
  name: 'scout_apm',
35
42
  version: '0.1.0'
@@ -41,11 +48,8 @@ module ScoutApm
41
48
  body: msg,
42
49
  context: ::OpenTelemetry::Context.current
43
50
  )
44
- message
45
51
  end
46
52
 
47
- private
48
-
49
53
  def format_datetime(time)
50
54
  time.utc.strftime(DATETIME_FORMAT)
51
55
  end
@@ -103,15 +107,6 @@ module ScoutApm
103
107
  { "scout_transaction_id": ScoutApm::RequestManager.lookup.transaction_id }
104
108
  end
105
109
 
106
- def local_log_location
107
- # Should give us the last local stack which called the log within just the last couple frames.
108
- last_local_location = caller[0..15].find { |path| path.include?(Rails.root.to_s) }
109
-
110
- return {} unless last_local_location
111
-
112
- { 'log_location' => last_local_location }
113
- end
114
-
115
110
  def context
116
111
  ScoutApm::Logging::Context.new.tap do |context|
117
112
  context.config = ScoutApm::Logging::Config.with_file(context, context.config.value('config_file'))
@@ -7,6 +7,70 @@ module ScoutApm
7
7
  class FileLogger < ::Logger
8
8
  include ::ActiveSupport::LoggerSilence if const_defined?('::ActiveSupport::LoggerSilence')
9
9
 
10
+ def initialize(*args, **kwargs, &block)
11
+ if ScoutApm::Logging::Context.instance.config.value('logs_capture_log_line')
12
+ self.class.send(:alias_method, :debug, :debug_patched)
13
+ self.class.send(:alias_method, :info, :info_patched)
14
+ self.class.send(:alias_method, :warn, :warn_patched)
15
+ self.class.send(:alias_method, :error, :error_patched)
16
+ self.class.send(:alias_method, :fatal, :fatal_patched)
17
+ self.class.send(:alias_method, :unknown, :unknown_patched)
18
+ end
19
+
20
+ super(*args, **kwargs, &block)
21
+ end
22
+
23
+ # Taken from ::Logger. Progname becomes message if no block is given.
24
+ def debug_patched(progname = nil, &block)
25
+ return true if level > DEBUG
26
+
27
+ # short circuit the block to update the message. #add would eventually call it.
28
+ # https://github.com/ruby/logger/blob/v1.7.0/lib/logger.rb#L675
29
+ progname = yield if block_given?
30
+ progname = add_log_file_and_line_to_message(progname) if progname
31
+ add(DEBUG, progname, nil, &block)
32
+ end
33
+
34
+ def info_patched(progname = nil, &block)
35
+ return true if level > INFO
36
+
37
+ progname = yield if block_given?
38
+ progname = add_log_file_and_line_to_message(progname) if progname
39
+ add(INFO, progname, nil, &block)
40
+ end
41
+
42
+ def warn_patched(progname = nil, &block)
43
+ return true if level > WARN
44
+
45
+ progname = yield if block_given?
46
+ progname = add_log_file_and_line_to_message(progname) if progname
47
+ add(WARN, progname, nil, &block)
48
+ end
49
+
50
+ def error_patched(progname = nil, &block)
51
+ return true if level > ERROR
52
+
53
+ progname = yield if block_given?
54
+ progname = add_log_file_and_line_to_message(progname) if progname
55
+ add(ERROR, progname, nil, &block)
56
+ end
57
+
58
+ def fatal_patched(progname = nil, &block)
59
+ return true if level > FATAL
60
+
61
+ progname = yield if block_given?
62
+ progname = add_log_file_and_line_to_message(progname) if progname
63
+ add(FATAL, progname, nil, &block)
64
+ end
65
+
66
+ def unknown_patched(progname = nil, &block)
67
+ return true if level > UNKNOWN
68
+
69
+ progname = yield if block_given?
70
+ progname = add_log_file_and_line_to_message(progname) if progname
71
+ add(UNKNOWN, progname, nil, &block)
72
+ end
73
+
10
74
  # Other loggers may be extended with additional methods that have not been applied to this file logger.
11
75
  # Most likely, these methods will still utilize the exiting logging methods to write to the IO device,
12
76
  # however, if this is not the case we may miss logs. With that being said, we shouldn't impact the original
@@ -14,13 +78,75 @@ module ScoutApm
14
78
  def method_missing(name, *_args)
15
79
  return unless defined?(::Rails)
16
80
 
17
- ::Rails.logger.warn("Method #{name} called on ScoutApm::Logging::Loggers::FileLogger, but it is not defined.")
81
+ return unless ScoutApm::Logging::Context.instance.config.value('logs_method_missing_warning')
82
+
83
+ if ScoutApm::Logging::Context.instance.config.value('logs_method_missing_call_stack')
84
+ cs = caller_locations(0, 20)
85
+ ::Rails.logger.warn("Method #{name} called on ScoutApm::Logging::Loggers::FileLogger, but it is not defined.")
86
+ ::Rails.logger.warn("Call stack: #{cs}")
87
+ else
88
+ ::Rails.logger.warn("Method #{name} called on ScoutApm::Logging::Loggers::FileLogger, but it is not defined. Try setting 'logs_method_missing_call_stack' to true in your Scout configuration to see the call stack.") # rubocop:disable Layout/LineLength
89
+ end
18
90
  end
19
91
 
20
92
  # More impactful for the broadcast logger.
21
93
  def respond_to_missing?(name, *_args)
22
94
  super
23
95
  end
96
+
97
+ private
98
+
99
+ # Useful for testing.
100
+ def filter_log_location(the_call_stack = caller_locations)
101
+ the_call_stack.find { |loc| loc.path.include?(Rails.root.to_s) }
102
+ end
103
+
104
+ # Cache call stack to reduce performance impact.
105
+ def call_stack
106
+ @call_stack ||= caller_locations(4, ScoutApm::Logging::Context.instance.config.value('logs_call_stack_search_depth'))
107
+ end
108
+
109
+ def find_log_location
110
+ filter_log_location(call_stack)
111
+ end
112
+
113
+ def get_call_stack_for_attribute
114
+ call_stack
115
+ .select { |loc| loc.path.include?(Rails.root.to_s) }
116
+ .map(&:to_s)
117
+ .slice(0, ScoutApm::Logging::Context.instance.config.value('logs_call_stack_capture_depth'))
118
+ .join("\n")
119
+ end
120
+
121
+ # Ideally, we would pass an additional argument to the formatter, but
122
+ # we run into issues with how tagged logging is implemented. As such,
123
+ # this is a work around for tagged logging and incorrect passed arguments.
124
+ # May need to move to fiber at some point.
125
+ def format_message(severity, datetime, progname, msg)
126
+ if ScoutApm::Logging::Context.instance.config.value('logs_capture_call_stack')
127
+ Thread.current[:scout_log_location] =
128
+ get_call_stack_for_attribute
129
+ end
130
+
131
+ super(severity, datetime, progname, msg)
132
+
133
+ # Reset for next logger call.
134
+ ensure
135
+ @call_stack = nil
136
+ Thread.current[:scout_log_location] = nil
137
+ end
138
+
139
+ def add_log_file_and_line_to_message(message)
140
+ return message unless message.is_a?(String)
141
+
142
+ file = find_log_location
143
+ return message unless file
144
+
145
+ file_path = file.path.split('/').last
146
+ line_number = file.lineno
147
+
148
+ "[#{file_path}:#{line_number}] #{message}"
149
+ end
24
150
  end
25
151
 
26
152
  # The newly created logger which we can configure, and will log to a filepath.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ScoutApm
4
4
  module Logging
5
- VERSION = '2.0.0'
5
+ VERSION = '2.1.0'
6
6
  end
7
7
  end
@@ -15,9 +15,7 @@ module ScoutApm
15
15
  # If we are in a Rails environment, setup the monitor daemon manager.
16
16
  class RailTie < ::Rails::Railtie
17
17
  initializer 'scout_apm_logging.monitor', after: :initialize_logger, before: :initialize_cache do
18
- context = Context.new
19
- context.config = Config.with_file(context, context.config.value('config_file'))
20
- context.config.log_settings(context.logger)
18
+ context = ScoutApm::Logging::Context.instance
21
19
 
22
20
  Loggers::Capture.new(context).setup!
23
21
  end
@@ -31,4 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.add_development_dependency 'rubocop', '1.50.2'
32
32
  s.add_development_dependency 'rubocop-ast', '1.30.0'
33
33
  s.add_development_dependency 'webmock'
34
+ # Old, but works. It is a small wrapper library around websocket-eventmachine
35
+ # for ActionCable protocols.
36
+ s.add_development_dependency 'action_cable_client'
34
37
  end
@@ -1,10 +1,20 @@
1
1
  require 'webmock/rspec'
2
2
 
3
+ # Old, but still works.
4
+ require 'action_cable_client'
5
+ require 'eventmachine'
3
6
  require 'spec_helper'
4
7
  require 'zlib'
5
8
  require 'stringio'
9
+ require 'securerandom'
6
10
  require_relative '../../rails/app'
7
11
 
12
+ ScoutApm::Logging::Loggers::FileLogger.class_exec do
13
+ define_method(:filter_log_location) do |locations|
14
+ locations.find { |loc| loc.path.include?(Rails.root.to_s) && !loc.path.include?('scout_apm/logging') }
15
+ end
16
+ end
17
+
8
18
  describe ScoutApm::Logging do
9
19
  before do
10
20
  @file_path = '/app/response_body.txt'
@@ -38,6 +48,27 @@ describe ScoutApm::Logging do
38
48
  # Call the app to generate the logs
39
49
  `curl localhost:9292`
40
50
 
51
+ channel_client = fork do
52
+ url = 'ws://localhost:9292/cable'
53
+ channel_name = 'TestChannel'
54
+ # Loops.
55
+ EventMachine.run do
56
+ client = ActionCableClient.new(url, channel_name)
57
+ # called whenever a welcome message is received from the server
58
+ client.connected { puts 'successfully connected.' }
59
+
60
+ client.subscribed do
61
+ puts 'client subscribed to the channel.'
62
+ client.perform('ding', { message: 'hello from client' })
63
+ end
64
+
65
+ # called whenever a message is received from the server
66
+ client.received do |message|
67
+ puts message
68
+ end
69
+ end
70
+ end
71
+
41
72
  sleep 5
42
73
 
43
74
  proxy_dir = context.config.value('logs_proxy_log_dir')
@@ -55,17 +86,16 @@ describe ScoutApm::Logging do
55
86
  end
56
87
 
57
88
  local_messages = lines.map { |item| item['msg'] }
89
+ puts local_messages
58
90
 
59
91
  # Verify we have all the logs in the local log file
60
92
  expect(local_messages.count('[TEST] Some log')).to eq(1)
61
93
  expect(local_messages.count('[YIELD] Yield Test')).to eq(1)
62
94
  expect(local_messages.count('Another Log')).to eq(1)
63
95
  expect(local_messages.count('Should not be captured')).to eq(0)
64
-
65
- log_locations = lines.map { |item| item['log_location'] }.compact
66
-
67
- # Verify that log attributes aren't persisted
68
- expect(log_locations.size).to eq(1)
96
+ expect(local_messages.count('Warn level log')).to eq(1)
97
+ expect(local_messages.count('Error level log')).to eq(1)
98
+ expect(local_messages.count('Fatal level log')).to eq(1)
69
99
 
70
100
  # Verify the logs are sent to the receiver
71
101
  receiver_contents = File.readlines(@file_path, chomp: true)
@@ -73,9 +103,21 @@ describe ScoutApm::Logging do
73
103
  expect(receiver_contents.count('[YIELD] Yield Test')).to eq(1)
74
104
  expect(receiver_contents.count('Another Log')).to eq(1)
75
105
  expect(receiver_contents.count('Should not be captured')).to eq(0)
106
+ expect(local_messages.count('Warn level log')).to eq(1)
107
+ expect(local_messages.count('Error level log')).to eq(1)
108
+ expect(local_messages.count('Fatal level log')).to eq(1)
109
+
110
+ # Verify we recorded server ActionCable messages
111
+ expect(receiver_contents.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
112
+ expect(receiver_contents.count('Subscribed to test_channel')).to eq(1)
113
+ expect(receiver_contents.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
114
+ expect(local_messages.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
115
+ expect(local_messages.count('Subscribed to test_channel')).to eq(1)
116
+ expect(local_messages.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
76
117
 
77
118
  # Kill the rails process. We use kill as using any other signal throws a long log line.
78
119
  Process.kill('KILL', rails_pid)
120
+ Process.kill('KILL', channel_client)
79
121
  end
80
122
 
81
123
  private
data/spec/rails/app.rb CHANGED
@@ -5,6 +5,7 @@ rescue LoadError # rubocop:disable Lint/SuppressedException
5
5
  end
6
6
 
7
7
  require 'action_controller/railtie'
8
+ require 'action_cable/engine'
8
9
  require 'logger'
9
10
  require 'scout_apm_logging'
10
11
 
@@ -13,24 +14,58 @@ Rails.logger = ActiveSupport::TaggedLogging.new(Logger.new($stdout))
13
14
  class App < ::Rails::Application
14
15
  config.eager_load = false
15
16
  config.log_level = :info
17
+ config.action_cable.cable = { 'adapter' => 'async' }
18
+ config.action_cable.connection_class = -> { ApplicationCable::Connection }
19
+ config.action_cable.disable_request_forgery_protection = true
16
20
 
17
21
  routes.append do
22
+ mount ActionCable.server => '/cable'
18
23
  root to: 'root#index'
19
24
  end
20
25
  end
21
26
 
22
27
  class RootController < ActionController::Base
23
- def index
24
- Rails.logger.warn('Add location log attributes')
28
+ def index # rubocop:disable Metrics/AbcSize
25
29
  Rails.logger.tagged('TEST').info('Some log')
26
30
  Rails.logger.tagged('YIELD') { logger.info('Yield Test') }
27
31
  Rails.logger.info('Another Log')
28
32
  Rails.logger.debug('Should not be captured')
33
+ Rails.logger.warn('Warn level log')
34
+ Rails.logger.error('Error level log')
35
+ Rails.logger.fatal('Fatal level log')
29
36
 
30
37
  render plain: Rails.version
31
38
  end
32
39
  end
33
40
 
41
+ module ApplicationCable
42
+ class Channel < ActionCable::Channel::Base
43
+ end
44
+ end
45
+
46
+ module ApplicationCable
47
+ class Connection < ActionCable::Connection::Base
48
+ identified_by :id
49
+
50
+ def connect
51
+ self.id = SecureRandom.uuid
52
+ logger.info("ActionCable Connected: #{id}")
53
+ end
54
+ end
55
+ end
56
+
57
+ class TestChannel < ApplicationCable::Channel
58
+ def subscribed
59
+ stream_from 'test_channel'
60
+ logger.info 'Subscribed to test_channel'
61
+ end
62
+
63
+ def ding(data)
64
+ logger.info "Ding received with data: #{data.inspect}"
65
+ transmit({ dong: "Server response to: '#{data['message']}'" })
66
+ end
67
+ end
68
+
34
69
  def initialize_app
35
70
  App.initialize!
36
71
 
@@ -0,0 +1,95 @@
1
+ require 'logger'
2
+ require 'stringio'
3
+
4
+ require 'spec_helper'
5
+
6
+ require 'scout_apm_logging'
7
+
8
+ ScoutApm::Logging::Loggers::Formatter.class_exec do
9
+ define_method(:emit_log) do |msg, severity, time, attributes_to_log|
10
+ end
11
+ end
12
+
13
+ def capture_stdout
14
+ old_stdout = $stdout
15
+ $stdout = StringIO.new
16
+ yield
17
+ $stdout.string
18
+ ensure
19
+ $stdout = old_stdout
20
+ end
21
+
22
+ describe ScoutApm::Logging::Loggers::Logger do
23
+ it 'should not capture call stack or log line' do
24
+ ScoutApm::Logging::Context.instance
25
+
26
+ output_from_log = capture_stdout do
27
+ logger = ScoutApm::Logging::Loggers::FileLogger.new($stdout).tap do |instance|
28
+ instance.level = 0
29
+ instance.formatter = ScoutApm::Logging::Loggers::Formatter.new
30
+ end
31
+
32
+ logger.info('Hi')
33
+ end
34
+
35
+ expect(output_from_log).not_to include('"log_location":"')
36
+ expect(output_from_log).not_to include('"msg":"[logger_spec.rb')
37
+ end
38
+
39
+ it 'should capture call stack' do
40
+ ENV['SCOUT_LOGS_CAPTURE_CALL_STACK'] = 'true'
41
+ ScoutApm::Logging::Context.instance
42
+
43
+ output_from_log = capture_stdout do
44
+ logger = ScoutApm::Logging::Loggers::FileLogger.new($stdout).tap do |instance|
45
+ instance.level = 0
46
+ instance.formatter = ScoutApm::Logging::Loggers::Formatter.new
47
+ end
48
+
49
+ logger.info('Hi')
50
+ end
51
+
52
+ expect(output_from_log).to include('"log_location":"')
53
+ expect(output_from_log).not_to include('"msg":"[logger_spec.rb')
54
+ ENV['SCOUT_LOGS_CAPTURE_LOG_LINE'] = 'false' # set back to default
55
+ end
56
+
57
+ it 'should capture log line and call stack' do
58
+ ENV['SCOUT_LOGS_CAPTURE_CALL_STACK'] = 'true'
59
+ ENV['SCOUT_LOGS_CAPTURE_LOG_LINE'] = 'true'
60
+
61
+ ScoutApm::Logging::Context.instance
62
+
63
+ output_from_log = capture_stdout do
64
+ logger = ScoutApm::Logging::Loggers::FileLogger.new($stdout).tap do |instance|
65
+ instance.level = 0
66
+ instance.formatter = ScoutApm::Logging::Loggers::Formatter.new
67
+ end
68
+
69
+ logger.info('Hi')
70
+ end
71
+
72
+ expect(output_from_log).to include('"msg":"[logger_spec.rb')
73
+ expect(output_from_log).to include('"log_location":"')
74
+ ENV['SCOUT_LOGS_CAPTURE_CALL_STACK'] = 'false' # set back to default
75
+ ENV['SCOUT_LOGS_CAPTURE_LOG_LINE'] = 'false' # set back to default
76
+ end
77
+
78
+ it 'should capture log line' do
79
+ ENV['SCOUT_LOGS_CAPTURE_LOG_LINE'] = 'true'
80
+ ScoutApm::Logging::Context.instance
81
+
82
+ output_from_log = capture_stdout do
83
+ logger = ScoutApm::Logging::Loggers::FileLogger.new($stdout).tap do |instance|
84
+ instance.level = 0
85
+ instance.formatter = ScoutApm::Logging::Loggers::Formatter.new
86
+ end
87
+
88
+ logger.info('Hi')
89
+ end
90
+
91
+ expect(output_from_log).not_to include('"log_location":"')
92
+ expect(output_from_log).to include('"msg":"[logger_spec.rb')
93
+ ENV['SCOUT_LOGS_CAPTURE_LOG_LINE'] = 'false' # set back to default
94
+ end
95
+ 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: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scout APM
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-28 00:00:00.000000000 Z
11
+ date: 2025-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: googleapis-common-protos-types
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: action_cable_client
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
195
209
  description: Sets up log monitoring for Scout APM Ruby clients.
196
210
  email:
197
211
  - support@scoutapp.com
@@ -235,6 +249,7 @@ files:
235
249
  - spec/spec_helper.rb
236
250
  - spec/unit/config_spec.rb
237
251
  - spec/unit/loggers/capture_spec.rb
252
+ - spec/unit/loggers/logger_spec.rb
238
253
  homepage: https://github.com/scoutapp/scout_apm_ruby_logging
239
254
  licenses:
240
255
  - MIT
@@ -266,3 +281,4 @@ test_files:
266
281
  - spec/spec_helper.rb
267
282
  - spec/unit/config_spec.rb
268
283
  - spec/unit/loggers/capture_spec.rb
284
+ - spec/unit/loggers/logger_spec.rb