scout_apm_logging 1.0.3 → 1.2.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 +18 -3
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +15 -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 +12 -17
- data/lib/scout_apm/logging/loggers/logger.rb +127 -1
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +102 -93
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +1 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +56 -1
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +1 -1
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_limits.rb +49 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +2 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +7 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +15 -3
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +2 -2
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +1 -0
- data/lib/scout_apm/logging/version.rb +1 -1
- data/lib/scout_apm_logging.rb +1 -3
- data/scout_apm_logging.gemspec +4 -1
- data/spec/integration/rails/lifecycle_spec.rb +49 -5
- data/spec/rails/app.rb +37 -2
- data/spec/unit/loggers/logger_spec.rb +95 -0
- metadata +20 -3
@@ -12,6 +12,9 @@ module ScoutApm
|
|
12
12
|
module Logs
|
13
13
|
# The SDK implementation of OpenTelemetry::Logs::LoggerProvider.
|
14
14
|
class LoggerProvider < OpenTelemetry::Logs::LoggerProvider
|
15
|
+
Key = Struct.new(:name, :version)
|
16
|
+
private_constant(:Key)
|
17
|
+
|
15
18
|
UNEXPECTED_ERROR_MESSAGE = 'unexpected error in ' \
|
16
19
|
'OpenTelemetry::SDK::Logs::LoggerProvider#%s'
|
17
20
|
|
@@ -21,13 +24,18 @@ module ScoutApm
|
|
21
24
|
#
|
22
25
|
# @param [optional Resource] resource The resource to associate with
|
23
26
|
# new LogRecords created by {Logger}s created by this LoggerProvider.
|
27
|
+
# @param [optional LogRecordLimits] log_record_limits The limits for
|
28
|
+
# attributes count and attribute length for LogRecords.
|
24
29
|
#
|
25
30
|
# @return [OpenTelemetry::SDK::Logs::LoggerProvider]
|
26
|
-
def initialize(resource: OpenTelemetry::SDK::Resources::Resource.create)
|
31
|
+
def initialize(resource: OpenTelemetry::SDK::Resources::Resource.create, log_record_limits: LogRecordLimits::DEFAULT)
|
27
32
|
@log_record_processors = []
|
33
|
+
@log_record_limits = log_record_limits
|
28
34
|
@mutex = Mutex.new
|
29
35
|
@resource = resource
|
30
36
|
@stopped = false
|
37
|
+
@registry = {}
|
38
|
+
@registry_mutex = Mutex.new
|
31
39
|
end
|
32
40
|
|
33
41
|
# Returns an {OpenTelemetry::SDK::Logs::Logger} instance.
|
@@ -44,7 +52,9 @@ module ScoutApm
|
|
44
52
|
"invalid name. Name provided: #{name.inspect}")
|
45
53
|
end
|
46
54
|
|
47
|
-
|
55
|
+
@registry_mutex.synchronize do
|
56
|
+
@registry[Key.new(name, version)] ||= Logger.new(name, version, self)
|
57
|
+
end
|
48
58
|
end
|
49
59
|
|
50
60
|
# Adds a new log record processor to this LoggerProvider's
|
@@ -134,6 +144,7 @@ module ScoutApm
|
|
134
144
|
trace_flags: nil,
|
135
145
|
instrumentation_scope: nil,
|
136
146
|
context: nil)
|
147
|
+
return if @stopped
|
137
148
|
|
138
149
|
log_record = LogRecord.new(timestamp: timestamp,
|
139
150
|
observed_timestamp: observed_timestamp,
|
@@ -145,7 +156,8 @@ module ScoutApm
|
|
145
156
|
span_id: span_id,
|
146
157
|
trace_flags: trace_flags,
|
147
158
|
resource: @resource,
|
148
|
-
instrumentation_scope: instrumentation_scope
|
159
|
+
instrumentation_scope: instrumentation_scope,
|
160
|
+
log_record_limits: @log_record_limits)
|
149
161
|
|
150
162
|
@log_record_processors.each { |processor| processor.on_emit(log_record, context) }
|
151
163
|
end
|
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
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.authors = 'Scout APM'
|
8
8
|
s.email = ['support@scoutapp.com']
|
9
9
|
s.homepage = 'https://github.com/scoutapp/scout_apm_ruby_logging'
|
10
|
-
s.summary = 'Ruby
|
10
|
+
s.summary = 'Managed log monitoring for Ruby applications.'
|
11
11
|
s.description = 'Sets up log monitoring for Scout APM Ruby clients.'
|
12
12
|
s.license = 'MIT'
|
13
13
|
|
@@ -29,4 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_development_dependency 'rubocop', '1.50.2'
|
30
30
|
s.add_development_dependency 'rubocop-ast', '1.30.0'
|
31
31
|
s.add_development_dependency 'webmock'
|
32
|
+
# Old, but works. It is a small wrapper library around websocket-eventmachine
|
33
|
+
# for ActionCable protocols.
|
34
|
+
s.add_development_dependency 'action_cable_client'
|
32
35
|
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')
|
@@ -54,18 +85,19 @@ describe ScoutApm::Logging do
|
|
54
85
|
end
|
55
86
|
end
|
56
87
|
|
88
|
+
puts lines
|
89
|
+
|
57
90
|
local_messages = lines.map { |item| item['msg'] }
|
91
|
+
puts local_messages
|
58
92
|
|
59
93
|
# Verify we have all the logs in the local log file
|
60
94
|
expect(local_messages.count('[TEST] Some log')).to eq(1)
|
61
95
|
expect(local_messages.count('[YIELD] Yield Test')).to eq(1)
|
62
96
|
expect(local_messages.count('Another Log')).to eq(1)
|
63
97
|
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)
|
98
|
+
expect(local_messages.count('Warn level log')).to eq(1)
|
99
|
+
expect(local_messages.count('Error level log')).to eq(1)
|
100
|
+
expect(local_messages.count('Fatal level log')).to eq(1)
|
69
101
|
|
70
102
|
# Verify the logs are sent to the receiver
|
71
103
|
receiver_contents = File.readlines(@file_path, chomp: true)
|
@@ -73,9 +105,21 @@ describe ScoutApm::Logging do
|
|
73
105
|
expect(receiver_contents.count('[YIELD] Yield Test')).to eq(1)
|
74
106
|
expect(receiver_contents.count('Another Log')).to eq(1)
|
75
107
|
expect(receiver_contents.count('Should not be captured')).to eq(0)
|
108
|
+
expect(local_messages.count('Warn level log')).to eq(1)
|
109
|
+
expect(local_messages.count('Error level log')).to eq(1)
|
110
|
+
expect(local_messages.count('Fatal level log')).to eq(1)
|
111
|
+
|
112
|
+
# Verify we recorded server ActionCable messages
|
113
|
+
expect(receiver_contents.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
|
114
|
+
expect(receiver_contents.count('Subscribed to test_channel')).to eq(1)
|
115
|
+
expect(receiver_contents.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
|
116
|
+
expect(local_messages.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
|
117
|
+
expect(local_messages.count('Subscribed to test_channel')).to eq(1)
|
118
|
+
expect(local_messages.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
|
76
119
|
|
77
120
|
# Kill the rails process. We use kill as using any other signal throws a long log line.
|
78
121
|
Process.kill('KILL', rails_pid)
|
122
|
+
Process.kill('KILL', channel_client)
|
79
123
|
end
|
80
124
|
|
81
125
|
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: 1.0
|
4
|
+
version: 1.2.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
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: action_cable_client
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
description: Sets up log monitoring for Scout APM Ruby clients.
|
168
182
|
email:
|
169
183
|
- support@scoutapp.com
|
@@ -208,6 +222,7 @@ files:
|
|
208
222
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb
|
209
223
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb
|
210
224
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb
|
225
|
+
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_limits.rb
|
211
226
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb
|
212
227
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb
|
213
228
|
- lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb
|
@@ -229,6 +244,7 @@ files:
|
|
229
244
|
- spec/spec_helper.rb
|
230
245
|
- spec/unit/config_spec.rb
|
231
246
|
- spec/unit/loggers/capture_spec.rb
|
247
|
+
- spec/unit/loggers/logger_spec.rb
|
232
248
|
homepage: https://github.com/scoutapp/scout_apm_ruby_logging
|
233
249
|
licenses:
|
234
250
|
- MIT
|
@@ -251,7 +267,7 @@ requirements: []
|
|
251
267
|
rubygems_version: 3.3.26
|
252
268
|
signing_key:
|
253
269
|
specification_version: 4
|
254
|
-
summary: Ruby
|
270
|
+
summary: Managed log monitoring for Ruby applications.
|
255
271
|
test_files:
|
256
272
|
- spec/data/config_test_1.yml
|
257
273
|
- spec/data/mock_config.yml
|
@@ -260,3 +276,4 @@ test_files:
|
|
260
276
|
- spec/spec_helper.rb
|
261
277
|
- spec/unit/config_spec.rb
|
262
278
|
- spec/unit/loggers/capture_spec.rb
|
279
|
+
- spec/unit/loggers/logger_spec.rb
|