scout_apm 2.3.5 → 2.4.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +0 -23
  3. data/lib/scout_apm.rb +21 -10
  4. data/lib/scout_apm/agent.rb +98 -336
  5. data/lib/scout_apm/agent/exit_handler.rb +64 -0
  6. data/lib/scout_apm/agent/preconditions.rb +69 -0
  7. data/lib/scout_apm/agent_context.rb +226 -0
  8. data/lib/scout_apm/app_server_load.rb +20 -18
  9. data/lib/scout_apm/background_job_integrations/resque.rb +7 -8
  10. data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -8
  11. data/lib/scout_apm/background_recorder.rb +8 -3
  12. data/lib/scout_apm/background_worker.rb +14 -7
  13. data/lib/scout_apm/config.rb +35 -29
  14. data/lib/scout_apm/context.rb +11 -4
  15. data/lib/scout_apm/db_query_metric_set.rb +17 -5
  16. data/lib/scout_apm/debug.rb +1 -1
  17. data/lib/scout_apm/environment.rb +10 -14
  18. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  19. data/lib/scout_apm/git_revision.rb +13 -8
  20. data/lib/scout_apm/histogram.rb +1 -1
  21. data/lib/scout_apm/instant/middleware.rb +7 -7
  22. data/lib/scout_apm/instant_reporting.rb +7 -7
  23. data/lib/scout_apm/instrument_manager.rb +87 -0
  24. data/lib/scout_apm/instruments/action_controller_rails_2.rb +12 -7
  25. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +16 -11
  26. data/lib/scout_apm/instruments/action_view.rb +11 -7
  27. data/lib/scout_apm/instruments/active_record.rb +28 -51
  28. data/lib/scout_apm/instruments/elasticsearch.rb +10 -6
  29. data/lib/scout_apm/instruments/grape.rb +12 -8
  30. data/lib/scout_apm/instruments/http_client.rb +11 -10
  31. data/lib/scout_apm/instruments/influxdb.rb +10 -6
  32. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -5
  33. data/lib/scout_apm/instruments/middleware_summary.rb +11 -5
  34. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  35. data/lib/scout_apm/instruments/moped.rb +11 -6
  36. data/lib/scout_apm/instruments/net_http.rb +11 -9
  37. data/lib/scout_apm/instruments/percentile_sampler.rb +8 -6
  38. data/lib/scout_apm/instruments/process/process_cpu.rb +8 -4
  39. data/lib/scout_apm/instruments/process/process_memory.rb +15 -10
  40. data/lib/scout_apm/instruments/rails_router.rb +12 -6
  41. data/lib/scout_apm/instruments/redis.rb +10 -6
  42. data/lib/scout_apm/instruments/samplers.rb +11 -0
  43. data/lib/scout_apm/instruments/sinatra.rb +5 -4
  44. data/lib/scout_apm/layaway.rb +26 -39
  45. data/lib/scout_apm/layaway_file.rb +8 -3
  46. data/lib/scout_apm/layer.rb +1 -1
  47. data/lib/scout_apm/layer_converters/converter_base.rb +4 -2
  48. data/lib/scout_apm/layer_converters/database_converter.rb +1 -1
  49. data/lib/scout_apm/layer_converters/histograms.rb +2 -2
  50. data/lib/scout_apm/layer_converters/slow_job_converter.rb +4 -3
  51. data/lib/scout_apm/layer_converters/slow_request_converter.rb +5 -4
  52. data/lib/scout_apm/logger.rb +143 -0
  53. data/lib/scout_apm/middleware.rb +7 -9
  54. data/lib/scout_apm/periodic_work.rb +28 -0
  55. data/lib/scout_apm/remote/server.rb +0 -2
  56. data/lib/scout_apm/reporter.rb +14 -8
  57. data/lib/scout_apm/reporting.rb +135 -0
  58. data/lib/scout_apm/request_manager.rb +4 -7
  59. data/lib/scout_apm/serializers/payload_serializer.rb +1 -1
  60. data/lib/scout_apm/slow_job_policy.rb +6 -2
  61. data/lib/scout_apm/slow_job_record.rb +5 -5
  62. data/lib/scout_apm/slow_request_policy.rb +6 -2
  63. data/lib/scout_apm/slow_transaction.rb +5 -5
  64. data/lib/scout_apm/store.rb +22 -16
  65. data/lib/scout_apm/synchronous_recorder.rb +7 -3
  66. data/lib/scout_apm/tasks/doctor.rb +75 -0
  67. data/lib/scout_apm/tasks/support.rb +22 -0
  68. data/lib/scout_apm/tracer.rb +5 -5
  69. data/lib/scout_apm/tracked_request.rb +23 -35
  70. data/lib/scout_apm/utils/backtrace_parser.rb +1 -1
  71. data/lib/scout_apm/utils/installed_gems.rb +7 -3
  72. data/lib/scout_apm/utils/klass_helper.rb +8 -2
  73. data/lib/scout_apm/utils/scm.rb +1 -1
  74. data/lib/scout_apm/utils/sql_sanitizer.rb +4 -6
  75. data/lib/scout_apm/version.rb +1 -1
  76. data/lib/tasks/doctor.rake +11 -0
  77. data/test/test_helper.rb +15 -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. metadata +15 -7
  92. data/lib/scout_apm/agent/logging.rb +0 -74
  93. data/lib/scout_apm/agent/reporting.rb +0 -129
  94. data/lib/scout_apm/utils/null_logger.rb +0 -13
@@ -1,19 +1,25 @@
1
1
  module ScoutApm
2
2
  module Instruments
3
3
  class RailsRouter
4
- def initalize(logger=ScoutApm::Agent.instance.logger)
5
- @logger = logger
4
+ attr_reader :context
5
+
6
+ def initialize(context)
7
+ @context = context
6
8
  @installed = false
7
9
  end
8
10
 
11
+ def logger
12
+ context.logger
13
+ end
14
+
9
15
  def installed?
10
16
  @installed
11
17
  end
12
18
 
13
19
  def install
14
- @installed = true
15
-
16
20
  if defined?(ActionDispatch) && defined?(ActionDispatch::Routing) && defined?(ActionDispatch::Routing::RouteSet)
21
+ @installed = true
22
+
17
23
  ActionDispatch::Routing::RouteSet.class_eval do
18
24
  def call_with_scout_instruments(*args)
19
25
  req = ScoutApm::RequestManager.lookup
@@ -26,8 +32,8 @@ module ScoutApm
26
32
  end
27
33
  end
28
34
 
29
- alias call_without_scout_instruments call
30
- alias call call_with_scout_instruments
35
+ alias_method :call_without_scout_instruments, :call
36
+ alias_method :call, :call_with_scout_instruments
31
37
  end
32
38
  end
33
39
  end
@@ -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,47 +35,33 @@ 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
- # This will happen constantly once we hit this case, so only log the first time
54
- @wrote_layaway_limit_error_message ||= 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")
55
55
  return false
56
56
  end
57
57
  filename = file_for(reporting_period.timestamp)
58
- layaway_file = LayawayFile.new(filename)
58
+ layaway_file = LayawayFile.new(context, filename)
59
59
  layaway_file.write(reporting_period)
60
60
  end
61
61
 
62
- # Claims a given timestamp by getting an exclusive lock on a timestamped
63
- # coordinator file. The coordinator file never contains data, it's just a
64
- # syncronization mechanism.
65
- #
66
- # Once the 'claim' is obtained:
67
- # * load and yield each ReportingPeriod from the layaway files.
68
- # * if there are reporting periods:
69
- # * yields any ReportingPeriods collected up from all the files.
70
- # * deletes all of the layaway files (including the coordinator) for the timestamp
71
- # * if not
72
- # * delete the coordinator
73
- # * remove any stale layaway files that may be hanging around.
74
- # * Finally unlock and ensure the coordinator file is cleared.
75
- #
76
- # If a claim file can't be obtained, return false without doing any work
77
- # Another process is handling the reporting.
62
+ # Claims a given timestamp (getting a lock on a particular filename),
63
+ # then yields ReportingPeriods collected up from all the files.
64
+ # If the yield returns truthy, delete the layaway files that made it up.
78
65
  def with_claim(timestamp)
79
66
  coordinator_file = glob_pattern(timestamp, :coordinator)
80
67
 
@@ -88,28 +75,28 @@ module ScoutApm
88
75
  begin
89
76
  if f
90
77
  begin
91
- ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")
78
+ logger.debug("Obtained Reporting Lock")
92
79
 
93
80
  log_layaway_file_information
94
81
 
95
82
  files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
96
- rps = files.map{ |layaway| LayawayFile.new(layaway).load }.compact
83
+ rps = files.map{ |layaway| LayawayFile.new(context, layaway).load }.compact
97
84
  if rps.any?
98
85
  yield rps
99
86
 
100
- 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}")
101
88
  delete_files_for(timestamp) # also removes the coodinator_file
89
+
90
+ logger.debug("Checking for any Stale layaway files")
91
+ delete_stale_files(timestamp.to_time - STALE_AGE)
102
92
  else
103
93
  File.unlink(coordinator_file)
104
- ScoutApm::Agent.instance.logger.debug("No layaway files to report")
94
+ logger.debug("No layaway files to report")
105
95
  end
106
96
 
107
- ScoutApm::Agent.instance.logger.debug("Checking for any Stale layaway files")
108
- delete_stale_files(timestamp.to_time - STALE_AGE)
109
-
110
97
  true
111
98
  rescue Exception => e
112
- 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}")
113
100
  raise
114
101
  ensure
115
102
  # Unlock the file when done!
@@ -125,7 +112,7 @@ module ScoutApm
125
112
 
126
113
  def delete_files_for(timestamp)
127
114
  all_files_for(timestamp).each { |layaway|
128
- ScoutApm::Agent.instance.logger.debug("Deleting layaway file: #{layaway}")
115
+ logger.debug("Deleting layaway file: #{layaway}")
129
116
  File.unlink(layaway)
130
117
  }
131
118
  end
@@ -136,10 +123,10 @@ module ScoutApm
136
123
  compact.
137
124
  uniq.
138
125
  select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
139
- 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}") }.
140
127
  map { |timestamp| delete_files_for(timestamp) }
141
128
  rescue => e
142
- 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}")
143
130
  end
144
131
 
145
132
  private
@@ -206,7 +193,7 @@ module ScoutApm
206
193
  ]
207
194
 
208
195
 
209
- 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}")
210
197
  end
211
198
  end
212
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
 
@@ -111,7 +111,7 @@ module ScoutApm
111
111
  # In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
112
112
  def caller_array
113
113
  # omits the first several callers which are in the ScoutAPM stack.
114
- if ScoutApm::Environment.instance.ruby_2?
114
+ if ScoutApm::Agent.instance.context.environment.ruby_2?
115
115
  caller(3...BACKTRACE_CALLER_LIMIT)
116
116
  else
117
117
  caller[3...BACKTRACE_CALLER_LIMIT]
@@ -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
@@ -143,7 +145,7 @@ module ScoutApm
143
145
  end
144
146
  end
145
147
 
146
- def make_meta_options_desc_hash(layer, max_desc_length=32768)
148
+ def make_meta_options_desc_hash(layer, max_desc_length=1000)
147
149
  if layer.desc
148
150
  desc_s = layer.desc.to_s
149
151
  trimmed_desc = desc_s[0 .. max_desc_length]
@@ -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