semantic_logger 0.0.2 → 0.1.0
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.
- data/FUTURE.rb +0 -2
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/lib/semantic_logger.rb +1 -3
- data/lib/semantic_logger/appender/logger.rb +44 -72
- data/lib/semantic_logger/appender/mongodb.rb +30 -65
- data/lib/semantic_logger/logger.rb +147 -26
- data/lib/semantic_logger/railtie.rb +5 -1
- data/lib/semantic_logger/version.rb +1 -1
- data/test/appender_logger_test.rb +38 -43
- data/test/appender_mongodb_test.rb +82 -80
- data/test/logger_test.rb +3 -2
- data/test/mock_logger.rb +4 -0
- metadata +4 -4
data/FUTURE.rb
CHANGED
data/README.md
CHANGED
@@ -39,7 +39,7 @@ This can be changed over time to:
|
|
39
39
|
Using this semantic example, a machine can easily find all queries for table
|
40
40
|
'users' that took longer than 100 ms.
|
41
41
|
|
42
|
-
db.logs.find({"
|
42
|
+
db.logs.find({"metadata.table":"users", "metadata.action":"query"})
|
43
43
|
|
44
44
|
Since SemanticLogger can call existing Loggers, it does not force end-users
|
45
45
|
to have to adopt a Semantic aware adapter. Although, such adapters create
|
data/Rakefile
CHANGED
@@ -18,7 +18,7 @@ task :gem do |t|
|
|
18
18
|
spec.homepage = 'https://github.com/ClarityServices/semantic_logger'
|
19
19
|
spec.date = Date.today.to_s
|
20
20
|
spec.summary = "Semantic Logger for Ruby, and Ruby on Rails"
|
21
|
-
spec.description = "
|
21
|
+
spec.description = "Machine readable document oriented logging with support for MongoDB and text files"
|
22
22
|
spec.files = FileList["./**/*"].exclude('*.gem', 'nbproject').map{|f| f.sub(/^\.\//, '')}
|
23
23
|
spec.has_rdoc = true
|
24
24
|
spec.add_development_dependency 'shoulda'
|
data/lib/semantic_logger.rb
CHANGED
@@ -7,9 +7,7 @@ module SemanticLogger
|
|
7
7
|
module Appender
|
8
8
|
autoload :Logger, 'semantic_logger/appender/logger'
|
9
9
|
# Only load the MongoDB appender if the Mongo Ruby Driver is loaded
|
10
|
-
|
11
|
-
autoload :MongoDB, 'semantic_logger/appender/mongodb'
|
12
|
-
end
|
10
|
+
autoload :MongoDB, 'semantic_logger/appender/mongodb'
|
13
11
|
end
|
14
12
|
end
|
15
13
|
|
@@ -2,18 +2,6 @@
|
|
2
2
|
#
|
3
3
|
# Maps the SemanticLogger API's to the Rails log, log4j, or Ruby Logger
|
4
4
|
#
|
5
|
-
# Installation:
|
6
|
-
# Rails.logger = SemanticLogger::Appender::Logger.new(Rails.logger)
|
7
|
-
#
|
8
|
-
# Also works with the Ruby Logger
|
9
|
-
# require 'logger'
|
10
|
-
# require 'semantic_logger'
|
11
|
-
# logger = Logger.new(STDOUT)
|
12
|
-
# Rails.log = SemanticLogger::Appender::Logger.new(logger)
|
13
|
-
#
|
14
|
-
# ActiveResource::BufferedLogger
|
15
|
-
# ...
|
16
|
-
#
|
17
5
|
# The log level is controlled by the Logging implementation passed into
|
18
6
|
# this appender
|
19
7
|
module SemanticLogger
|
@@ -21,80 +9,64 @@ module SemanticLogger
|
|
21
9
|
class Logger
|
22
10
|
attr_reader :logger
|
23
11
|
|
24
|
-
|
12
|
+
# Create a Logger or Rails Logger appender instance
|
13
|
+
#
|
14
|
+
# Ruby Logger
|
15
|
+
# require 'logger'
|
16
|
+
# require 'semantic_logger'
|
17
|
+
# ruby_logger = Logger.new(STDOUT)
|
18
|
+
# SemanticLogger::Logger.appenders << SemanticLogger::Appender::Logger.new(ruby_logger)
|
19
|
+
# logger = SemanticLogger::Logger.new('test')
|
20
|
+
# logger.info('Hello World', :some => :payload)
|
21
|
+
#
|
22
|
+
# Enhance the Rails Logger
|
23
|
+
# # Add the Rails logger to the list of appenders
|
24
|
+
# SemanticLogger::Logger.appenders << SemanticLogger::Appender::Logger.new(Rails.logger)
|
25
|
+
# Rails.logger = SemanticLogger::Logger.new('Rails')
|
26
|
+
#
|
27
|
+
# # Make ActiveRecord logging include its class name in every log entry
|
28
|
+
# ActiveRecord::Base.logger = SemanticLogger::Logger.new('ActiveRecord')
|
29
|
+
def initialize(logger, &block)
|
25
30
|
raise "logger cannot be null when initiailizing the SemanticLogging::Appender::Logger" unless logger
|
26
31
|
@logger = logger
|
32
|
+
|
33
|
+
# Set the formatter to the supplied block
|
34
|
+
@formatter = block || self.default_formatter
|
27
35
|
end
|
28
36
|
|
29
|
-
#
|
30
|
-
#
|
37
|
+
# Default log formatter
|
38
|
+
# Replace this formatter by supplying a Block to the initializer
|
39
|
+
# Generates logs of the form:
|
31
40
|
# 2011-07-19 14:36:15.660 D [1149:ScriptThreadProcess] Rails -- Hello World
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
41
|
+
def default_formatter
|
42
|
+
Proc.new do |log|
|
43
|
+
message = log.message.to_s
|
44
|
+
if log.payload
|
45
|
+
if log.payload.is_a?(Exception)
|
46
|
+
exception = log.payload
|
47
|
+
message << " -- " << "#{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
|
48
|
+
else
|
49
|
+
message << " -- " << log.payload.inspect
|
50
|
+
end
|
51
|
+
end
|
37
52
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
Java::java.lang::Thread.current_thread.name
|
42
|
-
end
|
43
|
-
else
|
44
|
-
def self.thread_name
|
45
|
-
Thread.object_id
|
53
|
+
str = "#{log.time.strftime("%Y-%m-%d %H:%M:%S")}.#{"%03d" % (log.time.usec/1000)} #{log.level.to_s[0..0].upcase} [#{$$}:#{log.thread_name}] #{log.name} -- #{message}\n"
|
54
|
+
str << " (#{log.duration}ms)" if log.duration
|
55
|
+
str
|
46
56
|
end
|
47
57
|
end
|
48
58
|
|
49
|
-
# Allow the global formatter to be replaced
|
50
|
-
def self.formatter=(formatter)
|
51
|
-
@@formatter = formatter
|
52
|
-
end
|
53
|
-
|
54
59
|
# Pass log calls to the underlying Rails, log4j or Ruby logger
|
55
60
|
# trace entries are mapped to debug since :trace is not supported by the
|
56
61
|
# Ruby or Rails Loggers
|
57
|
-
def log(
|
58
|
-
@logger.send(level == :trace ? :debug : level
|
62
|
+
def log(log)
|
63
|
+
@logger.send(log.level == :trace ? :debug : log.level, @formatter.call(log))
|
59
64
|
end
|
60
65
|
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if hash
|
66
|
-
msg = hash.delete(:message)
|
67
|
-
time = hash.delete(:time)
|
68
|
-
duration = hash.delete(:duration)
|
69
|
-
end
|
70
|
-
msg ||= message.to_s
|
71
|
-
time ||= Time.now
|
72
|
-
|
73
|
-
msg << " -- " << self.msg2str(hash) if hash
|
74
|
-
msg << " -- " << self.msg2str(block.call) if block
|
75
|
-
@@formatter.call(level, name, msg, time, duration)
|
76
|
-
end
|
77
|
-
|
78
|
-
# Convert Objects to Strings for text based logging
|
79
|
-
def self.msg2str(message)
|
80
|
-
case message
|
81
|
-
when ::String
|
82
|
-
message
|
83
|
-
when ::Exception
|
84
|
-
"#{message.class}: #{message.message}\n#{(message.backtrace || []).join("\n")}"
|
85
|
-
when ::Hash
|
86
|
-
# With a hash, the message can be an element of the hash itself
|
87
|
-
if msg = message[:message]
|
88
|
-
# Cannot change supplied hash
|
89
|
-
hash = message.clone
|
90
|
-
hash.delete(:message)
|
91
|
-
"#{msg} #{hash.inspect}"
|
92
|
-
else
|
93
|
-
message.inspect
|
94
|
-
end
|
95
|
-
else
|
96
|
-
message.inspect
|
97
|
-
end
|
66
|
+
# Flush all pending logs to disk.
|
67
|
+
# Waits for all sent documents to be writted to disk
|
68
|
+
def flush
|
69
|
+
@logger.flush
|
98
70
|
end
|
99
71
|
|
100
72
|
end
|
@@ -46,9 +46,8 @@ module SemanticLogger
|
|
46
46
|
attr_accessor :formatter, :host_name, :safe, :application
|
47
47
|
|
48
48
|
# Create a MongoDB Appender instance
|
49
|
-
#
|
50
|
-
#
|
51
|
-
# )
|
49
|
+
#
|
50
|
+
# SemanticLogger::Appender::MongoDB.new(:db => Mongo::Connection.new['database'])
|
52
51
|
#
|
53
52
|
# Parameters:
|
54
53
|
# :db [Mongo::Database]
|
@@ -128,34 +127,40 @@ module SemanticLogger
|
|
128
127
|
@collection ||= db[collection_name]
|
129
128
|
end
|
130
129
|
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
136
|
-
else
|
137
|
-
def self.thread_name
|
138
|
-
Thread.object_id
|
139
|
-
end
|
130
|
+
# Flush all pending logs to disk.
|
131
|
+
# Waits for all sent documents to be writted to disk
|
132
|
+
def flush
|
133
|
+
db.get_last_error
|
140
134
|
end
|
141
135
|
|
142
136
|
# Default log formatter
|
143
137
|
# Replace this formatter by supplying a Block to the initializer
|
144
138
|
def default_formatter
|
145
|
-
Proc.new do |
|
139
|
+
Proc.new do |log|
|
146
140
|
document = {
|
147
|
-
:time
|
148
|
-
:host_name
|
149
|
-
:pid
|
150
|
-
:
|
151
|
-
:name
|
152
|
-
:level
|
141
|
+
:time => log.time,
|
142
|
+
:host_name => host_name,
|
143
|
+
:pid => $PID,
|
144
|
+
:thread_name => log.thread_name,
|
145
|
+
:name => log.name,
|
146
|
+
:level => log.level,
|
153
147
|
}
|
154
148
|
document[:application] = application if application
|
155
|
-
document[:message]
|
149
|
+
document[:message] = self.class.strip_colorizing(log.message) if log.message
|
150
|
+
document[:duration] = log.duration if log.duration
|
156
151
|
|
157
|
-
|
158
|
-
|
152
|
+
if log.payload
|
153
|
+
if log.payload.is_a?(Exception)
|
154
|
+
exception = log.payload
|
155
|
+
document[:payload] = {
|
156
|
+
:exception => exception.class.name,
|
157
|
+
:message => exception.message,
|
158
|
+
:backtrace => exception.backtrace
|
159
|
+
}
|
160
|
+
else
|
161
|
+
document[:payload] = log.payload
|
162
|
+
end
|
163
|
+
end
|
159
164
|
document
|
160
165
|
end
|
161
166
|
end
|
@@ -165,55 +170,15 @@ module SemanticLogger
|
|
165
170
|
message.to_s.gsub(/(\e(\[([\d;]*[mz]?))?)?/, '').strip
|
166
171
|
end
|
167
172
|
|
168
|
-
# Log the message
|
169
|
-
def log(
|
173
|
+
# Log the message to MongoDB
|
174
|
+
def log(log)
|
170
175
|
# Insert log entry into Mongo
|
171
176
|
# Use safe=>false so that we do not wait for it to be written to disk, or
|
172
177
|
# for the response from the MongoDB server
|
173
|
-
document = formatter.call(
|
178
|
+
document = formatter.call(log)
|
174
179
|
collection.insert(document, :safe=>safe)
|
175
180
|
end
|
176
181
|
|
177
|
-
# Populate Log Hash
|
178
|
-
def self.populate(document, message)
|
179
|
-
case message
|
180
|
-
when ::String
|
181
|
-
if document[:message]
|
182
|
-
document[:message] << " " << strip_colorizing(message)
|
183
|
-
else
|
184
|
-
document[:message] = strip_colorizing(message)
|
185
|
-
end
|
186
|
-
when ::Exception
|
187
|
-
document[:exception] = {
|
188
|
-
:class => message.class.name,
|
189
|
-
:message => message.message,
|
190
|
-
:backtrace => message.backtrace
|
191
|
-
}
|
192
|
-
when ::Hash
|
193
|
-
# With a hash, the message can be an element of the hash itself
|
194
|
-
if msg = message[:message]
|
195
|
-
# Cannot change supplied hash
|
196
|
-
hash = message.clone
|
197
|
-
hash.delete(:message)
|
198
|
-
if document[:message]
|
199
|
-
document[:message] << " " << strip_colorizing(msg)
|
200
|
-
else
|
201
|
-
document[:message] = strip_colorizing(msg)
|
202
|
-
end
|
203
|
-
document[:metadata] = hash
|
204
|
-
else
|
205
|
-
document[:metadata] = message
|
206
|
-
end
|
207
|
-
else
|
208
|
-
if document[:message]
|
209
|
-
document[:message] << " " << msg.inspect
|
210
|
-
else
|
211
|
-
document[:message] = msg.inspect
|
212
|
-
end
|
213
|
-
end
|
214
|
-
document
|
215
|
-
end
|
216
|
-
|
217
182
|
end
|
218
183
|
end
|
219
184
|
end
|
@@ -26,8 +26,8 @@ module SemanticLogger
|
|
26
26
|
class Logger
|
27
27
|
include SyncAttr
|
28
28
|
|
29
|
-
# Logging levels in order of
|
30
|
-
LEVELS = [:trace, :debug, :info, :warn, :error]
|
29
|
+
# Logging levels in order of precedence
|
30
|
+
LEVELS = [:trace, :debug, :info, :warn, :error, :fatal]
|
31
31
|
|
32
32
|
# Mapping of Rails and Ruby Logger levels to SemanticLogger levels
|
33
33
|
MAP_LEVELS = []
|
@@ -49,7 +49,7 @@ module SemanticLogger
|
|
49
49
|
@@default_level
|
50
50
|
end
|
51
51
|
|
52
|
-
attr_reader :
|
52
|
+
attr_reader :name, :level
|
53
53
|
|
54
54
|
@@default_level = :info
|
55
55
|
|
@@ -59,8 +59,8 @@ module SemanticLogger
|
|
59
59
|
# to be used in the logger
|
60
60
|
# options:
|
61
61
|
# :level The initial log level to start with for this logger instance
|
62
|
-
def initialize(
|
63
|
-
@
|
62
|
+
def initialize(klass, options={})
|
63
|
+
@name = klass.is_a?(String) ? klass : klass.name
|
64
64
|
set_level(options[:level] || self.class.default_level)
|
65
65
|
end
|
66
66
|
|
@@ -77,7 +77,7 @@ module SemanticLogger
|
|
77
77
|
# logger.debug?
|
78
78
|
#
|
79
79
|
# Example:
|
80
|
-
# logger = SemanticLogging::Logger.new(
|
80
|
+
# logger = SemanticLogging::Logger.new(self)
|
81
81
|
# logger.debug("Only display this if log level is set to Debug or lower")
|
82
82
|
#
|
83
83
|
# # Log semantic information along with a text message
|
@@ -88,35 +88,110 @@ module SemanticLogger
|
|
88
88
|
#
|
89
89
|
LEVELS.each_with_index do |level, index|
|
90
90
|
class_eval <<-EOT, __FILE__, __LINE__
|
91
|
-
def #{level}(message = nil,
|
92
|
-
if @level_index <= #{index}
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
91
|
+
def #{level}(message = nil, payload = nil)
|
92
|
+
if @level_index <= #{index}
|
93
|
+
if block_given? && (result = yield)
|
94
|
+
if result.is_a?(String)
|
95
|
+
message = message.nil? ? result : "\#{message} -- \#{result.to_s}"
|
96
|
+
else
|
97
|
+
payload = payload.nil? ? sresult : payload.merge(result)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, Time.now)
|
101
|
+
true
|
102
|
+
else
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def #{level}?
|
108
|
+
@level_index <= #{index}
|
109
|
+
end
|
110
|
+
|
111
|
+
def benchmark_#{level}(message, payload = nil)
|
112
|
+
raise "Mandatory block missing" unless block_given?
|
113
|
+
if @level_index <= #{index}
|
114
|
+
start = Time.now
|
115
|
+
result = yield
|
116
|
+
self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, start, Time.now - start)
|
117
|
+
result
|
118
|
+
else
|
119
|
+
yield
|
120
|
+
end
|
121
|
+
end
|
103
122
|
EOT
|
104
123
|
end
|
105
124
|
|
106
|
-
# Semantic Logging does not support :
|
125
|
+
# Semantic Logging does not support :unknown level since these
|
107
126
|
# are not understood by the majority of the logging providers
|
108
|
-
# Map
|
109
|
-
alias :fatal :error
|
110
|
-
alias :fatal? :error?
|
127
|
+
# Map it to :error
|
111
128
|
alias :unknown :error
|
112
129
|
alias :unknown? :error?
|
113
130
|
|
114
|
-
#
|
115
|
-
|
116
|
-
#
|
131
|
+
# #TODO implement a thread safe #silence method
|
132
|
+
|
133
|
+
# Returns [Integer] the number of log entries that have not been written
|
134
|
+
# to the appenders
|
135
|
+
#
|
136
|
+
# When this number grows it is because the logging appender thread is not
|
137
|
+
# able to write to the appenders fast enough. Either reduce the amount of
|
138
|
+
# logging, increase the log level, reduce the number of appenders, or
|
139
|
+
# look into speeding up the appenders themselves
|
140
|
+
def self.cache_count
|
141
|
+
queue.size
|
142
|
+
end
|
143
|
+
|
144
|
+
# Flush all pending log entry disk, database, etc.
|
145
|
+
# All pending log writes are completed and each appender is flushed in turn
|
146
|
+
def flush
|
147
|
+
self.class.flush
|
148
|
+
end
|
117
149
|
|
150
|
+
# Flush all pending log entry disk, database, etc.
|
151
|
+
# All pending log writes are completed and each appender is flushed in turn
|
152
|
+
def self.flush
|
153
|
+
return false unless @@appender_thread.alive?
|
154
|
+
|
155
|
+
reply_queue = Queue.new
|
156
|
+
queue << { :command => :flush, :reply_queue => reply_queue }
|
157
|
+
reply_queue.pop
|
158
|
+
end
|
159
|
+
|
160
|
+
# Internal logger for SymanticLogger
|
161
|
+
# For example when an appender is not working etc..
|
162
|
+
# By default logs to STDERR, replace with another Ruby logger or Rails
|
163
|
+
# logger, but never to SemanticLogger itself
|
164
|
+
sync_cattr_accessor :logger do
|
165
|
+
require 'logger'
|
166
|
+
l = ::Logger.new(STDOUT)
|
167
|
+
l.level = ::Logger::INFO
|
168
|
+
l
|
169
|
+
end
|
170
|
+
|
171
|
+
############################################################################
|
118
172
|
protected
|
119
173
|
|
174
|
+
# Log to queue
|
175
|
+
# Starts the appender thread the first time a logging call is made
|
176
|
+
sync_cattr_reader :queue do
|
177
|
+
startup
|
178
|
+
at_exit { shutdown }
|
179
|
+
Queue.new
|
180
|
+
end
|
181
|
+
|
182
|
+
Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration)
|
183
|
+
|
184
|
+
# For JRuby include the Thread name rather than its id
|
185
|
+
if defined? Java
|
186
|
+
def self.thread_name
|
187
|
+
Java::java.lang::Thread.current_thread.name
|
188
|
+
end
|
189
|
+
else
|
190
|
+
def self.thread_name
|
191
|
+
Thread.object_id
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
120
195
|
# Verify and set the level
|
121
196
|
def set_level(level)
|
122
197
|
index = if level.is_a?(Integer)
|
@@ -132,5 +207,51 @@ module SemanticLogger
|
|
132
207
|
@level_index = index
|
133
208
|
@level = level
|
134
209
|
end
|
210
|
+
|
211
|
+
# Start a separate appender thread responsible for reading log messages and
|
212
|
+
# calling the appenders in it's thread
|
213
|
+
def self.startup
|
214
|
+
@@appender_thread = Thread.new do
|
215
|
+
begin
|
216
|
+
# #TODO Logger needs it's own "reliable" appender ;)
|
217
|
+
# For example if an appender constantly fails
|
218
|
+
# ( bad filename or path, invalid server )
|
219
|
+
logger.debug "SemanticLogger::Logger Appender Thread Started"
|
220
|
+
while message=queue.pop
|
221
|
+
if message.is_a? Log
|
222
|
+
appenders.each {|appender| appender.log(message) }
|
223
|
+
else
|
224
|
+
case message[:command]
|
225
|
+
when :shutdown
|
226
|
+
appenders.each {|appender| appender.flush }
|
227
|
+
message[:reply_queue] << true
|
228
|
+
logger.debug "SemanticLogger::Logger appenders flushed, now shutting down"
|
229
|
+
break
|
230
|
+
when :flush
|
231
|
+
appenders.each {|appender| appender.flush }
|
232
|
+
message[:reply_queue] << true
|
233
|
+
logger.debug "SemanticLogger::Logger appenders flushed"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
rescue Exception => exception
|
238
|
+
logger.fatal "SemanticLogger::Logger Appender Thread crashed: #{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
|
239
|
+
exit(-2)
|
240
|
+
ensure
|
241
|
+
logger.debug "SemanticLogger::Logger Appender Thread stopped"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Stop the log appender thread and flush all appenders
|
247
|
+
def self.shutdown
|
248
|
+
return false unless @@appender_thread.alive?
|
249
|
+
|
250
|
+
logger.debug "SemanticLogger::Logger Shutdown. Stopping appender thread"
|
251
|
+
reply_queue = Queue.new
|
252
|
+
queue << { :command => :shutdown, :reply_queue => reply_queue }
|
253
|
+
reply_queue.pop
|
254
|
+
end
|
255
|
+
|
135
256
|
end
|
136
|
-
end
|
257
|
+
end
|
@@ -9,7 +9,7 @@ module SemanticLogger #:nodoc:
|
|
9
9
|
# # Add the MongoDB logger appender only once Rails is initialized
|
10
10
|
# config.after_initialize do
|
11
11
|
# config.semantic_logger.appenders << SemanticLogger::Appender::Mongo.new(
|
12
|
-
# :db =>
|
12
|
+
# :db => Mongo::Connection.new['development_development']
|
13
13
|
# )
|
14
14
|
# end
|
15
15
|
# end
|
@@ -26,6 +26,10 @@ module SemanticLogger #:nodoc:
|
|
26
26
|
initializer :initialize_semantic_logger, :after => :initialize_logger do
|
27
27
|
config = Rails.application.config
|
28
28
|
|
29
|
+
# First set the internal logger to the one used by Rails in case something goes wrong
|
30
|
+
# with an appender
|
31
|
+
SemanticLogger::Logger.logger = Rails.logger
|
32
|
+
|
29
33
|
# Add the Rails Logger to the list of appenders
|
30
34
|
SemanticLogger::Logger.appenders << SemanticLogger::Appender::Logger.new(Rails.logger)
|
31
35
|
|
@@ -5,7 +5,7 @@ require 'rubygems'
|
|
5
5
|
require 'test/unit'
|
6
6
|
require 'shoulda'
|
7
7
|
require 'logger'
|
8
|
-
require 'semantic_logger
|
8
|
+
require 'semantic_logger'
|
9
9
|
require 'test/mock_logger'
|
10
10
|
|
11
11
|
# Unit Test for SemanticLogger::Appender::Logger
|
@@ -14,64 +14,59 @@ class AppenderLoggerTest < Test::Unit::TestCase
|
|
14
14
|
context SemanticLogger::Appender::Logger do
|
15
15
|
setup do
|
16
16
|
@time = Time.parse("2012-08-02 09:48:32.482")
|
17
|
+
@mock_logger = MockLogger.new
|
18
|
+
@appender = SemanticLogger::Appender::Logger.new(@mock_logger)
|
19
|
+
@hash = { :session_id => 'HSSKLEU@JDK767', :tracking_number => 12345 }
|
17
20
|
end
|
18
21
|
|
19
|
-
context "format
|
20
|
-
|
21
|
-
|
22
|
+
context "format logs into text form" do
|
23
|
+
should "handle nil name, message and payload" do
|
24
|
+
log = SemanticLogger::Logger::Log.new
|
25
|
+
log.time = Time.now
|
26
|
+
log.level = :debug
|
27
|
+
@appender.log(log)
|
28
|
+
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\] -- \n/, @mock_logger.message
|
22
29
|
end
|
23
30
|
|
24
|
-
should "handle nil
|
25
|
-
|
26
|
-
|
31
|
+
should "handle nil message and payload" do
|
32
|
+
log = SemanticLogger::Logger::Log.new
|
33
|
+
log.time = Time.now
|
34
|
+
log.level = :debug
|
35
|
+
log.name = 'class'
|
36
|
+
@appender.log(log)
|
37
|
+
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\] class -- \n/, @mock_logger.message
|
27
38
|
end
|
28
39
|
|
29
|
-
should "handle nil
|
30
|
-
|
31
|
-
|
40
|
+
should "handle nil payload" do
|
41
|
+
log = SemanticLogger::Logger::Log.new
|
42
|
+
log.time = Time.now
|
43
|
+
log.level = :debug
|
44
|
+
log.name = 'class'
|
45
|
+
log.message = 'hello world'
|
46
|
+
@appender.log(log)
|
47
|
+
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\] class -- hello world\n/, @mock_logger.message
|
32
48
|
end
|
33
49
|
|
34
|
-
should "handle
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
should "handle hash" do
|
45
|
-
msg = SemanticLogger::Appender::Logger.format_message(:debug, 'application', 'hello world', @hash)
|
46
|
-
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\w+\] application -- hello world -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\}\n/, msg
|
47
|
-
end
|
48
|
-
|
49
|
-
should "handle block" do
|
50
|
-
msg = SemanticLogger::Appender::Logger.format_message(:debug, 'application', 'hello world', @hash) { "Calculations" }
|
51
|
-
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\w+\] application -- hello world -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\} -- Calculations\n/, msg
|
52
|
-
end
|
53
|
-
|
54
|
-
should "handle block with no other parameters" do
|
55
|
-
msg = SemanticLogger::Appender::Logger.format_message(:debug, nil, nil, nil) { "Calculations" }
|
56
|
-
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\w+\] -- -- Calculations\n/, msg
|
50
|
+
should "handle payload" do
|
51
|
+
log = SemanticLogger::Logger::Log.new
|
52
|
+
log.time = Time.now
|
53
|
+
log.level = :debug
|
54
|
+
log.name = 'class'
|
55
|
+
log.message = 'hello world'
|
56
|
+
log.payload = @hash
|
57
|
+
@appender.log(log)
|
58
|
+
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\] class -- hello world -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\}\n/, @mock_logger.message
|
57
59
|
end
|
58
60
|
end
|
59
61
|
|
60
|
-
context "
|
61
|
-
setup do
|
62
|
-
@mock_logger = MockLogger.new
|
63
|
-
@appender = SemanticLogger::Appender::Logger.new(@mock_logger)
|
64
|
-
@hash = { :tracking_number => 12345, :session_id => 'HSSKLEU@JDK767'}
|
65
|
-
end
|
66
|
-
|
62
|
+
context "for each log level" do
|
67
63
|
# Ensure that any log level can be logged
|
68
64
|
Logger::Severity.constants.each do |level|
|
69
65
|
should "log #{level.downcase.to_sym} info" do
|
70
|
-
@appender.log(level.downcase.to_sym, '
|
71
|
-
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d
|
66
|
+
@appender.log SemanticLogger::Logger::Log.new(level.downcase.to_sym, 'thread', 'class', 'hello world -- Calculations', @hash, Time.now)
|
67
|
+
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:thread\] class -- hello world -- Calculations -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\}\n/, @mock_logger.message
|
72
68
|
end
|
73
69
|
end
|
74
|
-
|
75
70
|
end
|
76
71
|
|
77
72
|
end
|
@@ -4,11 +4,8 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'test/unit'
|
6
6
|
require 'shoulda'
|
7
|
-
require 'logger'
|
8
7
|
require 'mongo'
|
9
|
-
require '
|
10
|
-
require 'semantic_logger/logger'
|
11
|
-
require 'semantic_logger/appender/mongodb'
|
8
|
+
require 'semantic_logger'
|
12
9
|
|
13
10
|
# Unit Test for SemanticLogger::Appender::MongoDB
|
14
11
|
#
|
@@ -16,98 +13,103 @@ class AppenderMongoDBTest < Test::Unit::TestCase
|
|
16
13
|
context SemanticLogger::Appender::MongoDB do
|
17
14
|
setup do
|
18
15
|
@db = Mongo::Connection.new['test']
|
16
|
+
@appender = SemanticLogger::Appender::MongoDB.new(
|
17
|
+
:db => @db,
|
18
|
+
:collection_size => 10*1024**2, # 10MB
|
19
|
+
:host_name => 'test',
|
20
|
+
:application => 'test_application'
|
21
|
+
)
|
22
|
+
@hash = { :tracking_number => 12345, :session_id => 'HSSKLEU@JDK767'}
|
23
|
+
@time = Time.now
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
|
-
|
26
|
+
teardown do
|
27
|
+
@appender.purge_all
|
23
28
|
end
|
24
29
|
|
25
|
-
context "
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
context "format logs into documents" do
|
31
|
+
|
32
|
+
should "handle nil name, message and hash" do
|
33
|
+
@appender.log SemanticLogger::Logger::Log.new(:debug)
|
34
|
+
document = @appender.collection.find_one
|
35
|
+
assert_equal :debug, document['level']
|
36
|
+
assert_equal nil, document['message']
|
37
|
+
assert_equal nil, document['thread_name']
|
38
|
+
assert_equal nil, document['time']
|
39
|
+
assert_equal nil, document['payload']
|
40
|
+
assert_equal $PID, document['pid']
|
41
|
+
assert_equal 'test', document['host_name']
|
42
|
+
assert_equal 'test_application', document['application']
|
35
43
|
end
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
|
53
|
-
should "handle nil hash" do
|
54
|
-
document = @appender.formatter.call(:debug, 'myclass', 'hello world', nil)
|
55
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>'myclass', :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'hello world'}, document)
|
56
|
-
end
|
57
|
-
|
58
|
-
should "handle hash" do
|
59
|
-
document = @appender.formatter.call(:debug, 'myclass', 'hello world', @hash)
|
60
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>'myclass', :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'hello world', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
|
61
|
-
end
|
62
|
-
|
63
|
-
should "handle string block with no message" do
|
64
|
-
document = @appender.formatter.call(:debug, 'myclass', nil, @hash, Proc.new { "Calculations" })
|
65
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>'myclass', :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'Calculations', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
|
66
|
-
end
|
67
|
-
|
68
|
-
should "handle string block" do
|
69
|
-
document = @appender.formatter.call(:debug, 'myclass', 'hello world', @hash, Proc.new { "Calculations" })
|
70
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>'myclass', :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'hello world Calculations', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
|
71
|
-
end
|
72
|
-
|
73
|
-
should "handle hash block" do
|
74
|
-
document = @appender.formatter.call(:debug, 'myclass', 'hello world', nil, Proc.new { @hash })
|
75
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>'myclass', :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'hello world', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
|
76
|
-
end
|
77
|
-
|
78
|
-
should "handle string block with no other parameters" do
|
79
|
-
document = @appender.formatter.call(:debug, 'myclass', 'hello world', @hash, Proc.new { "Calculations" })
|
80
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>'myclass', :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'hello world Calculations', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
|
81
|
-
end
|
82
|
-
|
83
|
-
should "handle hash block with no other parameters" do
|
84
|
-
document = @appender.formatter.call(:debug, nil, nil, nil, Proc.new { @hash.merge(:message => 'hello world') })
|
85
|
-
assert_equal({ :level=>:debug, :time=>document[:time], :name=>nil, :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :message=>'hello world', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
|
86
|
-
end
|
45
|
+
should "handle nil message and payload" do
|
46
|
+
log = SemanticLogger::Logger::Log.new(:debug)
|
47
|
+
log.payload = @hash
|
48
|
+
@appender.log(log)
|
49
|
+
|
50
|
+
document = @appender.collection.find_one
|
51
|
+
assert_equal :debug, document['level']
|
52
|
+
assert_equal nil, document['message']
|
53
|
+
assert_equal nil, document['thread_name']
|
54
|
+
assert_equal nil, document['time']
|
55
|
+
assert_equal({ "tracking_number" => 12345, "session_id" => 'HSSKLEU@JDK767'}, document['payload'])
|
56
|
+
assert_equal $PID, document['pid']
|
57
|
+
assert_equal 'test', document['host_name']
|
58
|
+
assert_equal 'test_application', document['application']
|
87
59
|
end
|
88
|
-
end
|
89
60
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
61
|
+
should "handle message and payload" do
|
62
|
+
log = SemanticLogger::Logger::Log.new(:debug)
|
63
|
+
log.message = 'hello world'
|
64
|
+
log.payload = @hash
|
65
|
+
log.thread_name = 'thread'
|
66
|
+
log.time = @time
|
67
|
+
@appender.log(log)
|
68
|
+
|
69
|
+
document = @appender.collection.find_one
|
70
|
+
assert_equal :debug, document['level']
|
71
|
+
assert_equal 'hello world', document['message']
|
72
|
+
assert_equal 'thread', document['thread_name']
|
73
|
+
assert_equal @time.to_i, document['time'].to_i
|
74
|
+
assert_equal({ "tracking_number" => 12345, "session_id" => 'HSSKLEU@JDK767'}, document['payload'])
|
75
|
+
assert_equal $PID, document['pid']
|
76
|
+
assert_equal 'test', document['host_name']
|
77
|
+
assert_equal 'test_application', document['application']
|
99
78
|
end
|
100
79
|
|
101
|
-
|
102
|
-
|
80
|
+
should "handle message without payload" do
|
81
|
+
log = SemanticLogger::Logger::Log.new(:debug)
|
82
|
+
log.message = 'hello world'
|
83
|
+
log.thread_name = 'thread'
|
84
|
+
log.time = @time
|
85
|
+
@appender.log(log)
|
86
|
+
|
87
|
+
document = @appender.collection.find_one
|
88
|
+
assert_equal :debug, document['level']
|
89
|
+
assert_equal 'hello world', document['message']
|
90
|
+
assert_equal 'thread', document['thread_name']
|
91
|
+
assert_equal @time.to_i, document['time'].to_i
|
92
|
+
assert_equal nil, document['payload']
|
93
|
+
assert_equal $PID, document['pid']
|
94
|
+
assert_equal 'test', document['host_name']
|
95
|
+
assert_equal 'test_application', document['application']
|
103
96
|
end
|
97
|
+
end
|
104
98
|
|
99
|
+
context "for each log level" do
|
105
100
|
# Ensure that any log level can be logged
|
106
101
|
SemanticLogger::Logger::LEVELS.each do |level|
|
107
102
|
should "log #{level} information" do
|
108
|
-
@appender.log(level, 'my_class', 'hello world', @hash)
|
103
|
+
@appender.log SemanticLogger::Logger::Log.new(level, 'thread', 'my_class', 'hello world -- Calculations', @hash, @time)
|
109
104
|
document = @appender.collection.find_one
|
110
|
-
assert_equal
|
105
|
+
assert_equal level, document['level']
|
106
|
+
assert_equal 'hello world -- Calculations', document['message']
|
107
|
+
assert_equal 'thread', document['thread_name']
|
108
|
+
assert_equal @time.to_i, document['time'].to_i
|
109
|
+
assert_equal({ "tracking_number" => 12345, "session_id" => 'HSSKLEU@JDK767'}, document['payload'])
|
110
|
+
assert_equal $PID, document['pid']
|
111
|
+
assert_equal 'test', document['host_name']
|
112
|
+
assert_equal 'test_application', document['application']
|
111
113
|
end
|
112
114
|
end
|
113
115
|
|
data/test/logger_test.rb
CHANGED
@@ -23,7 +23,7 @@ class LoggerTest < Test::Unit::TestCase
|
|
23
23
|
# Use this test's class name as the application name in the log output
|
24
24
|
@logger = SemanticLogger::Logger.new('LoggerTest', :level => :trace)
|
25
25
|
|
26
|
-
@hash = { :
|
26
|
+
@hash = { :session_id => 'HSSKLEU@JDK767', :tracking_number => 12345 }
|
27
27
|
end
|
28
28
|
|
29
29
|
teardown do
|
@@ -34,7 +34,8 @@ class LoggerTest < Test::Unit::TestCase
|
|
34
34
|
SemanticLogger::Logger::LEVELS.each do |level|
|
35
35
|
should "log #{level} info" do
|
36
36
|
@logger.send(level, 'hello world', @hash) { "Calculations" }
|
37
|
-
|
37
|
+
@logger.flush
|
38
|
+
assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:.+\] LoggerTest -- hello world -- Calculations -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\}\n/, @mock_logger.message
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
data/test/mock_logger.rb
CHANGED
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.2
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Reid Morrison
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2012-08-
|
17
|
+
date: 2012-08-17 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: "0"
|
30
30
|
type: :development
|
31
31
|
version_requirements: *id001
|
32
|
-
description:
|
32
|
+
description: Machine readable document oriented logging with support for MongoDB and text files
|
33
33
|
email:
|
34
34
|
- reidmo@gmail.com
|
35
35
|
executables: []
|