semantic_logger 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/FUTURE.rb CHANGED
@@ -207,5 +207,3 @@
207
207
  # option helps to create more human readable output for
208
208
  # multithread application logs.
209
209
 
210
-
211
-
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({"data.table":"users", "data.action":"query"})
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 = "Logging with additional machine readable and parseable log data"
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'
@@ -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
- if defined?(Mongo)
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
- def initialize(logger)
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
- # The default log formatter
30
- # Generates logs of the form:
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
- @@formatter = Proc.new do |level, name, message, time, duration|
33
- str = "#{time.strftime("%Y-%m-%d %H:%M:%S")}.#{"%03d" % (time.usec/1000)} #{level.to_s[0..0].upcase} [#{$$}:#{thread_name}] #{name} -- #{message}\n"
34
- str << " (#{duration}ms)" if duration
35
- str
36
- end
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
- # For JRuby include the Thread name rather than its id
39
- if defined? Java
40
- def self.thread_name
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(level, name, message, hash, &block)
58
- @logger.send(level == :trace ? :debug : level) { self.class.format_message(level, name, message, hash, &block) }
62
+ def log(log)
63
+ @logger.send(log.level == :trace ? :debug : log.level, @formatter.call(log))
59
64
  end
60
65
 
61
- # Convert a semantic log entry into plain text
62
- def self.format_message(level, name, message, hash=nil, &block)
63
- # TODO need to define :try if not already defined. E.g. Outside Rails
64
- msg = time = duration = nil
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
- # SemanticLogger::Appender::MongoDB.new(
50
- # :db => Cache::Work.db
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
- # For JRuby include the Thread name rather than its id
132
- if defined? Java
133
- def self.thread_name
134
- Java::java.lang::Thread.current_thread.name
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 |level, name, message, hash, block|
139
+ Proc.new do |log|
146
140
  document = {
147
- :time => Time.now,
148
- :host_name => host_name,
149
- :pid => $PID,
150
- :thread => SemanticLogger::Appender::MongoDB.thread_name,
151
- :name => name,
152
- :level => 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] = self.class.strip_colorizing(message) if message
149
+ document[:message] = self.class.strip_colorizing(log.message) if log.message
150
+ document[:duration] = log.duration if log.duration
156
151
 
157
- SemanticLogger::Appender::MongoDB.populate(document, hash) if hash
158
- SemanticLogger::Appender::MongoDB.populate(document, block.call) if block
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(level, name, message, hash, &block)
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(level, name, message, hash, &block)
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 precendence
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 :application, :level
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(application, options={})
63
- @application = application.is_a?(String) ? application : application.name
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('MyApplication')
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, data = nil, &block) # def trace(message = nil, data = nil, &block)
92
- if @level_index <= #{index} # if @level_index <= 0
93
- self.class.appenders.each {|appender| appender.log(:#{level}, application, message, data, &block) } # self.class.appenders.each {|appender| appender.log(:trace, application, message, data, &block) }
94
- true # true
95
- else # else
96
- false # false
97
- end # end
98
- end # end
99
-
100
- def #{level}? # def trace?
101
- @level_index <= #{index} # @level_index <= 0
102
- end # end
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 :fatal or :unknown levels since these
125
+ # Semantic Logging does not support :unknown level since these
107
126
  # are not understood by the majority of the logging providers
108
- # Map them to :error
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
- # forward other calls to ActiveResource::BufferedLogger
115
- # #silence is not implemented since it is not thread safe prior to Rails 3.2
116
- # #TODO implement a thread safe silence method
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 => Cache::Work.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
 
@@ -1,3 +1,3 @@
1
1
  module SemanticLogger #:nodoc
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -5,7 +5,7 @@ require 'rubygems'
5
5
  require 'test/unit'
6
6
  require 'shoulda'
7
7
  require 'logger'
8
- require 'semantic_logger/appender/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 messages into text form" do
20
- setup do
21
- @hash = { :session_id=>"HSSKLEU@JDK767", :tracking_number=>12345 }
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 level, application, message and hash" do
25
- msg = SemanticLogger::Appender::Logger.format_message(nil, nil, nil, nil)
26
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \[\d+:\w+\] -- \n/, msg
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 application, message and hash" do
30
- msg = SemanticLogger::Appender::Logger.format_message(:debug, nil, nil, nil)
31
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\w+\] -- \n/, msg
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 nil message and hash" do
35
- msg = SemanticLogger::Appender::Logger.format_message(:debug, 'application', nil, nil)
36
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\w+\] application -- \n/, msg
37
- end
38
-
39
- should "handle nil hash" do
40
- msg = SemanticLogger::Appender::Logger.format_message(:debug, 'application', 'hello world', nil)
41
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\w+\] application -- hello world\n/, msg
42
- end
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 "log to Ruby logger" do
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, 'application', 'hello world', @hash) { "Calculations" }
71
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:\w+\] application -- hello world -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\} -- Calculations\n/, @mock_logger.message
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 'sync_attr'
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
- context "configuration" do
22
- #TODO verify configuration setting carry through
26
+ teardown do
27
+ @appender.purge_all
23
28
  end
24
29
 
25
- context "formatter" do
26
- setup do
27
- @appender = SemanticLogger::Appender::MongoDB.new(
28
- :db => @db,
29
- :collection_size => 10*1024**2, # 10MB
30
- :host_name => 'test',
31
- :application => 'test_application'
32
- )
33
- @time = Time.parse("2012-08-02 09:48:32.482")
34
- @hash = { :session_id=>"HSSKLEU@JDK767", :tracking_number=>12345 }
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
- context "format messages into text form" do
38
- should "handle nil level, application, message and hash" do
39
- document = @appender.formatter.call(nil, nil, nil, nil)
40
- assert_equal({ :level=>nil, :time=>document[:time], :name=>nil, :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application'}, document)
41
- end
42
-
43
- should "handle nil application, message and hash" do
44
- document = @appender.formatter.call(:debug, nil, nil, nil)
45
- assert_equal({ :level=>:debug, :time=>document[:time], :name=>nil, :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application'}, document)
46
- end
47
-
48
- should "handle nil message and hash" do
49
- document = @appender.formatter.call(:debug, nil, nil, @hash)
50
- assert_equal({ :level=>:debug, :time=>document[:time], :name=>nil, :pid=>$PID, :host_name=>"test", :thread=>document[:thread], :application=>'test_application', :metadata=>{:session_id=>"HSSKLEU@JDK767", :tracking_number=>12345}}, document)
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
- context "log to Mongo logger" do
91
- setup do
92
- @appender = SemanticLogger::Appender::MongoDB.new(
93
- :db => @db,
94
- :collection_size => 10*1024**2, # 10MB
95
- :host_name => 'test',
96
- :application => 'test_application'
97
- )
98
- @hash = { :tracking_number => 12345, :session_id => 'HSSKLEU@JDK767'}
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
- teardown do
102
- @appender.purge_all
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) { "Calculations" }
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({"_id"=>document['_id'], "level"=>level, "message"=>"hello world", "thread"=>document['thread'], "time"=>document['time'], 'metadata'=>{'session_id'=>"HSSKLEU@JDK767", 'tracking_number'=>12345}, "name"=>"my_class", "pid"=>document['pid'], "host_name"=>"test", "application"=>"test_application"}, document)
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 = { :tracking_number => 12345, :session_id => 'HSSKLEU@JDK767'}
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
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:\w+\] LoggerTest -- hello world -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\} -- Calculations\n/, @mock_logger.message
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
@@ -21,5 +21,9 @@ class MockLogger
21
21
  end
22
22
  EOT
23
23
  end
24
+
25
+ def flush
26
+ true
27
+ end
24
28
  end
25
29
 
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
- - 2
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-08 00:00:00 -04:00
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: Logging with additional machine readable and parseable log data
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: []