file_series 0.2.0 → 0.3.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/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: