catamaran 0.2.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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ Catamaran
2
+ =========
3
+
4
+ I think logging is a powerful and often undervalued tool in software development. When done right, it's a great way to document code, and it provides a simple & effective way to solve problems when things go awry. All an important part of maintainable code.
5
+
6
+ Gemfile
7
+ -------
8
+
9
+ gem 'catamaran', :git => 'git://github.com/jgithub/catamaran.git'
10
+
11
+ Rails-related setup:
12
+
13
+ rails g catamaran:install
14
+
15
+ Now modify `development.rb` as needed
16
+
17
+ Quickstart coding
18
+ -----------------
19
+
20
+ class WelcomeController < ApplicationController
21
+ LOGGER = CatLogger.MyCompany.MyAppName.App.Controller.WelcomeController
22
+
23
+ def index
24
+ # LOGGER.io methods are reserved for logs related to entering and returning from methods
25
+ LOGGER.io "Entering with params = #{params}" if LOGGER.io?
26
+
27
+ LOGGER.trace "This is my TRACE log" if LOGGER.trace?
28
+ LOGGER.debug "This is my DEBUG log" if LOGGER.debug?
29
+ LOGGER.info "This is my INFO log" if LOGGER.info?
30
+ LOGGER.warn "This is my WARN log" if LOGGER.warn?
31
+ LOGGER.error "This is my ERROR log" if LOGGER.error?
32
+
33
+ LOGGER.io "Returning" if LOGGER.io?
34
+ end
35
+ end
36
+
37
+ Load the `index` page and check out your `development.log` file
38
+
39
+ Sample log entry (in your development.log file)
40
+ -----------------------------------------------
41
+ IO pid-86000 [2013-12-17 17:26:39:176] MyAppName.App.Controller.WelcomeController - Entering with params = {"controller"=>"welcome", "action"=>"index"} (`/my_rails_project/app/controllers/welcome_controller.rb:7`:in `index`)
42
+
43
+ Inspiration
44
+ -----------
45
+ I'm looking for a logging utility that:
46
+
47
+ * records granular timestamps with each log entry
48
+ * records the process ID with each log entry
49
+ * captures from where each log entry was generated
50
+ * works equally well with classes that do and do *not* extend Rails base classes
51
+ * supports the TRACE log level (or other log level less critical than DEBUG).
52
+ * is capable of capturing logs at different log level thresholds from different parts of the app simultaneously
53
+ * readily works with Rails
54
+
55
+ Ideas around what's next
56
+ ------------------------
57
+
58
+ * Make it easier to change the formatting pattern
59
+ * Another iteration of improvements on logger.io
60
+ * Consider capturing log messages beyond stderr, stdout, and local files
61
+ * Ongoing performance considerations
62
+ * Something like `filter_parameter_logging`
63
+ * Optional backtrace support for certain logs
64
+ * Color support
65
+
66
+
67
+
data/catamaran.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + "/lib/catamaran/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "catamaran"
5
+ s.version = Catamaran::VERSION
6
+ s.authors = ["Jeano"]
7
+ s.email = ["catamaran@jeano.net"]
8
+ s.summary = "Catamaran Logger"
9
+ s.description = "A logging utility"
10
+ s.homepage = "http://github.com/jgithub/catamaran"
11
+ s.files = `git ls-files`.split("\n")
12
+ end
13
+
14
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/lib/catamaran'
@@ -0,0 +1,33 @@
1
+ module Catamaran
2
+ module Formatter
3
+ ##
4
+ # Construct a properly formatted log message based
5
+
6
+ class CallerFormatter
7
+ def self.construct_formatted_message( log_level, path, msg, opts )
8
+
9
+ ##
10
+ # If the client has specified a __FILE__ or a __LINE__ in the log opts, make use of them
11
+
12
+ if opts
13
+ if opts[:file] && opts[:line]
14
+ path = path + " (#{opts[:file]}:#{opts[:line]})"
15
+ end
16
+ end
17
+
18
+ # Truncate on the left
19
+ if path.length > 42
20
+ updated_path = path.dup[-42,42]
21
+ else
22
+ updated_path = path
23
+ end
24
+
25
+ # Making use of the caller here is slow.
26
+ # TODO: Abstract out the logger so that it's easy to use different formats
27
+ # Implicit return
28
+ sprintf( "%6s pid-#{Process.pid} [#{Time.now.strftime( "%G-%m-%d %H:%M:%S:%L" )}] %42s - #{msg} (#{caller(3)[0]})", LogLevel.to_s(log_level), updated_path )
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,32 @@
1
+ module Catamaran
2
+ module Formatter
3
+ ##
4
+ # Construct a properly formatted log message based
5
+
6
+ class NoCallerFormatter
7
+ def self.construct_formatted_message( log_level, path, msg, opts )
8
+
9
+ ##
10
+ # If the client has specified a __FILE__ or a __LINE__ in the log opts, make use of them
11
+
12
+ if opts
13
+ if opts[:file] && opts[:line]
14
+ path = path + " (#{opts[:file]}:#{opts[:line]})"
15
+ end
16
+ end
17
+
18
+ # Truncate on the left
19
+ if path.length > 42
20
+ updated_path = path.dup[-42,42]
21
+ else
22
+ updated_path = path
23
+ end
24
+
25
+ # TODO: Abstract out the logger so that it's easy to use different formats
26
+ # Implicit return
27
+ sprintf( "%6s pid-#{Process.pid} [#{Time.now.strftime( "%G-%m-%d %H:%M:%S:%L" )}] %42s - #{msg}", LogLevel.to_s(log_level), updated_path )
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,24 @@
1
+ ##
2
+ # Ties Rails to Catamaran
3
+
4
+ module Catamaran
5
+ module Integration
6
+ module Rails3
7
+ if defined?( Rails::Railtie )
8
+ class Railtie < Rails::Railtie
9
+ initializer :load_base_catamaran do
10
+ initializer = Rails.root.join( "config", "initializers", "catamaran.rb" )
11
+ require initializer if File.exist?( initializer )
12
+
13
+ Catamaran::Manager.add_output_file( Catamaran::OutputFile.new( Rails.root.join( "log", "#{Rails.env}.log" ) ) )
14
+ end
15
+
16
+ initializer :load_environment_specific_catamaran do
17
+ initializer = Rails.root.join( "config", "initializers", "catamaran", "#{Rails.env}.rb" )
18
+ require initializer if File.exist?( initializer )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,73 @@
1
+ module Catamaran
2
+ class LogLevel
3
+ TRACE = 1000
4
+ DEBUG = 2000
5
+ INFO = 3000
6
+ WARN = 4000
7
+ ERROR = 5000
8
+ SEVERE = 6000
9
+ FATAL = 7000
10
+
11
+ # By default, logger.io messages will be captured when the log level is DEBUG (IO_LESS_CRITICAL_THAN_INFO)
12
+ # If that's too verbose, the level can be changed to IO_LESS_CRITICAL_THAN_DEBUG and logger.io messages won't be
13
+ # visible unless the log level is set to TRACE.
14
+ # See development.rb for an example of this
15
+ IO_LESS_CRITICAL_THAN_DEBUG = 1080
16
+ IO_LESS_CRITICAL_THAN_INFO = 2080
17
+
18
+ IO = IO_LESS_CRITICAL_THAN_INFO
19
+
20
+ def self.reset
21
+ @@default_log_level = INFO
22
+
23
+ self.send( :remove_const, 'IO' ) if self.const_defined?( 'IO' )
24
+ self.const_set( 'IO', IO_LESS_CRITICAL_THAN_INFO )
25
+ end
26
+
27
+ self.reset()
28
+
29
+
30
+ def self.to_s( log_level )
31
+ case log_level
32
+ when TRACE
33
+ retval = 'TRACE'
34
+ when DEBUG
35
+ retval = 'DEBUG'
36
+ when INFO
37
+ retval = 'INFO'
38
+ when WARN
39
+ retval = 'WARN'
40
+ when ERROR
41
+ retval = 'ERROR'
42
+ when SEVERE
43
+ retval = 'SEVERE'
44
+ when FATAL
45
+ retval = 'FATAL'
46
+ when IO_LESS_CRITICAL_THAN_INFO
47
+ retval = 'IO'
48
+ when IO_LESS_CRITICAL_THAN_DEBUG
49
+ retval = 'IO'
50
+ end
51
+
52
+ retval
53
+ end
54
+
55
+ def self.default_log_level
56
+ @@default_log_level
57
+ end
58
+
59
+ def self.default_log_level=( value )
60
+ Catamaran.debugging( "Catamaran::LogLevel#default_log_level=() - Switching the default log level to #{value}" ) if Catamaran.debugging?
61
+ @@default_log_level = value
62
+ end
63
+
64
+ def self.log_level_io=( value )
65
+ self.send( :remove_const, 'IO' ) if self.const_defined?( 'IO' )
66
+ self.const_set( 'IO', value )
67
+ end
68
+
69
+ def self.log_level_io
70
+ IO
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,366 @@
1
+ module Catamaran
2
+ class Logger
3
+ attr_accessor :name
4
+ attr_accessor :path
5
+ attr_reader :parent
6
+
7
+ ##
8
+ # The getter associated with retrieving the current log level for this logger.
9
+ # Similar is the smart_log_level getter
10
+
11
+ def log_level( opts = {} )
12
+ if instance_variable_defined?( :@log_level ) && @log_level
13
+ retval = @log_level
14
+ elsif self.parent.nil?
15
+ # No parent means this logger(self) is the root logger. So use the default log level
16
+ retval = Catamaran::LogLevel.default_log_level()
17
+ else
18
+ recursive = opts[:recursive]
19
+ if recursive == true
20
+
21
+ # Remember the log level we found so we don't have to recursively look for it ever time
22
+ if !instance_variable_defined?( :@memoized_log_level ) || @memoized_log_level.nil?
23
+ @memoized_log_level = parent.log_level( opts ) if parent
24
+ end
25
+
26
+ retval = @memoized_log_level
27
+ else
28
+ Catamaran.debugging( "Catamaran::Logger#log_level() - non-recrusive request for log level. And log level is nil. This shouldn't happen too often." ) if Catamaran.debugging?
29
+ retval = nil
30
+ end
31
+ end
32
+
33
+ # Implicit return
34
+ retval
35
+ end
36
+
37
+ ##
38
+ # We usually want a logger to have a level. The smart_log_level() reader determines a log level even if one isn't
39
+ # explicitly set on this logger instance by making use of recursion up to the root logger if needed
40
+
41
+ def smart_log_level
42
+ self.log_level( { :recursive => true } )
43
+ end
44
+
45
+ ##
46
+ # Setter used to explicitly set the log level for this logger
47
+
48
+ def log_level=( value )
49
+ @log_level = value
50
+ remove_instance_variable( :@memoized_log_level ) if instance_variable_defined?( :@memoized_log_level )
51
+ end
52
+
53
+ ##
54
+ # Is trace-level logging currently enabled?
55
+
56
+ def trace?
57
+ if self.smart_log_level() <= LogLevel::TRACE
58
+ true
59
+ else
60
+ false
61
+ end
62
+ end
63
+
64
+ def trace( msg, opts = nil )
65
+ if trace?
66
+ _write_to_log( LogLevel::TRACE, msg, opts )
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Is debug-level logging currently enabled?
72
+
73
+ def debug?
74
+ if self.smart_log_level() <= LogLevel::DEBUG
75
+ true
76
+ else
77
+ false
78
+ end
79
+ end
80
+
81
+ def debug( msg, opts = nil )
82
+ if debug?
83
+ _write_to_log( LogLevel::DEBUG, msg, opts )
84
+ end
85
+ end
86
+
87
+
88
+ ##
89
+ # Is io-level logging currently enabled?
90
+
91
+ def io?
92
+ if self.smart_log_level() <= LogLevel::IO
93
+ true
94
+ else
95
+ false
96
+ end
97
+ end
98
+
99
+ def io( msg, opts = nil )
100
+ if io?
101
+ _write_to_log( LogLevel::IO, msg, opts )
102
+ end
103
+ end
104
+
105
+ ##
106
+ # Is info-level logging currently enabled?
107
+
108
+ def info?
109
+ if self.smart_log_level() <= LogLevel::INFO
110
+ true
111
+ else
112
+ false
113
+ end
114
+ end
115
+
116
+ def info( msg, opts = nil )
117
+ if info?
118
+ _write_to_log( LogLevel::INFO, msg, opts )
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Is warn-level logging currently enabled?
124
+
125
+ def warn?
126
+ if self.smart_log_level() <= LogLevel::WARN
127
+ true
128
+ else
129
+ false
130
+ end
131
+ end
132
+
133
+ def warn( msg, opts = nil )
134
+ if warn?
135
+ _write_to_log( LogLevel::WARN, msg, opts )
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Is error-level logging currently enabled?
141
+
142
+ def error?
143
+ if self.smart_log_level() <= LogLevel::ERROR
144
+ true
145
+ else
146
+ false
147
+ end
148
+ end
149
+
150
+ def error( msg, opts = nil )
151
+ if error?
152
+ _write_to_log( LogLevel::ERROR, msg, opts )
153
+ end
154
+ end
155
+
156
+ ##
157
+ # Is severe-level logging currently enabled?
158
+
159
+ def severe?
160
+ if self.smart_log_level() <= LogLevel::SEVERE
161
+ true
162
+ else
163
+ false
164
+ end
165
+ end
166
+
167
+ def severe( msg, opts = nil )
168
+ if severe?
169
+ _write_to_log( LogLevel::SEVERE, msg, opts )
170
+ end
171
+ end
172
+
173
+ ##
174
+ # Is fatal-level logging currently enabled?
175
+
176
+ # def fatal?
177
+ # if self.smart_log_level() <= LogLevel::FATAL
178
+ # true
179
+ # else
180
+ # false
181
+ # end
182
+ # end
183
+
184
+ # def fatal( msg, opts = nil )
185
+ # if fatal?
186
+ # _write_to_log( LogLevel::FATAL, msg, opts )
187
+ # end
188
+ # end
189
+
190
+ ##
191
+ # Usually get_logger is a reference to self, unless a path has been specified as a parameter
192
+
193
+ def get_logger( path = nil )
194
+ current_logger = self
195
+ if path
196
+ method_names = path.split(/\./)
197
+
198
+ method_names.each do |method_name|
199
+ current_logger = current_logger.send( method_name.to_sym )
200
+ end
201
+ end
202
+
203
+ # Implicit return
204
+ current_logger
205
+ end
206
+
207
+
208
+ ##
209
+ # method_missing provides a mechanism to create sub loggers.
210
+ # Let's say I have a logger and I want to create a sub logger
211
+ #
212
+ # root_logger = CatLogger
213
+ # logger_reserved_for_app_stuff = CatLogger.App # App is a missing method
214
+ # logger_reserved_just_for_controller_stuff = CatLogger.App.Controller # Controller is a missing method
215
+
216
+
217
+ def method_missing( meth, *args, &block )
218
+ Catamaran.debugging( "Catamaran::Logger#method_missing() - meth = #{meth}" )
219
+
220
+ if ( meth.to_sym == :define_method || meth.to_sym == :to_ary )
221
+ # Catamaran.debugging( "Catamaran::Logger#method_missing() - ERROR! Ignoring method: #{meth}" )
222
+ _msg = "[CATAMARAN INTERNAL] Catamaran::Logger#method_missing() - ERROR! Ignoring method: #{meth}"
223
+ $stderr.puts( _msg )
224
+ raise Exception.new _msg
225
+ else
226
+ # Create a new logger object
227
+ logger_obj = Logger.new( meth.to_s, self.path, self )
228
+
229
+ # Keep track of this new sub-logger
230
+ @sub_loggers[meth.to_sym] = logger_obj
231
+
232
+ # Define a method so method_missing will not be called next time this method name is called
233
+ singleton = class << self; self end
234
+ singleton.send :define_method, meth.to_sym, lambda {
235
+ @sub_loggers[meth.to_sym]
236
+ }
237
+ end
238
+
239
+ # Implicit return
240
+ logger_obj
241
+ end
242
+
243
+ ##
244
+ # Human-readable path
245
+
246
+ def path_to_s
247
+ _path = self.path
248
+ if _path
249
+ # Implicit return
250
+ _path.join( delimiter )
251
+ else
252
+ # Implicit return
253
+ nil
254
+ end
255
+ end
256
+
257
+ ##
258
+ # Forget any cached memoization log levels within this logger or within sub-loggers of this logger
259
+
260
+ def forget_memoizations
261
+ remove_instance_variable(:@memoized_log_level) if instance_variable_defined?( :@memoized_log_level )
262
+ @sub_loggers.values.each do |logger|
263
+ logger.forget_memoizations()
264
+ end
265
+ end
266
+
267
+
268
+ ##
269
+ # Number of loggers known about by this logger (including sub-loggers)
270
+
271
+ def num_loggers
272
+ retval = 1 # me!
273
+ @sub_loggers.values.each do |logger|
274
+ retval = retval + logger.num_loggers()
275
+ end
276
+
277
+ # Implicit return
278
+ Catamaran.debugging( "Catamaran::Logger#num_loggers() - Returning #{retval} from '#{path_to_s()}'" ) if Catamaran.debugging?
279
+
280
+ retval
281
+ end
282
+
283
+ def reset
284
+ _reset()
285
+ Catamaran.debugging( "Catamaran::Logger#reset() - Reset complete." ) if Catamaran.debugging?
286
+ end
287
+
288
+ def depth
289
+ @depth
290
+ end
291
+
292
+ def to_s
293
+ # Implicit return
294
+ "#<#{self.class}:0x#{object_id.to_s(16)}>[name=#{self.name},path=#{path_to_s},depth=#{self.depth},log_level=#{@log_level}]"
295
+ end
296
+
297
+ private
298
+
299
+ ##
300
+ # I used to delete the root level logger, but now I reset it instead.
301
+ # Among other reasons, the CatLogger constant now works
302
+
303
+ def _reset
304
+ remove_instance_variable(:@memoized_log_level) if instance_variable_defined?( :@memoized_log_level )
305
+ remove_instance_variable(:@log_level) if instance_variable_defined?( :@log_level )
306
+
307
+ self.name = @initialized_name
308
+ self.path = @initialized_path_so_far ? @initialized_path_so_far.dup : []
309
+
310
+ if @initialized_name && @initialized_name.length > 0 # Root logger has no name
311
+ self.path << @initialized_name
312
+ end
313
+
314
+ @parent = @initialized_parent
315
+
316
+ # @sub_loggers already exist, iterate through each and undefine the associated method missing
317
+ if @sub_loggers
318
+ @sub_loggers.keys.each do |method_name_as_symbol|
319
+ Catamaran.debugging( "Catamaran::Logger#_reset() - Attempting to remove dynamically created method: #{method_name_as_symbol}" ) if Catamaran.debugging?
320
+
321
+ singleton = class << self; self end
322
+ singleton.send :remove_method, method_name_as_symbol
323
+ end
324
+ end
325
+
326
+ @sub_loggers = {}
327
+ end
328
+
329
+ ##
330
+ # All log statements eventually call _write_to_log
331
+
332
+ def _write_to_log( log_level, msg, opts )
333
+ formatted_msg = Manager.formatter_class.construct_formatted_message( log_level, self.path_to_s(), msg, opts )
334
+ Outputter.write( formatted_msg )
335
+ end
336
+
337
+ ##
338
+ # Initialize this logger.
339
+
340
+ def initialize( name, path_so_far, parent = nil )
341
+ Catamaran.debugging( "Catamaran::Logger#initialize() - Entering with name = #{name ? name : '<nil>'}, path_so_far = #{path_so_far}, parent = #{parent ? parent : '<nil>'}" ) if Catamaran.debugging?
342
+
343
+ @initialized_name = name
344
+ @initialized_path_so_far = path_so_far ? path_so_far.dup : []
345
+ @initialized_parent = parent
346
+
347
+ if parent
348
+ @depth = parent.depth + 1
349
+ else
350
+ @depth = 0
351
+ end
352
+
353
+ _reset()
354
+
355
+ Catamaran.debugging( "Catamaran::Logger#initialize() - I am #{self.to_s}" ) if Catamaran.debugging?
356
+ end
357
+
358
+ ##
359
+ # The delimiter is a period
360
+
361
+ def delimiter
362
+ "."
363
+ end
364
+
365
+ end
366
+ end
@@ -0,0 +1,145 @@
1
+ module Catamaran
2
+ class Manager
3
+ class << self;
4
+ attr_accessor :_root_logger_instance;
5
+ attr_accessor :_stdout_flag;
6
+ attr_accessor :_stderr_flag;
7
+ attr_accessor :_output_files;
8
+
9
+
10
+ ##
11
+ # Class that's going to be performing the message formatting
12
+
13
+ def formatter_class
14
+ # The NoCallerFormatter class is the default formatter.
15
+ @formatter_class || Catamaran::Formatter::NoCallerFormatter
16
+ end
17
+
18
+ def formatter_class=( value )
19
+ @formatter_class = value
20
+ end
21
+ end
22
+
23
+
24
+ ##
25
+ # Used to reset Catamaran
26
+
27
+ def self.reset
28
+ Catamaran.debugging( "Catamaran::Manager#reset - Resetting Catamaran" ) if Catamaran.debugging?
29
+
30
+ # Old way. I used to null-out the old logger. But resetting it is better for using the CatLogger constant
31
+ # self.send( :_root_logger_instance=, nil )
32
+
33
+ # New way
34
+ root_logger = self.send( :_root_logger_instance )
35
+ root_logger.reset if root_logger
36
+
37
+ # Also reset the default log level
38
+ Catamaran::LogLevel::reset
39
+
40
+ # Resetting Catamaran probably should not reset the output settings
41
+ # self.send( :_stdout_flag=, nil )
42
+ # self.send( :_stderr_flag=, nil )
43
+ # self.send( :_output_files=, nil )
44
+ end
45
+
46
+ ##
47
+ # Allow the client to access the root logger
48
+
49
+ def self.root_logger
50
+ logger = self.send( :_root_logger_instance )
51
+
52
+ unless logger
53
+ Catamaran.debugging( "Catamaran::Logger#root_logger() - Initializing new root logger" ) if Catamaran.debugging?
54
+
55
+ path_so_far = []
56
+ logger = Logger.new( nil, path_so_far )
57
+
58
+ self.send( :_root_logger_instance=, logger )
59
+
60
+ # Get the instance back as a double-confirm
61
+ logger = self.send( :_root_logger_instance )
62
+ end
63
+
64
+ if logger && logger.instance_of?( Catamaran::Logger )
65
+ Catamaran.debugging( "Catamaran::Logger#root_logger() - Returning #{logger}" ) if Catamaran.debugging?
66
+ else
67
+ Catamaran.debugging( "Catamaran::Logger#root_logger() - Error! root logger is not an instance of Logger as would expect" ) if Catamaran.debugging?
68
+ end
69
+
70
+ logger
71
+ end
72
+
73
+ ##
74
+ # Specify a destination location for logs
75
+
76
+ def self.add_output_file( output_file )
77
+ unless self.send( :_output_files )
78
+ self.send( :_output_files=, [] )
79
+ end
80
+
81
+ self.send( :_output_files ).push( output_file )
82
+
83
+ Catamaran.debugging( "Catamaran::Logger#add_output_file() - Added output file: #{output_file}. Number of _loggers is now #{self.send( :_output_files ).length}" ) if Catamaran.debugging?
84
+ end
85
+
86
+ def self.output_files
87
+ self.send( :_output_files )
88
+ end
89
+
90
+ ##
91
+ # Memoizations are used to cache log levels. Used to clear out the memoization cache.
92
+
93
+ def self.forget_memoizations
94
+ self.root_logger().forget_memoizations()
95
+ end
96
+
97
+ ##
98
+ # How many loggers is Catamaran currently aware of
99
+
100
+ def self.num_loggers
101
+ self.root_logger().num_loggers()
102
+ end
103
+
104
+ ##
105
+ # Call this method with true if you'd like Catamaran to write logs to STDOUT
106
+
107
+ def self.stdout=( bool )
108
+ self.send( :_stdout_flag=, bool )
109
+ end
110
+
111
+ ##
112
+ # Is logging to STDOUT enabled?
113
+
114
+ def self.stdout?
115
+ if self.send( :_stdout_flag ) == true
116
+ true
117
+ else
118
+ false
119
+ end
120
+ end
121
+
122
+ ##
123
+ # Call this method with true if you'd like Catamaran to write logs to STDERR
124
+
125
+ def self.stderr=( bool )
126
+ self.send( :_stderr_flag=, bool )
127
+ end
128
+
129
+ ##
130
+ # Is logging to STDERR enabled?
131
+
132
+ def self.stderr?
133
+ if self.send( :_stderr_flag ) == true
134
+ true
135
+ else
136
+ false
137
+ end
138
+ end
139
+
140
+ private_class_method :_root_logger_instance
141
+ private_class_method :_stdout_flag
142
+ private_class_method :_stderr_flag
143
+
144
+ end
145
+ end
@@ -0,0 +1,18 @@
1
+ module Catamaran
2
+ class OutputFile
3
+ attr_accessor :filename
4
+
5
+ def initialize( filename = nil )
6
+ self.filename = filename
7
+ end
8
+
9
+ def write( line )
10
+ unless @file_descriptor
11
+ @file_descriptor = File.open( self.filename, "a" )
12
+ end
13
+
14
+ @file_descriptor.puts( line )
15
+ @file_descriptor.flush
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module Catamaran
2
+ class Outputter
3
+
4
+ ##
5
+ # Write the log message to the appropriate location. Currently that could mean stdout, stderr, or output files.
6
+
7
+ def self.write( formatted_msg )
8
+ if Manager.stdout?
9
+ $stdout.puts( formatted_msg )
10
+ end
11
+
12
+ if Manager.stderr?
13
+ $stderr.puts( formatted_msg )
14
+ end
15
+
16
+ _output_files = Manager._output_files
17
+ _output_files.each do |output_file|
18
+ begin
19
+ output_file.write( formatted_msg )
20
+ rescue Exception => e
21
+ $stderr.puts( "Catamaran is unable to write to logfile: #{output_file}" )
22
+ end
23
+ end if _output_files
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ module Catamaran
2
+ VERSION = '0.2.0'
3
+ end
4
+
data/lib/catamaran.rb ADDED
@@ -0,0 +1,54 @@
1
+ require 'catamaran/logger'
2
+ require 'catamaran/manager'
3
+ require 'catamaran/formatter/caller_formatter'
4
+ require 'catamaran/formatter/no_caller_formatter'
5
+ require 'catamaran/outputter'
6
+ require 'catamaran/output_file'
7
+
8
+ require 'catamaran/log_level'
9
+ require 'catamaran/version'
10
+
11
+
12
+ module Catamaran
13
+ def self.debugging?
14
+ @debugging || false
15
+ end
16
+
17
+ def self.debugging( msg )
18
+ $stderr.puts( "[CATAMARAN DEBUGGING] #{msg}" ) if self.debugging?
19
+ end
20
+
21
+ def self.debugging=( value )
22
+ @debugging = value
23
+ end
24
+
25
+ def self.logger( optional_string = nil )
26
+ Catamaran::Manager.root_logger().get_logger( optional_string )
27
+ end
28
+
29
+ ##
30
+ # get_logger() is an alias for logger()
31
+ class << self
32
+ alias_method :get_logger, :logger
33
+ end
34
+
35
+ LOG_LEVEL_TRACE = LogLevel::TRACE
36
+ LOG_LEVEL_DEBUG = LogLevel::DEBUG
37
+ LOG_LEVEL_INFO = LogLevel::INFO
38
+ LOG_LEVEL_WARN = LogLevel::WARN
39
+ LOG_LEVEL_ERROR = LogLevel::ERROR
40
+ LOG_LEVEL_SEVERE = LogLevel::SEVERE
41
+ LOG_LEVEL_FATAL = LogLevel::FATAL
42
+ end
43
+
44
+ # add rails integration
45
+ require('catamaran/integration/rails') if defined?(Rails)
46
+
47
+ # Catamaran::Manager.stderr = true
48
+ # Catamaran::Manager.stdout = false
49
+
50
+ ##
51
+ # Define the CatLogger alias for the Catamaran root logger
52
+ Kernel.send( :remove_const, 'CatLogger' ) if Kernel.const_defined?( 'CatLogger' )
53
+ Kernel.const_set( 'CatLogger', Catamaran.logger )
54
+
@@ -0,0 +1,21 @@
1
+ module Catamaran
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ desc "Generates a custom Catamaran initializer file."
5
+
6
+ def self.source_root
7
+ @_rails_config_source_root ||= File.expand_path("../templates", __FILE__
8
+ )
9
+ end
10
+
11
+ def copy_initializer
12
+ template "catamaran.rb", "config/initializers/catamaran.rb"
13
+ end
14
+
15
+ def copy_settings
16
+ directory "catamaran", "config/initializers/catamaran"
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ ##
2
+ # Custom settings for Catamaran in development
3
+
4
+ # Enable logging to STDERR
5
+ Catamaran::Manager.stderr = true
6
+
7
+ # The default log level is INFO. Switch to DEBUG in development
8
+ Catamaran::LogLevel.default_log_level = Catamaran::LogLevel::DEBUG
9
+
10
+ # By default, logger.io messages will be captured when the log level is DEBUG (IO_LESS_CRITICAL_THAN_INFO)
11
+ # If that's too verbose, uncomment this
12
+ # Catamaran::LogLevel.log_level_io = Catamaran::LogLevel::IO_LESS_CRITICAL_THAN_DEBUG
13
+
14
+ # The NoCallerFormatter is the default.
15
+ Catamaran::Manager.formatter_class = Catamaran::Formatter::CallerFormatter
@@ -0,0 +1,2 @@
1
+ ##
2
+ # Custom settings for Catamaran in production
@@ -0,0 +1,2 @@
1
+ ##
2
+ # Custom settings for Catamaran in staging
@@ -0,0 +1,2 @@
1
+ ##
2
+ # Custom settings for Catamaran in test
@@ -0,0 +1,5 @@
1
+ ##
2
+ # Custom settings for Catamaran in all Rails environments
3
+
4
+ # Catamaran::Manager.stderr = true
5
+ # Catamaran::Manager.stdout = false
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ describe Catamaran do
4
+ it "should define TRACE" do
5
+ Catamaran::LogLevel::TRACE.should > 0
6
+ end
7
+
8
+ it "should have a version" do
9
+ Catamaran::VERSION.length.should > 0
10
+ end
11
+
12
+ it "should reuse the same root logger instance" do
13
+ Catamaran.logger.object_id.should == Catamaran.logger.object_id
14
+ end
15
+
16
+ it "should reuse the same logger instance when contextually the same" do
17
+ Catamaran.logger.Company.Product.App.Model.User.object_id.should == Catamaran.logger.Company.Product.App.Model.User.object_id
18
+ Catamaran.logger( "Company.Product.App.Model.User" ).object_id.should == Catamaran.logger.Company.Product.App.Model.User.object_id
19
+ end
20
+
21
+ it "should provide access to the root logger through CatLogger" do
22
+ CatLogger.object_id.should == Catamaran::Manager.root_logger.object_id
23
+ Catamaran.logger.object_id.should == CatLogger.object_id
24
+ end
25
+
26
+ it "should define Catamaran.logger and Catamaran.get_logger methods. They should be equivalent." do
27
+ Catamaran.logger.object_id.should == Catamaran::get_logger.object_id
28
+ end
29
+
30
+ it "should provide access to loggers after a reset" do
31
+ logger = Catamaran.logger.Company.Product.App.Model.User
32
+ Catamaran::Manager.reset
33
+ logger = Catamaran.logger.Company.Product.App.Model.User
34
+ logger.should be_instance_of( Catamaran::Logger )
35
+ end
36
+
37
+ it "should create a new loggers for each point in the path" do
38
+ Catamaran.logger
39
+ Catamaran::Manager.num_loggers.should == 1
40
+ Catamaran.logger.Company.Product.App.Model.User
41
+ Catamaran::Manager.num_loggers.should == 6
42
+ end
43
+
44
+ it "should be possible for the user to modify the default log level" do
45
+ Catamaran::LogLevel.default_log_level = Catamaran::LogLevel::TRACE
46
+ Catamaran.logger.smart_log_level.should == Catamaran::LogLevel::TRACE
47
+ end
48
+
49
+ it "should not provide the same object ID for different paths" do
50
+ Catamaran.logger.A.C.object_id.should_not == Catamaran.logger.B.C
51
+ Catamaran.logger.A.C.object_id.should_not == Catamaran.logger.A.B
52
+ end
53
+
54
+ context "by default" do
55
+ it "should log INFO messages" do
56
+ logger = Catamaran.logger.Company.Product.App.Model.User
57
+ end
58
+
59
+ it "should NOT log DEBUG messages" do
60
+ logger = Catamaran.logger.Company.Product.App.Model.User
61
+ end
62
+
63
+ it "should have an INFO log level set for the root logger" do
64
+ Catamaran.logger.log_level.should == Catamaran::LogLevel::INFO
65
+ end
66
+
67
+ it "should have an UNSET log level for any non-root loggers" do
68
+ Catamaran.logger.log_level = Catamaran::LogLevel::ERROR
69
+ Catamaran.logger.Test.log_level.should be_nil
70
+ end
71
+
72
+ it "should have one logger configured" do
73
+ Catamaran::Manager.num_loggers.should == 1
74
+ end
75
+
76
+ it "should be that the IO log level is the same as IO_LESS_CRITICAL_THAN_INFO" do
77
+ Catamaran::LogLevel::IO.should == Catamaran::LogLevel::IO_LESS_CRITICAL_THAN_INFO
78
+ end
79
+ end
80
+
81
+ context "when the log level is set to INFO" do
82
+ Catamaran.logger.log_level = Catamaran::LogLevel::INFO
83
+ Catamaran.logger.Company.Product.App.Model.User
84
+ end
85
+
86
+ context "after a reset" do
87
+ it "should have a default log level of INFO" do
88
+ logger = Catamaran.logger
89
+ logger.log_level.should == Catamaran::LogLevel::INFO
90
+ logger.log_level = Catamaran::LogLevel::ERROR
91
+ logger.log_level.should == Catamaran::LogLevel::ERROR
92
+ Catamaran::Manager.reset
93
+ logger.log_level.should == Catamaran::LogLevel::INFO
94
+ end
95
+
96
+ it "should return the number of loggers to 1" do
97
+ Catamaran.logger
98
+ Catamaran::Manager.num_loggers.should == 1
99
+ Catamaran.logger.Company.Product.App.Model.User
100
+ Catamaran::Manager.num_loggers.should == 6
101
+ Catamaran::Manager.reset
102
+ Catamaran::Manager.num_loggers.should == 1
103
+ end
104
+
105
+ it "should have the same root logger object before and after the reset" do
106
+ before_logger = Catamaran.logger
107
+ Catamaran::Manager.reset
108
+ Catamaran.logger.object_id.should == before_logger.object_id
109
+ end
110
+ end
111
+
112
+ describe Catamaran.logger do
113
+ it "should be able to inherit it's parent's log level" do
114
+ Catamaran.logger.log_level = Catamaran::LogLevel::ERROR
115
+ Catamaran.logger.Test.smart_log_level.should == Catamaran::LogLevel::ERROR
116
+ end
117
+ end
118
+
119
+ describe Catamaran::Manager do
120
+ describe "#reset" do
121
+ it "should reset Catamaran" do
122
+ before_reset_logger = Catamaran.logger.Company.Product.App.Model.User
123
+ Catamaran.logger.num_loggers.should == 6
124
+ Catamaran::Manager.reset
125
+ Catamaran.logger.num_loggers.should == 1
126
+ after_reset_logger = Catamaran.logger.Company.Product.App.Model.User
127
+ before_reset_logger.object_id.should_not == after_reset_logger.object_id
128
+ end
129
+ end
130
+
131
+ describe "#forget_memoizations" do
132
+ it "should forget all the memoized log levels" do
133
+ Catamaran.logger.log_level = Catamaran::LogLevel::ERROR
134
+ Catamaran.logger.Test.smart_log_level.should == Catamaran::LogLevel::ERROR
135
+ Catamaran.logger.log_level = Catamaran::LogLevel::INFO
136
+ Catamaran.logger.Test.smart_log_level.should == Catamaran::LogLevel::ERROR
137
+ Catamaran.logger.forget_memoizations
138
+ Catamaran.logger.Test.smart_log_level.should == Catamaran::LogLevel::INFO
139
+ end
140
+ end
141
+ end
142
+
143
+ describe Catamaran::Logger do
144
+ it "should inherit the log level from the root logger as needed" do
145
+ Catamaran.logger.smart_log_level.should == Catamaran::LogLevel::INFO
146
+ Catamaran.logger.Company.Product.App.Model.User.log_level.should be_nil
147
+ Catamaran.logger.Company.Product.App.Model.User.smart_log_level.should == Catamaran::LogLevel::INFO
148
+ end
149
+
150
+ it "should have a nil log_level unless explicitly set" do
151
+ Catamaran.logger.Company.Product.App.Model.User.log_level.should be_nil
152
+ end
153
+
154
+ it "should always have a smart_log_level set" do
155
+ Catamaran.logger.Company.Product.App.Model.User.log_level.should be_nil
156
+ Catamaran.logger.Company.Product.App.Model.User.smart_log_level.should_not be_nil
157
+ end
158
+
159
+ it "should write the log if the log has sufficient weight" do
160
+ Catamaran.logger.smart_log_level.should == Catamaran::LogLevel::INFO
161
+ Catamaran.logger.should_receive( :_write_to_log ).once
162
+ Catamaran.logger.info( "Testing an INFO log" )
163
+ end
164
+
165
+ it "should NOT write the log if the log does NOT have sufficient" do
166
+ Catamaran.logger.smart_log_level.should == Catamaran::LogLevel::INFO
167
+ # DEBUG is disabled
168
+ Catamaran.logger.should_not_receive( :_write_to_log )
169
+ Catamaran.logger.debug( "Testing a DEBUG log" )
170
+ end
171
+
172
+ context "when using smart_log_level" do
173
+ it "should inherit the log level from a parent" do
174
+ Catamaran::LogLevel.default_log_level = Catamaran::LogLevel::INFO
175
+ Catamaran.logger.Company.Product.App.Model.log_level = Catamaran::LogLevel::ERROR
176
+ Catamaran.logger.Company.Product.App.Controller.log_level = Catamaran::LogLevel::WARN
177
+ Catamaran.logger.Company.Product.App.Model.User.smart_log_level.should == Catamaran::LogLevel::ERROR
178
+ Catamaran.logger.Company.Product.App.Controller.UsersController.smart_log_level.should == Catamaran::LogLevel::WARN
179
+ Catamaran.logger.Company.Product.App.smart_log_level.should == Catamaran::LogLevel::INFO
180
+ end
181
+ end
182
+
183
+ context "when the log level is specified, the default is no longer used" do
184
+ it "should be " do
185
+ Catamaran.logger.smart_log_level.should == Catamaran::LogLevel::INFO
186
+ Catamaran.logger.log_level = Catamaran::LogLevel::ERROR
187
+ Catamaran.logger.smart_log_level.should == Catamaran::LogLevel::ERROR
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,10 @@
1
+ require 'catamaran'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:suite) do
5
+ end
6
+
7
+ config.before(:each) do
8
+ Catamaran::Manager.reset
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: catamaran
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeano
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-12-20 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A logging utility
17
+ email:
18
+ - catamaran@jeano.net
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - .gitignore
27
+ - README.md
28
+ - catamaran.gemspec
29
+ - init.rb
30
+ - lib/catamaran.rb
31
+ - lib/catamaran/formatter/caller_formatter.rb
32
+ - lib/catamaran/formatter/no_caller_formatter.rb
33
+ - lib/catamaran/integration/rails.rb
34
+ - lib/catamaran/log_level.rb
35
+ - lib/catamaran/logger.rb
36
+ - lib/catamaran/manager.rb
37
+ - lib/catamaran/output_file.rb
38
+ - lib/catamaran/outputter.rb
39
+ - lib/catamaran/version.rb
40
+ - lib/generators/catamaran/install_generator.rb
41
+ - lib/generators/catamaran/templates/catamaran.rb
42
+ - lib/generators/catamaran/templates/catamaran/development.rb
43
+ - lib/generators/catamaran/templates/catamaran/production.rb
44
+ - lib/generators/catamaran/templates/catamaran/staging.rb
45
+ - lib/generators/catamaran/templates/catamaran/test.rb
46
+ - spec/catamaran_spec.rb
47
+ - spec/spec_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/jgithub/catamaran
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Catamaran Logger
76
+ test_files: []
77
+