sapience 0.1.1
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 +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
|