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.
- checksums.yaml +4 -4
- data/lib/scout_apm.rb +20 -10
- data/lib/scout_apm/agent.rb +114 -319
- data/lib/scout_apm/agent/exit_handler.rb +66 -0
- data/lib/scout_apm/agent/preconditions.rb +69 -0
- data/lib/scout_apm/agent_context.rb +234 -0
- data/lib/scout_apm/app_server_load.rb +24 -14
- data/lib/scout_apm/background_job_integrations/resque.rb +7 -8
- data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -2
- data/lib/scout_apm/background_recorder.rb +8 -3
- data/lib/scout_apm/background_worker.rb +14 -7
- data/lib/scout_apm/config.rb +35 -26
- data/lib/scout_apm/context.rb +11 -4
- data/lib/scout_apm/db_query_metric_set.rb +17 -5
- data/lib/scout_apm/debug.rb +1 -1
- data/lib/scout_apm/environment.rb +10 -14
- data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
- data/lib/scout_apm/git_revision.rb +13 -8
- data/lib/scout_apm/histogram.rb +1 -1
- data/lib/scout_apm/instant/middleware.rb +7 -7
- data/lib/scout_apm/instant_reporting.rb +7 -7
- data/lib/scout_apm/instrument_manager.rb +87 -0
- data/lib/scout_apm/instruments/action_controller_rails_2.rb +12 -7
- data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +17 -12
- data/lib/scout_apm/instruments/action_view.rb +11 -7
- data/lib/scout_apm/instruments/active_record.rb +25 -11
- data/lib/scout_apm/instruments/elasticsearch.rb +10 -6
- data/lib/scout_apm/instruments/grape.rb +12 -8
- data/lib/scout_apm/instruments/http_client.rb +10 -6
- data/lib/scout_apm/instruments/influxdb.rb +10 -6
- data/lib/scout_apm/instruments/middleware_detailed.rb +11 -5
- data/lib/scout_apm/instruments/middleware_summary.rb +11 -5
- data/lib/scout_apm/instruments/mongoid.rb +10 -5
- data/lib/scout_apm/instruments/moped.rb +11 -6
- data/lib/scout_apm/instruments/net_http.rb +10 -6
- data/lib/scout_apm/instruments/percentile_sampler.rb +8 -6
- data/lib/scout_apm/instruments/process/process_cpu.rb +8 -4
- data/lib/scout_apm/instruments/process/process_memory.rb +15 -10
- data/lib/scout_apm/instruments/rails_router.rb +12 -6
- data/lib/scout_apm/instruments/redis.rb +10 -6
- data/lib/scout_apm/instruments/samplers.rb +11 -0
- data/lib/scout_apm/instruments/sinatra.rb +5 -4
- data/lib/scout_apm/layaway.rb +21 -20
- data/lib/scout_apm/layaway_file.rb +8 -3
- data/lib/scout_apm/layer.rb +3 -3
- data/lib/scout_apm/layer_converters/converter_base.rb +6 -7
- data/lib/scout_apm/layer_converters/database_converter.rb +1 -1
- data/lib/scout_apm/layer_converters/histograms.rb +2 -2
- data/lib/scout_apm/layer_converters/slow_job_converter.rb +4 -3
- data/lib/scout_apm/layer_converters/slow_request_converter.rb +5 -4
- data/lib/scout_apm/logger.rb +143 -0
- data/lib/scout_apm/middleware.rb +7 -9
- data/lib/scout_apm/periodic_work.rb +28 -0
- data/lib/scout_apm/reporter.rb +14 -8
- data/lib/scout_apm/reporting.rb +135 -0
- data/lib/scout_apm/request_manager.rb +4 -6
- data/lib/scout_apm/serializers/payload_serializer.rb +1 -1
- data/lib/scout_apm/slow_job_policy.rb +6 -2
- data/lib/scout_apm/slow_job_record.rb +5 -5
- data/lib/scout_apm/slow_request_policy.rb +6 -2
- data/lib/scout_apm/slow_transaction.rb +5 -5
- data/lib/scout_apm/store.rb +22 -16
- data/lib/scout_apm/synchronous_recorder.rb +7 -3
- data/lib/scout_apm/tasks/doctor.rb +75 -0
- data/lib/scout_apm/tasks/support.rb +22 -0
- data/lib/scout_apm/tracer.rb +5 -5
- data/lib/scout_apm/tracked_request.rb +43 -19
- data/lib/scout_apm/utils/active_record_metric_name.rb +66 -8
- data/lib/scout_apm/utils/backtrace_parser.rb +1 -1
- data/lib/scout_apm/utils/installed_gems.rb +7 -3
- data/lib/scout_apm/utils/klass_helper.rb +8 -2
- data/lib/scout_apm/utils/scm.rb +1 -1
- data/lib/scout_apm/utils/sql_sanitizer.rb +3 -3
- data/lib/scout_apm/version.rb +1 -1
- data/lib/tasks/doctor.rake +11 -0
- data/scout_apm.gemspec +1 -0
- data/test/test_helper.rb +17 -2
- data/test/unit/agent_test.rb +1 -54
- data/test/unit/config_test.rb +9 -5
- data/test/unit/context_test.rb +4 -4
- data/test/unit/db_query_metric_set_test.rb +11 -4
- data/test/unit/fake_store_test.rb +1 -1
- data/test/unit/git_revision_test.rb +3 -3
- data/test/unit/instruments/net_http_test.rb +2 -1
- data/test/unit/instruments/percentile_sampler_test.rb +5 -9
- data/test/unit/layaway_test.rb +10 -5
- data/test/unit/layer_converters/metric_converter_test.rb +2 -2
- data/test/unit/slow_request_policy_test.rb +7 -3
- data/test/unit/sql_sanitizer_test.rb +0 -6
- data/test/unit/store_test.rb +11 -8
- data/test/unit/utils/active_record_metric_name_test.rb +45 -7
- metadata +27 -5
- data/lib/scout_apm/agent/logging.rb +0 -74
- data/lib/scout_apm/agent/reporting.rb +0 -129
- 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 :
|
|
4
|
+
attr_reader :context
|
|
5
5
|
|
|
6
|
-
def
|
|
7
|
-
@
|
|
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
|
-
|
|
21
|
+
@installed = true
|
|
22
|
+
|
|
23
|
+
logger.info "Instrumenting Redis"
|
|
20
24
|
|
|
21
25
|
::Redis::Client.class_eval do
|
|
22
26
|
include ScoutApm::Tracer
|
|
@@ -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
|
-
|
|
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
|
data/lib/scout_apm/layaway.rb
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
21
|
+
attr_reader :context
|
|
22
|
+
def initialize(context)
|
|
23
|
+
@context = context
|
|
24
|
+
end
|
|
23
25
|
|
|
24
|
-
def
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
logger.debug("No layaway files to report")
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
true
|
|
97
98
|
rescue Exception => e
|
|
98
|
-
|
|
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
|
-
|
|
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|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
data/lib/scout_apm/layer.rb
CHANGED
|
@@ -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::
|
|
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
|
-
|
|
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
|
-
|
|
230
|
+
context.logger.debug "****** Slow Request #{layer.type} Traces: Scoutprof is not enabled"
|
|
232
231
|
end
|
|
233
232
|
end
|
|
234
233
|
end
|
|
@@ -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
|
-
|
|
8
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|