semantic_logger 3.4.1 → 4.0.0.beta1
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/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
|