semantic_logger 3.4.1 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -0
- data/Rakefile +4 -8
- data/lib/semantic_logger.rb +2 -31
- data/lib/semantic_logger/appender.rb +76 -0
- data/lib/semantic_logger/appender/bugsnag.rb +3 -8
- data/lib/semantic_logger/appender/file.rb +1 -1
- data/lib/semantic_logger/appender/honeybadger.rb +1 -1
- data/lib/semantic_logger/appender/http.rb +1 -1
- data/lib/semantic_logger/appender/mongodb.rb +30 -28
- data/lib/semantic_logger/appender/sentry.rb +2 -2
- data/lib/semantic_logger/appender/splunk_http.rb +4 -4
- data/lib/semantic_logger/appender/syslog.rb +2 -2
- data/lib/semantic_logger/appender/tcp.rb +9 -5
- data/lib/semantic_logger/appender/udp.rb +1 -0
- data/lib/semantic_logger/base.rb +73 -140
- data/lib/semantic_logger/core_ext/thread.rb +4 -1
- data/lib/semantic_logger/formatters/color.rb +7 -0
- data/lib/semantic_logger/formatters/default.rb +7 -0
- data/lib/semantic_logger/formatters/syslog.rb +1 -1
- data/lib/semantic_logger/log.rb +115 -12
- data/lib/semantic_logger/logger.rb +6 -215
- data/lib/semantic_logger/metrics/new_relic.rb +1 -1
- data/lib/semantic_logger/metrics/statsd.rb +5 -1
- data/lib/semantic_logger/metrics/udp.rb +80 -0
- data/lib/semantic_logger/processor.rb +235 -0
- data/lib/semantic_logger/semantic_logger.rb +36 -65
- data/lib/semantic_logger/subscriber.rb +2 -2
- data/lib/semantic_logger/version.rb +1 -1
- data/test/appender/bugsnag_test.rb +10 -9
- data/test/appender/elasticsearch_test.rb +3 -2
- data/test/appender/graylog_test.rb +4 -3
- data/test/appender/honeybadger_test.rb +2 -2
- data/test/appender/http_test.rb +3 -2
- data/test/appender/mongodb_test.rb +24 -23
- data/test/appender/new_relic_test.rb +15 -8
- data/test/appender/sentry_test.rb +2 -2
- data/test/appender/splunk_http_test.rb +8 -7
- data/test/appender/splunk_test.rb +6 -5
- data/test/appender/tcp_test.rb +3 -4
- data/test/appender/udp_test.rb +4 -5
- data/test/appender/wrapper_test.rb +37 -38
- data/test/concerns/compatibility_test.rb +2 -2
- data/test/loggable_test.rb +1 -1
- data/test/logger_test.rb +149 -528
- data/test/measure_test.rb +249 -0
- data/test/semantic_logger_test.rb +257 -0
- metadata +24 -16
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'thread'
|
2
2
|
class Thread
|
3
|
+
undef :name if method_defined? :name
|
4
|
+
undef :name= if method_defined? :name=
|
5
|
+
|
3
6
|
# Returns the name of the current thread
|
4
7
|
# Default:
|
5
8
|
# JRuby: The underlying Java thread name
|
@@ -25,4 +28,4 @@ class Thread
|
|
25
28
|
@name = name.to_s
|
26
29
|
end
|
27
30
|
end
|
28
|
-
end
|
31
|
+
end
|
@@ -35,6 +35,13 @@ module SemanticLogger
|
|
35
35
|
# Tags
|
36
36
|
message << ' ' << log.tags.collect { |tag| "[#{level_color}#{tag}#{colors::CLEAR}]" }.join(' ') if log.tags && (log.tags.size > 0)
|
37
37
|
|
38
|
+
# Named Tags
|
39
|
+
if (named_tags = log.named_tags) && !named_tags.empty?
|
40
|
+
list = {}
|
41
|
+
named_tags.each_pair { |name, value| list << "[#{level_color}#{name}: #{value}#{colors::CLEAR}]" }
|
42
|
+
message << ' ' << list.join(' ')
|
43
|
+
end
|
44
|
+
|
38
45
|
# Duration
|
39
46
|
message << " (#{colors::BOLD}#{log.duration_human}#{colors::CLEAR})" if log.duration
|
40
47
|
|
@@ -14,6 +14,13 @@ module SemanticLogger
|
|
14
14
|
# Tags
|
15
15
|
message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
|
16
16
|
|
17
|
+
# Named Tags
|
18
|
+
if (named_tags = log.named_tags) && !named_tags.empty?
|
19
|
+
list = []
|
20
|
+
named_tags.each_pair { |name, value| list << "[#{name}: #{value}]" }
|
21
|
+
message << ' ' << list.join(' ')
|
22
|
+
end
|
23
|
+
|
17
24
|
# Duration
|
18
25
|
message << " (#{log.duration_human})" if log.duration
|
19
26
|
|
@@ -88,7 +88,7 @@ module SemanticLogger
|
|
88
88
|
@options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
|
89
89
|
@facility = options.delete(:facility) || ::Syslog::LOG_USER
|
90
90
|
@level_map = DEFAULT_LEVEL_MAP.dup
|
91
|
-
if level_map
|
91
|
+
if level_map = options.delete(:level_map)
|
92
92
|
@level_map.update(level_map)
|
93
93
|
end
|
94
94
|
# Time is already part of Syslog packet
|
data/lib/semantic_logger/log.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module SemanticLogger
|
2
|
-
# Log
|
2
|
+
# Log
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# Class to hold all log entry information
|
5
5
|
#
|
6
6
|
# level
|
7
7
|
# Log level of the supplied log call
|
@@ -43,7 +43,103 @@ module SemanticLogger
|
|
43
43
|
# metric_amount [Numeric]
|
44
44
|
# Used for numeric or counter metrics.
|
45
45
|
# For example, the number of inquiries or, the amount purchased etc.
|
46
|
-
Log
|
46
|
+
class Log
|
47
|
+
attr_accessor :level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount, :named_tags
|
48
|
+
|
49
|
+
def initialize(name, level, index = nil)
|
50
|
+
@level = level
|
51
|
+
@thread_name = Thread.current.name
|
52
|
+
@name = name
|
53
|
+
@time = Time.now
|
54
|
+
@tags = SemanticLogger.tags
|
55
|
+
@named_tags = SemanticLogger.named_tags
|
56
|
+
@level_index = index.nil? ? SemanticLogger.level_to_index(level) : index
|
57
|
+
end
|
58
|
+
|
59
|
+
# Assign named arguments to this log entry, supplying defaults where applicable
|
60
|
+
#
|
61
|
+
# Returns [true|false] whether this log entry should be logged
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
# logger.info(name: 'value')
|
65
|
+
def assign(message: nil, payload: nil, min_duration: 0.0, exception: nil, metric: nil, metric_amount: 1, duration: nil, backtrace: nil, log_exception: :full, on_exception_level: nil)
|
66
|
+
# Elastic logging: Log when :duration exceeds :min_duration
|
67
|
+
# Except if there is an exception when it will always be logged
|
68
|
+
if duration
|
69
|
+
self.duration = duration
|
70
|
+
return false if (duration <= min_duration) && exception.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
self.message = message
|
74
|
+
self.payload = payload
|
75
|
+
|
76
|
+
if exception
|
77
|
+
case log_exception
|
78
|
+
when :full
|
79
|
+
self.exception = exception
|
80
|
+
when :partial
|
81
|
+
self.message = "#{message} -- Exception: #{exception.class}: #{exception.message}"
|
82
|
+
when nil, :none
|
83
|
+
# Log the message without the exception that was raised
|
84
|
+
else
|
85
|
+
raise(ArgumentError, "Invalid value:#{log_exception.inspect} for argument :log_exception")
|
86
|
+
end
|
87
|
+
# On exception change the log level
|
88
|
+
if on_exception_level
|
89
|
+
self.level = on_exception_level
|
90
|
+
self.level_index = SemanticLogger.level_to_index(level)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
if backtrace
|
95
|
+
self.backtrace = self.class.cleanse_backtrace(backtrace)
|
96
|
+
elsif level_index >= SemanticLogger.backtrace_level_index
|
97
|
+
self.backtrace = self.class.cleanse_backtrace
|
98
|
+
end
|
99
|
+
|
100
|
+
if metric
|
101
|
+
self.metric = metric
|
102
|
+
self.metric_amount = metric_amount
|
103
|
+
end
|
104
|
+
|
105
|
+
self.payload = payload if payload && (payload.size > 0)
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
# Assign positional arguments to this log entry, supplying defaults where applicable
|
110
|
+
#
|
111
|
+
# Returns [true|false] whether this log entry should be logged
|
112
|
+
#
|
113
|
+
# Example:
|
114
|
+
# logger.info('value', :debug, 0, "hello world")
|
115
|
+
def assign_positional(message = nil, payload = nil, exception = nil)
|
116
|
+
# Exception being logged?
|
117
|
+
# Under JRuby a java exception is not a Ruby Exception
|
118
|
+
# Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
|
119
|
+
if exception.nil? && payload.nil? && message.respond_to?(:backtrace) && message.respond_to?(:message)
|
120
|
+
exception = message
|
121
|
+
message = nil
|
122
|
+
elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
|
123
|
+
exception = payload
|
124
|
+
payload = nil
|
125
|
+
end
|
126
|
+
|
127
|
+
# Add result of block as message or payload if not nil
|
128
|
+
if block_given? && (result = yield)
|
129
|
+
if result.is_a?(String)
|
130
|
+
message = message.nil? ? result : "#{message} -- #{result}"
|
131
|
+
assign(message: message, payload: payload, exception: exception)
|
132
|
+
elsif message.nil? && result.is_a?(Hash)
|
133
|
+
assign(result)
|
134
|
+
elsif payload && payload.respond_to?(:merge)
|
135
|
+
assign(message: message, payload: payload.merge(result), exception: exception)
|
136
|
+
else
|
137
|
+
assign(message: message, payload: result, exception: exception)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
assign(message: message, payload: payload, exception: exception)
|
141
|
+
end
|
142
|
+
end
|
47
143
|
|
48
144
|
MAX_EXCEPTIONS_TO_UNWRAP = 5
|
49
145
|
# Call the block for exception and any nested exception
|
@@ -196,7 +292,8 @@ module SemanticLogger
|
|
196
292
|
end
|
197
293
|
|
198
294
|
# Tags
|
199
|
-
h[:tags]
|
295
|
+
h[:tags] = tags if tags && !tags.empty?
|
296
|
+
h[:named_tags] = named_tags if named_tags && !named_tags.empty?
|
200
297
|
|
201
298
|
# Duration
|
202
299
|
if duration
|
@@ -208,13 +305,7 @@ module SemanticLogger
|
|
208
305
|
h[:message] = cleansed_message if message
|
209
306
|
|
210
307
|
# Payload
|
211
|
-
if payload
|
212
|
-
if payload.is_a?(Hash)
|
213
|
-
h.merge!(payload)
|
214
|
-
else
|
215
|
-
h[:payload] = payload
|
216
|
-
end
|
217
|
-
end
|
308
|
+
h[:payload] = payload if payload && payload.respond_to?(:empty?) && !payload.empty?
|
218
309
|
|
219
310
|
# Exceptions
|
220
311
|
if exception
|
@@ -231,11 +322,23 @@ module SemanticLogger
|
|
231
322
|
end
|
232
323
|
|
233
324
|
# Metric
|
234
|
-
h[:metric]
|
325
|
+
h[:metric] = metric if metric
|
235
326
|
h[:metric_amount] = metric_amount if metric_amount
|
236
327
|
h
|
237
328
|
end
|
238
329
|
|
330
|
+
private
|
331
|
+
|
332
|
+
SELF_PATTERN = File.join('lib', 'semantic_logger')
|
333
|
+
|
334
|
+
# Extract the backtrace leaving out Semantic Logger
|
335
|
+
def self.cleanse_backtrace(stack = caller)
|
336
|
+
while (first = stack.first) && first.include?(SELF_PATTERN)
|
337
|
+
stack.shift
|
338
|
+
end
|
339
|
+
stack
|
340
|
+
end
|
341
|
+
|
239
342
|
end
|
240
343
|
|
241
344
|
end
|
@@ -38,95 +38,26 @@ module SemanticLogger
|
|
38
38
|
# logging, increase the log level, reduce the number of appenders, or
|
39
39
|
# look into speeding up the appenders themselves
|
40
40
|
def self.queue_size
|
41
|
-
|
41
|
+
Processor.queue_size
|
42
42
|
end
|
43
43
|
|
44
44
|
# Flush all queued log entries disk, database, etc.
|
45
45
|
# All queued log messages are written and then each appender is flushed in turn
|
46
46
|
def self.flush
|
47
|
-
|
48
|
-
if queue_size > 1_000
|
49
|
-
logger.warn msg
|
50
|
-
elsif queue_size > 100
|
51
|
-
logger.info msg
|
52
|
-
elsif queue_size > 0
|
53
|
-
logger.trace msg
|
54
|
-
end
|
55
|
-
process_request(:flush)
|
47
|
+
Processor.submit_request(:flush)
|
56
48
|
end
|
57
49
|
|
58
50
|
# Close all appenders and flush any outstanding messages
|
59
51
|
def self.close
|
60
|
-
|
61
|
-
if queue_size > 1_000
|
62
|
-
logger.warn msg
|
63
|
-
elsif queue_size > 100
|
64
|
-
logger.info msg
|
65
|
-
elsif queue_size > 0
|
66
|
-
logger.trace msg
|
67
|
-
end
|
68
|
-
process_request(:close)
|
52
|
+
Processor.submit_request(:close)
|
69
53
|
end
|
70
54
|
|
71
|
-
|
72
|
-
@@lag_threshold_s = 30
|
73
|
-
|
74
|
-
# Returns the check_interval which is the number of messages between checks
|
75
|
-
# to determine if the appender thread is falling behind
|
76
|
-
def self.lag_check_interval
|
77
|
-
@@lag_check_interval
|
78
|
-
end
|
79
|
-
|
80
|
-
# Set the check_interval which is the number of messages between checks
|
81
|
-
# to determine if the appender thread is falling behind
|
82
|
-
def self.lag_check_interval=(lag_check_interval)
|
83
|
-
@@lag_check_interval = lag_check_interval
|
84
|
-
end
|
85
|
-
|
86
|
-
# Returns the amount of time in seconds
|
87
|
-
# to determine if the appender thread is falling behind
|
88
|
-
def self.lag_threshold_s
|
89
|
-
@@lag_threshold_s
|
90
|
-
end
|
91
|
-
|
92
|
-
# Allow the internal logger to be overridden from its default to STDERR
|
55
|
+
# Allow the internal logger to be overridden from its default of STDERR
|
93
56
|
# Can be replaced with another Ruby logger or Rails logger, but never to
|
94
57
|
# SemanticLogger::Logger itself since it is for reporting problems
|
95
58
|
# while trying to log to the various appenders
|
96
59
|
def self.logger=(logger)
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
# Supply a metrics appender to be called whenever a logging metric is encountered
|
101
|
-
#
|
102
|
-
# Parameters
|
103
|
-
# appender: [Symbol | Object | Proc]
|
104
|
-
# [Proc] the block to call.
|
105
|
-
# [Object] the block on which to call #call.
|
106
|
-
# [Symbol] :new_relic, or :statsd to forward metrics to
|
107
|
-
#
|
108
|
-
# block
|
109
|
-
# The block to be called
|
110
|
-
#
|
111
|
-
# Example:
|
112
|
-
# SemanticLogger.on_metric do |log|
|
113
|
-
# puts "#{log.metric} was received. Log Struct: #{log.inspect}"
|
114
|
-
# end
|
115
|
-
#
|
116
|
-
# Note:
|
117
|
-
# * This callback is called in the logging thread.
|
118
|
-
# * Does not slow down the application.
|
119
|
-
# * Only context is what is passed in the log struct, the original thread context is not available.
|
120
|
-
def self.on_metric(options = {}, &block)
|
121
|
-
# Backward compatibility
|
122
|
-
options = options.is_a?(Hash) ? options.dup : {appender: options}
|
123
|
-
appender = block || options.delete(:appender)
|
124
|
-
|
125
|
-
# Convert symbolized metrics appender to an actual object
|
126
|
-
appender = SemanticLogger.constantize_symbol(appender, 'SemanticLogger::Metrics').new(options) if appender.is_a?(Symbol)
|
127
|
-
|
128
|
-
raise('When supplying a metrics appender, it must support the #call method') unless appender.is_a?(Proc) || appender.respond_to?(:call)
|
129
|
-
(@@metric_subscribers ||= Concurrent::Array.new) << appender
|
60
|
+
Processor.logger = logger
|
130
61
|
end
|
131
62
|
|
132
63
|
# Place log request on the queue for the Appender thread to write to each
|
@@ -134,147 +65,7 @@ module SemanticLogger
|
|
134
65
|
def log(log, message = nil, progname = nil, &block)
|
135
66
|
# Compatibility with ::Logger
|
136
67
|
return add(log, message, progname, &block) unless log.is_a?(SemanticLogger::Log)
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
@@appender_thread = nil
|
143
|
-
@@queue = Queue.new
|
144
|
-
@@metric_subscribers = nil
|
145
|
-
|
146
|
-
# Queue to hold messages that need to be logged to the various appenders
|
147
|
-
def self.queue
|
148
|
-
@@queue
|
149
|
-
end
|
150
|
-
|
151
|
-
# Internal logger for SemanticLogger
|
152
|
-
# For example when an appender is not working etc..
|
153
|
-
# By default logs to STDERR
|
154
|
-
def self.logger
|
155
|
-
@@logger ||= begin
|
156
|
-
l = SemanticLogger::Appender::File.new(STDERR, :warn)
|
157
|
-
l.name = name
|
158
|
-
l
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# Start the appender thread
|
163
|
-
def self.start_appender_thread
|
164
|
-
return false if appender_thread_active?
|
165
|
-
@@appender_thread = Thread.new { appender_thread }
|
166
|
-
raise 'Failed to start Appender Thread' unless @@appender_thread
|
167
|
-
true
|
168
|
-
end
|
169
|
-
|
170
|
-
# Returns true if the appender_thread is active
|
171
|
-
def self.appender_thread_active?
|
172
|
-
@@appender_thread && @@appender_thread.alive?
|
173
|
-
end
|
174
|
-
|
175
|
-
# Separate appender thread responsible for reading log messages and
|
176
|
-
# calling the appenders in it's thread
|
177
|
-
def self.appender_thread
|
178
|
-
# This thread is designed to never go down unless the main thread terminates
|
179
|
-
# Before terminating at_exit is used to flush all the appenders
|
180
|
-
#
|
181
|
-
# Should any appender fail to log or flush, the exception is logged and
|
182
|
-
# other appenders will still be called
|
183
|
-
Thread.current.name = 'SemanticLogger::AppenderThread'
|
184
|
-
logger.trace "V#{VERSION} Appender thread active"
|
185
|
-
begin
|
186
|
-
count = 0
|
187
|
-
while message = queue.pop
|
188
|
-
if message.is_a?(Log)
|
189
|
-
SemanticLogger.appenders.each do |appender|
|
190
|
-
begin
|
191
|
-
appender.log(message)
|
192
|
-
rescue Exception => exc
|
193
|
-
logger.error "Appender thread: Failed to log to appender: #{appender.inspect}", exc
|
194
|
-
end
|
195
|
-
end
|
196
|
-
call_metric_subscribers(message) if message.metric
|
197
|
-
count += 1
|
198
|
-
# Check every few log messages whether this appender thread is falling behind
|
199
|
-
if count > lag_check_interval
|
200
|
-
if (diff = Time.now - message.time) > lag_threshold_s
|
201
|
-
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"
|
202
|
-
end
|
203
|
-
count = 0
|
204
|
-
end
|
205
|
-
else
|
206
|
-
case message[:command]
|
207
|
-
when :flush
|
208
|
-
SemanticLogger.appenders.each do |appender|
|
209
|
-
begin
|
210
|
-
logger.trace "Appender thread: Flushing appender: #{appender.name}"
|
211
|
-
appender.flush
|
212
|
-
rescue Exception => exc
|
213
|
-
logger.error "Appender thread: Failed to flush appender: #{appender.inspect}", exc
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
message[:reply_queue] << true if message[:reply_queue]
|
218
|
-
logger.trace 'Appender thread: All appenders flushed'
|
219
|
-
when :close
|
220
|
-
SemanticLogger.appenders.each do |appender|
|
221
|
-
begin
|
222
|
-
logger.trace "Appender thread: Closing appender: #{appender.name}"
|
223
|
-
appender.flush
|
224
|
-
appender.close
|
225
|
-
SemanticLogger.remove_appender(appender)
|
226
|
-
rescue Exception => exc
|
227
|
-
logger.error "Appender thread: Failed to close appender: #{appender.inspect}", exc
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
message[:reply_queue] << true if message[:reply_queue]
|
232
|
-
logger.trace 'Appender thread: All appenders flushed'
|
233
|
-
else
|
234
|
-
logger.warn "Appender thread: Ignoring unknown command: #{message[:command]}"
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
238
|
-
rescue Exception => exception
|
239
|
-
# This block may be called after the file handles have been released by Ruby
|
240
|
-
begin
|
241
|
-
logger.error 'Appender thread restarting due to exception', exception
|
242
|
-
rescue Exception
|
243
|
-
nil
|
244
|
-
end
|
245
|
-
retry
|
246
|
-
ensure
|
247
|
-
@@appender_thread = nil
|
248
|
-
# This block may be called after the file handles have been released by Ruby
|
249
|
-
begin
|
250
|
-
logger.trace 'Appender thread has stopped'
|
251
|
-
rescue Exception
|
252
|
-
nil
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
# Call Metric subscribers
|
258
|
-
def self.call_metric_subscribers(log)
|
259
|
-
# If no subscribers registered, then return immediately
|
260
|
-
return unless @@metric_subscribers
|
261
|
-
|
262
|
-
@@metric_subscribers.each do |subscriber|
|
263
|
-
begin
|
264
|
-
subscriber.call(log)
|
265
|
-
rescue Exception => exc
|
266
|
-
logger.error 'Exception calling metrics subscriber', exc
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
# Close all appenders and flush any outstanding messages
|
272
|
-
def self.process_request(command)
|
273
|
-
return false unless appender_thread_active?
|
274
|
-
|
275
|
-
reply_queue = Queue.new
|
276
|
-
queue << {command: command, reply_queue: reply_queue}
|
277
|
-
reply_queue.pop
|
68
|
+
Processor << log
|
278
69
|
end
|
279
70
|
|
280
71
|
end
|