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 +4 -4
- data/.github/workflows/test.yml +1 -1
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +12 -0
- data/lib/scout_apm/logging/config.rb +26 -15
- data/lib/scout_apm/logging/context.rb +8 -0
- data/lib/scout_apm/logging/loggers/capture.rb +1 -1
- data/lib/scout_apm/logging/loggers/formatter.rb +11 -16
- data/lib/scout_apm/logging/loggers/logger.rb +127 -1
- data/lib/scout_apm/logging/version.rb +1 -1
- data/lib/scout_apm_logging.rb +1 -3
- data/scout_apm_logging.gemspec +3 -0
- data/spec/integration/rails/lifecycle_spec.rb +47 -5
- data/spec/rails/app.rb +37 -2
- data/spec/unit/loggers/logger_spec.rb +95 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a472edfe7271717a5eb8b5c76f1dda7a84dc1b8ad2f0da4c6223173f87c44db
|
4
|
+
data.tar.gz: 1eaf6f5de049078460287b0b02e09583cad2db3fc31c3b54a7d83930d5fca787
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f74a53847327e6068ad671b8f2363516971b6ee04f9aea1f2c47d5ea069eae3ba70ea5b36be17c3bd57e2d378cd3c3835c6a229d1ea825793c8456585630c4a1
|
7
|
+
data.tar.gz: b3de4ed980c0129c615574b6e55e1bb245df9f85d515a3dba0fad8ebc2fe692cd99c9aeff2b96a47b4efb24fbe34fbbc540d78fcc96a048b25fab84ef6363b29
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
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
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
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
|
-
'
|
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 =
|
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)
|
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
|
-
|
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
|
-
|
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.
|
data/lib/scout_apm_logging.rb
CHANGED
@@ -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.
|
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
|
data/scout_apm_logging.gemspec
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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
|