lumberjack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.rdoc +86 -0
  3. data/Rakefile +56 -0
  4. data/VERSION +1 -0
  5. data/lib/lumberjack.rb +39 -0
  6. data/lib/lumberjack/device.rb +26 -0
  7. data/lib/lumberjack/device/date_rolling_log_file.rb +58 -0
  8. data/lib/lumberjack/device/log_file.rb +18 -0
  9. data/lib/lumberjack/device/null.rb +15 -0
  10. data/lib/lumberjack/device/rolling_log_file.rb +109 -0
  11. data/lib/lumberjack/device/size_rolling_log_file.rb +58 -0
  12. data/lib/lumberjack/device/writer.rb +119 -0
  13. data/lib/lumberjack/formatter.rb +76 -0
  14. data/lib/lumberjack/formatter/exception_formatter.rb +12 -0
  15. data/lib/lumberjack/formatter/inspect_formatter.rb +10 -0
  16. data/lib/lumberjack/formatter/pretty_print_formatter.rb +23 -0
  17. data/lib/lumberjack/formatter/string_formatter.rb +10 -0
  18. data/lib/lumberjack/log_entry.rb +36 -0
  19. data/lib/lumberjack/logger.rb +302 -0
  20. data/lib/lumberjack/rack.rb +5 -0
  21. data/lib/lumberjack/rack/unit_of_work.rb +15 -0
  22. data/lib/lumberjack/severity.rb +23 -0
  23. data/lib/lumberjack/template.rb +71 -0
  24. data/spec/device/date_rolling_log_file_spec.rb +66 -0
  25. data/spec/device/date_rolling_log_file_spec.rbc +2118 -0
  26. data/spec/device/log_file_spec.rb +26 -0
  27. data/spec/device/log_file_spec.rbc +727 -0
  28. data/spec/device/null_spec.rb +12 -0
  29. data/spec/device/null_spec.rbc +362 -0
  30. data/spec/device/rolling_log_file_spec.rb +117 -0
  31. data/spec/device/rolling_log_file_spec.rbc +2894 -0
  32. data/spec/device/size_rolling_log_file_spec.rb +54 -0
  33. data/spec/device/size_rolling_log_file_spec.rbc +1961 -0
  34. data/spec/device/stream_spec.rbc +3310 -0
  35. data/spec/device/writer_spec.rb +118 -0
  36. data/spec/entry_spec.rbc +2333 -0
  37. data/spec/formatter/exception_formatter_spec.rb +20 -0
  38. data/spec/formatter/exception_formatter_spec.rbc +620 -0
  39. data/spec/formatter/inspect_formatter_spec.rb +13 -0
  40. data/spec/formatter/inspect_formatter_spec.rbc +360 -0
  41. data/spec/formatter/pretty_print_formatter_spec.rb +14 -0
  42. data/spec/formatter/pretty_print_formatter_spec.rbc +380 -0
  43. data/spec/formatter/string_formatter_spec.rb +12 -0
  44. data/spec/formatter/string_formatter_spec.rbc +314 -0
  45. data/spec/formatter_spec.rb +45 -0
  46. data/spec/formatter_spec.rbc +1431 -0
  47. data/spec/log_entry_spec.rb +69 -0
  48. data/spec/logger_spec.rb +390 -0
  49. data/spec/logger_spec.rbc +10043 -0
  50. data/spec/lumberjack_spec.rb +22 -0
  51. data/spec/lumberjack_spec.rbc +523 -0
  52. data/spec/rack/unit_of_work_spec.rb +26 -0
  53. data/spec/rack/unit_of_work_spec.rbc +697 -0
  54. data/spec/severity_spec.rb +23 -0
  55. data/spec/spec_helper.rb +16 -0
  56. data/spec/spec_helper.rbc +391 -0
  57. data/spec/template_spec.rb +34 -0
  58. data/spec/template_spec.rbc +1563 -0
  59. data/spec/unique_identifier_spec.rbc +329 -0
  60. metadata +128 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Brian Durand
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,86 @@
1
+ = Lumberjack
2
+
3
+ Lumberjack is a simple, powerful, and fast logging implementation in Ruby. It uses nearly the same API as the Logger class in the Ruby standard library and as ActiveSupport::BufferedLogger in Rails.
4
+
5
+ == Usage
6
+
7
+ This code aims to be extremely simple to use. The core interface it the Lumberjack::Logger which is used to log messages (which can be any object) with a specified Severity. Each logger has a level associated with it and messages are only written if their severity is greater than or equal to the level.
8
+
9
+ logger = Lumberjack::Logger.new("logs/application.log") # Open a new log file with INFO level
10
+ logger.info("Begin request")
11
+ logger.debug(request.params) # Message not written unless the level is set to DEBUG
12
+ begin
13
+ # do something
14
+ rescue => exception
15
+ logger.error(exception)
16
+ raise
17
+ end
18
+ logger.info("End request")
19
+
20
+ This is all you need to know to log messages.
21
+
22
+ == Features
23
+
24
+ === Meta data
25
+
26
+ When messages are added to the log, additional data about the message is kept in a Lumberjack::LogEntry. This means you don't need to worry about adding the time or process id to your log messages as they will be automatically recorded.
27
+
28
+ The following information is recorded for each message:
29
+
30
+ * severity - The severity recorded for the message.
31
+ * time - The time at which the message was recorded.
32
+ * program name - The name of the program logging the message. This can be either set for all messages or customized with each message.
33
+ * process id - The process id (pid) of the process that logged the message.
34
+ * unit of work id - The unique 12 byte hexadecimal number generated for a unit of work.
35
+
36
+ === Units Of Work
37
+
38
+ A unit of work can be used to tie together all log messages within a block. This can be very useful to isolate a group of messages that represent one path through the system. For instance, in a web application, a single request represents a natural unit of work, and when you are looking through a log file, it is useful to see the entire set of log entries as a unit instead of interspersed with messages from other concurrent requests.
39
+
40
+ # All log entries in this block will get a common unit of work id.
41
+ Lumberjack.unit_of_work do
42
+ logger.info("Begin request")
43
+ yield
44
+ logger.info("End request")
45
+ end
46
+
47
+ === Pluggable Devices
48
+
49
+ When a Logger logs a LogEntry, it sends it to a Lumberjack::Device. Lumberjack comes with a variety of devices for logging to IO streams or files.
50
+
51
+ * Lumberjack::Device::Stream - Writes log entries to an IO stream.
52
+ * Lumberjack::Device::LogFile - Writes log entries to a file.
53
+ * Lumberjack::Device::DateRollingLogFile - Writes log entries to a file that will automatically roll itself based on date.
54
+ * Lumberjack::Device::SizeRollingLogFile - Writes log entries to a file that will automatically roll itself based on size.
55
+ * Lumberjack::Device::Null - This device produces no output and is intended for testing environments.
56
+
57
+ If you'd like to send you log to a different kind of output, you just need to extend the Device class and implement the +write+ method.
58
+
59
+ === Customize Formatting
60
+
61
+ When a message is logged, it is first converted into a string. You can customize how it is converted by adding mappings to a Formatter.
62
+
63
+ logger.formatter.add(Hash, :pretty_print) # use the Formatter::PrettyPrintFormatter for all Hashes
64
+ logger.formatter.add(MyClass){|obj| "#{obj.class}@#{obj.id}"} # use a block to provide a custom format
65
+
66
+ If you use the built in devices, you can also customize the Template used to format the LogEntry.
67
+
68
+ # Change the format of the time in the log
69
+ Lumberjack::Logger.new("application.log", :time_format => "%m/%d/%Y %H:%M:%S")
70
+
71
+ # Use a simple template that only includes the time and the message
72
+ Lumberjack::Logger.new("application.log", :template => ":time - :message")
73
+
74
+ # Use a custom template as a block that only includes the first character of the severity
75
+ template = lambda{|e| "#{e.severity_label[0, 1]} #{e.time} - #{e.message}"}
76
+ Lumberjack::Logger.new("application.log", :template => template)
77
+
78
+ === Buffered Performance
79
+
80
+ The built in stream based logging devices use an internal buffer to increase performance by batching physical writes.
81
+
82
+ === Automatic Log Rolling
83
+
84
+ The built in devices include two that can automatically roll log files based either on date or on file size. When a log file is rolled, it will be renamed with a suffix and a new file will be created to receive new log entries. This can keep your log files from growing to unusable sizes and removes the need to schedule an external process to roll the files.
85
+
86
+ There is a similar feature in the standard library Logger class, but the implementation here is safe to use with multiple processes writing to the same log file.
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'RVM likes to call it tests'
10
+ task :tests => :test
11
+
12
+ begin
13
+ require 'rspec'
14
+ require 'rspec/core/rake_task'
15
+ desc 'Run the unit tests'
16
+ RSpec::Core::RakeTask.new(:test)
17
+ rescue LoadError
18
+ task :test do
19
+ STDERR.puts "You must have rspec 2.0 installed to run the tests"
20
+ end
21
+ end
22
+
23
+ desc 'Generate rdoc.'
24
+ Rake::RDocTask.new(:rdoc) do |rdoc|
25
+ rdoc.rdoc_dir = 'rdoc'
26
+ rdoc.options << '--title' << 'Lumberjack' << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc'
27
+ rdoc.rdoc_files.include('README.rdoc')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ namespace :rbx do
32
+ desc "Cleanup *.rbc files in lib directory"
33
+ task :delete_rbc_files do
34
+ FileList["lib/**/*.rbc"].each do |rbc_file|
35
+ File.delete(rbc_file)
36
+ end
37
+ nil
38
+ end
39
+ end
40
+
41
+ spec_file = File.expand_path('../lumberjack.gemspec', __FILE__)
42
+ if File.exist?(spec_file)
43
+ spec = eval(File.read(spec_file))
44
+
45
+ Rake::GemPackageTask.new(spec) do |p|
46
+ p.gem_spec = spec
47
+ end
48
+ Rake.application["package"].prerequisites.unshift("rbx:delete_rbc_files")
49
+
50
+ desc "Release to rubygems.org"
51
+ task :release => :package do
52
+ require 'rake/gemcutter'
53
+ Rake::Gemcutter::Tasks.new(spec).define
54
+ Rake::Task['gem:push'].invoke
55
+ end
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,39 @@
1
+ require 'rbconfig'
2
+ require 'time'
3
+ require 'thread'
4
+
5
+ module Lumberjack
6
+ autoload :Device, File.expand_path("../lumberjack/device.rb", __FILE__)
7
+ autoload :Formatter, File.expand_path("../lumberjack/formatter.rb", __FILE__)
8
+ autoload :LogEntry, File.expand_path("../lumberjack/log_entry.rb", __FILE__)
9
+ autoload :Logger, File.expand_path("../lumberjack/logger.rb", __FILE__)
10
+ autoload :Rack, File.expand_path("../lumberjack/rack.rb", __FILE__)
11
+ autoload :Severity, File.expand_path("../lumberjack/severity.rb", __FILE__)
12
+ autoload :Template, File.expand_path("../lumberjack/template.rb", __FILE__)
13
+
14
+ LINE_SEPARATOR = (Config::CONFIG['host_os'].match(/mswin/i) ? "\r\n" : "\n")
15
+
16
+ class << self
17
+ # Define a unit of work within a block. Within the block supplied to this
18
+ # method, calling +unit_of_work_id+ will return the same 12 digit hexadecimal number string.
19
+ # This can then be used for tying together log entries.
20
+ #
21
+ # For the common use case of treating a single web request as a unit of work, see the
22
+ # Lumberjack::Rack::UnitOfWork class.
23
+ def unit_of_work
24
+ save_val = Thread.current[:lumberjack_logger_unit_of_work_id]
25
+ #Thread.current[:lumberjack_logger_unit_of_work_id] = UniqueIdentifier.new
26
+ Thread.current[:lumberjack_logger_unit_of_work_id] = rand(0xFFFFFFFFFFFF).to_s(16).rjust(12, '0').upcase
27
+ begin
28
+ return yield
29
+ ensure
30
+ Thread.current[:lumberjack_logger_unit_of_work_id] = save_val
31
+ end
32
+ end
33
+
34
+ # Get the UniqueIdentifier for the current unit of work.
35
+ def unit_of_work_id
36
+ Thread.current[:lumberjack_logger_unit_of_work_id]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module Lumberjack
2
+ # This is an abstract class for logging devices. Subclasses must implement the +write+ method and
3
+ # may implement the +close+ and +flush+ methods if applicable.
4
+ class Device
5
+ autoload :DateRollingLogFile, File.expand_path("../device/date_rolling_log_file.rb", __FILE__)
6
+ autoload :LogFile, File.expand_path("../device/log_file.rb", __FILE__)
7
+ autoload :Null, File.expand_path("../device/null.rb", __FILE__)
8
+ autoload :RollingLogFile, File.expand_path("../device/rolling_log_file.rb", __FILE__)
9
+ autoload :SizeRollingLogFile, File.expand_path("../device/size_rolling_log_file.rb", __FILE__)
10
+ autoload :Writer, File.expand_path("../device/writer.rb", __FILE__)
11
+
12
+ # Subclasses must implement this method to write a LogEntry.
13
+ def write(entry)
14
+ raise NotImpelementedError
15
+ end
16
+
17
+ # Subclasses may implement this method to close the device.
18
+ def close
19
+ flush
20
+ end
21
+
22
+ # Subclasses may implement this method to flush any buffers used by the device.
23
+ def flush
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,58 @@
1
+ require 'date'
2
+
3
+ module Lumberjack
4
+ class Device
5
+ # This log device will append entries to a file and roll the file periodically by date. Files
6
+ # are rolled at midnight and can be rolled daily, weekly, or monthly. Archive file names will
7
+ # have the date appended to them in the format ".YYYY-MM-DD" for daily, ".week-of-YYYY-MM-DD" for weekly
8
+ # and ".YYYY-MM" for monthly. It is not guaranteed that log messages will break exactly on the
9
+ # roll period as buffered entries will always be written to the same file.
10
+ class DateRollingLogFile < RollingLogFile
11
+ # Create a new logging device to the specified file. The period to roll the file is specified
12
+ # with the <tt>:roll</tt> option which may contain a value of <tt>:daily</tt>, <tt>:weekly</tt>,
13
+ # or <tt>:monthly</tt>.
14
+ def initialize(path, options = {})
15
+ @file_date = Date.today
16
+ if options[:roll] && options[:roll].to_s.match(/(daily)|(weekly)|(monthly)/i)
17
+ @roll_period = $~[0].downcase.to_sym
18
+ options.delete(:roll)
19
+ else
20
+ raise ArgumentError.new("illegal value for :roll (#{options[:roll].inspect})")
21
+ end
22
+ super
23
+ end
24
+
25
+ def archive_file_suffix
26
+ case @roll_period
27
+ when :weekly
28
+ "#{@file_date.strftime('week-of-%Y-%m-%d')}"
29
+ when :monthly
30
+ "#{@file_date.strftime('%Y-%m')}"
31
+ else
32
+ "#{@file_date.strftime('%Y-%m-%d')}"
33
+ end
34
+ end
35
+
36
+ def roll_file?
37
+ date = Date.today
38
+ if date.year > @file_date.year
39
+ true
40
+ elsif @roll_period == :daily && date.yday > @file_date.yday
41
+ true
42
+ elsif @roll_period == :weekly && date.cweek > @file_date.cweek
43
+ true
44
+ elsif @roll_period == :monthly && date.month > @file_date.month
45
+ true
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ def after_roll
54
+ @file_date = Date.today
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,18 @@
1
+ require 'fileutils'
2
+
3
+ module Lumberjack
4
+ class Device
5
+ # This is a logging device that appends log entries to a file.
6
+ class LogFile < Writer
7
+ # The absolute path of the file being logged to.
8
+ attr_reader :path
9
+
10
+ # Create a logger to the file at +path+. Options are passed through to the Writer constructor.
11
+ def initialize(path, options = {})
12
+ @path = File.expand_path(path)
13
+ FileUtils.mkdir_p(File.dirname(@path))
14
+ super(File.new(@path, 'a'), options)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'date'
2
+
3
+ module Lumberjack
4
+ class Device
5
+ # This is a logging device that produces no output. It can be useful in
6
+ # testing environments when log file output is not useful.
7
+ class Null < Device
8
+ def initialize(*args)
9
+ end
10
+
11
+ def write(entry)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,109 @@
1
+ module Lumberjack
2
+ class Device
3
+ # This is an abstract class for a device that appends entries to a file and periodically archives
4
+ # the existing file and starts a one. Subclasses must implement the roll_file? and archive_file_suffix
5
+ # methods.
6
+ #
7
+ # The <tt>:keep</tt> option can be used to specify a maximum number of rolled log files to keep.
8
+ # Older files will be deleted based on the time they were created. The default is to keep all files.
9
+ class RollingLogFile < LogFile
10
+ attr_reader :path
11
+ attr_accessor :keep
12
+
13
+ def initialize(path, options = {})
14
+ @path = File.expand_path(path)
15
+ @keep = options[:keep]
16
+ super(path, options)
17
+ @file_inode = stream.lstat.ino rescue nil
18
+ end
19
+
20
+ # Returns a suffix that will be appended to the file name when it is archived.. The suffix should
21
+ # change after it is time to roll the file. The log file will be renamed when it is rolled.
22
+ def archive_file_suffix
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Return +true+ if the file should be rolled.
27
+ def roll_file?
28
+ raise NotImplementedError
29
+ end
30
+
31
+ protected
32
+
33
+ # This method will be called after a file has been rolled. Subclasses can
34
+ # implement code to reset the state of the device. This method is thread safe.
35
+ def after_roll
36
+ end
37
+
38
+ # Handle rolling the file before flushing.
39
+ def before_flush # :nodoc:
40
+ path_inode = File.lstat(path).ino rescue nil
41
+ if path_inode != @file_inode
42
+ @file_inode = path_inode
43
+ reopen_file
44
+ else
45
+ roll_file! if roll_file?
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def reopen_file
52
+ old_stream = stream
53
+ self.stream = File.open(path, 'a')
54
+ stream.sync = true
55
+ @file_inode = stream.lstat.ino rescue nil
56
+ old_stream.close
57
+ end
58
+
59
+ # Roll the log file by renaming it to the archive file name and then re-opening a stream to the log
60
+ # file path. Rolling a file is safe in multi-threaded or multi-process environments.
61
+ def roll_file! #:nodoc:
62
+ do_once(stream) do
63
+ archive_file = "#{path}.#{archive_file_suffix}"
64
+ stream.flush
65
+ current_inode = File.stat(path).ino rescue nil
66
+ if @file_inode && current_inode == @file_inode && !File.exist?(archive_file)
67
+ begin
68
+ File.rename(path, archive_file)
69
+ after_roll
70
+ cleanup_files!
71
+ rescue SystemCallError
72
+ # Ignore rename errors since it indicates the file was already rolled
73
+ end
74
+ end
75
+ end
76
+ reopen_file
77
+ rescue => e
78
+ STDERR.write("Failed to roll file #{path}: #{e.inspect}\n#{e.backtrace.join("\n")}\n")
79
+ end
80
+ end
81
+
82
+ def cleanup_files!
83
+ if keep
84
+ files = Dir.glob("#{path}.*").collect{|f| [f, File.ctime(f)]}.sort{|a,b| b.last <=> a.last}.collect{|a| a.first}
85
+ if files.size > keep
86
+ files[keep, files.length].each do |f|
87
+ File.delete(f)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def do_once(file)
94
+ begin
95
+ file.flock(File::LOCK_EX)
96
+ rescue SystemCallError
97
+ # Most likely can't lock file because the stream is closed
98
+ return
99
+ end
100
+ begin
101
+ verify = file.lstat rescue nil
102
+ # Execute only if the file we locked is still the same one that needed to be rolled
103
+ yield if verify && verify.ino == @file_inode && verify.size > 0
104
+ ensure
105
+ stream.flock(File::LOCK_UN) rescue nil
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,58 @@
1
+ module Lumberjack
2
+ class Device
3
+ # This is a log device that appends entries to a file and rolls the file when it reaches a specified
4
+ # size threshold. When a file is rolled, it will have an number extension appended to the file name.
5
+ # For example, if the log file is named production.log, the first time it is rolled it will be renamed
6
+ # production.log.1, then production.log.2, etc.
7
+ class SizeRollingLogFile < RollingLogFile
8
+ attr_reader :max_size
9
+
10
+ # Create an new log device to the specified file. The maximum size of the log file is specified with
11
+ # the <tt>:max_size</tt> option. The unit can also be specified: "32K", "100M", "2G" are all valid.
12
+ def initialize(path, options = {})
13
+ @max_size = options[:max_size]
14
+ if @max_size.is_a?(String)
15
+ if @max_size.match(/^(\d+(\.\d+)?)([KMG])?$/i)
16
+ @max_size = $~[1].to_f
17
+ units = $~[3].to_s.upcase
18
+ case units
19
+ when "K"
20
+ @max_size *= 1024
21
+ when "M"
22
+ @max_size *= 1024 ** 2
23
+ when "G"
24
+ @max_size *= 1024 ** 3
25
+ end
26
+ @max_size = @max_size.round
27
+ else
28
+ raise ArgumentError.new("illegal value for :max_size (#{@max_size})")
29
+ end
30
+ end
31
+
32
+ super
33
+ end
34
+
35
+ def archive_file_suffix
36
+ next_archive_number.to_s
37
+ end
38
+
39
+ def roll_file?
40
+ stream.stat.size >= @max_size
41
+ end
42
+
43
+ protected
44
+
45
+ # Calculate the next archive file name extension.
46
+ def next_archive_number # :nodoc:
47
+ max = 0
48
+ Dir.glob("#{path}.*").each do |filename|
49
+ if filename.match(/\.\d+$/)
50
+ suffix = filename.split('.').last.to_i
51
+ max = suffix if suffix > max
52
+ end
53
+ end
54
+ max + 1
55
+ end
56
+ end
57
+ end
58
+ end