lumberjack_aziz_light 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT_LICENSE +20 -0
- data/README.rdoc +100 -0
- data/Rakefile +40 -0
- data/VERSION +1 -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 +110 -0
- data/lib/lumberjack/device/size_rolling_log_file.rb +60 -0
- data/lib/lumberjack/device/writer.rb +129 -0
- data/lib/lumberjack/device.rb +26 -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/formatter.rb +76 -0
- data/lib/lumberjack/log_entry.rb +36 -0
- data/lib/lumberjack/logger.rb +302 -0
- data/lib/lumberjack/rack/unit_of_work.rb +15 -0
- data/lib/lumberjack/rack.rb +5 -0
- data/lib/lumberjack/severity.rb +23 -0
- data/lib/lumberjack/template.rb +71 -0
- data/lib/lumberjack.rb +42 -0
- data/spec/device/date_rolling_log_file_spec.rb +66 -0
- data/spec/device/log_file_spec.rb +26 -0
- data/spec/device/null_spec.rb +12 -0
- data/spec/device/rolling_log_file_spec.rb +129 -0
- data/spec/device/size_rolling_log_file_spec.rb +54 -0
- data/spec/device/writer_spec.rb +118 -0
- data/spec/formatter/exception_formatter_spec.rb +20 -0
- data/spec/formatter/inspect_formatter_spec.rb +13 -0
- data/spec/formatter/pretty_print_formatter_spec.rb +14 -0
- data/spec/formatter/string_formatter_spec.rb +12 -0
- data/spec/formatter_spec.rb +45 -0
- data/spec/log_entry_spec.rb +69 -0
- data/spec/logger_spec.rb +390 -0
- data/spec/lumberjack_spec.rb +29 -0
- data/spec/rack/unit_of_work_spec.rb +26 -0
- data/spec/severity_spec.rb +23 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/template_spec.rb +34 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3ae9171da4a378479133de30af3e891550667e9a
|
4
|
+
data.tar.gz: aa5e35929402d35ecff363c776c8ff2fc1620ab9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c92d8d0fb6d685146e00c3bffa0f44abffd7d7834d155409fb82a13645dac65f32d3507d7e71c5a18cced4b4ea3a9b41be17f4f0a0de2c098a6dd00b032c9807
|
7
|
+
data.tar.gz: 65d396ab9d6e02007c0976ed95fc5c942112f7d40dd635a2fcb9bc2b988ff918b4ed8981307db214630ee4b2f2584c280bd6dfb8f9f99e6e6f0d4ad8adac1c5f
|
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,100 @@
|
|
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::Writer - 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. Or check out these plugins:
|
58
|
+
|
59
|
+
* lumberjack_syslog_device[https://github.com/bdurand/lumberjack_syslog_device] - send your log messages to the system wide syslog service
|
60
|
+
* lumberjack_mongo_device[https://github.com/bdurand/lumberjack_mongo_device] - store your log messages to a MongoDB[http://www.mongodb.org/] NoSQL data store
|
61
|
+
* lumberjack-couchdb-driver[https://github.com/narkisr/lumberjack-couchdb-driver] - store your log messages to a CouchDB[http://couchdb.apache.org/] NoSQL data store
|
62
|
+
|
63
|
+
=== Customize Formatting
|
64
|
+
|
65
|
+
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.
|
66
|
+
|
67
|
+
logger.formatter.add(Hash, :pretty_print) # use the Formatter::PrettyPrintFormatter for all Hashes
|
68
|
+
logger.formatter.add(MyClass){|obj| "#{obj.class}@#{obj.id}"} # use a block to provide a custom format
|
69
|
+
|
70
|
+
If you use the built in devices, you can also customize the Template used to format the LogEntry.
|
71
|
+
|
72
|
+
# Change the format of the time in the log
|
73
|
+
Lumberjack::Logger.new("application.log", :time_format => "%m/%d/%Y %H:%M:%S")
|
74
|
+
|
75
|
+
# Use a simple template that only includes the time and the message
|
76
|
+
Lumberjack::Logger.new("application.log", :template => ":time - :message")
|
77
|
+
|
78
|
+
# Use a custom template as a block that only includes the first character of the severity
|
79
|
+
template = lambda{|e| "#{e.severity_label[0, 1]} #{e.time} - #{e.message}"}
|
80
|
+
Lumberjack::Logger.new("application.log", :template => template)
|
81
|
+
|
82
|
+
=== Buffered Performance
|
83
|
+
|
84
|
+
The logger has hooks for devices that support buffering to increase performance by batching physical writes. Log entries are not guaranteed to be written until the Lumberjack::Logger#flush method is called.
|
85
|
+
|
86
|
+
You can use the <tt>:flush_seconds</tt> option on the logger to periodically flush the log. This is usually a good idea so you can more easily debug hung processes. Without periodic flushing, a process that hangs may never write anything to the log because the messages are sitting in a buffer. By turning on periodic flushing, the logged messages will be written which can greatly aid in debugging the problem.
|
87
|
+
|
88
|
+
The built in stream based logging devices use an internal buffer. The size of the buffer (in bytes) can be set with the <tt>:buffer_size</tt> options when initializing a logger. The default behavior is to not to buffer.
|
89
|
+
|
90
|
+
# Set buffer to flush after 8K has been written to the log.
|
91
|
+
logger = Lumberjack::Logger.new("application.log", :buffer_size => 8192)
|
92
|
+
|
93
|
+
# Turn off buffering so entries are immediately written to disk.
|
94
|
+
logger = Lumberjack::Logger.new("application.log", :buffer_size => 0)
|
95
|
+
|
96
|
+
=== Automatic Log Rolling
|
97
|
+
|
98
|
+
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.
|
99
|
+
|
100
|
+
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,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'RVM likes to call it tests'
|
9
|
+
task :tests => :test
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'rspec'
|
13
|
+
require 'rspec/core/rake_task'
|
14
|
+
desc 'Run the unit tests'
|
15
|
+
RSpec::Core::RakeTask.new(:test)
|
16
|
+
rescue LoadError
|
17
|
+
task :test do
|
18
|
+
STDERR.puts "You must have rspec 2.0 installed to run the tests"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
namespace :rbx do
|
23
|
+
desc "Cleanup *.rbc files in lib directory"
|
24
|
+
task :delete_rbc_files do
|
25
|
+
FileList["**/*.rbc"].each do |rbc_file|
|
26
|
+
File.delete(rbc_file)
|
27
|
+
end
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
spec_file = File.expand_path('../lumberjack.gemspec', __FILE__)
|
33
|
+
if File.exist?(spec_file)
|
34
|
+
spec = eval(File.read(spec_file))
|
35
|
+
|
36
|
+
Gem::PackageTask.new(spec) do |p|
|
37
|
+
p.gem_spec = spec
|
38
|
+
end
|
39
|
+
Rake.application["package"].prerequisites.unshift("rbx:delete_rbc_files")
|
40
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.5
|
@@ -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', :encoding => "ascii-8bit"), 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,110 @@
|
|
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
|
+
@@rolls = []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns a suffix that will be appended to the file name when it is archived.. The suffix should
|
22
|
+
# change after it is time to roll the file. The log file will be renamed when it is rolled.
|
23
|
+
def archive_file_suffix
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return +true+ if the file should be rolled.
|
28
|
+
def roll_file?
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# Roll the log file by renaming it to the archive file name and then re-opening a stream to the log
|
33
|
+
# file path. Rolling a file is safe in multi-threaded or multi-process environments.
|
34
|
+
def roll_file! #:nodoc:
|
35
|
+
do_once(stream) do
|
36
|
+
archive_file = "#{path}.#{archive_file_suffix}"
|
37
|
+
stream.flush
|
38
|
+
current_inode = File.stat(path).ino rescue nil
|
39
|
+
if @file_inode && current_inode == @file_inode && !File.exist?(archive_file) && File.exist?(path)
|
40
|
+
begin
|
41
|
+
File.rename(path, archive_file)
|
42
|
+
after_roll
|
43
|
+
cleanup_files!
|
44
|
+
rescue SystemCallError
|
45
|
+
# Ignore rename errors since it indicates the file was already rolled
|
46
|
+
end
|
47
|
+
end
|
48
|
+
reopen_file
|
49
|
+
end
|
50
|
+
rescue => e
|
51
|
+
STDERR.write("Failed to roll file #{path}: #{e.inspect}\n#{e.backtrace.join("\n")}\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# This method will be called after a file has been rolled. Subclasses can
|
57
|
+
# implement code to reset the state of the device. This method is thread safe.
|
58
|
+
def after_roll
|
59
|
+
end
|
60
|
+
|
61
|
+
# Handle rolling the file before flushing.
|
62
|
+
def before_flush # :nodoc:
|
63
|
+
path_inode = File.lstat(path).ino rescue nil
|
64
|
+
if path_inode != @file_inode
|
65
|
+
@file_inode = path_inode
|
66
|
+
reopen_file
|
67
|
+
else
|
68
|
+
roll_file! if roll_file?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def reopen_file
|
75
|
+
old_stream = stream
|
76
|
+
self.stream = File.open(path, 'a')
|
77
|
+
stream.sync = true
|
78
|
+
@file_inode = stream.lstat.ino rescue nil
|
79
|
+
old_stream.close
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def cleanup_files!
|
84
|
+
if keep
|
85
|
+
files = Dir.glob("#{path}.*").collect{|f| [f, File.ctime(f)]}.sort{|a,b| b.last <=> a.last}.collect{|a| a.first}
|
86
|
+
if files.size > keep
|
87
|
+
files[keep, files.length].each do |f|
|
88
|
+
File.delete(f)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def do_once(file)
|
95
|
+
begin
|
96
|
+
file.flock(File::LOCK_EX)
|
97
|
+
rescue SystemCallError
|
98
|
+
# Most likely can't lock file because the stream is closed
|
99
|
+
return
|
100
|
+
end
|
101
|
+
begin
|
102
|
+
verify = file.lstat rescue nil
|
103
|
+
# Execute only if the file we locked is still the same one that needed to be rolled
|
104
|
+
yield if verify && verify.ino == @file_inode && verify.size > 0
|
105
|
+
ensure
|
106
|
+
file.flock(File::LOCK_UN) rescue nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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
|
+
rescue SystemCallError => e
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Calculate the next archive file name extension.
|
48
|
+
def next_archive_number # :nodoc:
|
49
|
+
max = 0
|
50
|
+
Dir.glob("#{path}.*").each do |filename|
|
51
|
+
if filename.match(/\.\d+$/)
|
52
|
+
suffix = filename.split('.').last.to_i
|
53
|
+
max = suffix if suffix > max
|
54
|
+
end
|
55
|
+
end
|
56
|
+
max + 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Lumberjack
|
2
|
+
class Device
|
3
|
+
# This logging device writes log entries as strings to an IO stream. By default, messages will be buffered
|
4
|
+
# and written to the stream in a batch when the buffer is full or when +flush+ is called.
|
5
|
+
class Writer < Device
|
6
|
+
DEFAULT_FIRST_LINE_TEMPLATE = "[:time :severity :progname(:pid) #:unit_of_work_id] :message".freeze
|
7
|
+
DEFAULT_ADDITIONAL_LINES_TEMPLATE = "#{Lumberjack::LINE_SEPARATOR}> [#:unit_of_work_id] :message".freeze
|
8
|
+
|
9
|
+
# The size of the internal buffer. Defaults to 32K.
|
10
|
+
attr_reader :buffer_size
|
11
|
+
|
12
|
+
# Internal buffer to batch writes to the stream.
|
13
|
+
class Buffer # :nodoc:
|
14
|
+
attr_reader :size
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@values = []
|
18
|
+
@size = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(string)
|
22
|
+
@values << string
|
23
|
+
@size += string.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
@values.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def join(delimiter)
|
31
|
+
@values.join(delimiter)
|
32
|
+
end
|
33
|
+
|
34
|
+
def clear
|
35
|
+
@values = []
|
36
|
+
@size = 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a new device to write log entries to a stream. Entries are converted to strings
|
41
|
+
# using a Template. The template can be specified using the <tt>:template</tt> option. This can
|
42
|
+
# either be a Proc or a string that will compile into a Template object.
|
43
|
+
#
|
44
|
+
# If the template is a Proc, it should accept an LogEntry as its only argument and output a string.
|
45
|
+
#
|
46
|
+
# If the template is a template string, it will be used to create a Template. The
|
47
|
+
# <tt>:additional_lines</tt> and <tt>:time_format</tt> options will be passed through to the
|
48
|
+
# Template constuctor.
|
49
|
+
#
|
50
|
+
# The default template is <tt>"[:time :severity :progname(:pid) #:unit_of_work_id] :message"</tt>
|
51
|
+
# with additional lines formatted as <tt>"\n [#:unit_of_work_id] :message"</tt>. The unit of
|
52
|
+
# work id will only appear if it is present.
|
53
|
+
#
|
54
|
+
# The size of the internal buffer in bytes can be set by providing <tt>:buffer_size</tt> (defaults to 32K).
|
55
|
+
def initialize(stream, options = {})
|
56
|
+
@lock = Mutex.new
|
57
|
+
@stream = stream
|
58
|
+
@stream.sync = true if @stream.respond_to?(:sync=)
|
59
|
+
@buffer = Buffer.new
|
60
|
+
@buffer_size = (options[:buffer_size] || 0)
|
61
|
+
template = (options[:template] || DEFAULT_FIRST_LINE_TEMPLATE)
|
62
|
+
if template.respond_to?(:call)
|
63
|
+
@template = template
|
64
|
+
else
|
65
|
+
additional_lines = (options[:additional_lines] || DEFAULT_ADDITIONAL_LINES_TEMPLATE)
|
66
|
+
@template = Template.new(template, :additional_lines => additional_lines, :time_format => options[:time_format])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set the buffer size in bytes. The device will only be physically written to when the buffer size
|
71
|
+
# is exceeded.
|
72
|
+
def buffer_size=(value)
|
73
|
+
@buffer_size = value
|
74
|
+
flush
|
75
|
+
end
|
76
|
+
|
77
|
+
# Write an entry to the stream. The entry will be converted into a string using the defined template.
|
78
|
+
def write(entry)
|
79
|
+
string = @template.call(entry)
|
80
|
+
@lock.synchronize do
|
81
|
+
@buffer << string
|
82
|
+
end
|
83
|
+
flush if @buffer.size >= buffer_size
|
84
|
+
end
|
85
|
+
|
86
|
+
# Close the underlying stream.
|
87
|
+
def close
|
88
|
+
flush
|
89
|
+
stream.close
|
90
|
+
end
|
91
|
+
|
92
|
+
# Flush the underlying stream.
|
93
|
+
def flush
|
94
|
+
@lock.synchronize do
|
95
|
+
before_flush
|
96
|
+
unless @buffer.empty?
|
97
|
+
out = @buffer.join(Lumberjack::LINE_SEPARATOR) << Lumberjack::LINE_SEPARATOR
|
98
|
+
begin
|
99
|
+
stream.write(out)
|
100
|
+
stream.flush
|
101
|
+
rescue => e
|
102
|
+
$stderr.write("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}")
|
103
|
+
$stderr.write(out)
|
104
|
+
$stderr.flush
|
105
|
+
end
|
106
|
+
@buffer.clear
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
# Callback method that will be executed before data is written to the stream. Subclasses
|
114
|
+
# can override this method if needed.
|
115
|
+
def before_flush
|
116
|
+
end
|
117
|
+
|
118
|
+
# Set the underlying stream.
|
119
|
+
def stream=(stream)
|
120
|
+
@stream = stream
|
121
|
+
end
|
122
|
+
|
123
|
+
# Get the underlying stream.
|
124
|
+
def stream
|
125
|
+
@stream
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|