file_series 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/file_series.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "file_series"
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Alex Dean"]
12
- s.date = "2012-07-19"
12
+ s.date = "2012-10-26"
13
13
  s.description = "Automatically start writing to a new file every X seconds without any locking or file moving/renaming."
14
14
  s.email = "alex@crackpot.org"
15
15
  s.extra_rdoc_files = [
data/lib/file_series.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  # Writes to this logger will be directed to new files at a configurable frequency.
2
4
  #
3
5
  # => logger = FileSeries.new('.', :prefix=>'test', :rotate_every=>60)
@@ -17,84 +19,84 @@
17
19
  #
18
20
 
19
21
  class FileSeries
20
- DEFAULT_DIR = '.'
21
- DEFAULT_PREFIX = 'log'
22
- DEFAULT_FREQ = 60
23
- DEFAULT_SEPARATOR = "\n"
22
+ DEFAULT_DIR = '.'
23
+ DEFAULT_PREFIX = 'log'
24
+ DEFAULT_FREQ = 60
25
+ DEFAULT_SEPARATOR = "\n"
24
26
 
25
- attr_accessor :separator
26
- attr_accessor :dir
27
- attr_accessor :file
28
- attr_accessor :current_ts
27
+ attr_accessor :separator
28
+ attr_accessor :dir
29
+ attr_accessor :file
30
+ attr_accessor :current_ts
29
31
 
30
- def initialize(options={})
31
- @dir = options[:dir] || DEFAULT_DIR
32
- @file = nil
33
- @current_ts = nil
34
- @filename_prefix = options[:prefix] || DEFAULT_PREFIX
35
- @rotate_freq = options[:rotate_every] || DEFAULT_FREQ #seconds
36
- @binary_mode = options[:binary]
37
- @separator = options[:separator] || DEFAULT_SEPARATOR
38
- end
32
+ def initialize(options={})
33
+ @dir = options[:dir] || DEFAULT_DIR
34
+ @file = nil
35
+ @current_ts = nil
36
+ @filename_prefix = options[:prefix] || DEFAULT_PREFIX
37
+ @rotate_freq = options[:rotate_every] || DEFAULT_FREQ #seconds
38
+ @binary_mode = options[:binary]
39
+ @separator = options[:separator] || DEFAULT_SEPARATOR
40
+ end
39
41
 
40
- # write something to the current log file.
41
- def write(message)
42
- log_file.write(message.to_s + @separator)
43
- end
42
+ # write something to the current log file.
43
+ def write(message)
44
+ log_file.write(message.to_s + @separator)
45
+ end
44
46
 
45
- # return a File object for the current log file.
46
- def log_file
47
- ts = this_period
47
+ # return a File object for the current log file.
48
+ def log_file
49
+ ts = this_period
48
50
 
49
- # if we're in a new time period, start writing to new file.
50
- if (! file) || (ts != current_ts)
51
- rotate(ts)
52
- end
51
+ # if we're in a new time period, start writing to new file.
52
+ if (! file) || (ts != current_ts)
53
+ rotate(ts)
54
+ end
53
55
 
54
- file
55
- end
56
+ file
57
+ end
56
58
 
57
- # compute the current time period.
58
- def this_period
59
- t = Time.now.to_i
60
- t - (t % @rotate_freq)
61
- end
59
+ # compute the current time period.
60
+ def this_period
61
+ t = Time.now.to_i
62
+ t - (t % @rotate_freq)
63
+ end
62
64
 
63
- # close current file handle and open a new one for a new logging period.
64
- # ts defaults to the current time period.
65
- def rotate(ts=nil)
66
- ts ||= this_period
67
- @file.close if @file
68
- @file = File.open(filename(ts), "a#{'b' if @binary_mode}")
69
- @current_ts = ts
70
- end
65
+ # close current file handle and open a new one for a new logging period.
66
+ # ts defaults to the current time period.
67
+ def rotate(ts=nil)
68
+ ts ||= this_period
69
+ @file.close if @file
70
+ @file = File.open(filename(ts), "a#{'b' if @binary_mode}")
71
+ @current_ts = ts
72
+ end
71
73
 
72
- # return a string filename for the logfile for the supplied timestamp.
73
- # defaults to current time period.
74
- def filename(ts=nil)
75
- ts ||= this_period
76
- File.join(@dir, "#{@filename_prefix}-#{ts}-#{@rotate_freq}.log")
77
- end
74
+ # return a string filename for the logfile for the supplied timestamp.
75
+ # defaults to current time period.
76
+ def filename(ts=nil)
77
+ ts ||= this_period
78
+ File.join(@dir, "#{@filename_prefix}-#{Time.at(ts).utc.strftime('%Y%m%d-%H%M%SZ')}-#{@rotate_freq}.log")
79
+ end
78
80
 
79
- # get all files which match our pattern which are not current.
80
- # (safe for consumption. no longer being written to.)
81
- def complete_files
82
- current_file = filename
81
+ # get all files which match our pattern which are not current.
82
+ # (safe for consumption. no longer being written to.)
83
+ def complete_files
84
+ current_file = filename
83
85
 
84
- Dir.glob(
85
- File.join(@dir, "#{@filename_prefix}-*-#{@rotate_freq}.log")
86
- ).select do |name|
87
- name != current_file
88
- end
89
- end
86
+ Dir.glob(
87
+ File.join(@dir, "#{@filename_prefix}-*-#{@rotate_freq}.log")
88
+ ).select do |name|
89
+ name != current_file
90
+ end
91
+ end
90
92
 
91
- # enumerate over all the writes in a series, across all files.
92
- def each
93
- complete_files.sort.each do |file|
94
- File.open(file,"r#{'b' if @binary_mode}").each_line(@separator) do |raw|
95
- yield raw
96
- end
97
- end
98
- end
93
+ # enumerate over all the writes in a series, across all files.
94
+ def each
95
+ complete_files.sort.each do |file|
96
+ File.open(file,"r#{'b' if @binary_mode}").each_line(@separator) do |raw|
97
+ yield raw
98
+ end
99
+ end
100
+ end
99
101
 
100
102
  end
@@ -1,181 +1,182 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'time'
2
3
  require 'timecop'
3
4
 
4
5
  def test_dir
5
- '/tmp/file_series_tests'
6
+ '/tmp/file_series_tests'
6
7
  end
7
8
 
8
9
  describe "FileSeries" do
9
10
 
10
- before(:all) do
11
- `mkdir -p #{test_dir}`
12
- end
13
- after(:all) do
14
- `rm -Rf #{test_dir}`
15
- end
16
- before(:each) do
17
- `rm -f #{test_dir}/*`
18
- end
19
-
20
- it "should write to a new file if we are in a new time period" do
21
-
22
- Timecop.freeze(Time.at(100))
23
- fs = FileSeries.new(:dir=>test_dir, :rotate_every => 10)
24
- fs.write('foo')
25
-
26
- name = File.join(test_dir,'log-100-10.log')
27
- File.exist?(name).should == true
28
- fs.file.flush
29
- IO.read(name).should == "foo\n"
30
-
31
- Timecop.freeze(Time.at(115))
32
- fs.write('foo again')
33
-
34
- name = File.join(test_dir,'log-110-10.log')
35
- File.exist?(name).should == true
36
- fs.file.flush
37
- IO.read(name).should == "foo again\n"
38
-
39
- end
40
-
41
- it "should support binary file writing" do
42
- # TODO: need to come up with some sequence which can't be written in non-binary mode.
43
- # Trying to write a Marshal'd version of
44
- # https://github.com/tedconf/videometrics/blob/83ad528b013ce591ac2500d7317e0904270356f9/spec/controllers/imports_controller_spec.rb#L92
45
- # in non-binary mode fails with
46
- # Failure/Error: post :record_event, :event => @event
11
+ before(:all) do
12
+ `mkdir -p #{test_dir}`
13
+ end
14
+ after(:all) do
15
+ `rm -Rf #{test_dir}`
16
+ end
17
+ before(:each) do
18
+ `rm -f #{test_dir}/*`
19
+ end
20
+
21
+ it "should write to a new file if we are in a new time period" do
22
+
23
+ Timecop.freeze(Time.parse('1970-01-01 00:01:00Z'))
24
+ fs = FileSeries.new(:dir=>test_dir, :rotate_every => 10)
25
+ fs.write('foo')
26
+
27
+ name = File.join(test_dir,'log-19700101-000100Z-10.log')
28
+ File.exist?(name).should eq true
29
+ fs.file.flush
30
+ IO.read(name).should eq "foo\n"
31
+
32
+ Timecop.freeze(Time.parse('1970-01-01 00:01:15Z'))
33
+ fs.write('foo again')
34
+
35
+ name = File.join(test_dir,'log-19700101-000110Z-10.log')
36
+ File.exist?(name).should eq true
37
+ fs.file.flush
38
+ IO.read(name).should == "foo again\n"
39
+
40
+ end
41
+
42
+ it "should support binary file writing" do
43
+ # TODO: need to come up with some sequence which can't be written in non-binary mode.
44
+ # Trying to write a Marshal'd version of
45
+ # https://github.com/tedconf/videometrics/blob/83ad528b013ce591ac2500d7317e0904270356f9/spec/controllers/imports_controller_spec.rb#L92
46
+ # in non-binary mode fails with
47
+ # Failure/Error: post :record_event, :event => @event
47
48
  # Encoding::UndefinedConversionError:
48
49
  # "\x87" from ASCII-8BIT to UTF-8
49
50
  # Writing in binary mode works fine. How to reproduce that more simply for a test?
50
- end
51
-
52
- describe "#write" do
53
- it "should call log_file.write with message and separator" do
54
- fs = FileSeries.new(:separator=>'...')
55
-
56
- fs.should_receive(:log_file) {
57
- d = double('log_file')
58
- d.should_receive(:write).with("foo...")
59
- d
60
- }
61
-
62
- fs.write('foo')
63
- end
64
- end
65
-
66
- describe "#log_file" do
67
- it "should call rotate if no file is open" do
68
- fs = FileSeries.new
69
- fs.should_receive(:rotate)
70
- fs.log_file
71
- end
72
- end
73
-
74
- describe "#this_period" do
75
- it "should floor timestamp to the beginning of the current period" do
76
- fs = FileSeries.new( :rotate_every=>20 )
77
- now = Time.now.to_i
78
-
79
- fs.this_period.should == (now - (now%20))
80
- end
81
- end
82
-
83
- describe "#filename" do
84
- it "should accept a timestamp argument" do
85
- fs = FileSeries.new( :dir=>'/tmp', :prefix=>'test', :rotate_every=>60)
86
- fs.filename(1234).should == "/tmp/test-1234-60.log"
87
- end
88
-
89
- it "should use this_period when no timestamp is supplied" do
90
- fs = FileSeries.new( :dir=>'/tmp', :prefix=>'test', :rotate_every=>3600)
91
- fs.should_receive(:this_period) {4321}
92
- fs.filename.should == "/tmp/test-4321-3600.log"
93
- end
94
- end
95
-
96
- describe "#complete_files" do
97
- it "should find files in our series which are not in use" do
98
- list = [
99
- '/tmp/prefix-100-10.log',
100
- '/tmp/prefix-110-10.log',
101
- '/tmp/prefix-120-10.log',
102
- '/tmp/prefix-130-10.log',
103
- ]
104
-
105
- Dir.should_receive(:glob).with('/tmp/prefix-*-10.log') {list}
106
-
107
- Timecop.freeze(Time.at(136)) do
108
- fs = FileSeries.new(:dir=>'/tmp', :prefix=>'prefix', :rotate_every=>10)
109
- fs.complete_files.should == (list - ['/tmp/prefix-130-10.log'])
110
- end
111
- end
112
- end
113
-
114
- describe "#each" do
115
- it "should enumerate all lines in all files in a series" do
116
- fs = FileSeries.new(:dir=>test_dir, :rotate_every=>60, :prefix=>'events')
117
-
118
- # write 3 files with consecutive integers.
119
- Timecop.freeze(100) do
120
- (0..9).each do |i|
121
- fs.write i
122
- end
123
- end
124
- Timecop.freeze(160) do
125
- (10..19).each do |i|
126
- fs.write i
127
- end
128
- end
129
- Timecop.freeze(220) do
130
- (20..29).each do |i|
131
- fs.write i
132
- end
133
- end
134
- fs.file.flush
135
-
136
- out = []
137
- fs.each do |line|
138
- out << line
139
- end
140
-
141
- out.should == (0..29).to_a.map {|i| i.to_s+"\n" }
142
- end
143
-
144
- it "should enumerate all entries in a binary file series" do
145
- fs = FileSeries.new(
146
- :binary=>true,
147
- :separator=>'!~!~!',
148
- :dir=>test_dir,
149
- :prefix=>'bin',
150
- :rotate_every=>60
151
- )
152
-
153
- Timecop.freeze(100) do
154
- (0..9).each do |i|
155
- fs.write Marshal.dump(i)
156
- end
157
- end
158
- Timecop.freeze(160) do
159
- (10..19).each do |i|
160
- fs.write Marshal.dump(i)
161
- end
162
- end
163
- Timecop.freeze(220) do
164
- (20..29).each do |i|
165
- fs.write Marshal.dump(i)
166
- end
167
- end
168
- fs.file.flush
169
-
170
- out = []
171
- fs.each do |line|
172
- out << Marshal.load(line)
173
- end
174
-
175
- # note that we don't get the separator, and they're Fixnum not String
176
- out.should == (0..29).to_a.map {|i| i }
177
-
178
- end
179
- end
51
+ end
52
+
53
+ describe "#write" do
54
+ it "should call log_file.write with message and separator" do
55
+ fs = FileSeries.new(:separator=>'...')
56
+
57
+ fs.should_receive(:log_file) {
58
+ d = double('log_file')
59
+ d.should_receive(:write).with("foo...")
60
+ d
61
+ }
62
+
63
+ fs.write('foo')
64
+ end
65
+ end
66
+
67
+ describe "#log_file" do
68
+ it "should call rotate if no file is open" do
69
+ fs = FileSeries.new
70
+ fs.should_receive(:rotate)
71
+ fs.log_file
72
+ end
73
+ end
74
+
75
+ describe "#this_period" do
76
+ it "should floor timestamp to the beginning of the current period" do
77
+ fs = FileSeries.new( :rotate_every=>20 )
78
+ now = Time.now.to_i
79
+
80
+ fs.this_period.should == (now - (now%20))
81
+ end
82
+ end
83
+
84
+ describe "#filename" do
85
+ it "should accept a timestamp argument" do
86
+ fs = FileSeries.new( :dir=>'/tmp', :prefix=>'test', :rotate_every=>60)
87
+ fs.filename(Time.parse('1970-01-01 00:20:34Z').to_i).should eq "/tmp/test-19700101-002034Z-60.log"
88
+ end
89
+
90
+ it "should use this_period when no timestamp is supplied" do
91
+ fs = FileSeries.new( :dir=>'/tmp', :prefix=>'test', :rotate_every=>3600)
92
+ fs.should_receive(:this_period) { Time.parse('1970-01-01 00:20:00Z').to_i }
93
+ fs.filename.should == "/tmp/test-19700101-002000Z-3600.log"
94
+ end
95
+ end
96
+
97
+ describe "#complete_files" do
98
+ it "should find files in our series which are not in use" do
99
+ list = [
100
+ '/tmp/prefix-19700101-000200Z-60.log',
101
+ '/tmp/prefix-19700101-000300Z-60.log',
102
+ '/tmp/prefix-19700101-000400Z-60.log',
103
+ '/tmp/prefix-19700101-000500Z-60.log',
104
+ ]
105
+
106
+ Dir.should_receive(:glob).with('/tmp/prefix-*-60.log') {list}
107
+
108
+ Timecop.freeze(Time.parse('1970-01-01 00:05:05Z')) do
109
+ fs = FileSeries.new(:dir=>'/tmp', :prefix=>'prefix', :rotate_every=>60)
110
+ fs.complete_files.should == (list - ['/tmp/prefix-19700101-000500Z-60.log'])
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "#each" do
116
+ it "should enumerate all lines in all files in a series" do
117
+ fs = FileSeries.new(:dir=>test_dir, :rotate_every=>60, :prefix=>'events')
118
+
119
+ # write 3 files with consecutive integers.
120
+ Timecop.freeze(Time.parse('1970-01-01 01:00:00')) do
121
+ (0..9).each do |i|
122
+ fs.write i
123
+ end
124
+ end
125
+ Timecop.freeze(Time.parse('1970-01-01 01:01:00')) do
126
+ (10..19).each do |i|
127
+ fs.write i
128
+ end
129
+ end
130
+ Timecop.freeze(Time.parse('1970-01-01 01:02:00')) do
131
+ (20..29).each do |i|
132
+ fs.write i
133
+ end
134
+ end
135
+ fs.file.flush
136
+
137
+ out = []
138
+ fs.each do |line|
139
+ out << line
140
+ end
141
+
142
+ out.should == (0..29).to_a.map {|i| i.to_s+"\n" }
143
+ end
144
+
145
+ it "should enumerate all entries in a binary file series" do
146
+ fs = FileSeries.new(
147
+ :binary=>true,
148
+ :separator=>'!~!~!',
149
+ :dir=>test_dir,
150
+ :prefix=>'bin',
151
+ :rotate_every=>60
152
+ )
153
+
154
+ Timecop.freeze(Time.parse('1970-01-01 01:00:00')) do
155
+ (0..9).each do |i|
156
+ fs.write Marshal.dump(i)
157
+ end
158
+ end
159
+ Timecop.freeze(Time.parse('1970-01-01 01:01:00')) do
160
+ (10..19).each do |i|
161
+ fs.write Marshal.dump(i)
162
+ end
163
+ end
164
+ Timecop.freeze(Time.parse('1970-01-01 01:02:00')) do
165
+ (20..29).each do |i|
166
+ fs.write Marshal.dump(i)
167
+ end
168
+ end
169
+ fs.file.flush
170
+
171
+ out = []
172
+ fs.each do |line|
173
+ out << Marshal.load(line)
174
+ end
175
+
176
+ # note that we don't get the separator, and they're Fixnum not String
177
+ out.should == (0..29).to_a.map {|i| i }
178
+
179
+ end
180
+ end
180
181
 
181
182
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file_series
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-19 00:00:00.000000000 Z
12
+ date: 2012-10-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: autotest-standalone
@@ -176,7 +176,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
176
  version: '0'
177
177
  segments:
178
178
  - 0
179
- hash: 2322774718501649979
179
+ hash: 2396804050019200525
180
180
  required_rubygems_version: !ruby/object:Gem::Requirement
181
181
  none: false
182
182
  requirements: