birling 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +20 -0
- data/README.md +79 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/lib/birling/formatter.rb +15 -0
- data/lib/birling/log.rb +34 -0
- data/lib/birling/logger.rb +257 -0
- data/lib/birling/support.rb +20 -0
- data/lib/birling.rb +13 -0
- data/test/helper.rb +75 -0
- data/test/test_birling.rb +51 -0
- data/test/test_birling_logger.rb +203 -0
- data/test/test_birling_support.rb +54 -0
- metadata +95 -0
data/.document
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Scott Tadman, The Working Group.
|
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.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# birling
|
2
|
+
|
3
|
+
A replacement for Logger that offers more robust handling of log rotation.
|
4
|
+
|
5
|
+
The built-in logger supports log rotation on a daily, weekly or monthly basis,
|
6
|
+
but not with more fine-grained control. Birling will allow rotation by hour,
|
7
|
+
by day, or by an arbitrary amount of time expressed in seconds.
|
8
|
+
|
9
|
+
Additionally, Birling will automatically remove old log files. These can be
|
10
|
+
pruned off by age, by retaining a minimum number of them, or a combination of
|
11
|
+
both.
|
12
|
+
|
13
|
+
## Example
|
14
|
+
|
15
|
+
The interface is very similar to the [built in logger](http://rubygems.org/gems/logger)
|
16
|
+
facility that ships with Ruby:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
logger = Birling::Logger.new('test.log')
|
20
|
+
|
21
|
+
logger.info("Application starting up.")
|
22
|
+
logger.debug("application_init()")
|
23
|
+
```
|
24
|
+
|
25
|
+
A short-hand method is available:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
logger = Birling.open('test.log')
|
29
|
+
```
|
30
|
+
|
31
|
+
Log rotation parameters are quite flexible. For example, to retain a maximum
|
32
|
+
of ten hourly logs:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
logger = Birling.open(
|
36
|
+
'test.log',
|
37
|
+
:period => :hourly,
|
38
|
+
:retain_count => 10
|
39
|
+
)
|
40
|
+
```
|
41
|
+
|
42
|
+
Alternatively the retention period can be expressed in terms of time where
|
43
|
+
log files that could have been created by this logger which are older than
|
44
|
+
that period will be removed:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
logger = Birling.open(
|
48
|
+
'test.log',
|
49
|
+
:period => :hourly,
|
50
|
+
:retain_period => 10 * 3600
|
51
|
+
)
|
52
|
+
```
|
53
|
+
|
54
|
+
The format of the resulting log-file can be adjusted by supplying a formatter.
|
55
|
+
Several arguments passed to the formatter's `call` method, so a `lambda`, a
|
56
|
+
`module` or an object instance could be used for this purpose.
|
57
|
+
|
58
|
+
Example:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
logger = Birling.open(
|
62
|
+
'test.log',
|
63
|
+
:formatter => lambda { |severity, time, program, message| "#{time}> #{message}\n" }
|
64
|
+
)
|
65
|
+
```
|
66
|
+
|
67
|
+
Note that the formatter is responsible for introducing any line-feeds into
|
68
|
+
the resulting output stream. This gives the formatter complete control over
|
69
|
+
what is written to the log.
|
70
|
+
|
71
|
+
## Limitations
|
72
|
+
|
73
|
+
The log rotation feature, for reasons that should be obvious, will not work
|
74
|
+
on loggers that are created with an existing file-handle. For example, when
|
75
|
+
using `STDOUT` the logger will not rotate.
|
76
|
+
|
77
|
+
## Copyright
|
78
|
+
|
79
|
+
Copyright (c) 2011-2012 Scott Tadman, The Working Group Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
begin
|
5
|
+
Bundler.setup(:default, :development)
|
6
|
+
rescue Bundler::BundlerError => e
|
7
|
+
$stderr.puts e.message
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
9
|
+
exit e.status_code
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rake'
|
13
|
+
require 'jeweler'
|
14
|
+
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "birling"
|
17
|
+
gem.homepage = "http://github.com/twg/birling"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.summary = %Q{Logger with simple log rotation system}
|
20
|
+
gem.description = %Q{Mostly drop-in replacement for Logger with a more robust log rotation facility}
|
21
|
+
gem.email = "github@tadman.ca"
|
22
|
+
gem.authors = [ "Scott Tadman" ]
|
23
|
+
end
|
24
|
+
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Birling::Formatter
|
2
|
+
# == Constants ============================================================
|
3
|
+
|
4
|
+
TIME_FORMAT_DEFAULT = '%Y-%m-%d %H:%M:%S'.freeze
|
5
|
+
|
6
|
+
# == Module Methods =======================================================
|
7
|
+
|
8
|
+
def self.call(severity, time, program, message)
|
9
|
+
if (program)
|
10
|
+
"[#{time.strftime(TIME_FORMAT_DEFAULT)}] <#{program}> #{message}"
|
11
|
+
else
|
12
|
+
"[#{time.strftime(TIME_FORMAT_DEFAULT)}] #{message}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/birling/log.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
class Birling::Log < File
|
2
|
+
# == Constants ============================================================
|
3
|
+
|
4
|
+
# == Class Methods ========================================================
|
5
|
+
|
6
|
+
def self.open(log, mode = nil)
|
7
|
+
case (log)
|
8
|
+
when IO
|
9
|
+
yield(log) if (block_given?)
|
10
|
+
|
11
|
+
log
|
12
|
+
when String
|
13
|
+
io = new(log, mode || 'a')
|
14
|
+
|
15
|
+
yield(io) if (block_given?)
|
16
|
+
|
17
|
+
io
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# == Instance Methods =====================================================
|
22
|
+
|
23
|
+
def size
|
24
|
+
self.stat.size
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_time
|
28
|
+
self.stat.ctime
|
29
|
+
end
|
30
|
+
|
31
|
+
def age(relative_to = nil)
|
32
|
+
(relative_to || Time.now) - self.stat.ctime
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
class Birling::Logger
|
2
|
+
# == Constants ============================================================
|
3
|
+
|
4
|
+
# These level constants are the same as the syslog system utility
|
5
|
+
SEVERITY = {
|
6
|
+
:emergency => EMERGENCY = 0,
|
7
|
+
:alert => ALERT = 1,
|
8
|
+
:critical => CRITICAL = 2,
|
9
|
+
:error => ERROR = 3,
|
10
|
+
:warning => WARNING = 4,
|
11
|
+
:notice => NOTICE = 5,
|
12
|
+
:info => INFO = 6,
|
13
|
+
:debug => DEBUG = 7,
|
14
|
+
:unknown => UNKNOWN = 999
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
DEFAULT_SEVERITY = UNKNOWN
|
18
|
+
|
19
|
+
SEVERITY_LABEL = SEVERITY.invert.freeze
|
20
|
+
|
21
|
+
PATH_TIME_DEFAULT = {
|
22
|
+
:hourly => '%Y%m%d%H'.freeze,
|
23
|
+
:daily => '%Y%m%d'.freeze,
|
24
|
+
:default => '%s'.freeze
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
# == Properties ===========================================================
|
28
|
+
|
29
|
+
attr_reader :severity
|
30
|
+
attr_accessor :formatter
|
31
|
+
attr_accessor :program
|
32
|
+
attr_accessor :time_source
|
33
|
+
attr_reader :path
|
34
|
+
attr_reader :path_format
|
35
|
+
attr_reader :path_time_format
|
36
|
+
attr_reader :current_path
|
37
|
+
attr_reader :retain_count
|
38
|
+
attr_reader :retain_period
|
39
|
+
attr_reader :period
|
40
|
+
attr_reader :rotation_time
|
41
|
+
|
42
|
+
# == Class Methods ========================================================
|
43
|
+
|
44
|
+
def self.severity(value)
|
45
|
+
case (value)
|
46
|
+
when Symbol
|
47
|
+
SEVERITY[value] or DEFAULT_SEVERITY
|
48
|
+
when String
|
49
|
+
SEVERITY[value.to_sym] or DEFAULT_SEVERITY
|
50
|
+
when Fixnum
|
51
|
+
SEVERITY_LABEL[value] and value or DEFAULT_SEVERITY
|
52
|
+
else
|
53
|
+
DEFAULT_SEVERITY
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# == Instance Methods =====================================================
|
58
|
+
|
59
|
+
def initialize(log, options = nil)
|
60
|
+
@period = (options and options[:period])
|
61
|
+
@severity = self.class.severity(options && options[:severity])
|
62
|
+
@retain_count = (options and options[:retain_count])
|
63
|
+
@retain_period = (options and options[:retain_period])
|
64
|
+
@formatter = (options and options[:formatter] or Birling::Formatter)
|
65
|
+
@program = (options and options[:program] or nil)
|
66
|
+
@time_source = (options and options[:time_source] or Time)
|
67
|
+
@path_format = (options and options[:path_format])
|
68
|
+
|
69
|
+
case (log)
|
70
|
+
when IO, StringIO
|
71
|
+
@log = log
|
72
|
+
when String
|
73
|
+
@path = log
|
74
|
+
end
|
75
|
+
|
76
|
+
if (@path and @period)
|
77
|
+
@rotation_time = self.next_rotation_time
|
78
|
+
|
79
|
+
@path_time_format = (PATH_TIME_DEFAULT[@period] or PATH_TIME_DEFAULT[:default])
|
80
|
+
|
81
|
+
@path_format ||=
|
82
|
+
@path.sub(/\.(\w+)$/) do |s|
|
83
|
+
'.' + @path_time_format + '.' + $1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
if (@path and !@log)
|
88
|
+
self.log_open!
|
89
|
+
end
|
90
|
+
|
91
|
+
yield(self) if (block_given?)
|
92
|
+
end
|
93
|
+
|
94
|
+
def severity=(value)
|
95
|
+
@severity = self.class.severity(value)
|
96
|
+
end
|
97
|
+
|
98
|
+
def can_rotate?
|
99
|
+
!!@path
|
100
|
+
end
|
101
|
+
|
102
|
+
def size
|
103
|
+
@log and @log.respond_to
|
104
|
+
end
|
105
|
+
|
106
|
+
def retain=(value)
|
107
|
+
@retain = value ? value.to_i : nil
|
108
|
+
|
109
|
+
if (@retain_period and @retain_period <= 0)
|
110
|
+
@retain_period = nil
|
111
|
+
end
|
112
|
+
|
113
|
+
@retain_period
|
114
|
+
end
|
115
|
+
|
116
|
+
def log(level, message = nil, program = nil)
|
117
|
+
return unless (@log)
|
118
|
+
|
119
|
+
level = self.class.severity(level)
|
120
|
+
program ||= @program
|
121
|
+
|
122
|
+
self.check_log_rotation!
|
123
|
+
|
124
|
+
@log.write(@formatter.call(level, @time_source.now, program, message))
|
125
|
+
end
|
126
|
+
alias_method :add, :log
|
127
|
+
|
128
|
+
def <<(message)
|
129
|
+
return unless (@log)
|
130
|
+
|
131
|
+
self.check_log_rotation!
|
132
|
+
|
133
|
+
@log.write(message)
|
134
|
+
end
|
135
|
+
|
136
|
+
SEVERITY.each do |name, level|
|
137
|
+
define_method(:"#{name}?") do
|
138
|
+
@severity >= level
|
139
|
+
end
|
140
|
+
|
141
|
+
define_method(name) do |message = nil, program = nil|
|
142
|
+
return unless (@log and @severity >= level)
|
143
|
+
|
144
|
+
program ||= @program
|
145
|
+
|
146
|
+
if (!message and block_given?)
|
147
|
+
message = yield
|
148
|
+
end
|
149
|
+
|
150
|
+
self.check_log_rotation!
|
151
|
+
|
152
|
+
@log.write(@formatter.call(level, @time_source.now, program, message))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def close
|
157
|
+
return unless (@log)
|
158
|
+
|
159
|
+
@log.close
|
160
|
+
@log = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def opened?
|
164
|
+
!!@log
|
165
|
+
end
|
166
|
+
|
167
|
+
def closed?
|
168
|
+
!@log
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_time
|
172
|
+
@log and @log.ctime
|
173
|
+
end
|
174
|
+
|
175
|
+
def size
|
176
|
+
@log and @log.size
|
177
|
+
end
|
178
|
+
|
179
|
+
def age(relative_to = nil)
|
180
|
+
(relative_to || @time_source.now) - @log.ctime
|
181
|
+
end
|
182
|
+
|
183
|
+
protected
|
184
|
+
def next_rotation_time
|
185
|
+
case (@period)
|
186
|
+
when Fixnum, Float
|
187
|
+
@time_source.now + @period
|
188
|
+
when :daily
|
189
|
+
Birling::Support.next_day(@time_source.now)
|
190
|
+
when :hourly
|
191
|
+
Birling::Support.next_hour(@time_source.now)
|
192
|
+
else
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def prune_logs!
|
198
|
+
return unless (@path and (@retain_period or @retain_count))
|
199
|
+
|
200
|
+
log_spec = @path.sub(/\.(\w+)$/, '*')
|
201
|
+
|
202
|
+
logs = (Dir.glob(log_spec) - [ @path ]).collect do |p|
|
203
|
+
stat = File.stat(p)
|
204
|
+
create_time = (stat and stat.ctime or @time_source.now)
|
205
|
+
|
206
|
+
[ p, create_time ]
|
207
|
+
end.sort_by do |r|
|
208
|
+
r[1] || @time_source.now
|
209
|
+
end
|
210
|
+
|
211
|
+
if (@retain_period)
|
212
|
+
logs.reject! do |r|
|
213
|
+
if (Time.now - r[1] > @retain_period)
|
214
|
+
FileUtils.rm_f(r[0])
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
if (@retain_count)
|
220
|
+
# The logs array is sorted from oldest to newest, so retaining the N
|
221
|
+
# newest entries entails stripping them off the end with pop.
|
222
|
+
|
223
|
+
logs.pop(@retain_count)
|
224
|
+
|
225
|
+
FileUtils.rm_f(logs.collect { |r| r[0] })
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def check_log_rotation!
|
230
|
+
return unless (@rotation_time)
|
231
|
+
|
232
|
+
if (@time_source.now >= @rotation_time)
|
233
|
+
self.log_open!
|
234
|
+
|
235
|
+
@rotation_time = self.next_rotation_time
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def log_open!
|
240
|
+
if (@path_format)
|
241
|
+
@current_path = @time_source.now.strftime(@path_format)
|
242
|
+
|
243
|
+
@log = File.open(@current_path, 'a')
|
244
|
+
|
245
|
+
if (File.exist?(@path) and File.symlink?(@path))
|
246
|
+
File.unlink(@path)
|
247
|
+
File.symlink(@path, @current_path)
|
248
|
+
end
|
249
|
+
|
250
|
+
self.prune_logs!
|
251
|
+
else
|
252
|
+
@current_path = @path
|
253
|
+
|
254
|
+
@log = File.open(@current_path, 'a')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Birling::Support
|
2
|
+
def next_day(time, time_source = nil)
|
3
|
+
(time_source || Time).local(
|
4
|
+
time.year,
|
5
|
+
time.month,
|
6
|
+
time.day,
|
7
|
+
23,
|
8
|
+
59,
|
9
|
+
59
|
10
|
+
) + 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def next_hour(time, time_source = nil)
|
14
|
+
seconds_left = time.to_i % 3600
|
15
|
+
|
16
|
+
time + (seconds_left > 0 ? seconds_left : 3600)
|
17
|
+
end
|
18
|
+
|
19
|
+
extend self
|
20
|
+
end
|
data/lib/birling.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class Birling
|
2
|
+
# == Submodules ===========================================================
|
3
|
+
|
4
|
+
autoload(:Formatter, 'birling/formatter')
|
5
|
+
autoload(:Logger, 'birling/logger')
|
6
|
+
autoload(:Support, 'birling/support')
|
7
|
+
|
8
|
+
# == Module Methods =======================================================
|
9
|
+
|
10
|
+
def self.open(path, options = nil)
|
11
|
+
Birling::Logger.new(path, options)
|
12
|
+
end
|
13
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
begin
|
5
|
+
Bundler.setup(:default, :development)
|
6
|
+
rescue Bundler::BundlerError => e
|
7
|
+
$stderr.puts e.message
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
9
|
+
exit e.status_code
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'fileutils'
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
17
|
+
|
18
|
+
require 'birling'
|
19
|
+
|
20
|
+
class Time::Warped < Time
|
21
|
+
def self.now
|
22
|
+
@now || super
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.now=(value)
|
26
|
+
@now = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Test::Unit::TestCase
|
31
|
+
def in_time_zone(zone)
|
32
|
+
tz = ENV['TZ']
|
33
|
+
|
34
|
+
ENV['tz'] = zone
|
35
|
+
|
36
|
+
yield if (block_given?)
|
37
|
+
|
38
|
+
ensure
|
39
|
+
ENV['tz'] = tz
|
40
|
+
end
|
41
|
+
|
42
|
+
def temp_path(name = nil)
|
43
|
+
name ||= begin
|
44
|
+
@temp_path_inc ||= 0
|
45
|
+
|
46
|
+
_name = '%05d.%05d.tmp' % [ @temp_path_inc, $$ ]
|
47
|
+
@temp_path_inc += 1
|
48
|
+
|
49
|
+
_name
|
50
|
+
end
|
51
|
+
|
52
|
+
case (name)
|
53
|
+
when Symbol
|
54
|
+
name = "#{name}.log"
|
55
|
+
end
|
56
|
+
|
57
|
+
@temp_path ||= File.expand_path('../tmp', File.dirname(__FILE__))
|
58
|
+
|
59
|
+
full_path = File.expand_path(name, @temp_path)
|
60
|
+
|
61
|
+
FileUtils::mkdir_p(File.dirname(full_path))
|
62
|
+
|
63
|
+
if (block_given?)
|
64
|
+
begin
|
65
|
+
yield(full_path)
|
66
|
+
ensure
|
67
|
+
remove_spec = File.expand_path('*', @temp_path)
|
68
|
+
|
69
|
+
FileUtils.rm_f(Dir.glob(remove_spec))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
full_path
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path('helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class TestBirling < Test::Unit::TestCase
|
4
|
+
def test_module
|
5
|
+
assert Birling
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_open
|
9
|
+
_path = nil
|
10
|
+
|
11
|
+
temp_path do |path|
|
12
|
+
_path = path
|
13
|
+
log = Birling.open(path)
|
14
|
+
|
15
|
+
assert_equal Birling::Logger, log.class
|
16
|
+
|
17
|
+
assert log
|
18
|
+
assert File.exist?(path)
|
19
|
+
|
20
|
+
assert log.opened?
|
21
|
+
assert !log.closed?
|
22
|
+
assert_equal 0, log.size
|
23
|
+
|
24
|
+
assert log.debug?
|
25
|
+
|
26
|
+
log.debug("Test")
|
27
|
+
|
28
|
+
assert log.size > 0
|
29
|
+
|
30
|
+
log.close
|
31
|
+
|
32
|
+
assert !log.opened?
|
33
|
+
assert log.closed?
|
34
|
+
end
|
35
|
+
|
36
|
+
assert !File.exist?(_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_time_warped
|
40
|
+
_now = Time.now
|
41
|
+
|
42
|
+
Time::Warped.now = _now
|
43
|
+
|
44
|
+
assert_not_equal Time.now, Time.now
|
45
|
+
assert_equal _now, Time::Warped.now
|
46
|
+
|
47
|
+
Time::Warped.now = nil
|
48
|
+
|
49
|
+
assert_not_equal _now, Time::Warped.now
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require File.expand_path('helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class TestBirlingLogger < Test::Unit::TestCase
|
4
|
+
def test_defaults
|
5
|
+
temp_path do |path|
|
6
|
+
log = Birling::Logger.new(path)
|
7
|
+
|
8
|
+
assert log
|
9
|
+
|
10
|
+
assert log.opened?
|
11
|
+
assert !log.closed?
|
12
|
+
assert_equal 0, log.size
|
13
|
+
assert Time.now >= log.create_time
|
14
|
+
|
15
|
+
assert_equal Birling::Formatter, log.formatter
|
16
|
+
assert_equal Time, log.time_source
|
17
|
+
assert_equal nil, log.period
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_with_handle
|
22
|
+
s = StringIO.new
|
23
|
+
|
24
|
+
log = Birling::Logger.new(s)
|
25
|
+
|
26
|
+
assert log.opened?
|
27
|
+
|
28
|
+
assert_equal 0, log.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_formatter
|
32
|
+
formatter_called = false
|
33
|
+
formatter = lambda do |severity, time, program, message|
|
34
|
+
formatter_called = true
|
35
|
+
message
|
36
|
+
end
|
37
|
+
|
38
|
+
output = StringIO.new
|
39
|
+
log = Birling::Logger.new(output, :formatter => formatter)
|
40
|
+
|
41
|
+
log.debug("Test")
|
42
|
+
|
43
|
+
assert_equal true, formatter_called
|
44
|
+
|
45
|
+
output.rewind
|
46
|
+
assert_equal "Test", output.read
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_default_level
|
50
|
+
temp_path do |path|
|
51
|
+
log = Birling::Logger.new(path)
|
52
|
+
|
53
|
+
assert log
|
54
|
+
|
55
|
+
assert log.opened?
|
56
|
+
assert !log.closed?
|
57
|
+
assert_equal 0, log.size
|
58
|
+
|
59
|
+
assert log.debug?
|
60
|
+
|
61
|
+
log.debug("Test")
|
62
|
+
|
63
|
+
current_size = log.size
|
64
|
+
assert current_size > 0
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_direct_write
|
69
|
+
output = StringIO.new
|
70
|
+
|
71
|
+
log = Birling::Logger.new(output)
|
72
|
+
|
73
|
+
log << "TEST"
|
74
|
+
|
75
|
+
output.rewind
|
76
|
+
assert_equal "TEST", output.read
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_level_filter
|
80
|
+
output = StringIO.new
|
81
|
+
|
82
|
+
log = Birling::Logger.new(
|
83
|
+
output,
|
84
|
+
:formatter => lambda { |s, t, p, m| "#{m}\n" },
|
85
|
+
:severity => :info
|
86
|
+
)
|
87
|
+
|
88
|
+
log.debug("DEBUG")
|
89
|
+
log.info("INFO")
|
90
|
+
|
91
|
+
output.rewind
|
92
|
+
assert_equal "INFO\n", output.read
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_reopen
|
96
|
+
temp_path do |path|
|
97
|
+
log = Birling::Logger.new(path)
|
98
|
+
|
99
|
+
assert log.debug?
|
100
|
+
|
101
|
+
log.debug("Test")
|
102
|
+
|
103
|
+
current_size = log.size
|
104
|
+
assert current_size > 0
|
105
|
+
|
106
|
+
create_time = log.create_time
|
107
|
+
assert create_time <= Time.now
|
108
|
+
|
109
|
+
log.close
|
110
|
+
|
111
|
+
log = Birling::Logger.new(path)
|
112
|
+
|
113
|
+
assert_equal current_size, log.size
|
114
|
+
assert_equal create_time, log.create_time
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_time_source
|
119
|
+
temp_path do |path|
|
120
|
+
frozen_time = Time.now
|
121
|
+
Time::Warped.now = frozen_time
|
122
|
+
|
123
|
+
logger = Birling::Logger.new(path, :time_source => Time::Warped)
|
124
|
+
|
125
|
+
assert_equal frozen_time, logger.time_source.now
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_cycling
|
130
|
+
temp_path(:cycle) do |path|
|
131
|
+
start = Time.now
|
132
|
+
Time::Warped.now = start
|
133
|
+
logger = Birling::Logger.new(path, :period => 1, :time_source => Time::Warped)
|
134
|
+
|
135
|
+
assert_equal 1, logger.period
|
136
|
+
|
137
|
+
current_path = logger.current_path
|
138
|
+
assert_equal '%s', logger.path_time_format
|
139
|
+
|
140
|
+
logger.debug("Test")
|
141
|
+
|
142
|
+
Time::Warped.now += 1
|
143
|
+
|
144
|
+
logger.debug("Test")
|
145
|
+
|
146
|
+
assert_not_equal current_path, logger.current_path
|
147
|
+
|
148
|
+
current_path = logger.current_path
|
149
|
+
|
150
|
+
Time::Warped.now += 1
|
151
|
+
|
152
|
+
logger.debug("Test")
|
153
|
+
|
154
|
+
assert_not_equal current_path, logger.current_path
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_retain_count
|
159
|
+
temp_path(:cycle) do |path|
|
160
|
+
start = Time.now
|
161
|
+
Time::Warped.now = start
|
162
|
+
|
163
|
+
retain_count = 10
|
164
|
+
|
165
|
+
logger = Birling::Logger.new(
|
166
|
+
path,
|
167
|
+
:period => 1,
|
168
|
+
:time_source => Time::Warped,
|
169
|
+
:retain_count => retain_count
|
170
|
+
)
|
171
|
+
|
172
|
+
(retain_count + 5).times do |n|
|
173
|
+
logger.debug("Test")
|
174
|
+
Time::Warped.now += 1
|
175
|
+
logger.debug("Test")
|
176
|
+
end
|
177
|
+
|
178
|
+
assert_equal retain_count, Dir.glob(logger.path_format % '*').length
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def test_retain_period
|
183
|
+
temp_path(:cycle) do |path|
|
184
|
+
retain_period = 3
|
185
|
+
|
186
|
+
logger = Birling::Logger.new(
|
187
|
+
path,
|
188
|
+
:period => 1,
|
189
|
+
:retain_period => retain_period
|
190
|
+
)
|
191
|
+
|
192
|
+
finish = Time.now + retain_period + 5
|
193
|
+
|
194
|
+
while (Time.now < finish)
|
195
|
+
logger.debug("Test")
|
196
|
+
Time::Warped.now += 1
|
197
|
+
logger.debug("Test")
|
198
|
+
end
|
199
|
+
|
200
|
+
assert_equal retain_period + 1, Dir.glob(logger.path_format % '*').length
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path('helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class TestBirlingSupport < Test::Unit::TestCase
|
4
|
+
def test_next_day_on_dst_flip
|
5
|
+
in_time_zone('EST5EDT') do
|
6
|
+
time = Time.new(2012, 11, 4)
|
7
|
+
|
8
|
+
assert_equal time.day, (time + 86400).day
|
9
|
+
|
10
|
+
next_day = Birling::Support.next_day(time)
|
11
|
+
|
12
|
+
assert_equal 2012, next_day.year
|
13
|
+
assert_equal 11, next_day.month
|
14
|
+
assert_equal 5, next_day.day
|
15
|
+
assert_equal 0, next_day.hour
|
16
|
+
assert_equal 0, next_day.min
|
17
|
+
assert_equal 0, next_day.sec
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_hour_day_on_dst_flip
|
22
|
+
in_time_zone('EST5EDT') do
|
23
|
+
time = Time.new(2012, 11, 4, 0, 59, 59) + 1
|
24
|
+
|
25
|
+
assert_equal time.hour, (time + 3600).hour
|
26
|
+
|
27
|
+
next_hour = Birling::Support.next_hour(time)
|
28
|
+
|
29
|
+
assert_equal 2012, next_hour.year
|
30
|
+
assert_equal 11, next_hour.month
|
31
|
+
assert_equal 4, next_hour.day
|
32
|
+
assert_equal 1, next_hour.hour
|
33
|
+
assert_equal 0, next_hour.min
|
34
|
+
assert_equal 0, next_hour.sec
|
35
|
+
|
36
|
+
assert_equal 3600, next_hour - time
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_next_day_at_year_end
|
41
|
+
in_time_zone('EST5EDT') do
|
42
|
+
time = Time.new(2012, 12, 31)
|
43
|
+
|
44
|
+
next_day = Birling::Support.next_day(time)
|
45
|
+
|
46
|
+
assert_equal 2013, next_day.year
|
47
|
+
assert_equal 1, next_day.month
|
48
|
+
assert_equal 1, next_day.day
|
49
|
+
assert_equal 0, next_day.hour
|
50
|
+
assert_equal 0, next_day.min
|
51
|
+
assert_equal 0, next_day.sec
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: birling
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Scott Tadman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: jeweler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Mostly drop-in replacement for Logger with a more robust log rotation
|
47
|
+
facility
|
48
|
+
email: github@tadman.ca
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE.txt
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- lib/birling.rb
|
62
|
+
- lib/birling/formatter.rb
|
63
|
+
- lib/birling/log.rb
|
64
|
+
- lib/birling/logger.rb
|
65
|
+
- lib/birling/support.rb
|
66
|
+
- test/helper.rb
|
67
|
+
- test/test_birling.rb
|
68
|
+
- test/test_birling_logger.rb
|
69
|
+
- test/test_birling_support.rb
|
70
|
+
homepage: http://github.com/twg/birling
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.24
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Logger with simple log rotation system
|
95
|
+
test_files: []
|