scout_apm 3.0.0.pre13 → 3.0.0.pre14

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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/scout_apm.rb +20 -10
  3. data/lib/scout_apm/agent.rb +114 -319
  4. data/lib/scout_apm/agent/exit_handler.rb +66 -0
  5. data/lib/scout_apm/agent/preconditions.rb +69 -0
  6. data/lib/scout_apm/agent_context.rb +234 -0
  7. data/lib/scout_apm/app_server_load.rb +24 -14
  8. data/lib/scout_apm/background_job_integrations/resque.rb +7 -8
  9. data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -2
  10. data/lib/scout_apm/background_recorder.rb +8 -3
  11. data/lib/scout_apm/background_worker.rb +14 -7
  12. data/lib/scout_apm/config.rb +35 -26
  13. data/lib/scout_apm/context.rb +11 -4
  14. data/lib/scout_apm/db_query_metric_set.rb +17 -5
  15. data/lib/scout_apm/debug.rb +1 -1
  16. data/lib/scout_apm/environment.rb +10 -14
  17. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  18. data/lib/scout_apm/git_revision.rb +13 -8
  19. data/lib/scout_apm/histogram.rb +1 -1
  20. data/lib/scout_apm/instant/middleware.rb +7 -7
  21. data/lib/scout_apm/instant_reporting.rb +7 -7
  22. data/lib/scout_apm/instrument_manager.rb +87 -0
  23. data/lib/scout_apm/instruments/action_controller_rails_2.rb +12 -7
  24. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +17 -12
  25. data/lib/scout_apm/instruments/action_view.rb +11 -7
  26. data/lib/scout_apm/instruments/active_record.rb +25 -11
  27. data/lib/scout_apm/instruments/elasticsearch.rb +10 -6
  28. data/lib/scout_apm/instruments/grape.rb +12 -8
  29. data/lib/scout_apm/instruments/http_client.rb +10 -6
  30. data/lib/scout_apm/instruments/influxdb.rb +10 -6
  31. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -5
  32. data/lib/scout_apm/instruments/middleware_summary.rb +11 -5
  33. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  34. data/lib/scout_apm/instruments/moped.rb +11 -6
  35. data/lib/scout_apm/instruments/net_http.rb +10 -6
  36. data/lib/scout_apm/instruments/percentile_sampler.rb +8 -6
  37. data/lib/scout_apm/instruments/process/process_cpu.rb +8 -4
  38. data/lib/scout_apm/instruments/process/process_memory.rb +15 -10
  39. data/lib/scout_apm/instruments/rails_router.rb +12 -6
  40. data/lib/scout_apm/instruments/redis.rb +10 -6
  41. data/lib/scout_apm/instruments/samplers.rb +11 -0
  42. data/lib/scout_apm/instruments/sinatra.rb +5 -4
  43. data/lib/scout_apm/layaway.rb +21 -20
  44. data/lib/scout_apm/layaway_file.rb +8 -3
  45. data/lib/scout_apm/layer.rb +3 -3
  46. data/lib/scout_apm/layer_converters/converter_base.rb +6 -7
  47. data/lib/scout_apm/layer_converters/database_converter.rb +1 -1
  48. data/lib/scout_apm/layer_converters/histograms.rb +2 -2
  49. data/lib/scout_apm/layer_converters/slow_job_converter.rb +4 -3
  50. data/lib/scout_apm/layer_converters/slow_request_converter.rb +5 -4
  51. data/lib/scout_apm/logger.rb +143 -0
  52. data/lib/scout_apm/middleware.rb +7 -9
  53. data/lib/scout_apm/periodic_work.rb +28 -0
  54. data/lib/scout_apm/reporter.rb +14 -8
  55. data/lib/scout_apm/reporting.rb +135 -0
  56. data/lib/scout_apm/request_manager.rb +4 -6
  57. data/lib/scout_apm/serializers/payload_serializer.rb +1 -1
  58. data/lib/scout_apm/slow_job_policy.rb +6 -2
  59. data/lib/scout_apm/slow_job_record.rb +5 -5
  60. data/lib/scout_apm/slow_request_policy.rb +6 -2
  61. data/lib/scout_apm/slow_transaction.rb +5 -5
  62. data/lib/scout_apm/store.rb +22 -16
  63. data/lib/scout_apm/synchronous_recorder.rb +7 -3
  64. data/lib/scout_apm/tasks/doctor.rb +75 -0
  65. data/lib/scout_apm/tasks/support.rb +22 -0
  66. data/lib/scout_apm/tracer.rb +5 -5
  67. data/lib/scout_apm/tracked_request.rb +43 -19
  68. data/lib/scout_apm/utils/active_record_metric_name.rb +66 -8
  69. data/lib/scout_apm/utils/backtrace_parser.rb +1 -1
  70. data/lib/scout_apm/utils/installed_gems.rb +7 -3
  71. data/lib/scout_apm/utils/klass_helper.rb +8 -2
  72. data/lib/scout_apm/utils/scm.rb +1 -1
  73. data/lib/scout_apm/utils/sql_sanitizer.rb +3 -3
  74. data/lib/scout_apm/version.rb +1 -1
  75. data/lib/tasks/doctor.rake +11 -0
  76. data/scout_apm.gemspec +1 -0
  77. data/test/test_helper.rb +17 -2
  78. data/test/unit/agent_test.rb +1 -54
  79. data/test/unit/config_test.rb +9 -5
  80. data/test/unit/context_test.rb +4 -4
  81. data/test/unit/db_query_metric_set_test.rb +11 -4
  82. data/test/unit/fake_store_test.rb +1 -1
  83. data/test/unit/git_revision_test.rb +3 -3
  84. data/test/unit/instruments/net_http_test.rb +2 -1
  85. data/test/unit/instruments/percentile_sampler_test.rb +5 -9
  86. data/test/unit/layaway_test.rb +10 -5
  87. data/test/unit/layer_converters/metric_converter_test.rb +2 -2
  88. data/test/unit/slow_request_policy_test.rb +7 -3
  89. data/test/unit/sql_sanitizer_test.rb +0 -6
  90. data/test/unit/store_test.rb +11 -8
  91. data/test/unit/utils/active_record_metric_name_test.rb +45 -7
  92. metadata +27 -5
  93. data/lib/scout_apm/agent/logging.rb +0 -74
  94. data/lib/scout_apm/agent/reporting.rb +0 -129
  95. data/lib/scout_apm/utils/null_logger.rb +0 -13
@@ -1,22 +1,26 @@
1
1
  module ScoutApm
2
2
  module Instruments
3
3
  class Redis
4
- attr_reader :logger
4
+ attr_reader :context
5
5
 
6
- def initalize(logger=ScoutApm::Agent.instance.logger)
7
- @logger = logger
6
+ def initialize(context)
7
+ @context = context
8
8
  @installed = false
9
9
  end
10
10
 
11
+ def logger
12
+ context.logger
13
+ end
14
+
11
15
  def installed?
12
16
  @installed
13
17
  end
14
18
 
15
19
  def install
16
- @installed = true
17
-
18
20
  if defined?(::Redis) && defined?(::Redis::Client)
19
- ScoutApm::Agent.instance.logger.info "Instrumenting Redis"
21
+ @installed = true
22
+
23
+ logger.info "Instrumenting Redis"
20
24
 
21
25
  ::Redis::Client.class_eval do
22
26
  include ScoutApm::Tracer
@@ -0,0 +1,11 @@
1
+ module ScoutApm
2
+ module Instruments
3
+ class Samplers
4
+ DEFAULT_SAMPLERS = [
5
+ ScoutApm::Instruments::Process::ProcessCpu,
6
+ ScoutApm::Instruments::Process::ProcessMemory,
7
+ ScoutApm::Instruments::PercentileSampler,
8
+ ]
9
+ end
10
+ end
11
+ end
@@ -1,9 +1,10 @@
1
+ # XXX: Is this file used?
1
2
  module ScoutApm
2
3
  module Instruments
3
4
  class Sinatra
4
5
  attr_reader :logger
5
6
 
6
- def initalize(logger=ScoutApm::Agent.instance.logger)
7
+ def initalize(logger=ScoutApm::Agent.instance.context.logger)
7
8
  @logger = logger
8
9
  @installed = false
9
10
  end
@@ -13,10 +14,10 @@ module ScoutApm
13
14
  end
14
15
 
15
16
  def install
16
- @installed = true
17
-
18
17
  if defined?(::Sinatra) && defined?(::Sinatra::Base) && ::Sinatra::Base.private_method_defined?(:dispatch!)
19
- ScoutApm::Agent.instance.logger.info "Instrumenting Sinatra"
18
+ @installed = true
19
+
20
+ logger.info "Instrumenting Sinatra"
20
21
  ::Sinatra::Base.class_eval do
21
22
  include ScoutApm::Tracer
22
23
  include ScoutApm::Instruments::SinatraInstruments
@@ -18,12 +18,13 @@ module ScoutApm
18
18
  # Must be sortable as an integer
19
19
  TIME_FORMAT = "%Y%m%d%H%M"
20
20
 
21
- attr_accessor :config
22
- attr_reader :environment
21
+ attr_reader :context
22
+ def initialize(context)
23
+ @context = context
24
+ end
23
25
 
24
- def initialize(config, environment)
25
- @config = config
26
- @environment = environment
26
+ def logger
27
+ context.logger
27
28
  end
28
29
 
29
30
  # Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
@@ -34,27 +35,27 @@ module ScoutApm
34
35
  def directory
35
36
  return @directory if @directory
36
37
 
37
- data_file = config.value("data_file")
38
+ data_file = context.config.value("data_file")
38
39
  data_file = File.dirname(data_file) if data_file && !File.directory?(data_file)
39
40
 
40
41
  candidates = [
41
42
  data_file,
42
- "#{environment.root}/tmp",
43
+ "#{context.environment.root}/tmp",
43
44
  "/tmp"
44
45
  ].compact
45
46
 
46
47
  found = candidates.detect { |dir| File.writable?(dir) }
47
- ScoutApm::Agent.instance.logger.debug("Storing Layaway Files in #{found}")
48
+ logger.debug("Storing Layaway Files in #{found}")
48
49
  @directory = Pathname.new(found)
49
50
  end
50
51
 
51
52
  def write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT)
52
53
  if at_layaway_file_limit?(files_limit)
53
- ScoutApm::Agent.instance.logger.error("Hit layaway file limit. Not writing to layaway file")
54
+ logger.error("Hit layaway file limit. Not writing to layaway file")
54
55
  return false
55
56
  end
56
57
  filename = file_for(reporting_period.timestamp)
57
- layaway_file = LayawayFile.new(filename)
58
+ layaway_file = LayawayFile.new(context, filename)
58
59
  layaway_file.write(reporting_period)
59
60
  end
60
61
 
@@ -74,28 +75,28 @@ module ScoutApm
74
75
  begin
75
76
  if f
76
77
  begin
77
- ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")
78
+ logger.debug("Obtained Reporting Lock")
78
79
 
79
80
  log_layaway_file_information
80
81
 
81
82
  files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
82
- rps = files.map{ |layaway| LayawayFile.new(layaway).load }.compact
83
+ rps = files.map{ |layaway| LayawayFile.new(context, layaway).load }.compact
83
84
  if rps.any?
84
85
  yield rps
85
86
 
86
- ScoutApm::Agent.instance.logger.debug("Deleting the now-reported layaway files for #{timestamp.to_s}")
87
+ logger.debug("Deleting the now-reported layaway files for #{timestamp.to_s}")
87
88
  delete_files_for(timestamp) # also removes the coodinator_file
88
89
 
89
- ScoutApm::Agent.instance.logger.debug("Checking for any Stale layaway files")
90
+ logger.debug("Checking for any Stale layaway files")
90
91
  delete_stale_files(timestamp.to_time - STALE_AGE)
91
92
  else
92
93
  File.unlink(coordinator_file)
93
- ScoutApm::Agent.instance.logger.debug("No layaway files to report")
94
+ logger.debug("No layaway files to report")
94
95
  end
95
96
 
96
97
  true
97
98
  rescue Exception => e
98
- ScoutApm::Agent.instance.logger.debug("Caught an exception in with_claim, with the coordination file locked: #{e.message}, #{e.backtrace.inspect}")
99
+ logger.debug("Caught an exception in with_claim, with the coordination file locked: #{e.message}, #{e.backtrace.inspect}")
99
100
  raise
100
101
  ensure
101
102
  # Unlock the file when done!
@@ -111,7 +112,7 @@ module ScoutApm
111
112
 
112
113
  def delete_files_for(timestamp)
113
114
  all_files_for(timestamp).each { |layaway|
114
- ScoutApm::Agent.instance.logger.debug("Deleting layaway file: #{layaway}")
115
+ logger.debug("Deleting layaway file: #{layaway}")
115
116
  File.unlink(layaway)
116
117
  }
117
118
  end
@@ -122,10 +123,10 @@ module ScoutApm
122
123
  compact.
123
124
  uniq.
124
125
  select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
125
- tap { |timestamps| ScoutApm::Agent.instance.logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
126
+ tap { |timestamps| logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
126
127
  map { |timestamp| delete_files_for(timestamp) }
127
128
  rescue => e
128
- ScoutApm::Agent.instance.logger.debug("Problem deleting stale files: #{e.message}, #{e.backtrace.inspect}")
129
+ logger.debug("Problem deleting stale files: #{e.message}, #{e.backtrace.inspect}")
129
130
  end
130
131
 
131
132
  private
@@ -192,7 +193,7 @@ module ScoutApm
192
193
  ]
193
194
 
194
195
 
195
- ScoutApm::Agent.instance.logger.debug("Total in #{directory}: #{files_in_temp}. Total Layaway Files: #{all_filenames.size}. By Timestamp: #{count_per_timestamp.inspect}")
196
+ logger.debug("Total in #{directory}: #{files_in_temp}. Total Layaway Files: #{all_filenames.size}. By Timestamp: #{count_per_timestamp.inspect}")
196
197
  end
197
198
  end
198
199
  end
@@ -2,18 +2,23 @@
2
2
  module ScoutApm
3
3
  class LayawayFile
4
4
  attr_reader :path
5
+ attr_reader :context
5
6
 
6
- def initialize(path)
7
+ def initialize(context, path)
7
8
  @path = path
8
9
  end
9
10
 
11
+ def logger
12
+ context.logger
13
+ end
14
+
10
15
  def load
11
16
  data = File.open(path, "r") { |f| read_raw(f) }
12
17
  deserialize(data)
13
18
  rescue NameError, ArgumentError, TypeError => e
14
19
  # Marshal error
15
- ScoutApm::Agent.instance.logger.info("Unable to load data from Layaway file, resetting.")
16
- ScoutApm::Agent.instance.logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
20
+ logger.info("Unable to load data from Layaway file, resetting.")
21
+ logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
17
22
  nil
18
23
  end
19
24
 
@@ -138,7 +138,7 @@ module ScoutApm
138
138
  # In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
139
139
  def caller_array
140
140
  # omits the first several callers which are in the ScoutAPM stack.
141
- if ScoutApm::Environment.instance.ruby_2?
141
+ if ScoutApm::Agent.instance.context.environment.ruby_2?
142
142
  caller(3...BACKTRACE_CALLER_LIMIT)
143
143
  else
144
144
  caller[3...BACKTRACE_CALLER_LIMIT]
@@ -152,7 +152,7 @@ module ScoutApm
152
152
  end
153
153
 
154
154
  def start_sampling
155
- if ScoutApm::Agent.instance.config.value('profile') && traced?
155
+ if ScoutApm::Agent.instance.context.config.value('profile') && traced?
156
156
  ScoutApm::Instruments::Stacks.update_indexes(frame_index, trace_index)
157
157
  ScoutApm::Instruments::Stacks.start_sampling
158
158
  else
@@ -161,7 +161,7 @@ module ScoutApm
161
161
  end
162
162
 
163
163
  def record_traces!
164
- if ScoutApm::Agent.instance.config.value('profile')
164
+ if ScoutApm::Agent.instance.context.config.value('profile')
165
165
  ScoutApm::Instruments::Stacks.stop_sampling(false)
166
166
  if traced?
167
167
  traces.raw_traces = ScoutApm::Instruments::Stacks.profile_frames
@@ -2,11 +2,13 @@ module ScoutApm
2
2
  module LayerConverters
3
3
  class ConverterBase
4
4
 
5
+ attr_reader :context
5
6
  attr_reader :request
6
7
  attr_reader :root_layer
7
8
  attr_reader :layer_finder
8
9
 
9
- def initialize(request, layer_finder, store=nil)
10
+ def initialize(context, request, layer_finder, store=nil)
11
+ @context = context
10
12
  @request = request
11
13
  @layer_finder = layer_finder
12
14
  @store = store
@@ -218,17 +220,14 @@ module ScoutApm
218
220
 
219
221
  # Debug logging for scoutprof traces
220
222
  def debug_scoutprof(layer)
221
- agent = ScoutApm::Agent.instance
222
- config = agent.config
223
-
224
223
  if layer.type =~ %r{^(Controller|Queue|Job)$}.freeze
225
- if config.value('profile')
226
- agent.logger.debug do
224
+ if context.config.value('profile')
225
+ context.logger.debug do
227
226
  traces_inspect = layer.traces.inspect
228
227
  "****** Slow Request #{layer.type} Traces (#{layer.name}, tet: #{layer.total_exclusive_time}, tct: #{layer.total_call_time}), total raw traces: #{layer.traces.cube.total_count}, total clean traces: #{layer.traces.total_count}, skipped gc: #{layer.traces.skipped_in_gc}, skipped handler: #{layer.traces.skipped_in_handler}, skipped registered #{layer.traces.skipped_in_job_registered}, skipped not_running #{layer.traces.skipped_in_not_running}:\n#{traces_inspect}"
229
228
  end
230
229
  else
231
- agent.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
230
+ context.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
232
231
  end
233
232
  end
234
233
  end
@@ -3,7 +3,7 @@ module ScoutApm
3
3
  class DatabaseConverter < ConverterBase
4
4
  def initialize(*)
5
5
  super
6
- @db_query_metric_set = DbQueryMetricSet.new
6
+ @db_query_metric_set = DbQueryMetricSet.new(context)
7
7
  end
8
8
 
9
9
  def register_hooks(walker)
@@ -4,8 +4,8 @@ module ScoutApm
4
4
  # Updates immediate and long-term histograms for both job and web requests
5
5
  def record!
6
6
  if request.unique_name != :unknown
7
- ScoutApm::Agent.instance.request_histograms.add(request.unique_name, root_layer.total_call_time)
8
- ScoutApm::Agent.instance.request_histograms_by_time[@store.current_timestamp].
7
+ context.request_histograms.add(request.unique_name, root_layer.total_call_time)
8
+ context.request_histograms_by_time[@store.current_timestamp].
9
9
  add(request.unique_name, root_layer.total_call_time)
10
10
  end
11
11
  end
@@ -10,7 +10,7 @@ module ScoutApm
10
10
  ###################
11
11
  def record!
12
12
  return nil unless request.job?
13
- @points = ScoutApm::Agent.instance.slow_job_policy.score(request)
13
+ @points = context.slow_job_policy.score(request)
14
14
 
15
15
  # Let the store know we're here, and if it wants our data, it will call
16
16
  # back into #call
@@ -29,10 +29,10 @@ module ScoutApm
29
29
  return nil unless layer_finder.queue
30
30
  return nil unless layer_finder.job
31
31
 
32
- ScoutApm::Agent.instance.slow_job_policy.stored!(request)
32
+ context.slow_job_policy.stored!(request)
33
33
 
34
34
  # record the change in memory usage
35
- mem_delta = ScoutApm::Instruments::Process::ProcessMemory.rss_to_mb(request.capture_mem_delta!)
35
+ mem_delta = ScoutApm::Instruments::Process::ProcessMemory.new(context).rss_to_mb(request.capture_mem_delta!)
36
36
 
37
37
  timing_metrics, allocation_metrics = create_metrics
38
38
 
@@ -41,6 +41,7 @@ module ScoutApm
41
41
  end
42
42
 
43
43
  SlowJobRecord.new(
44
+ context,
44
45
  queue_layer.name,
45
46
  job_layer.name,
46
47
  root_layer.stop_time,
@@ -6,7 +6,7 @@ module ScoutApm
6
6
  ###################
7
7
  def record!
8
8
  return nil unless request.web?
9
- @points = ScoutApm::Agent.instance.slow_request_policy.score(request)
9
+ @points = context.slow_request_policy.score(request)
10
10
 
11
11
  # Let the store know we're here, and if it wants our data, it will call
12
12
  # back into #call
@@ -25,10 +25,10 @@ module ScoutApm
25
25
  return nil unless request.web?
26
26
  return nil unless scope_layer
27
27
 
28
- ScoutApm::Agent.instance.slow_request_policy.stored!(request)
28
+ context.slow_request_policy.stored!(request)
29
29
 
30
30
  # record the change in memory usage
31
- mem_delta = ScoutApm::Instruments::Process::ProcessMemory.rss_to_mb(@request.capture_mem_delta!)
31
+ mem_delta = ScoutApm::Instruments::Process::ProcessMemory.new(context).rss_to_mb(@request.capture_mem_delta!)
32
32
 
33
33
  uri = request.annotations[:uri] || ""
34
34
 
@@ -38,7 +38,8 @@ module ScoutApm
38
38
  allocation_metrics = {}
39
39
  end
40
40
 
41
- SlowTransaction.new(uri,
41
+ SlowTransaction.new(context,
42
+ uri,
42
43
  scope_layer.legacy_metric_name,
43
44
  root_layer.total_call_time,
44
45
  timing_metrics,
@@ -0,0 +1,143 @@
1
+ # Pass in the "root" of the application you're using.
2
+ # - Rails.root
3
+ # - `Dir.pwd`
4
+ #
5
+ # Currently Valid opts:
6
+ # :force => Boolean - used to reinitialize logger.
7
+ # :log_file_path => String - explicitly set what file to send the log to
8
+ # :stdout => true - explicitly force the log to write to stdout (if set, ignore log_file_path)
9
+ # :stderr => true - explicitly force the log to write to stderr (if set, ignore log_file_path)
10
+ # :logger_class => Class or String - a class to use as the underlying logger. Defaults to Ruby's Logger. See notes
11
+ # :log_level => symbol, string, or integer - defualts to INFO level
12
+ #
13
+ # The :logger_class option
14
+ # - allows any class to be used as the underlying logger. Currently requires to respond to:
15
+ # - debug, info, warn, error, fatal in both string and block form.
16
+ # - #level= with a number (0 = debug, 1 = info, 2= warn, 3=error, 4=fatal)
17
+ # - #formatter= that takes a Ruby Logger::Formatter class. This method must be here, but the value may be ignored
18
+ #
19
+ #config.value('log_level').downcase
20
+ #
21
+ module ScoutApm
22
+ class Logger
23
+ attr_reader :log_destination
24
+
25
+ def initialize(environment_root, opts={})
26
+ @opts = opts
27
+ @environment_root = environment_root
28
+
29
+ @log_destination = determine_log_destination
30
+ @logger = build_logger
31
+ self.log_level = log_level_from_opts
32
+ @logger.formatter = build_formatter
33
+ end
34
+
35
+ # Delegate calls to the underlying logger
36
+ def debug(*args, &block); @logger.debug(*args, &block); end
37
+ def info(*args, &block); @logger.info(*args, &block); end
38
+ def warn(*args, &block); @logger.warn(*args, &block); end
39
+ def error(*args, &block); @logger.error(*args, &block); end
40
+ def fatal(*args, &block); @logger.fatal(*args, &block); end
41
+
42
+ def log_level=(level)
43
+ @logger.level = level
44
+ end
45
+
46
+ def log_file_path
47
+ @opts.fetch(:log_file_path, "#{@environment_root}/log") || "#{@environment_root}/log"
48
+ end
49
+
50
+ def stdout?
51
+ @opts[:stdout] || @opts[:log_file_path] == "STDOUT"
52
+ end
53
+
54
+ def stderr?
55
+ @opts[:stderr] || @opts[:log_file_path] == "STDERR"
56
+ end
57
+
58
+ private
59
+
60
+ def build_logger
61
+ logger_class.new(@log_destination)
62
+ end
63
+
64
+ def logger_class
65
+ klass = @opts.fetch(:logger_class, ::Logger)
66
+ case klass
67
+ when String
68
+ result = KlassHelper.lookup(klass)
69
+ if result == :missing_class
70
+ ::Logger
71
+ else
72
+ result
73
+ end
74
+ when Class
75
+ klass
76
+ else
77
+ ::Logger
78
+ end
79
+ end
80
+
81
+ def build_formatter
82
+ if stdout? || stderr?
83
+ TaggedFormatter.new
84
+ else
85
+ DefaultFormatter.new
86
+ end
87
+ end
88
+
89
+ def log_level_from_opts
90
+ case @opts[:log_level]
91
+ when "debug" then ::Logger::DEBUG
92
+ when "info" then ::Logger::INFO
93
+ when "warn" then ::Logger::WARN
94
+ when "error" then ::Logger::ERROR
95
+ when "fatal" then ::Logger::FATAL
96
+ else ::Logger::INFO
97
+ end
98
+ end
99
+
100
+ def determine_log_destination
101
+ case true
102
+ when stdout?
103
+ STDOUT
104
+ when stderr?
105
+ STDERR
106
+ when validate_path(@opts[:log_file])
107
+ @opts[:log_file]
108
+ when validate_path("#{log_file_path}/scout_apm.log")
109
+ "#{log_file_path}/scout_apm.log"
110
+ else
111
+ # Safe fallback
112
+ STDOUT
113
+ end
114
+ end
115
+
116
+ # Check if this path is ok for a log file.
117
+ # Does it exist?
118
+ # Is it writable?
119
+ # XXX: Implement
120
+ def validate_path(candidate)
121
+ !candidate.nil?
122
+ end
123
+
124
+
125
+ class DefaultFormatter < ::Logger::Formatter
126
+ def call(severity, time, progname, msg)
127
+ # since STDOUT isn't exclusive like the scout_apm.log file, apply a prefix.
128
+ # XXX: Pass in context to the formatter
129
+ "[#{Utils::Time.to_s(time)} #{ScoutApm::Agent.instance.context.environment.hostname} (#{$$})] #{severity} : #{msg}\n"
130
+ end
131
+ end
132
+
133
+ # since STDOUT & STDERR isn't only used for ScoutApm logging, apply a
134
+ # prefix to make it easily greppable
135
+ class TaggedFormatter < DefaultFormatter
136
+ TAG = "[Scout] "
137
+
138
+ def call(severity, time, progname, msg)
139
+ TAG + super
140
+ end
141
+ end
142
+ end
143
+ end