scout_apm_logging 0.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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