scout_apm_logging 0.0.0.1

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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +37 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +65 -0
  5. data/Dockerfile +18 -0
  6. data/Gemfile +5 -0
  7. data/README.md +58 -0
  8. data/Rakefile +35 -0
  9. data/bin/scout_apm_logging_monitor +5 -0
  10. data/gems/rails.gemfile +3 -0
  11. data/lib/scout_apm/logging/config.rb +265 -0
  12. data/lib/scout_apm/logging/context.rb +58 -0
  13. data/lib/scout_apm/logging/logger.rb +26 -0
  14. data/lib/scout_apm/logging/loggers/capture.rb +46 -0
  15. data/lib/scout_apm/logging/loggers/formatter.rb +86 -0
  16. data/lib/scout_apm/logging/loggers/logger.rb +82 -0
  17. data/lib/scout_apm/logging/loggers/proxy.rb +39 -0
  18. data/lib/scout_apm/logging/loggers/swap.rb +82 -0
  19. data/lib/scout_apm/logging/monitor/collector/checksum.rb +51 -0
  20. data/lib/scout_apm/logging/monitor/collector/configuration.rb +148 -0
  21. data/lib/scout_apm/logging/monitor/collector/downloader.rb +78 -0
  22. data/lib/scout_apm/logging/monitor/collector/extractor.rb +37 -0
  23. data/lib/scout_apm/logging/monitor/collector/manager.rb +57 -0
  24. data/lib/scout_apm/logging/monitor/monitor.rb +214 -0
  25. data/lib/scout_apm/logging/monitor_manager/manager.rb +150 -0
  26. data/lib/scout_apm/logging/state.rb +70 -0
  27. data/lib/scout_apm/logging/utils.rb +86 -0
  28. data/lib/scout_apm/logging/version.rb +7 -0
  29. data/lib/scout_apm_logging.rb +35 -0
  30. data/scout_apm_logging.gemspec +27 -0
  31. data/spec/data/config_test_1.yml +27 -0
  32. data/spec/data/empty_logs_config.yml +0 -0
  33. data/spec/data/logs_config.yml +3 -0
  34. data/spec/data/mock_config.yml +29 -0
  35. data/spec/data/state_file.json +3 -0
  36. data/spec/integration/loggers/capture_spec.rb +78 -0
  37. data/spec/integration/monitor/collector/downloader/will_verify_checksum.rb +47 -0
  38. data/spec/integration/monitor/collector_healthcheck_spec.rb +27 -0
  39. data/spec/integration/monitor/continuous_state_collector_spec.rb +29 -0
  40. data/spec/integration/monitor/previous_collector_setup_spec.rb +42 -0
  41. data/spec/integration/monitor_manager/disable_agent_spec.rb +28 -0
  42. data/spec/integration/monitor_manager/monitor_pid_file_spec.rb +36 -0
  43. data/spec/integration/monitor_manager/single_monitor_spec.rb +53 -0
  44. data/spec/integration/rails/lifecycle_spec.rb +29 -0
  45. data/spec/spec_helper.rb +65 -0
  46. data/spec/unit/config_spec.rb +25 -0
  47. data/spec/unit/loggers/capture_spec.rb +64 -0
  48. data/spec/unit/monitor/collector/configuration_spec.rb +64 -0
  49. data/spec/unit/state_spec.rb +20 -0
  50. data/tooling/checksums.rb +106 -0
  51. metadata +167 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 198bf309d17f0abfcf2c33f820a2d44e1e92ef5af9e321ee9fbaacfec256f032
4
+ data.tar.gz: 2a09b14a45e702fe1e4f4caee6ff89475e14971ba9edad41ae5cf3c72ab97850
5
+ SHA512:
6
+ metadata.gz: ebd30ceac50e2f14f3693946e8dd883112cc502513678b3a9963e253f2622d1ea488c2a66bce01f3e20726d7c6f08f61392cde6f22f1d9d0412987780f6399fa
7
+ data.tar.gz: c0cfeb3cfbed2628772550b18b1a2f7965a144fafea14846ed65936043ae283b48cef6ba0109a05dfe9df3b826080d0f24676c0957b3909311cae12098b1e2de
@@ -0,0 +1,37 @@
1
+ name: Test
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ name: RSpec
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3' ]
12
+ env:
13
+ DOCKER_RUBY_VERSION: ${{ matrix.ruby }}
14
+ BUNDLE_GEMFILE: gems/rails.gemfile
15
+ runs-on: ubuntu-20.04
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ bundler-cache: true
23
+ - name: Run RSpec
24
+ run: bundle exec rake test
25
+
26
+ rubocop:
27
+ name: RuboCop
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v2
31
+ - name: Set up Ruby
32
+ uses: ruby/setup-ruby@v1
33
+ with:
34
+ ruby-version: "3.2"
35
+ bundler-cache: true
36
+ - name: Run check
37
+ run: bundle exec rubocop
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ # Ignore built gems.
2
+ *.gem
3
+ # Ignore lock files.
4
+ Gemfile.lock
5
+ gems/*.lock
6
+ # Ignore .idea
7
+ .idea
8
+ # Ignore log files
9
+ log/*.log
10
+ # Ignore checksum files
11
+ tooling/*
12
+ !tooling/*.md
13
+ !tooling/*.rb
data/.rubocop.yml ADDED
@@ -0,0 +1,65 @@
1
+ AllCops:
2
+ Exclude:
3
+ - "bin/*"
4
+ - "tmp/**/*"
5
+ - "vendor/**/*"
6
+ - "spec/spec_helper.rb"
7
+ - "gems/*"
8
+ - "Rakefile"
9
+ - "tooling/*"
10
+ NewCops: disable
11
+ TargetRubyVersion: 2.6
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - "spec/**/*"
16
+
17
+ Metrics/ClassLength:
18
+ Max: 150
19
+
20
+ Style/FrozenStringLiteralComment:
21
+ EnforcedStyle: always
22
+ Exclude:
23
+ - "Gemfile*"
24
+ - "*.gemspec"
25
+ - "spec/**/*"
26
+
27
+ Naming/FileName:
28
+ Exclude:
29
+ - "lib/scout_apm/scout_apm_logging.rb"
30
+ - "spec/scout_apm_logging_spec.rb"
31
+
32
+ Style/Documentation:
33
+ Enabled: true
34
+
35
+ Naming/PredicateName:
36
+ Enabled: false
37
+
38
+ Metrics/PerceivedComplexity:
39
+ Enabled: false
40
+
41
+ Metrics/CyclomaticComplexity:
42
+ Enabled: false
43
+
44
+ Style/ClassVars:
45
+ Enabled: false
46
+
47
+ Style/EmptyElse:
48
+ Enabled: false
49
+
50
+ Naming/AccessorMethodName:
51
+ Enabled: false
52
+
53
+ Metrics/MethodLength:
54
+ Max: 25
55
+
56
+ Lint/LiteralAsCondition:
57
+ Enabled: false
58
+
59
+ Layout/LineLength:
60
+ Max: 130
61
+
62
+ Style/PercentLiteralDelimiters:
63
+ PreferredDelimiters:
64
+ '%w': "[]"
65
+ '%W': "[]"
data/Dockerfile ADDED
@@ -0,0 +1,18 @@
1
+ ARG RUBY_VERSION=3.3
2
+ FROM ruby:$RUBY_VERSION
3
+
4
+ # Set the working directory inside the container
5
+ WORKDIR /app
6
+
7
+ ARG BUNDLE_GEMFILE=./gems/rails.gemfile
8
+
9
+ # # Copy the entire project directory into the container
10
+ COPY . .
11
+
12
+ # Remove any local .lock files after copying
13
+ RUN find . -type f -name "*.lock" -exec rm -f {} \;
14
+
15
+ # Install dependencies
16
+ RUN bundle install
17
+
18
+ ENV BUNDLE_GEMFILE=./gems/rails.gemfile
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake', '~> 13.0'
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # ScoutApm Ruby Logging
2
+
3
+ [![Build Status](https://github.com/scoutapp/scout_apm_ruby_logging/actions/workflows/test.yml/badge.svg)](https://github.com/scoutapp/scout_apm_ruby_logging/actions)
4
+
5
+ A Ruby gem for detailed, easy to navigate, managed log monitoring.
6
+
7
+ Sign up for an account at https://www.scoutapm.com to start monitoring your logs and application performance in minutes.
8
+
9
+ ## Getting Started
10
+ Add the gem to your Gemfile:
11
+
12
+ ```ruby
13
+ gem 'scout_apm_logging'
14
+ ```
15
+
16
+ Update your Gemfile:
17
+ ```ruby
18
+ bundle install
19
+ ```
20
+
21
+ Update your [RAILS_ROOT/config/scout_apm.yml](https://scoutapm.com/apps/new_ruby_application_configuration) and add the following:
22
+
23
+ ```yaml
24
+ # ... Previous &defaults or environment defined configurations
25
+
26
+ # ENV equivalent: SCOUT_LOGS_MONITOR=true
27
+ # ENV equivalent: SCOUT_LOGS_INGEST_KEY=...
28
+
29
+ logs_monitor: true
30
+ logs_ingest_key: ...
31
+ ```
32
+
33
+ Deploy :rocket:
34
+
35
+ ## Testing
36
+ To run the entire test suite:
37
+ ```ruby
38
+ bundle exec rake test
39
+ ```
40
+
41
+ To run an individual test file within the suite:
42
+ ```ruby
43
+ bundle exec rake test file=/path/to/spec/_spec.rb
44
+ ```
45
+
46
+ To run test(s) against a specific Ruby version:
47
+ ```ruby
48
+ DOCKER_RUBY_VERSION=3.3 bundle exec rake test
49
+ ```
50
+
51
+ ## Local
52
+ Point your Gemfile at your local checkout:
53
+ ```ruby
54
+ gem 'scout_apm_logging', path: '/path/to/scout_apm_ruby_logging'
55
+ ```
56
+
57
+ ## Help
58
+ Email support@scoutapm.com if you need a hand.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ task :default => :test
2
+
3
+ task :test do
4
+ ruby_version = ENV.has_key?("DOCKER_RUBY_VERSION") ? ENV["DOCKER_RUBY_VERSION"] : "3.3"
5
+
6
+ unless system("docker image inspect rspec-runner-#{ruby_version} > /dev/null 2>&1")
7
+ puts "Building RSpec runner Docker image..."
8
+ system("docker build --build-arg RUBY_VERSION=#{ruby_version} -t rspec-runner-#{ruby_version} .")
9
+ end
10
+
11
+ additional_options = ENV["debug"] ? "-it" : ""
12
+
13
+ if ENV["file"]
14
+ puts "Running RSpec test for #{ENV["file"]}..."
15
+ system("docker run -v #{Dir.pwd}/lib:/app/lib -v #{Dir.pwd}/spec:/app/spec #{additional_options} --rm rspec-runner-#{ruby_version} bundle exec rspec #{ENV["file"]} 2>&1")
16
+ else
17
+ puts "Running RSpec tests..."
18
+ Dir.glob("spec/**/*_spec.rb") do |spec_file|
19
+ puts "Running #{spec_file}..."
20
+ system("docker run -v #{Dir.pwd}/lib:/app/lib -v #{Dir.pwd}/spec:/app/spec #{additional_options} --rm rspec-runner-#{ruby_version} bundle exec rspec #{spec_file} 2>&1")
21
+
22
+ # Exit the task if a test fails
23
+ unless $?.success?
24
+ abort("Test failed: #{spec_file}")
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ task :access_container do
31
+ puts "Accessing a new rspec-runner-#{ruby_version} container..."
32
+
33
+ ruby_version = ENV.has_key?("DOCKER_RUBY_VERSION") ? ENV["DOCKER_RUBY_VERSION"] : "3.3"
34
+ system("docker run -it --rm -v #{Dir.pwd}/lib:/app/lib -v #{Dir.pwd}/spec:/app/spec --entrypoint /bin/bash rspec-runner-#{ruby_version}")
35
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/scout_apm/logging/monitor/monitor.rb"
4
+
5
+ ScoutApm::Logging::Monitor.instance.setup!
@@ -0,0 +1,3 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem "rails"
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Valid Config Options:
4
+ #
5
+ # This list is complete, but some are for developers of
6
+ # scout_apm_logging itself. See the documentation at https://scoutapm.com/docs for
7
+ # customer-focused documentation.
8
+ #
9
+ # config_file - location of the scout_apm.yml configuration file
10
+ # log_level - log level for the internal library itself
11
+ # log_stdout - true or false. If true, log to STDOUT
12
+ # log_stderr - true or false. If true, log to STDERR
13
+ # log_file_path - either a directory or "STDOUT"
14
+ # log_class - the underlying class to use for logging. Defaults to Ruby's Logger class
15
+ # logs_monitor - true or false. If true, monitor logs
16
+ # logs_monitored - an array of log file paths to monitor. Overrides the default log destination detection
17
+ # logs_ingest_key - the ingest key to use for logs
18
+ # logs_capture_level - the minimum log level to start capturing logs for
19
+ # logs_config - a hash of configuration options for merging into the collector's config
20
+ # logs_reporting_endpoint - the endpoint to send logs to
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
35
+ #
36
+ # Any of these config settings can be set with an environment variable prefixed
37
+ # by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
38
+
39
+ module ScoutApm
40
+ module Logging
41
+ # Holds the configuration values for Scout APM Logging.
42
+ class Config < ScoutApm::Config
43
+ KNOWN_CONFIG_OPTIONS = %w[
44
+ config_file
45
+ log_level
46
+ log_stderr
47
+ log_stdout
48
+ log_file_path
49
+ log_class
50
+ logs_monitor
51
+ logs_monitored
52
+ logs_ingest_key
53
+ logs_capture_level
54
+ logs_config
55
+ logs_reporting_endpoint
56
+ logs_proxy_log_dir
57
+ manager_lock_file
58
+ monitor_pid_file
59
+ monitor_state_file
60
+ monitor_interval
61
+ monitor_interval_delay
62
+ collector_sending_queue_storage_dir
63
+ collector_offset_storage_dir
64
+ collector_pid_file
65
+ collector_download_dir
66
+ collector_config_file
67
+ collector_log_level
68
+ collector_version
69
+ health_check_port
70
+ ].freeze
71
+
72
+ SETTING_COERCIONS = {
73
+ 'logs_monitor' => BooleanCoercion.new,
74
+ 'logs_monitored' => JsonCoercion.new,
75
+ 'monitor_interval' => IntegerCoercion.new,
76
+ 'monitor_interval_delay' => IntegerCoercion.new,
77
+ 'health_check_port' => IntegerCoercion.new
78
+ }.freeze
79
+
80
+ # The bootstrapped, and initial config that we attach to the context. Will be swapped out by
81
+ # both the monitor and manager on initialization to the one with a file (which also has the dynamic
82
+ # and state configs).
83
+ def self.without_file(context)
84
+ overlays = [
85
+ ConfigEnvironment.new,
86
+ ConfigDefaults.new,
87
+ ConfigNull.new
88
+ ]
89
+ new(context, overlays)
90
+ end
91
+
92
+ def self.with_file(context, file_path = nil, config = {})
93
+ overlays = [
94
+ ConfigEnvironment.new,
95
+ ConfigFile.new(context, file_path, config),
96
+ ConfigDynamic.new,
97
+ ConfigState.new(context),
98
+ ConfigDefaults.new,
99
+ ConfigNull.new
100
+ ]
101
+ new(context, overlays)
102
+ end
103
+
104
+ def state
105
+ @overlays.find { |overlay| overlay.is_a? ConfigState }
106
+ end
107
+
108
+ def value(key)
109
+ unless KNOWN_CONFIG_OPTIONS.include?(key)
110
+ logger.debug("Requested looking up a unknown configuration key: #{key} (not a problem. Evaluate and add to config.rb)")
111
+ end
112
+
113
+ o = overlay_for_key(key)
114
+ raw_value = if o
115
+ o.value(key)
116
+ else
117
+ # No overlay said it could handle this key, bail out with nil.
118
+ nil
119
+ end
120
+
121
+ coercion = SETTING_COERCIONS.fetch(key, NullCoercion.new)
122
+ coercion.coerce(raw_value)
123
+ end
124
+
125
+ def all_settings
126
+ KNOWN_CONFIG_OPTIONS.inject([]) do |memo, key|
127
+ o = overlay_for_key(key)
128
+ memo << { key: key, value: value(key).inspect, source: o.name }
129
+ end
130
+ end
131
+
132
+ # Dynamically set state based on the application.
133
+ class ConfigDynamic
134
+ @values_to_set = {
135
+ 'health_check_port': nil
136
+ }
137
+
138
+ class << self
139
+ attr_reader :values_to_set
140
+
141
+ def set_value(key, value)
142
+ @values_to_set[key] = value
143
+ end
144
+ end
145
+
146
+ def value(key)
147
+ self.class.values_to_set[key]
148
+ end
149
+
150
+ def has_key?(key)
151
+ self.class.values_to_set.key?(key)
152
+ end
153
+
154
+ def name
155
+ 'dynamic'
156
+ end
157
+ end
158
+
159
+ # State that is persisted and communicated upon by multiple processes.
160
+ class ConfigState
161
+ @values_to_set = {
162
+ 'logs_monitored': [],
163
+ 'health_check_port': nil
164
+ }
165
+
166
+ class << self
167
+ attr_reader :values_to_set
168
+
169
+ def set_value(key, value)
170
+ @values_to_set[key] = value
171
+ end
172
+
173
+ def get_values_to_set
174
+ @values_to_set.keys.map(&:to_s)
175
+ end
176
+ end
177
+
178
+ attr_reader :context, :state
179
+
180
+ def initialize(context)
181
+ @context = context
182
+
183
+ # Note, the config on the context we are passing in here comes from the Config.without_file. We
184
+ # won't be aware of a state file that was defined in a config file, but this would be a very
185
+ # rare thing to have happen as this is more of an internal config value.
186
+ @state = State.new(context)
187
+
188
+ set_values_from_state
189
+ end
190
+
191
+ def value(key)
192
+ self.class.values_to_set[key]
193
+ end
194
+
195
+ def has_key?(key)
196
+ self.class.values_to_set.key?(key)
197
+ end
198
+
199
+ def name
200
+ 'state'
201
+ end
202
+
203
+ def flush_state!
204
+ state.flush_to_file!
205
+ end
206
+
207
+ def add_log_locations!(updated_log_locations)
208
+ state.flush_to_file!(updated_log_locations)
209
+ end
210
+
211
+ private
212
+
213
+ def set_values_from_state
214
+ data = state.load_state_from_file
215
+
216
+ return unless data
217
+
218
+ data.each do |key, value|
219
+ self.class.set_value(key, value)
220
+ end
221
+ end
222
+ end
223
+
224
+ # Defaults in case no config file has been found.
225
+ class ConfigDefaults
226
+ DEFAULTS = {
227
+ 'log_level' => 'info',
228
+ 'logs_monitored' => [],
229
+ 'logs_capture_level' => 'debug',
230
+ 'logs_reporting_endpoint' => 'https://otlp.telemetryhub.com:4317',
231
+ 'logs_proxy_log_dir' => '/tmp/scout_apm/logs/',
232
+ 'manager_lock_file' => '/tmp/scout_apm/monitor_lock_file.lock',
233
+ 'monitor_pid_file' => '/tmp/scout_apm/scout_apm_log_monitor.pid',
234
+ 'monitor_state_file' => '/tmp/scout_apm/scout_apm_log_monitor_state.json',
235
+ 'monitor_interval' => 60,
236
+ 'monitor_interval_delay' => 60,
237
+ 'collector_log_level' => 'error',
238
+ 'collector_offset_storage_dir' => '/tmp/scout_apm/file_storage/receiver/',
239
+ 'collector_sending_queue_storage_dir' => '/tmp/scout_apm/file_storage/otc/',
240
+ 'collector_pid_file' => '/tmp/scout_apm/scout_apm_otel_collector.pid',
241
+ 'collector_download_dir' => '/tmp/scout_apm/',
242
+ 'collector_config_file' => '/tmp/scout_apm/config.yml',
243
+ 'collector_version' => '0.102.1'
244
+ }.freeze
245
+
246
+ def value(key)
247
+ DEFAULTS[key]
248
+ end
249
+
250
+ def has_key?(key)
251
+ DEFAULTS.key?(key)
252
+ end
253
+
254
+ # Dyanmic/computed values are here, but not counted as user specified.
255
+ def any_keys_found?
256
+ false
257
+ end
258
+
259
+ def name
260
+ 'defaults'
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ # Contains context around Scout APM logging, such as environment, configuration, and the logger.
6
+ class Context
7
+ # The root of the application.
8
+ attr_accessor :application_root
9
+
10
+ # Initially start up without attempting to load a configuration file. We
11
+ # need to be able to lookup configuration options like "application_root"
12
+ # which would then in turn influence where the yaml configuration file is
13
+ # located
14
+ #
15
+ # Later in initialization, we set config= to include the file.
16
+ def initialize
17
+ @logger = LoggerFactory.build_minimal_logger
18
+ end
19
+
20
+ def config
21
+ @config ||= Config.without_file(self)
22
+ end
23
+
24
+ def environment
25
+ @environment ||= ScoutApm::Environment.instance
26
+ end
27
+
28
+ def logger
29
+ @logger ||= LoggerFactory.build(config, environment, application_root)
30
+ end
31
+
32
+ def config=(config)
33
+ @config = config
34
+
35
+ @logger = nil
36
+ end
37
+ end
38
+
39
+ # Create a logger based on the configuration settings.
40
+ class LoggerFactory
41
+ def self.build(config, environment, application_root = nil)
42
+ root = application_root || environment.root
43
+ Logger.new(root,
44
+ {
45
+ log_level: config.value('log_level'),
46
+ log_file_path: config.value('log_file_path'),
47
+ stdout: config.value('log_stdout') || environment.platform_integration.log_to_stdout?,
48
+ stderr: config.value('log_stderr'),
49
+ logger_class: config.value('log_class')
50
+ })
51
+ end
52
+
53
+ def self.build_minimal_logger
54
+ Logger.new(nil, stdout: true)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScoutApm
4
+ module Logging
5
+ # Custom internal logger for ScoutApm Logging.
6
+ class Logger < ScoutApm::Logger
7
+ private
8
+
9
+ def determine_log_destination
10
+ case true
11
+ when stdout?
12
+ $stdout
13
+ when stderr?
14
+ $stderr
15
+ when validate_path(@opts[:log_file])
16
+ @opts[:log_file]
17
+ when validate_path("#{log_file_path}/scout_apm_logging.log")
18
+ "#{log_file_path}/scout_apm_logging.log"
19
+ else
20
+ # Safe fallback
21
+ $stdout
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative './swap'
6
+ require_relative './proxy'
7
+
8
+ module ScoutApm
9
+ module Logging
10
+ module Loggers
11
+ # Will capture the log destinations from the application's loggers.
12
+ class Capture
13
+ attr_reader :context
14
+
15
+ def initialize(context)
16
+ @context = context
17
+ end
18
+
19
+ def capture_log_locations! # rubocop:disable Metrics/AbcSize
20
+ logger_instances << Rails.logger if defined?(Rails)
21
+ logger_instances << Sidekiq.logger if defined?(Sidekiq)
22
+ logger_instances << ObjectSpace.each_object(::ScoutTestLogger).to_a if defined?(::ScoutTestLogger)
23
+
24
+ # Swap in our logger for each logger instance, in conjunction with the original class.
25
+ updated_log_locations = logger_instances.compact.flatten.map do |logger|
26
+ swapped_in_location(logger)
27
+ end
28
+
29
+ context.config.state.add_log_locations!(updated_log_locations)
30
+ end
31
+
32
+ private
33
+
34
+ def logger_instances
35
+ @logger_instances ||= []
36
+ end
37
+
38
+ def swapped_in_location(log_instance)
39
+ swap = Swap.new(context, log_instance)
40
+ swap.update_logger!
41
+ swap.log_location
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end