semantic_logger 0.5.3 → 0.6.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.
@@ -2,14 +2,17 @@
2
2
  require 'sync_attr'
3
3
 
4
4
  module SemanticLogger
5
+ autoload :Base, 'semantic_logger/base'
5
6
  autoload :Logger, 'semantic_logger/logger'
6
7
 
7
8
  module Appender
8
- autoload :File, 'semantic_logger/appender/file'
9
- autoload :Logger, 'semantic_logger/appender/logger'
10
- # Only load the MongoDB appender if the Mongo Ruby Driver is loaded
9
+ autoload :File, 'semantic_logger/appender/file'
10
+ autoload :Wrapper, 'semantic_logger/appender/wrapper'
11
11
  autoload :MongoDB, 'semantic_logger/appender/mongodb'
12
12
  end
13
+
14
+ # Logging levels in order with most detailed logging first
15
+ LEVELS = [:trace, :debug, :info, :warn, :error, :fatal]
13
16
  end
14
17
 
15
18
  if defined?(Rails)
@@ -4,14 +4,16 @@
4
4
  #
5
5
  module SemanticLogger
6
6
  module Appender
7
- class File
8
- attr_accessor :formatter
7
+ class File < SemanticLogger::Base
9
8
 
10
9
  # Create a File Logger appender instance
11
10
  #
12
11
  # Example
13
12
  # require 'semantic_logger'
14
13
  #
14
+ # # Enable trace level logging
15
+ # SemanticLogger::Logger.level = :info
16
+ #
15
17
  # # Log to screen
16
18
  # SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDOUT)
17
19
  #
@@ -21,7 +23,23 @@ module SemanticLogger
21
23
  # logger = SemanticLogger::Logger.new('test')
22
24
  # logger.info 'Hello World'
23
25
  #
24
- def initialize(filename, &block)
26
+ # Example 2. To log all levels to file and only :info and above to screen:
27
+ #
28
+ # require 'semantic_logger'
29
+ #
30
+ # # Enable trace level logging
31
+ # SemanticLogger::Logger.level = :trace
32
+ #
33
+ # # Log to screen but only display :info and above
34
+ # SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDOUT, :info)
35
+ #
36
+ # # And log to a file at the same time, including all :trace level data
37
+ # SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('application.log')
38
+ #
39
+ # logger = SemanticLogger::Logger.new('test')
40
+ # logger.info 'Hello World'
41
+ #
42
+ def initialize(filename, level=nil, &block)
25
43
  raise "logger cannot be null when initializing the SemanticLogging::Appender::Logger" unless filename
26
44
  @filename = filename
27
45
  @log = if filename.respond_to?(:write) and filename.respond_to?(:close)
@@ -29,38 +47,14 @@ module SemanticLogger
29
47
  else
30
48
  @log = open(filename, (::File::WRONLY | ::File::APPEND | ::File::CREAT))
31
49
  # Force all log entries to write immediately without buffering
50
+ # Allows multiple processes to write to the same log file simultaneously
32
51
  @log.sync = true
33
52
  @log.set_encoding(Encoding::BINARY) if @log.respond_to?(:set_encoding)
34
53
  @log
35
54
  end
36
55
 
37
- # Set the formatter to the supplied block
38
- @formatter = block || self.default_formatter
39
- end
40
-
41
- # Default log formatter
42
- # Replace this formatter by supplying a Block to the initializer
43
- # Generates logs of the form:
44
- # 2011-07-19 14:36:15.660 D [1149:ScriptThreadProcess] Rails -- Hello World\n
45
- def default_formatter
46
- Proc.new do |log|
47
- message = log.message.to_s
48
- tags = log.tags.collect { |tag| "[#{tag}]" }.join(" ") + " " if log.tags && (log.tags.size > 0)
49
-
50
- if log.payload
51
- if log.payload.is_a?(Exception)
52
- exception = log.payload
53
- message << " -- " << "#{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
54
- else
55
- message << " -- " << log.payload.inspect
56
- end
57
- end
58
-
59
- 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}] #{tags}#{log.name} -- #{message}"
60
- str << " (#{'%.1f' % log.duration}ms)" if log.duration
61
- str << "\n"
62
- str
63
- end
56
+ # Set the log level and formatter if supplied
57
+ super(level, &block)
64
58
  end
65
59
 
66
60
  # Pass log calls to the underlying Rails, log4j or Ruby logger
@@ -69,7 +63,8 @@ module SemanticLogger
69
63
  def log(log)
70
64
  # Since only one appender thread will be writing to the file at a time
71
65
  # it is not necessary to protect access to the file with a semaphore
72
- @log.write(@formatter.call(log))
66
+ # Allow this logger to filter out log levels lower than it's own
67
+ @log.write(@formatter.call(log) << "\n") if level_index <= (log.level_index || 0)
73
68
  end
74
69
 
75
70
  # Flush all pending logs to disk.
@@ -1,3 +1,4 @@
1
+ require 'socket'
1
2
  module SemanticLogger
2
3
  module Appender
3
4
  # The Mongo Appender for the SemanticLogger
@@ -41,9 +42,9 @@ module SemanticLogger
41
42
  # params: Hash
42
43
  #
43
44
  # tracking_number: 'user defined tracking number'
44
- class MongoDB
45
+ class MongoDB < SemanticLogger::Base
45
46
  attr_reader :db, :collection_name
46
- attr_accessor :formatter, :host_name, :safe, :application
47
+ attr_accessor :host_name, :safe, :application
47
48
 
48
49
  # Create a MongoDB Appender instance
49
50
  #
@@ -84,6 +85,9 @@ module SemanticLogger
84
85
  # :collection_max [Integer]
85
86
  # Maximum number of log entries that the capped collection will hold
86
87
  #
88
+ # :level [Symbol]
89
+ # Only allow log entries of this level or higher to be written to MongoDB
90
+ #
87
91
  def initialize(params={}, &block)
88
92
  @db = params[:db] || raise('Missing mandatory parameter :db')
89
93
  @collection_name = params[:collection_name] || 'semantic_logger'
@@ -95,11 +99,11 @@ module SemanticLogger
95
99
  @collection_size = params[:collection_size] || 1024**3
96
100
  @collection_max = params[:collection_max]
97
101
 
98
- # Set the formatter to the supplied block
99
- @formatter = block || self.default_formatter
100
-
101
102
  # Create the collection and necessary indexes
102
103
  create_indexes
104
+
105
+ # Set the log level and formatter
106
+ super(params[:level], &block)
103
107
  end
104
108
 
105
109
  # Create the required capped collection
@@ -189,8 +193,7 @@ module SemanticLogger
189
193
  # Insert log entry into Mongo
190
194
  # Use safe=>false so that we do not wait for it to be written to disk, or
191
195
  # for the response from the MongoDB server
192
- document = formatter.call(log)
193
- collection.insert(document, :safe=>safe)
196
+ collection.insert(formatter.call(log), :safe=>safe) if level_index <= (log.level_index || 0)
194
197
  end
195
198
 
196
199
  end
@@ -1,14 +1,11 @@
1
- # Logger appender
1
+ # Wrapper appender
2
2
  #
3
- # Maps the SemanticLogger API's to the Rails log, log4j, or Ruby Logger
3
+ # Wraps the Rails log, log4r, or Ruby Logger with the SemanticLogger API's
4
4
  #
5
- # The log level is controlled by the Logging implementation passed into
6
- # this appender
7
5
  module SemanticLogger
8
6
  module Appender
9
- class Logger
7
+ class Wrapper < SemanticLogger::Base
10
8
  attr_reader :logger
11
- attr_accessor :formatter
12
9
 
13
10
  # Create a Logger or Rails Logger appender instance
14
11
  #
@@ -16,48 +13,30 @@ module SemanticLogger
16
13
  # require 'logger'
17
14
  # require 'semantic_logger'
18
15
  # ruby_logger = Logger.new(STDOUT)
19
- # SemanticLogger::Logger.appenders << SemanticLogger::Appender::Logger.new(ruby_logger)
16
+ # SemanticLogger::Logger.appenders << SemanticLogger::Appender::Wrapper.new(ruby_logger)
20
17
  # logger = SemanticLogger::Logger.new('test')
21
18
  # logger.info('Hello World', :some => :payload)
22
19
  #
23
20
  # Enhance the Rails Logger
24
21
  # # Add the Rails logger to the list of appenders
25
- # SemanticLogger::Logger.appenders << SemanticLogger::Appender::Logger.new(Rails.logger)
22
+ # SemanticLogger::Logger.appenders << SemanticLogger::Appender::Wrapper.new(Rails.logger)
26
23
  # Rails.logger = SemanticLogger::Logger.new('Rails')
27
24
  #
28
25
  # # Make ActiveRecord logging include its class name in every log entry
29
26
  # ActiveRecord::Base.logger = SemanticLogger::Logger.new('ActiveRecord')
27
+ #
28
+ # Note: Since the log level is controlled by setting the Ruby or Rails logger directly
29
+ # the level is ignored for this appender
30
30
  def initialize(logger, &block)
31
31
  raise "logger cannot be null when initiailizing the SemanticLogging::Appender::Logger" unless logger
32
32
  @logger = logger
33
33
 
34
34
  # Set the formatter to the supplied block
35
35
  @formatter = block || self.default_formatter
36
+ super(:trace, &block)
36
37
  end
37
38
 
38
- # Default log formatter
39
- # Replace this formatter by supplying a Block to the initializer
40
- # Generates logs of the form:
41
- # 2011-07-19 14:36:15.660 D [1149:ScriptThreadProcess] Rails -- Hello World
42
- def default_formatter
43
- Proc.new do |log|
44
- message = log.message.to_s
45
- tags = log.tags.collect { |tag| "[#{tag}]" }.join(" ") + " " if log.tags && (log.tags.size > 0)
46
-
47
- if log.payload
48
- if log.payload.is_a?(Exception)
49
- exception = log.payload
50
- message << " -- " << "#{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
51
- else
52
- message << " -- " << log.payload.inspect
53
- end
54
- end
55
-
56
- 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}] #{tags}#{log.name} -- #{message}"
57
- str << " (#{'%.1f' % log.duration}ms)" if log.duration
58
- str
59
- end
60
- end
39
+ # TODO Compatible calls to #level and #level= against the underlying Rails/Ruby logger
61
40
 
62
41
  # Pass log calls to the underlying Rails, log4j or Ruby logger
63
42
  # trace entries are mapped to debug since :trace is not supported by the
@@ -0,0 +1,280 @@
1
+ # Base appender
2
+ #
3
+ # Abstract base class for appenders
4
+ #
5
+ # Implements common behavior such as log level, default text formatter etc
6
+ #
7
+ # Note: Do not create instances of this class directly
8
+ #
9
+ module SemanticLogger
10
+ class Base
11
+ attr_accessor :formatter
12
+ attr_reader :level
13
+
14
+ def initialize(level, &block)
15
+ # Set the formatter to the supplied block
16
+ @formatter = block || default_formatter
17
+
18
+ # Log everything that comes to the appender by default
19
+ self.level = level || :trace
20
+ end
21
+
22
+ # Default log formatter
23
+ # Replace this formatter by supplying a Block to the initializer
24
+ # Generates logs of the form:
25
+ # 2011-07-19 14:36:15.660 D [1149:ScriptThreadProcess] Rails -- Hello World
26
+ def default_formatter
27
+ Proc.new do |log|
28
+ message = log.message.to_s
29
+ tags = log.tags.collect { |tag| "[#{tag}]" }.join(" ") + " " if log.tags && (log.tags.size > 0)
30
+
31
+ if log.payload
32
+ if log.payload.is_a?(Exception)
33
+ exception = log.payload
34
+ message << " -- " << "#{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
35
+ else
36
+ message << " -- " << log.payload.inspect
37
+ end
38
+ end
39
+
40
+ 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}] #{tags}#{log.name} -- #{message}"
41
+ str << " (#{'%.1f' % log.duration}ms)" if log.duration
42
+ str
43
+ end
44
+ end
45
+
46
+ # Write log data to underlying data storage
47
+ def log(log_)
48
+ raise "Logging Appender must implement #log(log)"
49
+ end
50
+
51
+ # Set the logging level for this appender
52
+ #
53
+ # Note: This level is only for this particular appender. It does not override
54
+ # the log level in any logging instance or the default log level
55
+ # SemanticLogger::Logger.level
56
+ #
57
+ # Must be one of the values in SemanticLogger::Logger::LEVELS
58
+ def level=(level)
59
+ @level_index = self.class.map_level_to_index(level)
60
+ @level = level
61
+ end
62
+
63
+ # Implement the log level calls
64
+ # logger.debug(message, hash|exception=nil, &block)
65
+ #
66
+ # Implement the log level query
67
+ # logger.debug?
68
+ #
69
+ # Example:
70
+ # require 'semantic_logger'
71
+ #
72
+ # # Enable trace level logging
73
+ # SemanticLogger::Logger.level = :info
74
+ #
75
+ # # Log to screen
76
+ # SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDOUT)
77
+ #
78
+ # # And log to a file at the same time
79
+ # SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new('application.log')
80
+ #
81
+ # logger = SemanticLogger::Logger.new('MyApplication')
82
+ # logger.debug("Only display this if log level is set to Debug or lower")
83
+ #
84
+ # # Log semantic information along with a text message
85
+ # logger.info("Request received", :user => "joe", :duration => 100)
86
+ #
87
+ # # Log an exception in a semantic way
88
+ # logger.info("Parsing received XML", exc)
89
+ #
90
+ SemanticLogger::LEVELS.each_with_index do |level, index|
91
+ class_eval <<-EOT, __FILE__, __LINE__
92
+ def #{level}(message = nil, payload = nil)
93
+ if @level_index <= #{index}
94
+ if block_given? && (result = yield)
95
+ if result.is_a?(String)
96
+ message = message.nil? ? result : "\#{message} -- \#{result.to_s}"
97
+ else
98
+ payload = payload.nil? ? result : payload.merge(result)
99
+ end
100
+ end
101
+ # Add scoped payload
102
+ if self.payload
103
+ payload = payload.nil? ? self.payload : self.payload.merge(payload)
104
+ end
105
+ log Log.new(:#{level}, self.class.thread_name, name, message, payload, Time.now, nil, tags, #{index})
106
+ true
107
+ else
108
+ false
109
+ end
110
+ end
111
+
112
+ def #{level}?
113
+ @level_index <= #{index}
114
+ end
115
+
116
+ # Log the duration of the supplied block
117
+ # If an exception occurs in the block the exception is logged using the
118
+ # same log level. The exception will flow through to the caller unchanged
119
+ def benchmark_#{level}(message, payload = nil)
120
+ raise "Mandatory block missing" unless block_given?
121
+ if @level_index <= #{index}
122
+ start = Time.now
123
+ begin
124
+ result = yield
125
+ end_time = Time.now
126
+ # Add scoped payload
127
+ if self.payload
128
+ payload = payload.nil? ? self.payload : self.payload.merge(payload)
129
+ end
130
+ log Log.new(:#{level}, self.class.thread_name, name, message, payload, end_time, 1000.0 * (end_time - start), tags, #{index})
131
+ result
132
+ rescue Exception => exc
133
+ # TODO Need to be able to have both an exception and a Payload
134
+ log Log.new(:#{level}, self.class.thread_name, name, message, exc, Time.now, 1000.0 * (Time.now - start), tags, #{index})
135
+ raise exc
136
+ end
137
+ else
138
+ yield
139
+ end
140
+ end
141
+ EOT
142
+ end
143
+
144
+ # Add the supplied tags to the list of tags to log for this thread whilst
145
+ # the supplied block is active
146
+ # Returns nil if no tags are currently set
147
+ def with_tags(*tags)
148
+ current_tags = self.tags
149
+ # Check for nil tags
150
+ if tags
151
+ Thread.current[:semantic_logger_tags] = current_tags ? current_tags + tags : tags
152
+ end
153
+ yield
154
+ ensure
155
+ Thread.current[:semantic_logger_tags] = current_tags
156
+ end
157
+
158
+ # Add support for the ActiveSupport::TaggedLogging
159
+ alias_method :tagged, :with_tags
160
+
161
+ # Returns [Array] of [String] tags currently active for this thread
162
+ # Returns nil if no tags are set
163
+ def tags
164
+ Thread.current[:semantic_logger_tags]
165
+ end
166
+
167
+ # Thread specific context information to be logged with every log entry
168
+ #
169
+ # Add a payload to all log calls on This Thread within the supplied block
170
+ #
171
+ # logger.with_payload(:tracking_number=>12345) do
172
+ # logger.debug('Hello World')
173
+ # end
174
+ #
175
+ # If a log call already includes a pyload, this payload will be merged with
176
+ # the supplied payload, with the supplied payload taking precedence
177
+ #
178
+ # logger.with_payload(:tracking_number=>12345) do
179
+ # logger.debug('Hello World', :result => 'blah')
180
+ # end
181
+ def with_payload(payload)
182
+ current_payload = self.payload
183
+ Thread.current[:semantic_logger_payload] = current_payload ? current_payload.merge(payload) : payload
184
+ yield
185
+ ensure
186
+ Thread.current[:semantic_logger_payload] = current_payload
187
+ end
188
+
189
+ # Returns [Hash] payload to be added to every log entry in the current scope
190
+ # on this thread.
191
+ # Returns nil if no payload is currently set
192
+ def payload
193
+ Thread.current[:semantic_logger_payload]
194
+ end
195
+
196
+ # Semantic Logging does not support :unknown level since these
197
+ # are not understood by the majority of the logging providers
198
+ # Map it to :error
199
+ alias :unknown :error
200
+ alias :unknown? :error?
201
+
202
+ # #TODO implement a thread safe #silence method
203
+
204
+ ############################################################################
205
+ protected
206
+
207
+ # Return the level index for fast comparisons
208
+ attr_reader :level_index
209
+
210
+ # Struct Log
211
+ #
212
+ # level
213
+ # Log level of the supplied log call
214
+ # :trace, :debug, :info, :warn, :error, :fatal
215
+ #
216
+ # thread_name
217
+ # Name of the thread in which the logging call was called
218
+ #
219
+ # name
220
+ # Class name supplied to the logging instance
221
+ #
222
+ # message
223
+ # Text message to be logged
224
+ #
225
+ # payload
226
+ # Optional Hash or Ruby Exception object to be logged
227
+ #
228
+ # time
229
+ # The time at which the log entry was created
230
+ #
231
+ # duration
232
+ # The time taken to complete a benchmark call
233
+ #
234
+ # tags
235
+ # Any tags active on the thread when the log call was made
236
+ #
237
+ # level_index
238
+ # Internal index of the log level
239
+ Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index)
240
+
241
+ # For JRuby include the Thread name rather than its id
242
+ if defined? Java
243
+ def self.thread_name
244
+ Java::java.lang::Thread.current_thread.name
245
+ end
246
+ else
247
+ def self.thread_name
248
+ Thread.current.object_id
249
+ end
250
+ end
251
+
252
+ # Internal method to return the log level as an internal index
253
+ # Also supports mapping the ::Logger levels to SemanticLogger levels
254
+ def self.map_level_to_index(level)
255
+ index = if level.is_a?(Integer)
256
+ # Mapping of Rails and Ruby Logger levels to SemanticLogger levels
257
+ @@map_levels ||= begin
258
+ levels = []
259
+ ::Logger::Severity.constants.each do |constant|
260
+ levels[::Logger::Severity.const_get(constant)] = LEVELS.find_index(constant.downcase.to_sym) || LEVELS.find_index(:error)
261
+ end
262
+ end [level] if defined?(::Logger::Severity)
263
+ elsif level.is_a?(String)
264
+ level = level.downcase.to_sym
265
+ LEVELS.index(level)
266
+ else
267
+ LEVELS.index(level)
268
+ end
269
+ raise "Invalid level:#{level.inspect} being requested. Must be one of #{LEVELS.inspect}" unless index
270
+ index
271
+ end
272
+
273
+ # Appenders don't take a class name, so use this class name if an appender
274
+ # is logged to directly
275
+ def name
276
+ self.class.name
277
+ end
278
+
279
+ end
280
+ end
@@ -19,7 +19,7 @@
19
19
  #
20
20
  # # Now log to the Logger above as well as MongoDB at the same time
21
21
  #
22
- # db = Mongo::Connection.new['production_logging']
22
+ # db = Mongodb::Connection.new['production_logging']
23
23
  #
24
24
  # SemanticLogger::Logger.appenders << SemanticLogger::Appender::MongoDB.new(
25
25
  # :db => db,
@@ -29,40 +29,31 @@
29
29
  # # This will be logged to both the Ruby Logger and MongoDB
30
30
  # logger.debug("Login time", :user => 'Mary', :duration => 230, :ip_address=>'192.168.0.1')
31
31
  #
32
- require 'logger'
33
32
  module SemanticLogger
34
- class Logger
33
+ class Logger < Base
35
34
  include SyncAttr
36
35
 
37
- # Logging levels in order of precedence
38
- LEVELS = [:trace, :debug, :info, :warn, :error, :fatal]
39
-
40
- # Mapping of Rails and Ruby Logger levels to SemanticLogger levels
41
- MAP_LEVELS = []
42
- ::Logger::Severity.constants.each do |constant|
43
- MAP_LEVELS[::Logger::Severity.const_get(constant)] = LEVELS.find_index(constant.downcase.to_sym) || LEVELS.find_index(:error)
44
- end
45
-
46
36
  # Thread safe Class Attribute accessor for appenders array
47
37
  sync_cattr_accessor :appenders do
48
38
  []
49
39
  end
50
40
 
41
+ # Initial default Level for all new instances of SemanticLogger::Logger
42
+ @@default_level = :info
43
+
51
44
  # Allow for setting the global default log level
52
45
  # This change only applies to _new_ loggers, existing logger levels
53
46
  # will not be changed in any way
54
- def self.level=(level)
55
- @@level = level
47
+ def self.default_level=(level)
48
+ @@default_level = level
56
49
  end
57
50
 
58
51
  # Returns the global default log level for new Logger instances
59
- def self.level
60
- @@level
52
+ def self.default_level
53
+ @@default_level
61
54
  end
62
55
 
63
- attr_reader :name, :level
64
-
65
- @@level = :info
56
+ attr_reader :name
66
57
 
67
58
  # Returns a Logger instance
68
59
  #
@@ -76,144 +67,11 @@ module SemanticLogger
76
67
  # to be used in the logger
77
68
  # options:
78
69
  # :level The initial log level to start with for this logger instance
79
- def initialize(klass, options={})
70
+ def initialize(klass, level=self.class.default_level)
80
71
  @name = klass.is_a?(String) ? klass : klass.name
81
- set_level(options[:level] || self.class.level)
72
+ self.level = level
82
73
  end
83
74
 
84
- # Set the logging level
85
- # Must be one of the values in #LEVELS
86
- def level=(level)
87
- set_level(level)
88
- end
89
-
90
- # Implement the log level calls
91
- # logger.debug(message|hash|exception, hash|exception=nil, &block)
92
- #
93
- # Implement the log level query
94
- # logger.debug?
95
- #
96
- # Example:
97
- # logger = SemanticLogging::Logger.new(self)
98
- # logger.debug("Only display this if log level is set to Debug or lower")
99
- #
100
- # # Log semantic information along with a text message
101
- # logger.info("Request received", :user => "joe", :duration => 100)
102
- #
103
- # # Log an exception in a semantic way
104
- # logger.info("Parsing received XML", exc)
105
- #
106
- LEVELS.each_with_index do |level, index|
107
- class_eval <<-EOT, __FILE__, __LINE__
108
- def #{level}(message = nil, payload = nil)
109
- if @level_index <= #{index}
110
- if block_given? && (result = yield)
111
- if result.is_a?(String)
112
- message = message.nil? ? result : "\#{message} -- \#{result.to_s}"
113
- else
114
- payload = payload.nil? ? result : payload.merge(result)
115
- end
116
- end
117
- # Add scoped payload
118
- if self.payload
119
- payload = payload.nil? ? self.payload : self.payload.merge(payload)
120
- end
121
- self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, Time.now, nil, tags)
122
- true
123
- else
124
- false
125
- end
126
- end
127
-
128
- def #{level}?
129
- @level_index <= #{index}
130
- end
131
-
132
- # Log the duration of the supplied block
133
- # If an exception occurs in the block the exception is logged using the
134
- # same log level. The exception will flow through to the caller unchanged
135
- def benchmark_#{level}(message, payload = nil)
136
- raise "Mandatory block missing" unless block_given?
137
- if @level_index <= #{index}
138
- start = Time.now
139
- begin
140
- result = yield
141
- end_time = Time.now
142
- # Add scoped payload
143
- if self.payload
144
- payload = payload.nil? ? self.payload : self.payload.merge(payload)
145
- end
146
- self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, payload, end_time, 1000.0 * (end_time - start), tags)
147
- result
148
- rescue Exception => exc
149
- # TODO Need to be able to have both an exception and a Payload
150
- self.class.queue << Log.new(:#{level}, self.class.thread_name, name, message, exc, Time.now, 1000.0 * (Time.now - start), tags)
151
- raise exc
152
- end
153
- else
154
- yield
155
- end
156
- end
157
- EOT
158
- end
159
-
160
- # Add the supplied tags to the list of tags to log for this thread whilst
161
- # the supplied block is active
162
- # Returns nil if no tags are currently set
163
- def with_tags(*tags)
164
- current_tags = self.tags
165
- # Check for nil tags
166
- if tags
167
- Thread.current[:semantic_logger_tags] = current_tags ? current_tags + tags : tags
168
- end
169
- yield
170
- ensure
171
- Thread.current[:semantic_logger_tags] = current_tags
172
- end
173
-
174
- # Returns [Array] of [String] tags currently active for this thread
175
- # Returns nil if no tags are set
176
- def tags
177
- Thread.current[:semantic_logger_tags]
178
- end
179
-
180
- # Thread specific context information to be logged with every log entry
181
- #
182
- # Add a payload to all log calls on This Thread within the supplied block
183
- #
184
- # logger.with_payload(:tracking_number=>12345) do
185
- # logger.debug('Hello World')
186
- # end
187
- #
188
- # If a log call already includes a pyload, this payload will be merged with
189
- # the supplied payload, with the supplied payload taking precedence
190
- #
191
- # logger.with_payload(:tracking_number=>12345) do
192
- # logger.debug('Hello World', :result => 'blah')
193
- # end
194
- def with_payload(payload)
195
- current_payload = self.payload
196
- Thread.current[:semantic_logger_payload] = current_payload ? current_payload.merge(payload) : payload
197
- yield
198
- ensure
199
- Thread.current[:semantic_logger_payload] = current_payload
200
- end
201
-
202
- # Returns [Hash] payload to be added to every log entry in the current scope
203
- # on this thread.
204
- # Returns nil if no payload is currently set
205
- def payload
206
- Thread.current[:semantic_logger_payload]
207
- end
208
-
209
- # Semantic Logging does not support :unknown level since these
210
- # are not understood by the majority of the logging providers
211
- # Map it to :error
212
- alias :unknown :error
213
- alias :unknown? :error?
214
-
215
- # #TODO implement a thread safe #silence method
216
-
217
75
  # Returns [Integer] the number of log entries that have not been written
218
76
  # to the appenders
219
77
  #
@@ -235,22 +93,27 @@ module SemanticLogger
235
93
  reply_queue.pop
236
94
  end
237
95
 
96
+ ############################################################################
97
+ protected
98
+
99
+ # Place log request on the queue for the Appender thread to write to each
100
+ # appender in the order that they were registered
101
+ def log(log)
102
+ self.class.queue << log
103
+ end
104
+
238
105
  # Internal logger for SemanticLogger
239
106
  # For example when an appender is not working etc..
240
- # By default logs to STDERR, replace with another Ruby logger or Rails
241
- # logger, but never to SemanticLogger itself
107
+ # By default logs to STDERR
108
+ # Can be replaced with another Ruby logger or Rails logger, but never to
109
+ # SemanticLogger::Logger itself
242
110
  #
243
111
  # Warning: Do not use this logger directly it is intended for internal logging
244
112
  # within Semantic Logger itself
245
113
  sync_cattr_accessor :logger do
246
- l = ::Logger.new(STDERR)
247
- l.level = ::Logger::INFO
248
- l
114
+ SemanticLogger::Appender::File.new(STDERR, :warn)
249
115
  end
250
116
 
251
- ############################################################################
252
- protected
253
-
254
117
  # Log to queue
255
118
  # Starts the appender thread the first time a logging call is made
256
119
  sync_cattr_reader :queue do
@@ -259,70 +122,11 @@ module SemanticLogger
259
122
  Queue.new
260
123
  end
261
124
 
262
- # Struct Log
263
- #
264
- # level
265
- # Log level of the supplied log call
266
- # :trace, :debug, :info, :warn, :error, :fatal
267
- #
268
- # thread_name
269
- # Name of the thread in which the logging call was called
270
- #
271
- # name
272
- # Class name supplied to the logging instance
273
- #
274
- # message
275
- # Text message to be logged
276
- #
277
- # payload
278
- # Optional Hash or Ruby Exception object to be logged
279
- #
280
- # time
281
- # The time at which the log entry was created
282
- #
283
- # duration
284
- # The time taken to complete a benchmark call
285
- #
286
- # tags
287
- # Any tags active on the thread when the log call was made
288
- #
289
- Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags)
290
-
291
- # For JRuby include the Thread name rather than its id
292
- if defined? Java
293
- def self.thread_name
294
- Java::java.lang::Thread.current_thread.name
295
- end
296
- else
297
- def self.thread_name
298
- Thread.current.object_id
299
- end
300
- end
301
-
302
- # Verify and set the level
303
- def set_level(level)
304
- index = if level.is_a?(Integer)
305
- MAP_LEVELS[level]
306
- elsif level.is_a?(String)
307
- level = level.downcase.to_sym
308
- LEVELS.index(level)
309
- else
310
- LEVELS.index(level)
311
- end
312
-
313
- raise "Invalid level:#{level.inspect} being requested. Must be one of #{LEVELS.inspect}" unless index
314
- @level_index = index
315
- @level = level
316
- end
317
-
318
125
  # Start a separate appender thread responsible for reading log messages and
319
126
  # calling the appenders in it's thread
320
127
  def self.startup
321
128
  @@appender_thread = Thread.new do
322
129
  begin
323
- # #TODO Logger needs it's own "reliable" appender ;)
324
- # For example if an appender constantly fails
325
- # ( bad filename or path, invalid server )
326
130
  logger.debug "SemanticLogger::Logger Appender thread started"
327
131
  while message=queue.pop
328
132
  if message.is_a? Log
@@ -365,5 +169,11 @@ module SemanticLogger
365
169
  result
366
170
  end
367
171
 
172
+ # Formatting does not occur within this thread, it is done by each appender
173
+ # in the appender thread
174
+ def default_formatter
175
+ nil
176
+ end
177
+
368
178
  end
369
179
  end
@@ -27,7 +27,7 @@ module SemanticLogger #:nodoc:
27
27
  config = Rails.application.config
28
28
 
29
29
  # Set the default log level based on the Rails config
30
- SemanticLogger::Logger.level = config.log_level
30
+ SemanticLogger::Logger.default_level = config.log_level
31
31
 
32
32
  # Existing loggers are ignored because servers like trinidad supply their
33
33
  # own file loggers which would result in duplicate logging to the same log file
@@ -38,26 +38,24 @@ module SemanticLogger #:nodoc:
38
38
  FileUtils.mkdir_p File.dirname path
39
39
  end
40
40
 
41
- # First set the internal logger in case something goes wrong
42
- # with an appender
43
- SemanticLogger::Logger.logger = begin
44
- l = ::Logger.new(path)
45
- l.level = ::Logger.const_get(config.log_level.to_s.upcase)
46
- l
47
- end
48
-
49
41
  # Add the log file to the list of appenders
50
- SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(path)
51
-
52
- #logger = ActiveSupport::TaggedLogging.new(logger) if defined?(ActiveSupport::TaggedLogging)
42
+ file_appender = SemanticLogger::Appender::File.new(path)
53
43
 
44
+ # Set internal logger to log to file only, in case another appender
45
+ # experiences logging problems
46
+ SemanticLogger::Logger.logger = file_appender
47
+ SemanticLogger::Logger.appenders << file_appender
48
+
54
49
  SemanticLogger::Logger.new(Rails)
55
50
  rescue StandardError
56
- SemanticLogger::Logger.appenders << SemanticLogger::Appender::File.new(STDERR)
51
+ # If not able to log to file, log to standard error with warning level only
52
+ SemanticLogger::Logger.default_level = :warn
53
+
54
+ file_appender = SemanticLogger::Appender::File.new(STDERR)
55
+ SemanticLogger::Logger.logger = file_appender
56
+ SemanticLogger::Logger.appenders << file_appender
57
57
 
58
58
  logger = SemanticLogger::Logger.new(Rails)
59
- logger.level = :warn
60
- #logger = ActiveSupport::TaggedLogging.new(logger) if defined?(ActiveSupport::TaggedLogging)
61
59
  logger.warn(
62
60
  "Rails Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " +
63
61
  "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
@@ -1,3 +1,3 @@
1
1
  module SemanticLogger #:nodoc
2
- VERSION = "0.5.3"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1,52 @@
1
+ # Allow test to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'logger'
8
+ require 'semantic_logger'
9
+ require 'test/mock_logger'
10
+
11
+ # Unit Test for SemanticLogger::Appender::File
12
+ #
13
+ class AppenderFileTest < Test::Unit::TestCase
14
+ context SemanticLogger::Appender::File do
15
+ setup do
16
+ @time = Time.parse("2012-08-02 09:48:32.482")
17
+ @io = StringIO.new
18
+ @appender = SemanticLogger::Appender::File.new(@io)
19
+ @hash = { :session_id => 'HSSKLEU@JDK767', :tracking_number => 12345 }
20
+ @hash_str = @hash.inspect.sub("{", "\\{").sub("}", "\\}")
21
+ @thread_name = SemanticLogger::Base.thread_name
22
+ end
23
+
24
+ context "format logs into text form" do
25
+ should "handle no message or payload" do
26
+ @appender.debug
27
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:#{@thread_name}\] SemanticLogger::Appender::File -- \n/, @io.string
28
+ end
29
+
30
+ should "handle message" do
31
+ @appender.debug 'hello world'
32
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:#{@thread_name}\] SemanticLogger::Appender::File -- hello world\n/, @io.string
33
+ end
34
+
35
+ should "handle message and payload" do
36
+ @appender.debug 'hello world', @hash
37
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:#{@thread_name}\] SemanticLogger::Appender::File -- hello world -- #{@hash_str}\n/, @io.string
38
+ end
39
+ end
40
+
41
+ context "for each log level" do
42
+ # Ensure that any log level can be logged
43
+ SemanticLogger::LEVELS.each do |level|
44
+ should "log #{level} information" do
45
+ @appender.send(level, 'hello world', @hash)
46
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:#{@thread_name}\] SemanticLogger::Appender::File -- hello world -- #{@hash_str}\n/, @io.string
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -98,7 +98,7 @@ class AppenderMongoDBTest < Test::Unit::TestCase
98
98
 
99
99
  context "for each log level" do
100
100
  # Ensure that any log level can be logged
101
- SemanticLogger::Logger::LEVELS.each do |level|
101
+ SemanticLogger::LEVELS.each do |level|
102
102
  should "log #{level} information" do
103
103
  @appender.log SemanticLogger::Logger::Log.new(level, 'thread', 'my_class', 'hello world -- Calculations', @hash, @time)
104
104
  document = @appender.collection.find_one
@@ -8,15 +8,16 @@ require 'logger'
8
8
  require 'semantic_logger'
9
9
  require 'test/mock_logger'
10
10
 
11
- # Unit Test for SemanticLogger::Appender::Logger
11
+ # Unit Test for SemanticLogger::Appender::Wrapper
12
12
  #
13
- class AppenderLoggerTest < Test::Unit::TestCase
14
- context SemanticLogger::Appender::Logger do
13
+ class AppenderWrapperTest < Test::Unit::TestCase
14
+ context SemanticLogger::Appender::Wrapper do
15
15
  setup do
16
16
  @time = Time.parse("2012-08-02 09:48:32.482")
17
17
  @mock_logger = MockLogger.new
18
- @appender = SemanticLogger::Appender::Logger.new(@mock_logger)
18
+ @appender = SemanticLogger::Appender::Wrapper.new(@mock_logger)
19
19
  @hash = { :session_id => 'HSSKLEU@JDK767', :tracking_number => 12345 }
20
+ @hash_str = @hash.inspect.sub("{", "\\{").sub("}", "\\}")
20
21
  end
21
22
 
22
23
  context "format logs into text form" do
@@ -55,7 +56,7 @@ class AppenderLoggerTest < Test::Unit::TestCase
55
56
  log.message = 'hello world'
56
57
  log.payload = @hash
57
58
  @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\}/, @mock_logger.message
59
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ D \[\d+:\] class -- hello world -- #{@hash_str}/, @mock_logger.message
59
60
  end
60
61
  end
61
62
 
@@ -63,8 +64,8 @@ class AppenderLoggerTest < Test::Unit::TestCase
63
64
  # Ensure that any log level can be logged
64
65
  Logger::Severity.constants.each do |level|
65
66
  should "log #{level.downcase.to_sym} info" do
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\}/, @mock_logger.message
67
+ @appender.log SemanticLogger::Logger::Log.new(level.downcase.to_sym, 'thread', 'class', 'hello world', @hash, Time.now)
68
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:thread\] class -- hello world -- #{@hash_str}/, @mock_logger.message
68
69
  end
69
70
  end
70
71
  end
data/test/logger_test.rb CHANGED
@@ -16,13 +16,14 @@ class LoggerTest < Test::Unit::TestCase
16
16
  setup do
17
17
  # Use a mock logger that just keeps the last logged entry in an instance variable
18
18
  @mock_logger = MockLogger.new
19
- @appender = SemanticLogger::Appender::Logger.new(@mock_logger)
19
+ @appender = SemanticLogger::Appender::Wrapper.new(@mock_logger)
20
20
  SemanticLogger::Logger.appenders << @appender
21
21
 
22
22
  # Use this test's class name as the application name in the log output
23
- @logger = SemanticLogger::Logger.new('LoggerTest', :level => :trace)
23
+ @logger = SemanticLogger::Logger.new('LoggerTest', :trace)
24
24
 
25
25
  @hash = { :session_id => 'HSSKLEU@JDK767', :tracking_number => 12345 }
26
+ @hash_str = @hash.inspect.sub("{", "\\{").sub("}", "\\}")
26
27
  end
27
28
 
28
29
  teardown do
@@ -30,11 +31,11 @@ class LoggerTest < Test::Unit::TestCase
30
31
  end
31
32
 
32
33
  # Ensure that any log level can be logged
33
- SemanticLogger::Logger::LEVELS.each do |level|
34
+ SemanticLogger::LEVELS.each do |level|
34
35
  should "log #{level} info" do
35
36
  @logger.send(level, 'hello world', @hash) { "Calculations" }
36
37
  SemanticLogger::Logger.flush
37
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:.+\] LoggerTest -- hello world -- Calculations -- \{:session_id=>\"HSSKLEU@JDK767\", :tracking_number=>12345\}/, @mock_logger.message
38
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:.+\] LoggerTest -- hello world -- Calculations -- #{@hash_str}/, @mock_logger.message
38
39
  end
39
40
  end
40
41
 
@@ -58,12 +59,13 @@ class LoggerTest < Test::Unit::TestCase
58
59
  end
59
60
 
60
61
  should "add payload to log entries" do
62
+ hash = {:tracking_number=>"123456", :even=>2, :more=>"data"}
63
+ hash_str = hash.inspect.sub("{", "\\{").sub("}", "\\}")
61
64
  @logger.with_payload(:tracking_number => '123456') do
62
65
  @logger.with_payload(:even => 2, :more => 'data') do
63
66
  @logger.info('Hello world')
64
67
  SemanticLogger::Logger.flush
65
- # TODO make test ignore order of Hash elements
66
- assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:.+\] LoggerTest -- Hello world -- \{:even=>2, :more=>\"data\", :tracking_number=>\"123456\"\}/, @mock_logger.message
68
+ assert_match /\d+-\d+-\d+ \d+:\d+:\d+.\d+ \w \[\d+:.+\] LoggerTest -- Hello world -- #{hash_str}/, @mock_logger.message
67
69
  end
68
70
  end
69
71
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
8
- - 3
9
- version: 0.5.3
7
+ - 6
8
+ - 0
9
+ version: 0.6.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-09-14 00:00:00 -04:00
17
+ date: 2012-09-18 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -54,8 +54,9 @@ files:
54
54
  - FUTURE.rb
55
55
  - init.txt
56
56
  - lib/semantic_logger/appender/file.rb
57
- - lib/semantic_logger/appender/logger.rb
58
57
  - lib/semantic_logger/appender/mongodb.rb
58
+ - lib/semantic_logger/appender/wrapper.rb
59
+ - lib/semantic_logger/base.rb
59
60
  - lib/semantic_logger/logger.rb
60
61
  - lib/semantic_logger/railtie.rb
61
62
  - lib/semantic_logger/version.rb
@@ -63,8 +64,9 @@ files:
63
64
  - LICENSE.txt
64
65
  - Rakefile
65
66
  - README.md
66
- - test/appender_logger_test.rb
67
+ - test/appender_file_test.rb
67
68
  - test/appender_mongodb_test.rb
69
+ - test/appender_wrapper_test.rb
68
70
  - test/logger_test.rb
69
71
  - test/mock_logger.rb
70
72
  has_rdoc: true