sapience 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.simplecov +15 -0
- data/.travis.yml +27 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/sapience/ansi_colors.rb +27 -0
- data/lib/sapience/appender/file.rb +138 -0
- data/lib/sapience/appender/sentry.rb +72 -0
- data/lib/sapience/appender/statsd.rb +68 -0
- data/lib/sapience/appender/wrapper.rb +74 -0
- data/lib/sapience/base.rb +381 -0
- data/lib/sapience/concerns/compatibility.rb +53 -0
- data/lib/sapience/configuration.rb +86 -0
- data/lib/sapience/core_ext/thread.rb +14 -0
- data/lib/sapience/formatters/base.rb +36 -0
- data/lib/sapience/formatters/color.rb +68 -0
- data/lib/sapience/formatters/default.rb +41 -0
- data/lib/sapience/formatters/json.rb +22 -0
- data/lib/sapience/formatters/raw.rb +12 -0
- data/lib/sapience/log.rb +221 -0
- data/lib/sapience/loggable.rb +29 -0
- data/lib/sapience/logger.rb +236 -0
- data/lib/sapience/rails.rb +15 -0
- data/lib/sapience/sapience.rb +357 -0
- data/lib/sapience/subscriber.rb +127 -0
- data/lib/sapience/version.rb +3 -0
- data/lib/sapience.rb +35 -0
- data/sapience.gemspec +39 -0
- data/test_app/.gitignore +42 -0
- data/test_app/.rspec +2 -0
- data/test_app/.ruby-version +1 -0
- data/test_app/Gemfile +36 -0
- data/test_app/README.md +24 -0
- data/test_app/Rakefile +6 -0
- data/test_app/app/assets/config/manifest.js +2 -0
- data/test_app/app/assets/images/.keep +0 -0
- data/test_app/app/assets/javascripts/posts.js +2 -0
- data/test_app/app/assets/stylesheets/application.css +15 -0
- data/test_app/app/assets/stylesheets/posts.css +4 -0
- data/test_app/app/assets/stylesheets/scaffold.css +84 -0
- data/test_app/app/channels/application_cable/channel.rb +4 -0
- data/test_app/app/channels/application_cable/connection.rb +4 -0
- data/test_app/app/controllers/application_controller.rb +3 -0
- data/test_app/app/controllers/concerns/.keep +0 -0
- data/test_app/app/controllers/posts_controller.rb +58 -0
- data/test_app/app/helpers/application_helper.rb +2 -0
- data/test_app/app/helpers/posts_helper.rb +2 -0
- data/test_app/app/jobs/application_job.rb +2 -0
- data/test_app/app/mailers/application_mailer.rb +4 -0
- data/test_app/app/models/application_record.rb +3 -0
- data/test_app/app/models/concerns/.keep +0 -0
- data/test_app/app/models/post.rb +3 -0
- data/test_app/app/models/user.rb +2 -0
- data/test_app/app/views/layouts/application.html.erb +13 -0
- data/test_app/app/views/layouts/mailer.html.erb +13 -0
- data/test_app/app/views/layouts/mailer.text.erb +1 -0
- data/test_app/app/views/posts/_form.html.erb +32 -0
- data/test_app/app/views/posts/create.html.erb +2 -0
- data/test_app/app/views/posts/destroy.html.erb +2 -0
- data/test_app/app/views/posts/edit.html.erb +6 -0
- data/test_app/app/views/posts/index.html.erb +31 -0
- data/test_app/app/views/posts/new.html.erb +5 -0
- data/test_app/app/views/posts/show.html.erb +19 -0
- data/test_app/app/views/posts/update.html.erb +2 -0
- data/test_app/bin/bundle +3 -0
- data/test_app/bin/rails +4 -0
- data/test_app/bin/rake +4 -0
- data/test_app/bin/setup +34 -0
- data/test_app/bin/update +29 -0
- data/test_app/config/application.rb +26 -0
- data/test_app/config/boot.rb +3 -0
- data/test_app/config/cable.yml +9 -0
- data/test_app/config/database.yml +25 -0
- data/test_app/config/environment.rb +5 -0
- data/test_app/config/environments/development.rb +49 -0
- data/test_app/config/environments/production.rb +78 -0
- data/test_app/config/environments/test.rb +42 -0
- data/test_app/config/initializers/application_controller_renderer.rb +6 -0
- data/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test_app/config/initializers/cookies_serializer.rb +5 -0
- data/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/test_app/config/initializers/inflections.rb +16 -0
- data/test_app/config/initializers/mime_types.rb +4 -0
- data/test_app/config/initializers/new_framework_defaults.rb +24 -0
- data/test_app/config/initializers/session_store.rb +3 -0
- data/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/test_app/config/locales/en.yml +23 -0
- data/test_app/config/puma.rb +45 -0
- data/test_app/config/routes.rb +3 -0
- data/test_app/config/secrets.yml +22 -0
- data/test_app/config.ru +5 -0
- data/test_app/db/migrate/20160812092236_create_users.rb +13 -0
- data/test_app/db/migrate/20160812093621_create_posts.rb +11 -0
- data/test_app/db/schema.rb +33 -0
- data/test_app/db/seeds.rb +7 -0
- data/test_app/lib/assets/.keep +0 -0
- data/test_app/lib/tasks/.keep +0 -0
- data/test_app/log/.keep +0 -0
- data/test_app/public/404.html +67 -0
- data/test_app/public/422.html +67 -0
- data/test_app/public/500.html +66 -0
- data/test_app/public/apple-touch-icon-precomposed.png +0 -0
- data/test_app/public/apple-touch-icon.png +0 -0
- data/test_app/public/favicon.ico +0 -0
- data/test_app/public/robots.txt +5 -0
- data/test_app/spec/controllers/posts_controller_spec.rb +7 -0
- data/test_app/spec/helpers/posts_helper_spec.rb +15 -0
- data/test_app/spec/models/post_spec.rb +5 -0
- data/test_app/spec/models/user_spec.rb +5 -0
- data/test_app/spec/rails_helper.rb +23 -0
- data/test_app/spec/requests/posts_spec.rb +10 -0
- data/test_app/spec/routing/posts_routing_spec.rb +39 -0
- data/test_app/spec/spec_helper.rb +60 -0
- data/test_app/tmp/.keep +0 -0
- data/test_app/vendor/assets/stylesheets/.keep +0 -0
- metadata +298 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
require "json"
|
2
|
+
module Sapience
|
3
|
+
module Formatters
|
4
|
+
class Json < Raw
|
5
|
+
# Default JSON time format is ISO8601
|
6
|
+
def initialize(options = {})
|
7
|
+
options = options.dup
|
8
|
+
options[:time_format] = :iso_8601 unless options.key?(:time_format)
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns log messages in JSON format
|
13
|
+
def call(log, logger)
|
14
|
+
h = super(log, logger)
|
15
|
+
h.delete(:time)
|
16
|
+
h[:timestamp] = format_time(log.time)
|
17
|
+
h.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/sapience/log.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
module Sapience
|
2
|
+
# Log Struct
|
3
|
+
#
|
4
|
+
# Structure for holding all log entries
|
5
|
+
#
|
6
|
+
# level
|
7
|
+
# Log level of the supplied log call
|
8
|
+
# :trace, :debug, :info, :warn, :error, :fatal
|
9
|
+
#
|
10
|
+
# thread_name
|
11
|
+
# Name of the thread in which the logging call was called
|
12
|
+
#
|
13
|
+
# name
|
14
|
+
# Class name supplied to the logging instance
|
15
|
+
#
|
16
|
+
# message
|
17
|
+
# Text message to be logged
|
18
|
+
#
|
19
|
+
# payload
|
20
|
+
# Optional Hash or Ruby Exception object to be logged
|
21
|
+
#
|
22
|
+
# time
|
23
|
+
# The time at which the log entry was created
|
24
|
+
#
|
25
|
+
# duration
|
26
|
+
# The time taken to complete a measure call
|
27
|
+
#
|
28
|
+
# tags
|
29
|
+
# Any tags active on the thread when the log call was made
|
30
|
+
#
|
31
|
+
# level_index
|
32
|
+
# Internal index of the log level
|
33
|
+
#
|
34
|
+
# exception
|
35
|
+
# Ruby Exception object to log
|
36
|
+
#
|
37
|
+
# metric [Object]
|
38
|
+
# Object supplied when measure_x was called
|
39
|
+
#
|
40
|
+
# backtrace [Array<String>]
|
41
|
+
# The backtrace captured at source when the log level >= Sapience.config.backtrace_level
|
42
|
+
#
|
43
|
+
# metric_amount [Numeric]
|
44
|
+
# Used for numeric or counter metrics.
|
45
|
+
# For example, the number of inquiries or, the amount purchased etc.
|
46
|
+
Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount) do
|
47
|
+
MAX_EXCEPTIONS_TO_UNWRAP = 5
|
48
|
+
# Call the block for exception and any nested exception
|
49
|
+
def each_exception # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
50
|
+
# With thanks to https://github.com/bugsnag/bugsnag-ruby/blob/6348306e44323eee347896843d16c690cd7c4362/lib/bugsnag/notification.rb#L81
|
51
|
+
depth = 0
|
52
|
+
exceptions = []
|
53
|
+
ex = exception
|
54
|
+
while !ex.nil? && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
|
55
|
+
exceptions << ex
|
56
|
+
yield(ex, depth)
|
57
|
+
|
58
|
+
depth += 1
|
59
|
+
ex =
|
60
|
+
if ex.respond_to?(:cause) && ex.cause
|
61
|
+
ex.cause
|
62
|
+
elsif ex.respond_to?(:continued_exception) && ex.continued_exception
|
63
|
+
ex.continued_exception
|
64
|
+
elsif ex.respond_to?(:original_exception) && ex.original_exception
|
65
|
+
ex.original_exception
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns [String] the exception backtrace including all of the child / caused by exceptions
|
71
|
+
def backtrace_to_s
|
72
|
+
trace = ""
|
73
|
+
each_exception do |exception, i|
|
74
|
+
if i == 0
|
75
|
+
trace = (exception.backtrace || []).join("\n")
|
76
|
+
else
|
77
|
+
trace << "\nCause: #{exception.class.name}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
trace
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns [String] duration of the log entry as a string
|
84
|
+
# Returns nil if their is no duration
|
85
|
+
def duration_to_s
|
86
|
+
return unless duration
|
87
|
+
duration < 10.0 ? "#{"%.3f" % duration}ms" : "#{"%.1f" % duration}ms"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns [String] the duration in human readable form
|
91
|
+
def duration_human # rubocop:disable AbcSize
|
92
|
+
return nil unless duration
|
93
|
+
seconds = duration / 1000
|
94
|
+
if seconds >= 86_400.0 # 1 day
|
95
|
+
"#{(seconds / 86_400).to_i}d #{Time.at(seconds).strftime("%-Hh %-Mm")}"
|
96
|
+
elsif seconds >= 3600.0 # 1 hour
|
97
|
+
Time.at(seconds).strftime("%-Hh %-Mm")
|
98
|
+
elsif seconds >= 60.0 # 1 minute
|
99
|
+
Time.at(seconds).strftime("%-Mm %-Ss")
|
100
|
+
elsif seconds >= 1.0 # 1 second
|
101
|
+
"#{"%.3f" % seconds}s"
|
102
|
+
else
|
103
|
+
duration_to_s
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns [String] single character upper case log level
|
108
|
+
def level_to_s
|
109
|
+
level.to_s[0..0].upcase
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns [String] the available process info
|
113
|
+
# Example:
|
114
|
+
# 18934:thread 23 test_logging.rb:51
|
115
|
+
def process_info(thread_name_length = 30)
|
116
|
+
file, line = file_name_and_line(true)
|
117
|
+
file_name = " #{file}:#{line}" if file
|
118
|
+
|
119
|
+
"#{$PROCESS_ID}:#{"%.#{thread_name_length}s" % thread_name}#{file_name}"
|
120
|
+
end
|
121
|
+
|
122
|
+
CALLER_REGEXP = /^(.*):(\d+).*/
|
123
|
+
|
124
|
+
# Extract the filename and line number from the last entry in the supplied backtrace
|
125
|
+
def extract_file_and_line(stack, short_name = false)
|
126
|
+
match = CALLER_REGEXP.match(stack.first)
|
127
|
+
[short_name ? File.basename(match[1]) : match[1], match[2].to_i]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns [String, String] the file_name and line_number from the backtrace supplied
|
131
|
+
# in either the backtrace or exception
|
132
|
+
def file_name_and_line(short_name = false)
|
133
|
+
return unless backtrace || (exception && exception.backtrace)
|
134
|
+
stack = backtrace || exception.backtrace
|
135
|
+
extract_file_and_line(stack, short_name) if stack && stack.size > 0
|
136
|
+
end
|
137
|
+
|
138
|
+
# Strip the standard Rails colorizing from the logged message
|
139
|
+
def cleansed_message
|
140
|
+
message.to_s.gsub(/(\e(\[([\d;]*[mz]?))?)?/, "").strip
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return the payload in text form
|
144
|
+
# Returns nil if payload is missing or empty
|
145
|
+
def payload_to_s
|
146
|
+
payload.inspect if payload?
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns [true|false] whether the log entry has a payload
|
150
|
+
def payload?
|
151
|
+
!(payload.nil? || (payload.respond_to?(:empty?) && payload.empty?))
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return the Time as a formatted string
|
155
|
+
# Ruby MRI supports micro seconds
|
156
|
+
# DEPRECATED
|
157
|
+
def formatted_time
|
158
|
+
"#{time.strftime("%Y-%m-%d %H:%M:%S")}.#{"%06d" % (time.usec)}"
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns [Hash] representation of this log entry
|
162
|
+
def to_h(host = Sapience.config.host, application = Sapience.config.application) # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity, LineLength
|
163
|
+
# Header
|
164
|
+
h = {
|
165
|
+
name: name,
|
166
|
+
pid: $PROCESS_ID,
|
167
|
+
thread: thread_name,
|
168
|
+
time: time,
|
169
|
+
level: level,
|
170
|
+
level_index: level_index,
|
171
|
+
}
|
172
|
+
h[:host] = host if host
|
173
|
+
h[:application] = application if application
|
174
|
+
file, line = file_name_and_line
|
175
|
+
if file
|
176
|
+
h[:file] = file
|
177
|
+
h[:line] = line.to_i
|
178
|
+
end
|
179
|
+
|
180
|
+
# Tags
|
181
|
+
h[:tags] = tags if tags && (tags.size > 0)
|
182
|
+
|
183
|
+
# Duration
|
184
|
+
if duration
|
185
|
+
h[:duration_ms] = duration
|
186
|
+
h[:duration] = duration_human
|
187
|
+
end
|
188
|
+
|
189
|
+
# Log message
|
190
|
+
h[:message] = cleansed_message if message
|
191
|
+
|
192
|
+
# Payload
|
193
|
+
if payload
|
194
|
+
if payload.is_a?(Hash)
|
195
|
+
h.merge!(payload)
|
196
|
+
else
|
197
|
+
h[:payload] = payload
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Exceptions
|
202
|
+
if exception
|
203
|
+
root = h
|
204
|
+
each_exception do |exception, i|
|
205
|
+
name = i == 0 ? :exception : :cause
|
206
|
+
root[name] = {
|
207
|
+
name: exception.class.name,
|
208
|
+
message: exception.message,
|
209
|
+
stack_trace: exception.backtrace,
|
210
|
+
}
|
211
|
+
root = root[name]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Metric
|
216
|
+
h[:metric] = metric if metric
|
217
|
+
h
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Sapience
|
2
|
+
# rubocop:disable TrivialAccessors
|
3
|
+
module Loggable
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
# Returns [Sapience::Logger] class level logger
|
7
|
+
def self.logger
|
8
|
+
@sapience ||= Sapience[self]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Replace instance class level logger
|
12
|
+
def self.logger=(logger)
|
13
|
+
@sapience = logger
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns [Sapience::Logger] instance level logger
|
17
|
+
def logger
|
18
|
+
@sapience ||= self.class.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
# Replace instance level logger
|
22
|
+
def logger=(logger)
|
23
|
+
@sapience = logger
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# rubocop:enable TrivialAccessors
|
29
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
require "concurrent"
|
2
|
+
|
3
|
+
# rubocop:disable ClassVars
|
4
|
+
module Sapience
|
5
|
+
# Logger stores the class name to be used for all log messages so that every
|
6
|
+
# log message written by this instance will include the class name
|
7
|
+
class Logger < Base # rubocop:disable ClassLength, ClassVars
|
8
|
+
include Sapience::Concerns::Compatibility
|
9
|
+
|
10
|
+
# Returns a Logger instance
|
11
|
+
#
|
12
|
+
# Return the logger for a specific class, supports class specific log levels
|
13
|
+
# logger = Sapience::Logger.new(self)
|
14
|
+
# OR
|
15
|
+
# logger = Sapience::Logger.new('MyClass')
|
16
|
+
#
|
17
|
+
# Parameters:
|
18
|
+
# application
|
19
|
+
# A class, module or a string with the application/class name
|
20
|
+
# to be used in the logger
|
21
|
+
#
|
22
|
+
# level
|
23
|
+
# The initial log level to start with for this logger instance
|
24
|
+
# Default: Sapience.config.default_level
|
25
|
+
#
|
26
|
+
# filter [Regexp|Proc]
|
27
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
28
|
+
# regular expression. All other messages will be ignored
|
29
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
30
|
+
# The Proc must return true or false
|
31
|
+
def initialize(klass, level = nil, filter = nil)
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns [Integer] the number of log entries that have not been written
|
36
|
+
# to the appenders
|
37
|
+
#
|
38
|
+
# When this number grows it is because the logging appender thread is not
|
39
|
+
# able to write to the appenders fast enough. Either reduce the amount of
|
40
|
+
# logging, increase the log level, reduce the number of appenders, or
|
41
|
+
# look into speeding up the appenders themselves
|
42
|
+
def self.queue_size
|
43
|
+
queue.size
|
44
|
+
end
|
45
|
+
|
46
|
+
# Flush all queued log entries disk, database, etc.
|
47
|
+
# All queued log messages are written and then each appender is flushed in turn
|
48
|
+
def self.flush
|
49
|
+
msg = "Flushing appenders with #{queue_size} log messages on the queue"
|
50
|
+
if queue_size > 1_000
|
51
|
+
logger.warn msg
|
52
|
+
elsif queue_size > 100
|
53
|
+
logger.info msg
|
54
|
+
elsif queue_size > 0
|
55
|
+
logger.trace msg
|
56
|
+
end
|
57
|
+
process_request(:flush)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Close all appenders and flush any outstanding messages
|
61
|
+
def self.close
|
62
|
+
msg = "Closing appenders with #{queue_size} log messages on the queue"
|
63
|
+
if queue_size > 1_000
|
64
|
+
logger.warn msg
|
65
|
+
elsif queue_size > 100
|
66
|
+
logger.info msg
|
67
|
+
elsif queue_size > 0
|
68
|
+
logger.trace msg
|
69
|
+
end
|
70
|
+
process_request(:close)
|
71
|
+
end
|
72
|
+
|
73
|
+
@@lag_check_interval = 5000
|
74
|
+
@@lag_threshold_s = 30
|
75
|
+
|
76
|
+
# Returns the check_interval which is the number of messages between checks
|
77
|
+
# to determine if the appender thread is falling behind
|
78
|
+
def self.lag_check_interval
|
79
|
+
@@lag_check_interval
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set the check_interval which is the number of messages between checks
|
83
|
+
# to determine if the appender thread is falling behind
|
84
|
+
def self.lag_check_interval=(lag_check_interval)
|
85
|
+
@@lag_check_interval = lag_check_interval
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the amount of time in seconds
|
89
|
+
# to determine if the appender thread is falling behind
|
90
|
+
def self.lag_threshold_s
|
91
|
+
@@lag_threshold_s
|
92
|
+
end
|
93
|
+
|
94
|
+
# Allow the internal logger to be overridden from its default to STDERR
|
95
|
+
# Can be replaced with another Ruby logger or Rails logger, but never to
|
96
|
+
# Sapience::Logger itself since it is for reporting problems
|
97
|
+
# while trying to log to the various appenders
|
98
|
+
def self.logger=(logger)
|
99
|
+
@@logger = logger
|
100
|
+
end
|
101
|
+
|
102
|
+
# Place log request on the queue for the Appender thread to write to each
|
103
|
+
# appender in the order that they were registered
|
104
|
+
def log(log, message = nil, progname = nil, &block)
|
105
|
+
# Compatibility with ::Logger
|
106
|
+
return add(log, message, progname, &block) unless log.is_a?(Sapience::Log)
|
107
|
+
self.class.queue << log if @@appender_thread
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
@@appender_thread = nil
|
113
|
+
@@queue = Queue.new
|
114
|
+
|
115
|
+
# Queue to hold messages that need to be logged to the various appenders
|
116
|
+
def self.queue
|
117
|
+
@@queue
|
118
|
+
end
|
119
|
+
|
120
|
+
# Internal logger for Sapience
|
121
|
+
# For example when an appender is not working etc..
|
122
|
+
# By default logs to STDERR
|
123
|
+
def self.logger
|
124
|
+
@@logger ||= begin
|
125
|
+
l = Sapience::Appender::File.new(STDERR, :warn)
|
126
|
+
l.name = name
|
127
|
+
l
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Start the appender thread
|
132
|
+
def self.start_appender_thread
|
133
|
+
return false if appender_thread_active?
|
134
|
+
@@appender_thread = Thread.new { appender_thread }
|
135
|
+
fail "Failed to start Appender Thread" unless @@appender_thread
|
136
|
+
true
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns true if the appender_thread is active
|
140
|
+
def self.appender_thread_active?
|
141
|
+
@@appender_thread && @@appender_thread.alive?
|
142
|
+
end
|
143
|
+
|
144
|
+
# Separate appender thread responsible for reading log messages and
|
145
|
+
# calling the appenders in it's thread
|
146
|
+
# rubocop:disable BlockNesting, AssignmentInCondition, PerceivedComplexity, CyclomaticComplexity, AbcSize, LineLength, RescueException
|
147
|
+
def self.appender_thread
|
148
|
+
# This thread is designed to never go down unless the main thread terminates
|
149
|
+
# Before terminating at_exit is used to flush all the appenders
|
150
|
+
#
|
151
|
+
# Should any appender fail to log or flush, the exception is logged and
|
152
|
+
# other appenders will still be called
|
153
|
+
Thread.current.name = "Sapience::AppenderThread"
|
154
|
+
logger.trace "V#{VERSION} Appender thread active"
|
155
|
+
begin
|
156
|
+
count = 0
|
157
|
+
while message = queue.pop
|
158
|
+
if message.is_a?(Log)
|
159
|
+
Sapience.appenders.each do |appender|
|
160
|
+
begin
|
161
|
+
appender.log(message)
|
162
|
+
rescue Exception => exc
|
163
|
+
logger.error "Appender thread: Failed to log to appender: #{appender.inspect}", exc
|
164
|
+
end
|
165
|
+
end
|
166
|
+
count += 1
|
167
|
+
# Check every few log messages whether this appender thread is falling behind
|
168
|
+
if count > lag_check_interval
|
169
|
+
if (diff = Time.now - message.time) > lag_threshold_s
|
170
|
+
logger.warn "Appender thread has fallen behind by #{diff} seconds with #{queue_size} messages queued up. Consider reducing the log level or changing the appenders"
|
171
|
+
end
|
172
|
+
count = 0
|
173
|
+
end
|
174
|
+
else
|
175
|
+
case message[:command]
|
176
|
+
when :flush
|
177
|
+
Sapience.appenders.each do |appender|
|
178
|
+
begin
|
179
|
+
logger.trace "Appender thread: Flushing appender: #{appender.name}"
|
180
|
+
appender.flush
|
181
|
+
rescue Exception => exc
|
182
|
+
logger.error "Appender thread: Failed to flush appender: #{appender.inspect}", exc
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
message[:reply_queue] << true if message[:reply_queue]
|
187
|
+
logger.trace "Appender thread: All appenders flushed"
|
188
|
+
when :close
|
189
|
+
Sapience.appenders.each do |appender|
|
190
|
+
begin
|
191
|
+
logger.trace "Appender thread: Closing appender: #{appender.name}"
|
192
|
+
appender.flush
|
193
|
+
appender.close
|
194
|
+
Sapience.remove_appender(appender)
|
195
|
+
rescue Exception => exc
|
196
|
+
logger.error "Appender thread: Failed to close appender: #{appender.inspect}", exc
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
message[:reply_queue] << true if message[:reply_queue]
|
201
|
+
logger.trace "Appender thread: All appenders flushed"
|
202
|
+
else
|
203
|
+
logger.warn "Appender thread: Ignoring unknown command: #{message[:command]}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
rescue Exception => exception
|
208
|
+
# This block may be called after the file handles have been released by Ruby
|
209
|
+
begin
|
210
|
+
logger.error "Appender thread restarting due to exception", exception
|
211
|
+
rescue Exception
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
retry
|
215
|
+
ensure
|
216
|
+
@@appender_thread = nil
|
217
|
+
# This block may be called after the file handles have been released by Ruby
|
218
|
+
begin
|
219
|
+
logger.trace "Appender thread has stopped"
|
220
|
+
rescue Exception
|
221
|
+
nil
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
# rubocop:enable BlockNesting, AssignmentInCondition, PerceivedComplexity, CyclomaticComplexity, AbcSize, LineLength, RescueException
|
226
|
+
|
227
|
+
# Close all appenders and flush any outstanding messages
|
228
|
+
def self.process_request(command)
|
229
|
+
return false unless appender_thread_active?
|
230
|
+
|
231
|
+
reply_queue = Queue.new
|
232
|
+
queue << { command: command, reply_queue: reply_queue }
|
233
|
+
reply_queue.pop
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sapience
|
2
|
+
class Rails < ::Rails::Engine
|
3
|
+
::Rails::Application::Bootstrap.initializers.delete_if { |i| i.name == :initialize_logger }
|
4
|
+
initializer :initialize_logger, group: :all do
|
5
|
+
# TODO: Is this really needed?
|
6
|
+
# Existing loggers are ignored because servers like trinidad supply their
|
7
|
+
# own file loggers which would result in duplicate logging to the same log file
|
8
|
+
# ::Rails.logger = config.logger = begin
|
9
|
+
# Sapience[::Rails]
|
10
|
+
# end
|
11
|
+
|
12
|
+
# Replace Rails loggers
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|