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 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: []