logstash-output-googlecloudstorage 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-output-googlecloudstorage'
3
+ s.version = '0.1.0'
4
+ s.licenses = ['Apache-2.0']
5
+ s.summary = "plugin to upload log events to Google Cloud Storage (GCS)"
6
+ s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
7
+ s.authors = ["Shailesh"]
8
+ s.email = 'shailesh17mar@gmail.com'
9
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
10
+ s.require_paths = ["lib"]
11
+ s.platform = Gem::Platform::JAVA if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
12
+
13
+ # Files
14
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION", "docs/**/*"]
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
+ s.add_development_dependency 'logstash-devutils'
25
+
26
+ s.add_runtime_dependency 'stud'
27
+ s.add_runtime_dependency 'google-api-client', '~> 0.8.7' # version 0.9.x works only with ruby 2.x
28
+ s.add_runtime_dependency 'logstash-codec-plain'
29
+ s.add_runtime_dependency 'mime-types', '~> 2' # last version compatible with ruby 2.x
30
+ s.add_runtime_dependency 'concurrent-ruby', '1.0.5'
31
+ end
32
+
@@ -0,0 +1,129 @@
1
+ # encoding: utf-8
2
+ require 'logstash/outputs/gcs/log_rotate'
3
+ require 'logstash/outputs/gcs/path_factory'
4
+ require 'logstash/outputs/gcs/temp_log_file'
5
+
6
+ describe LogStash::Outputs::Gcs::LogRotate do
7
+ let(:tempdir){ Stud::Temporary.directory }
8
+ let(:path_factory) do
9
+ LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
10
+ builder.set_directory tempdir
11
+ builder.set_prefix 'prefix'
12
+ builder.set_include_host true
13
+ builder.set_date_pattern ''
14
+ builder.set_include_part true
15
+ builder.set_include_uuid true
16
+ builder.set_is_gzipped true
17
+ end
18
+ end
19
+ let(:open_file_1) { double('open-temp-1', :size => 5, :path => 'one', :close! => true, :time_since_sync => 10, :fsync => true)}
20
+ let(:open_file_2) { double('open-temp-2', :size => 5, :path => 'two', :close! => true, :time_since_sync => 60, :fsync => true)}
21
+
22
+ describe '#initialize' do
23
+ it 'opens the first file' do
24
+ expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
25
+
26
+ LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30)
27
+ end
28
+ end
29
+
30
+ describe '#writeln' do
31
+ subject { LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30) }
32
+
33
+ it 'does not rotate if size is small and path is the same' do
34
+ expect(path_factory).to receive(:should_rotate?).and_return(false)
35
+ # once for init
36
+ expect(path_factory).to receive(:rotate_path!).once
37
+
38
+ subject.writeln('foo')
39
+ end
40
+
41
+ it 'rotates the file if the size is too big' do
42
+ # once for init, once for writeln
43
+ expect(path_factory).to receive(:rotate_path!).twice
44
+
45
+ subject.writeln('this line is longer than ten characters' * 1000)
46
+ subject.writeln('flush')
47
+ end
48
+
49
+ it 'rotates the file if the path changed' do
50
+ expect(path_factory).to receive(:should_rotate?).and_return(true)
51
+ # once for init, once for writeln
52
+ expect(path_factory).to receive(:rotate_path!).twice
53
+
54
+ subject.writeln('foo')
55
+ end
56
+
57
+ it 'writes the message' do
58
+ expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
59
+ expect(open_file_1).to receive(:write).with('foo', "\n")
60
+
61
+ subject.writeln('foo')
62
+ end
63
+
64
+ it 'does not write nil messages' do
65
+ expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
66
+ expect(open_file_1).not_to receive(:write)
67
+
68
+ subject.writeln(nil)
69
+ end
70
+
71
+ it 'does not fsync if delta less than limit' do
72
+ expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1)
73
+ expect(open_file_1).not_to receive(:fsync)
74
+
75
+ subject.writeln(nil)
76
+ end
77
+
78
+ it 'fsyncs if delta greater than limit' do
79
+ expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_2)
80
+ expect(open_file_2).to receive(:fsync)
81
+
82
+ subject.writeln(nil)
83
+ end
84
+ end
85
+
86
+ describe '#rotate_log!' do
87
+ subject { LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30) }
88
+
89
+ before :each do
90
+ allow(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1, open_file_2)
91
+ end
92
+
93
+ it 'closes the old file' do
94
+ expect(open_file_1).to receive(:close!)
95
+
96
+ subject.rotate_log!
97
+ end
98
+
99
+ it 'calls the callback with the old file name' do
100
+ value = nil
101
+ subject.on_rotate { |old_path| value = old_path }
102
+
103
+ subject.rotate_log!
104
+ expect(value).to eq(open_file_1.path)
105
+ end
106
+
107
+ it 'opens a new file based on the new path' do
108
+ expect(LogStash::Outputs::Gcs::LogFileFactory).to receive(:create).and_return(open_file_1, open_file_2)
109
+ expect(open_file_2).to receive(:write).with('foo', "\n")
110
+
111
+ subject.rotate_log!
112
+ subject.writeln('foo')
113
+ end
114
+ end
115
+
116
+ describe '#on_rotate' do
117
+ subject { LogStash::Outputs::Gcs::LogRotate.new(path_factory, 10, false, 30) }
118
+
119
+ it 'replaces an existing callback' do
120
+ value = :none
121
+
122
+ subject.on_rotate { value = :first }
123
+ subject.on_rotate { value = :second }
124
+
125
+ subject.rotate_log!
126
+ expect(value).to eq(:second)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,189 @@
1
+ # encoding: utf-8
2
+ require 'logstash/outputs/gcs/path_factory'
3
+
4
+ describe LogStash::Outputs::Gcs::PathFactory do
5
+ describe '#initialize' do
6
+ it 'includes optional fields if requested' do
7
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
8
+ builder.set_directory 'path/to/directory'
9
+ builder.set_prefix 'prefix'
10
+ builder.set_include_host true
11
+ builder.set_date_pattern ''
12
+ builder.set_include_part true
13
+ builder.set_include_uuid true
14
+ builder.set_is_gzipped true
15
+ end
16
+
17
+ vars = {
18
+ prefix: 'prefix',
19
+ host: 'hostname',
20
+ date: '2018-01-01',
21
+ uuid: '00000000-0000-0000-0000-000000000000',
22
+ partf: '333'
23
+ }
24
+
25
+ expected = 'prefix_hostname_2018-01-01.part333.00000000-0000-0000-0000-000000000000.log.gz'
26
+ expected = File.join('path/to/directory', expected)
27
+
28
+ actual = pf.current_path(vars)
29
+
30
+ expect(actual).to eq(expected)
31
+ end
32
+
33
+ it 'excludes optional fields if not requested' do
34
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
35
+ builder.set_directory 'path/to/directory'
36
+ builder.set_prefix 'prefix'
37
+ builder.set_include_host false
38
+ builder.set_date_pattern ''
39
+ builder.set_include_part false
40
+ builder.set_include_uuid false
41
+ builder.set_is_gzipped false
42
+ end
43
+
44
+ vars = {
45
+ prefix: 'prefix',
46
+ host: 'hostname',
47
+ date: '2018-01-01',
48
+ uuid: '00000000-0000-0000-0000-000000000000',
49
+ partf: '333'
50
+ }
51
+
52
+ expected = 'prefix_2018-01-01.log'
53
+ expected = File.join('path/to/directory', expected)
54
+
55
+ actual = pf.current_path(vars)
56
+
57
+ expect(actual).to eq(expected)
58
+ end
59
+
60
+ it 'loads a path immediately' do
61
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
62
+ builder.set_directory ''
63
+ builder.set_prefix ''
64
+ builder.set_include_host false
65
+ builder.set_date_pattern ''
66
+ builder.set_include_part false
67
+ builder.set_include_uuid false
68
+ builder.set_is_gzipped false
69
+ end
70
+
71
+ expect(pf.current_path).to_not eq(nil)
72
+ end
73
+
74
+ it 'recovers the starting part number' do
75
+ contents = ['pre_date.part009.log.gz', 'pre_date.part091.log.gz', 'pre_date.part000.log.gz']
76
+
77
+ allow(::File).to receive(:directory?).with('dir').and_return(true)
78
+ allow(Dir).to receive(:glob).and_return(contents)
79
+
80
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
81
+ builder.set_directory 'dir'
82
+ builder.set_prefix 'pre'
83
+ builder.set_include_host false
84
+ builder.set_date_pattern 'date'
85
+ builder.set_include_part true
86
+ builder.set_include_uuid false
87
+ builder.set_is_gzipped false
88
+ end
89
+
90
+ expect(pf.current_path).to include('part092')
91
+ end
92
+ end
93
+
94
+ describe 'rotate_path!' do
95
+ it 'increments the part number if the base has not changed' do
96
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
97
+ builder.set_directory 'dir'
98
+ builder.set_prefix 'pre'
99
+ builder.set_include_host false
100
+ builder.set_date_pattern 'date'
101
+ builder.set_include_part true
102
+ builder.set_include_uuid false
103
+ builder.set_is_gzipped false
104
+ end
105
+
106
+ expect(pf.current_path).to eq(File.join('dir', 'pre_date.part000.log'))
107
+
108
+ pf.rotate_path!
109
+ expect(pf.current_path).to eq(File.join('dir', 'pre_date.part001.log'))
110
+ end
111
+
112
+ it 'resets the part number if the base has changed' do
113
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
114
+ builder.set_directory 'dir'
115
+ builder.set_prefix 'pre'
116
+ builder.set_include_host false
117
+ builder.set_date_pattern '%N'
118
+ builder.set_include_part true
119
+ builder.set_include_uuid false
120
+ builder.set_is_gzipped false
121
+ end
122
+ expect(pf.current_path).to include('part000')
123
+
124
+ pf.rotate_path!
125
+ expect(pf.current_path).to include('part000')
126
+ end
127
+
128
+ it 'returns the path being rotated out' do
129
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
130
+ builder.set_directory 'dir'
131
+ builder.set_prefix 'pre'
132
+ builder.set_include_host false
133
+ builder.set_date_pattern 'date'
134
+ builder.set_include_part true
135
+ builder.set_include_uuid false
136
+ builder.set_is_gzipped false
137
+ end
138
+ last = pf.current_path
139
+ after = pf.rotate_path!
140
+ expect(after).to eq(last)
141
+ end
142
+ end
143
+
144
+ describe 'should_rotate?' do
145
+ it 'returns false when the times in the bases are the same' do
146
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
147
+ builder.set_directory ''
148
+ builder.set_prefix ''
149
+ builder.set_include_host false
150
+ builder.set_date_pattern ''
151
+ builder.set_include_part false
152
+ builder.set_include_uuid false
153
+ builder.set_is_gzipped false
154
+ end
155
+ sleep 1.0
156
+ expect(pf.should_rotate?).to eq(false)
157
+ end
158
+
159
+ it 'returns true when the times in the bases are different' do
160
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
161
+ builder.set_directory ''
162
+ builder.set_prefix ''
163
+ builder.set_include_host false
164
+ builder.set_date_pattern '%N'
165
+ builder.set_include_part false
166
+ builder.set_include_uuid false
167
+ builder.set_is_gzipped false
168
+ end
169
+ sleep 1.0
170
+ expect(pf.should_rotate?).to eq(true)
171
+ end
172
+ end
173
+
174
+ describe 'current_path' do
175
+ it 'joins the directory and filename' do
176
+ pf = LogStash::Outputs::Gcs::PathFactoryBuilder.build do |builder|
177
+ builder.set_directory 'dir'
178
+ builder.set_prefix 'pre'
179
+ builder.set_include_host false
180
+ builder.set_date_pattern 'date'
181
+ builder.set_include_part false
182
+ builder.set_include_uuid false
183
+ builder.set_is_gzipped false
184
+ end
185
+
186
+ expect(pf.current_path).to eq(File.join('dir', 'pre_date.log'))
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,155 @@
1
+ # encoding: utf-8
2
+ require 'logstash/outputs/gcs/temp_log_file'
3
+ require 'stud/temporary'
4
+ require 'zlib'
5
+
6
+ shared_examples 'a log file' do
7
+ describe '#initialize' do
8
+ it 'opens a file' do
9
+ expect{subject.fd}.to_not raise_error
10
+ expect(subject.fd).to_not be_nil
11
+ end
12
+
13
+ it 'sets the path' do
14
+ expect{subject.path}.to_not raise_error
15
+ expect(subject.path).to_not be_nil
16
+ end
17
+
18
+ it 'sets last sync' do
19
+ expect{subject.time_since_sync}.to_not raise_error
20
+ end
21
+ end
22
+
23
+ describe '#write' do
24
+ it 'writes the content' do
25
+ expect(subject.fd).to receive(:write).with('foo')
26
+ expect(subject.fd).to receive(:write).with("\n")
27
+
28
+ subject.write('foo', "\n")
29
+ end
30
+
31
+ it 'fails if the file is closed' do
32
+ subject.close!
33
+
34
+ expect{ subject.write('foo') }.to raise_error(IOError)
35
+ end
36
+ end
37
+
38
+ describe '#fsync' do
39
+ it 'fails if the file is closed' do
40
+ subject.close!
41
+
42
+ expect{ subject.fsync }.to raise_error(IOError)
43
+ end
44
+ end
45
+
46
+ describe '#close!' do
47
+ it 'fails if the file is closed' do
48
+ subject.close!
49
+
50
+ expect{ subject.close! }.to raise_error(IOError)
51
+ end
52
+ end
53
+
54
+ describe '#size' do
55
+ it 'gets the size of the file on disk' do
56
+ subject.write('hello, world!')
57
+ subject.fsync
58
+
59
+ expect(subject.size).to eq(File.stat(subject.path).size)
60
+ end
61
+
62
+ it 'does not fail if the file is closed' do
63
+ subject.close!
64
+
65
+ expect{ subject.size }.to_not raise_error
66
+ end
67
+ end
68
+
69
+ describe '#time_since_sync' do
70
+ it 'returns a delta' do
71
+ expect(Time).to receive(:now).and_return(Time.at(30), Time.at(40), Time.at(50))
72
+
73
+ subject.fsync
74
+
75
+ expect(subject.time_since_sync).to eq(10)
76
+ end
77
+ end
78
+ end
79
+
80
+ describe LogStash::Outputs::Gcs::PlainLogFile do
81
+ let(:tempdir) { Stud::Temporary.directory }
82
+ let(:path) { ::File.join(tempdir, 'logfile.log') }
83
+ subject { LogStash::Outputs::Gcs::LogFileFactory.create(path, false, false) }
84
+
85
+ it_behaves_like 'a log file'
86
+
87
+ it 'creates a valid plain text file' do
88
+ subject.write('Hello, world!')
89
+ subject.close!
90
+ data = File.read(path)
91
+
92
+ expect(data).to eq('Hello, world!')
93
+ end
94
+ end
95
+
96
+ describe LogStash::Outputs::Gcs::GzipLogFile do
97
+ let(:tempdir) { Stud::Temporary.directory }
98
+ let(:path) { ::File.join(tempdir, 'logfile.log') }
99
+ subject { LogStash::Outputs::Gcs::LogFileFactory.create(path, true, false) }
100
+
101
+ it_behaves_like 'a log file'
102
+
103
+ it 'creates a valid gzip' do
104
+ subject.write('Hello, world!')
105
+ subject.close!
106
+
107
+ Zlib::GzipReader.open(path) do |gz|
108
+ expect(gz.read).to eq('Hello, world!')
109
+ end
110
+ end
111
+ end
112
+
113
+ describe LogStash::Outputs::Gcs::SynchronizedLogFile do
114
+ let(:tempdir) { Stud::Temporary.directory }
115
+ let(:path) { ::File.join(tempdir, 'logfile.log') }
116
+ subject { LogStash::Outputs::Gcs::LogFileFactory.create(path, false, true) }
117
+
118
+ it_behaves_like 'a log file'
119
+ end
120
+
121
+ describe 'gzip encoded file' do
122
+ let(:tempdir) { Stud::Temporary.directory }
123
+ let(:path) { ::File.join(tempdir, 'logfile.log') }
124
+ subject { LogStash::Outputs::Gcs::LogFileFactory.create(path, false, false, true) }
125
+
126
+ it_behaves_like 'a log file'
127
+
128
+ it 'creates a valid gzip' do
129
+ subject.write('Hello, world!')
130
+ subject.close!
131
+
132
+ Zlib::GzipReader.open(path) do |gz|
133
+ expect(gz.read).to eq('Hello, world!')
134
+ end
135
+ end
136
+ end
137
+
138
+ describe 'double gzip encoded file' do
139
+ let(:tempdir) { Stud::Temporary.directory }
140
+ let(:path) { ::File.join(tempdir, 'logfile.log') }
141
+ subject { LogStash::Outputs::Gcs::LogFileFactory.create(path, true, false, true) }
142
+
143
+ it_behaves_like 'a log file'
144
+
145
+ it 'creates a valid double gzip' do
146
+ subject.write('Hello, world!')
147
+ subject.close!
148
+
149
+ Zlib::GzipReader.open(path) do |outer|
150
+ Zlib::GzipReader.new(outer) do |inner|
151
+ expect(inner.read).to eq('Hello, world!')
152
+ end
153
+ end
154
+ end
155
+ end