lumberjack 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT_LICENSE +20 -0
- data/README.rdoc +86 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/lib/lumberjack.rb +39 -0
- data/lib/lumberjack/device.rb +26 -0
- data/lib/lumberjack/device/date_rolling_log_file.rb +58 -0
- data/lib/lumberjack/device/log_file.rb +18 -0
- data/lib/lumberjack/device/null.rb +15 -0
- data/lib/lumberjack/device/rolling_log_file.rb +109 -0
- data/lib/lumberjack/device/size_rolling_log_file.rb +58 -0
- data/lib/lumberjack/device/writer.rb +119 -0
- data/lib/lumberjack/formatter.rb +76 -0
- data/lib/lumberjack/formatter/exception_formatter.rb +12 -0
- data/lib/lumberjack/formatter/inspect_formatter.rb +10 -0
- data/lib/lumberjack/formatter/pretty_print_formatter.rb +23 -0
- data/lib/lumberjack/formatter/string_formatter.rb +10 -0
- data/lib/lumberjack/log_entry.rb +36 -0
- data/lib/lumberjack/logger.rb +302 -0
- data/lib/lumberjack/rack.rb +5 -0
- data/lib/lumberjack/rack/unit_of_work.rb +15 -0
- data/lib/lumberjack/severity.rb +23 -0
- data/lib/lumberjack/template.rb +71 -0
- data/spec/device/date_rolling_log_file_spec.rb +66 -0
- data/spec/device/date_rolling_log_file_spec.rbc +2118 -0
- data/spec/device/log_file_spec.rb +26 -0
- data/spec/device/log_file_spec.rbc +727 -0
- data/spec/device/null_spec.rb +12 -0
- data/spec/device/null_spec.rbc +362 -0
- data/spec/device/rolling_log_file_spec.rb +117 -0
- data/spec/device/rolling_log_file_spec.rbc +2894 -0
- data/spec/device/size_rolling_log_file_spec.rb +54 -0
- data/spec/device/size_rolling_log_file_spec.rbc +1961 -0
- data/spec/device/stream_spec.rbc +3310 -0
- data/spec/device/writer_spec.rb +118 -0
- data/spec/entry_spec.rbc +2333 -0
- data/spec/formatter/exception_formatter_spec.rb +20 -0
- data/spec/formatter/exception_formatter_spec.rbc +620 -0
- data/spec/formatter/inspect_formatter_spec.rb +13 -0
- data/spec/formatter/inspect_formatter_spec.rbc +360 -0
- data/spec/formatter/pretty_print_formatter_spec.rb +14 -0
- data/spec/formatter/pretty_print_formatter_spec.rbc +380 -0
- data/spec/formatter/string_formatter_spec.rb +12 -0
- data/spec/formatter/string_formatter_spec.rbc +314 -0
- data/spec/formatter_spec.rb +45 -0
- data/spec/formatter_spec.rbc +1431 -0
- data/spec/log_entry_spec.rb +69 -0
- data/spec/logger_spec.rb +390 -0
- data/spec/logger_spec.rbc +10043 -0
- data/spec/lumberjack_spec.rb +22 -0
- data/spec/lumberjack_spec.rbc +523 -0
- data/spec/rack/unit_of_work_spec.rb +26 -0
- data/spec/rack/unit_of_work_spec.rbc +697 -0
- data/spec/severity_spec.rb +23 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/spec_helper.rbc +391 -0
- data/spec/template_spec.rb +34 -0
- data/spec/template_spec.rbc +1563 -0
- data/spec/unique_identifier_spec.rbc +329 -0
- metadata +128 -0
data/MIT_LICENSE
ADDED
@@ -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.
|
data/README.rdoc
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/lumberjack.rb
ADDED
@@ -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
|