sawmill 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +3 -0
- data/README.rdoc +81 -0
- data/Rakefile +147 -0
- data/lib/sawmill/entry.rb +307 -0
- data/lib/sawmill/entry_classifier.rb +75 -0
- data/lib/sawmill/entry_processor/build_records.rb +157 -0
- data/lib/sawmill/entry_processor/conditionals.rb +444 -0
- data/lib/sawmill/entry_processor/filter_basic_fields.rb +145 -0
- data/lib/sawmill/entry_processor/format.rb +228 -0
- data/lib/sawmill/entry_processor/simple_queue.rb +116 -0
- data/lib/sawmill/entry_processor.rb +158 -0
- data/lib/sawmill/errors.rb +66 -0
- data/lib/sawmill/level.rb +264 -0
- data/lib/sawmill/log_record_middleware.rb +93 -0
- data/lib/sawmill/logger.rb +373 -0
- data/lib/sawmill/parser.rb +181 -0
- data/lib/sawmill/record.rb +255 -0
- data/lib/sawmill/record_processor/conditionals.rb +330 -0
- data/lib/sawmill/record_processor/decompose.rb +75 -0
- data/lib/sawmill/record_processor/filter_by_attributes.rb +87 -0
- data/lib/sawmill/record_processor/filter_by_record_id.rb +77 -0
- data/lib/sawmill/record_processor/format.rb +88 -0
- data/lib/sawmill/record_processor/simple_queue.rb +117 -0
- data/lib/sawmill/record_processor.rb +137 -0
- data/lib/sawmill/rotater/base.rb +90 -0
- data/lib/sawmill/rotater/date_based_log_file.rb +145 -0
- data/lib/sawmill/rotater/shifting_log_file.rb +166 -0
- data/lib/sawmill/rotater.rb +236 -0
- data/lib/sawmill/util/queue.rb +138 -0
- data/lib/sawmill/version.rb +47 -0
- data/lib/sawmill.rb +78 -0
- data/tests/tc_entry_processors.rb +138 -0
- data/tests/tc_formatter_parser.rb +144 -0
- data/tests/tc_levels.rb +118 -0
- data/tests/tc_logger.rb +315 -0
- data/tests/tc_record_processors.rb +117 -0
- data/tests/tc_records.rb +206 -0
- metadata +116 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Sawmill formatter utility
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
module Sawmill
|
38
|
+
|
39
|
+
class Rotater
|
40
|
+
|
41
|
+
|
42
|
+
# A rotation strategy that produces log files with the date stamp in the
|
43
|
+
# file name. For example, you could set up an hourly log rotation that
|
44
|
+
# produces the following files:
|
45
|
+
#
|
46
|
+
# rails.2009-10-09-22.log
|
47
|
+
# rails.2009-10-09-23.log
|
48
|
+
# rails.2009-10-10-00.log
|
49
|
+
# rails.2009-10-10-01.log
|
50
|
+
# etc...
|
51
|
+
#
|
52
|
+
# The exact format depends on the rotation frequency, which could be
|
53
|
+
# anywhere from yearly to hourly. For settings less frequent than
|
54
|
+
# hourly, fewer fields will appear in the date stamp portion of the
|
55
|
+
# file name.
|
56
|
+
|
57
|
+
class DateBasedLogFile
|
58
|
+
|
59
|
+
|
60
|
+
# Create a new date-based log file rotation strategy.
|
61
|
+
#
|
62
|
+
# Recognized options include:
|
63
|
+
#
|
64
|
+
# <tt>:turnover_frequency</tt>::
|
65
|
+
# How often the log files should turn over. Allowed values are:
|
66
|
+
# <tt>:yearly</tt>, <tt>:monthly</tt>, <tt>:daily</tt>,
|
67
|
+
# <tt>:hourly</tt>, and <tt>:never</tt>.
|
68
|
+
# <tt>:dirname</tt>::
|
69
|
+
# The directory for the logfiles to be output.
|
70
|
+
# If not specified, the current working directory is used.
|
71
|
+
# <tt>:prefix</tt>::
|
72
|
+
# The logfile name prefix.
|
73
|
+
# In the filename "rails.2009-10-11.log", the prefix is "rails".
|
74
|
+
# If not specified, defaults to "sawmill".
|
75
|
+
# <tt>:suffix</tt>::
|
76
|
+
# The logfile name prefix.
|
77
|
+
# In the filename "rails.2009-10-11.log", the suffix is ".log".
|
78
|
+
# If not specified, defaults to ".log".
|
79
|
+
# <tt>:local_timezone</tt>::
|
80
|
+
# If true, use the local timezone to create datestamps.
|
81
|
+
# The default is to use UTC.
|
82
|
+
|
83
|
+
def initialize(options_)
|
84
|
+
@turnover_frequency = options_[:turnover_frequency] || :none
|
85
|
+
dirname_ = options_[:dirname] || Dir.getwd
|
86
|
+
@prefix = File.join(dirname_, options_[:prefix] || 'sawmill')
|
87
|
+
@suffix = options_[:suffix] || '.log'
|
88
|
+
@local_timezone = options_[:local_timezone]
|
89
|
+
@date_pattern =
|
90
|
+
case @turnover_frequency
|
91
|
+
when :yearly then "%Y"
|
92
|
+
when :monthly then "%Y-%m"
|
93
|
+
when :daily then "%Y-%m-%d"
|
94
|
+
when :hourly then "%Y-%m-%d-%H"
|
95
|
+
else nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
# Implements the rotation strategy contract.
|
101
|
+
|
102
|
+
def preferred_handle
|
103
|
+
if @date_pattern
|
104
|
+
time_ = Time.now
|
105
|
+
time_.utc unless @local_timezone
|
106
|
+
time_.strftime(@date_pattern)
|
107
|
+
else
|
108
|
+
''
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# Implements the rotation strategy contract.
|
114
|
+
|
115
|
+
def open_handle(handle_)
|
116
|
+
if @date_pattern
|
117
|
+
path_ = "#{@prefix}.#{handle_}#{@suffix}"
|
118
|
+
else
|
119
|
+
path_ = @prefix+@suffix
|
120
|
+
end
|
121
|
+
file_ = File.open(path_, File::CREAT | File::WRONLY | File::APPEND)
|
122
|
+
file_.sync = true
|
123
|
+
file_
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Implements the rotation strategy contract.
|
128
|
+
|
129
|
+
def close_handle(handle_, io_)
|
130
|
+
io_.close
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# Implements the rotation strategy contract.
|
135
|
+
|
136
|
+
def before_write
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Sawmill formatter utility
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
module Sawmill
|
38
|
+
|
39
|
+
class Rotater
|
40
|
+
|
41
|
+
|
42
|
+
# A rotation strategy that "shifts" log files by appending index numbers
|
43
|
+
# to the filename when the file reaches a certain size or age. So when
|
44
|
+
# the file "foo.log" is ready to rotate, it is renamed "foo.log.0", and
|
45
|
+
# a new "foo.log" is started. When that one is ready to rotate, the
|
46
|
+
# oldest "foo.log.0" is shifted down to "foo.log.1", "foo.log" is
|
47
|
+
# renamed to "foo.log.0", and a new "foo.log" is started. So the oldest
|
48
|
+
# logfile is always the one with the largest number suffix, and the
|
49
|
+
# file currently being written to has no suffix.
|
50
|
+
# This is a common rotation strategy for many unix tools.
|
51
|
+
|
52
|
+
class ShiftingLogFile
|
53
|
+
|
54
|
+
|
55
|
+
# Create a new shifting log file rotation strategy.
|
56
|
+
#
|
57
|
+
# Recognized options include:
|
58
|
+
#
|
59
|
+
# <tt>:dirname</tt>::
|
60
|
+
# The directory for the logfiles to be output.
|
61
|
+
# If not specified, the current working directory is used.
|
62
|
+
# <tt>:filename</tt>::
|
63
|
+
# The logfile name.
|
64
|
+
# If not specified, defaults to "sawmill.log".
|
65
|
+
# <tt>:max_logfile_size</tt>::
|
66
|
+
# A logfile will try to rotate once it has reached this size in
|
67
|
+
# bytes. If not specified, the file size is not checked.
|
68
|
+
# <tt>:shift_period</tt>::
|
69
|
+
# A logfile will try to rotate once it has been in service for
|
70
|
+
# this many seconds. This parameter also recognizes the values
|
71
|
+
# <tt>:yearly</tt>, <tt>:monthly</tt>, <tt>:daily</tt>,
|
72
|
+
# and <tt>:hourly</tt>. If not specified, the file's age is
|
73
|
+
# not checked.
|
74
|
+
# <tt>:history_size</tt>::
|
75
|
+
# The maximum number of old logfiles (files with indexes) to
|
76
|
+
# keep. Files beyond this history size will be automatically
|
77
|
+
# deleted. Default is 1. This value must be at least 1.
|
78
|
+
|
79
|
+
def initialize(options_)
|
80
|
+
@max_logfile_size = options_[:max_logfile_size]
|
81
|
+
@shift_period = options_[:shift_period]
|
82
|
+
case @shift_period
|
83
|
+
when :yearly
|
84
|
+
@shift_period = 60*60*24*365
|
85
|
+
when :monthly
|
86
|
+
@shift_period = 60*60*24*30
|
87
|
+
when :daily
|
88
|
+
@shift_period = 60*60*24
|
89
|
+
when :hourly
|
90
|
+
@shift_period = 60*60
|
91
|
+
end
|
92
|
+
@history_size = options_[:history_size].to_i
|
93
|
+
@history_size = 1 if @history_size < 1 && (@max_logfile_size || @shift_period)
|
94
|
+
dirname_ = options_[:dirname] || Dir.getwd
|
95
|
+
@normal_path = File.join(dirname_, options_[:filename] || 'sawmill.log')
|
96
|
+
@preferred_handle = 0
|
97
|
+
@open_handles = {}
|
98
|
+
@last_shift = Time.now
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# Implements the rotation strategy contract.
|
103
|
+
|
104
|
+
def preferred_handle
|
105
|
+
@preferred_handle
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Implements the rotation strategy contract.
|
110
|
+
|
111
|
+
def open_handle(handle_)
|
112
|
+
if handle_ == @preferred_handle
|
113
|
+
path_ = @normal_path
|
114
|
+
else
|
115
|
+
path_ = "#{@normal_path}.#{@preferred_handle-handle_-1}"
|
116
|
+
end
|
117
|
+
file_ = File.open(path_, File::CREAT | File::WRONLY | File::APPEND)
|
118
|
+
file_.sync = true
|
119
|
+
@open_handles[handle_] = true
|
120
|
+
file_
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Implements the rotation strategy contract.
|
125
|
+
|
126
|
+
def close_handle(handle_, io_)
|
127
|
+
io_.close
|
128
|
+
if @preferred_handle - handle_ > @history_size
|
129
|
+
File.delete("#{@normal_path}.#{@preferred_handle-handle_-1}") rescue nil
|
130
|
+
end
|
131
|
+
@open_handles.delete(handle_)
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# Implements the rotation strategy contract.
|
137
|
+
|
138
|
+
def before_write
|
139
|
+
return unless @max_logfile_size || @shift_period
|
140
|
+
turnover_ = false
|
141
|
+
if @max_logfile_size && File.file?(@normal_path) && File.size(@normal_path) > @max_logfile_size
|
142
|
+
turnover_ = true
|
143
|
+
end
|
144
|
+
if @shift_period && (Time.now - @last_shift) > @shift_period
|
145
|
+
turnover_ = true
|
146
|
+
end
|
147
|
+
if turnover_
|
148
|
+
max_ = @preferred_handle - @open_handles.keys.min + 1
|
149
|
+
max_ = @history_size if max_ < @history_size
|
150
|
+
File.delete("#{@normal_path}.#{max_-1}") rescue nil
|
151
|
+
(max_-1).downto(1) do |index_|
|
152
|
+
File.rename("#{@normal_path}.#{index_-1}", "#{@normal_path}.#{index_}") rescue nil
|
153
|
+
end
|
154
|
+
File.rename("#{@normal_path}", "#{@normal_path}.0") rescue nil
|
155
|
+
@preferred_handle += 1
|
156
|
+
@last_shift = Time.now
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Sawmill log rotation utility
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
require 'thread'
|
38
|
+
|
39
|
+
|
40
|
+
module Sawmill
|
41
|
+
|
42
|
+
|
43
|
+
# The Sawmill Rotater provides log rotation services for logfile
|
44
|
+
# formatting, supporting several different log rotation strategies.
|
45
|
+
#
|
46
|
+
# The formatter implemented by Sawmill::EntryProcessor::Format already
|
47
|
+
# recognizes a Rotater as a supported destination, and automatically
|
48
|
+
# interfaces with it to ensure that log records aren't split into
|
49
|
+
# multiple files by rotation.
|
50
|
+
#
|
51
|
+
# You may also interface with a rotater manually. The core of rotater
|
52
|
+
# usage is the Channel, which lets you ensure that groups of log
|
53
|
+
# entries end up in the same log file regardless of log rotation.
|
54
|
+
# Generally, to use a rotater, you obtain one or more channels and
|
55
|
+
# write formatted entries to them, either telling them explicitly
|
56
|
+
# where the allowable file breaks are, or by letting the rotater
|
57
|
+
# break the file anywhere it wants. See the create_channel method
|
58
|
+
# and the Channel object for more details.
|
59
|
+
|
60
|
+
class Rotater
|
61
|
+
|
62
|
+
|
63
|
+
# Create a rotater using the given rotation strategy.
|
64
|
+
# See Sawmill::Rotater::DateBasedLogFile and
|
65
|
+
# Sawmill::Rotater::ShiftingLogFile for examples of common strategies.
|
66
|
+
#
|
67
|
+
# The rotation strategy can be passed as an object or as a class with a
|
68
|
+
# set of options that will be used to instantiate the strategy.
|
69
|
+
# In addition to those options, the following options are recognized:
|
70
|
+
#
|
71
|
+
# <tt>:omit_directives</tt>::
|
72
|
+
# If true, omit standard logfile directives. Default is false.
|
73
|
+
|
74
|
+
def initialize(io_manager_, opts_={})
|
75
|
+
@omit_directives = opts_.delete(:omit_directives)
|
76
|
+
if io_manager_.kind_of?(Class)
|
77
|
+
@io_manager = io_manager_.new(opts_)
|
78
|
+
else
|
79
|
+
@io_manager = io_manager_
|
80
|
+
end
|
81
|
+
@handles ||= {}
|
82
|
+
@mutex ||= Monitor.new
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# Create a new Channel for this Rotater. See Sawmill::Rotater::Channel
|
87
|
+
# for details on the Channel object.
|
88
|
+
#
|
89
|
+
# The following options are recognized:
|
90
|
+
#
|
91
|
+
# <tt>:auto_rotate</tt>::
|
92
|
+
# Put the channel in auto-rotate mode. In this mode, the rotater is
|
93
|
+
# allowed to rotate the logfile at any time for that channel. It is
|
94
|
+
# the equivalent of calling check_rotate on the channel after every
|
95
|
+
# write. Default is false.
|
96
|
+
|
97
|
+
def create_channel(opts_={})
|
98
|
+
Channel.new(self, opts_)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def _obtain_handle # :nodoc:
|
103
|
+
handle_ = @io_manager.preferred_handle
|
104
|
+
if @handles.include?(handle_)
|
105
|
+
@handles[handle_][2] += 1
|
106
|
+
else
|
107
|
+
io_ = @io_manager.open_handle(handle_)
|
108
|
+
unless @omit_directives
|
109
|
+
io_.write("# sawmill_format: version=1\n")
|
110
|
+
end
|
111
|
+
@handles[handle_] = [handle_, io_, 1]
|
112
|
+
end
|
113
|
+
handle_
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def _release_handle(handle_) # :nodoc:
|
118
|
+
info_ = @handles[handle_]
|
119
|
+
info_[2] -= 1
|
120
|
+
if info_[2] == 0
|
121
|
+
@io_manager.close_handle(handle_, info_[1])
|
122
|
+
@handles.delete(handle_)
|
123
|
+
end
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def _check_rotate_handle(handle_) # :nodoc:
|
129
|
+
if handle_ != @io_manager.preferred_handle
|
130
|
+
_release_handle(handle_)
|
131
|
+
_obtain_handle
|
132
|
+
else
|
133
|
+
handle_
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def _do_open # :nodoc:
|
139
|
+
@mutex.synchronize do
|
140
|
+
_obtain_handle
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def _do_write(handle_, str_, auto_rotate_) # :nodoc:
|
146
|
+
@mutex.synchronize do
|
147
|
+
@io_manager.before_write
|
148
|
+
if auto_rotate_
|
149
|
+
handle_ = _check_rotate_handle(handle_)
|
150
|
+
end
|
151
|
+
info_ = @handles[handle_]
|
152
|
+
info_[1].write(str_)
|
153
|
+
handle_
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def _do_close(handle_) # :nodoc:
|
159
|
+
@mutex.synchronize do
|
160
|
+
_release_handle(handle_)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def _do_check_rotate(handle_) # :nodoc:
|
166
|
+
@mutex.synchronize do
|
167
|
+
_check_rotate_handle(handle_)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# A channel is a lightweight object that responds to the write and close
|
173
|
+
# methods; that is, it is sufficient for Sawmill::Formatter.
|
174
|
+
#
|
175
|
+
# When a channel is opened, it locks down a path to the logfile and
|
176
|
+
# ensures that the logfile will not rotate out from under it; that is,
|
177
|
+
# writes to a channel are ensured to end up in the same physical file.
|
178
|
+
#
|
179
|
+
# You may choose, at intervals, to explicitly tell the channel that it
|
180
|
+
# is okay to rotate the logfile now, by calling check_rotate.
|
181
|
+
#
|
182
|
+
# You must close a channel when you are done with it. Closing a channel
|
183
|
+
# does not close the underlying logfile, but instead tells the rotater
|
184
|
+
# that you are done with this channel and that the logfile is free to
|
185
|
+
# rotate independent of it.
|
186
|
+
#
|
187
|
+
# You may have any number of channels open at any time, each on a
|
188
|
+
# different rotation schedule. Each may possibly be writing to different
|
189
|
+
# files in the rotation at any time, but this is all done automatically
|
190
|
+
# behind the scenes.
|
191
|
+
|
192
|
+
class Channel
|
193
|
+
|
194
|
+
def initialize(rotater_, opts_={}) # :nodoc:
|
195
|
+
@rotater = rotater_
|
196
|
+
@auto_rotate = opts_[:auto_rotate]
|
197
|
+
@io_handle = @rotater._do_open
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# Write a string to this channel.
|
202
|
+
|
203
|
+
def write(str_)
|
204
|
+
if @io_handle
|
205
|
+
@rotater._do_write(@io_handle, str_, @auto_rotate)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
# Close this channel, telling the rotater that this channel no longer
|
211
|
+
# needs to constrain the log rotation.
|
212
|
+
|
213
|
+
def close
|
214
|
+
if @io_handle
|
215
|
+
@rotater._do_close(@io_handle)
|
216
|
+
@io_handle = nil
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
# Manually tell the rotater that this channel is at a stopping point
|
222
|
+
# and that the log file may rotate at this time.
|
223
|
+
|
224
|
+
def check_rotate
|
225
|
+
if @io_handle
|
226
|
+
@io_handle = @rotater._do_check_rotate(@io_handle)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Sawmill entry stream processor interface
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
module Sawmill
|
38
|
+
|
39
|
+
module Util
|
40
|
+
|
41
|
+
|
42
|
+
# A simple queue that optionally provides an upper size limit.
|
43
|
+
|
44
|
+
class Queue
|
45
|
+
|
46
|
+
|
47
|
+
# Recognized options include:
|
48
|
+
#
|
49
|
+
# <tt>:limit</tt>::
|
50
|
+
# Size limit for the queue. If not specified, the queue can grow
|
51
|
+
# arbitrarily large.
|
52
|
+
# <tt>:drop_oldest</tt>::
|
53
|
+
# If set to true, then when an item is added to a full queue, the
|
54
|
+
# oldest item is dropped. If set to false or not specified, then
|
55
|
+
# the new item is not added.
|
56
|
+
|
57
|
+
def initialize(opts_={})
|
58
|
+
limit_ = opts_[:limit]
|
59
|
+
@buffer = limit_ ? ::Array.new(limit_) : []
|
60
|
+
@push_ptr = limit_ ? 0 : nil
|
61
|
+
@pop_ptr = nil
|
62
|
+
@drop_oldest = limit_ && opts_[:drop_oldest]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Attempt to push an item on the queue.
|
67
|
+
#
|
68
|
+
# If the queue is full, then the behavior is determined by the
|
69
|
+
# :drop_oldest setting provided to the constructor.
|
70
|
+
#
|
71
|
+
# Returns true if the object was pushed on the queue, or false if the
|
72
|
+
# queue was full.
|
73
|
+
|
74
|
+
def enqueue(object_)
|
75
|
+
result_ = true
|
76
|
+
if @push_ptr
|
77
|
+
if @pop_ptr == @push_ptr
|
78
|
+
if @drop_oldest
|
79
|
+
@pop_ptr += 1
|
80
|
+
@pop_ptr = 0 if @pop_ptr == @buffer.size
|
81
|
+
result_ = false
|
82
|
+
else
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
elsif @pop_ptr.nil?
|
86
|
+
@pop_ptr = @push_ptr
|
87
|
+
end
|
88
|
+
@buffer[@push_ptr] = object_
|
89
|
+
@push_ptr += 1
|
90
|
+
@push_ptr = 0 if @push_ptr == @buffer.size
|
91
|
+
else
|
92
|
+
@buffer.push(object_)
|
93
|
+
end
|
94
|
+
result_
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Return the oldest item in the queue, or nil if the queue is empty.
|
99
|
+
|
100
|
+
def dequeue
|
101
|
+
if @push_ptr
|
102
|
+
if @pop_ptr
|
103
|
+
object_ = @buffer[@pop_ptr]
|
104
|
+
@pop_ptr += 1
|
105
|
+
@pop_ptr = 0 if @pop_ptr == @buffer.size
|
106
|
+
@pop_ptr = nil if @pop_ptr == @push_ptr
|
107
|
+
object_
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
else
|
112
|
+
@buffer.shift
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Return the size of the queue, which is 0 if the queue is empty.
|
118
|
+
|
119
|
+
def size
|
120
|
+
if @push_ptr
|
121
|
+
if @pop_ptr
|
122
|
+
value_ = @push_ptr - @pop_ptr
|
123
|
+
value_ > 0 ? value_ : value_ + @buffer.size
|
124
|
+
else
|
125
|
+
0
|
126
|
+
end
|
127
|
+
else
|
128
|
+
@buffer.size
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|