jmoses_fluent-logger 0.4.8

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.
@@ -0,0 +1,39 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+ module Logger
20
+
21
+ class LoggerBase
22
+ def self.open(*args, &block)
23
+ Fluent::Logger.open(self, *args, &block)
24
+ end
25
+
26
+ def post(tag, map)
27
+ raise ArgumentError.new("Second argument should kind of Hash (tag: #{map})") unless map.kind_of? Hash
28
+ post_with_time(tag, map, Time.now)
29
+ end
30
+
31
+ #def post_with_time(tag, map)
32
+ #end
33
+
34
+ def close
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+ module Logger
20
+
21
+ class NullLogger < LoggerBase
22
+ def post_with_time(tag, map, time)
23
+ false
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+ module Logger
20
+
21
+ class TestLogger < LoggerBase
22
+ def initialize(queue=[])
23
+ @queue = queue
24
+ @max = 1024
25
+ end
26
+
27
+ attr_accessor :max
28
+ attr_reader :queue
29
+
30
+ def post_with_time(tag, map, time)
31
+ while @queue.size > @max-1
32
+ @queue.shift
33
+ end
34
+ (class<<map;self;end).module_eval do
35
+ define_method(:tag) { tag }
36
+ define_method(:time) { time }
37
+ end
38
+ @queue << map
39
+ true
40
+ end
41
+
42
+ def tag_queue(tag)
43
+ @queue.find_all {|map| map.tag == tag }
44
+ end
45
+
46
+ def close
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+ module Logger
20
+
21
+ class TextLogger < LoggerBase
22
+ def initialize
23
+ require 'yajl'
24
+ @time_format = "%b %e %H:%M:%S"
25
+ end
26
+
27
+ def post_with_time(tag, map, time)
28
+ a = [time.strftime(@time_format), " ", tag, ":"]
29
+ map.each_pair {|k,v|
30
+ a << " #{k}="
31
+ a << Yajl::Encoder.encode(v)
32
+ }
33
+ post_text a.join
34
+ true
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Fluent
2
+ module Logger
3
+
4
+ VERSION = '0.4.8'
5
+
6
+ end
7
+ end
@@ -0,0 +1,69 @@
1
+ #
2
+ # Fluent
3
+ #
4
+ # Copyright (C) 2011 FURUHASHI Sadayuki
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ module Fluent
19
+
20
+ module Logger
21
+ autoload :ConsoleLogger , 'fluent/logger/console_logger'
22
+ autoload :FluentLogger , 'fluent/logger/fluent_logger'
23
+ autoload :LoggerBase , 'fluent/logger/logger_base'
24
+ autoload :TestLogger , 'fluent/logger/test_logger'
25
+ autoload :TextLogger , 'fluent/logger/text_logger'
26
+ autoload :NullLogger , 'fluent/logger/null_logger'
27
+ autoload :VERSION , 'fluent/logger/version'
28
+
29
+ @@default_logger = nil
30
+
31
+ def self.new(*args)
32
+ if args.first.is_a?(Class) && args.first.ancestors.include?(LoggerBase)
33
+ type = args.shift
34
+ else
35
+ type = FluentLogger
36
+ end
37
+ type.new(*args)
38
+ end
39
+
40
+ def self.open(*args)
41
+ close
42
+ @@default_logger = new(*args)
43
+ end
44
+
45
+ def self.close
46
+ if @@default_logger
47
+ @@default_logger.close
48
+ @@default_logger = nil
49
+ end
50
+ end
51
+
52
+ def self.post(tag, map)
53
+ @@default_logger.post(tag, map)
54
+ end
55
+
56
+ def self.post_with_time(tag, map, time)
57
+ @@default_logger.post_with_time(tag, map, time)
58
+ end
59
+
60
+ def self.default
61
+ @@default_logger ||= ConsoleLogger.new(STDOUT)
62
+ end
63
+
64
+ def self.default=(logger)
65
+ @@default_logger = logger
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'fluent', 'logger')
@@ -0,0 +1,69 @@
1
+
2
+ require 'spec_helper'
3
+ require 'stringio'
4
+ require 'tempfile'
5
+ require 'pathname'
6
+
7
+ describe Fluent::Logger::ConsoleLogger do
8
+ before(:each) {
9
+ Timecop.freeze Time.local(2008, 9, 1, 10, 5, 0)
10
+ }
11
+ after(:each) {
12
+ Timecop.return
13
+ }
14
+
15
+ context "IO output" do
16
+ let(:io) { StringIO.new }
17
+ let(:logger) { Fluent::Logger::ConsoleLogger.new(io) }
18
+
19
+ subject {
20
+ io
21
+ }
22
+
23
+ context "post and read" do
24
+ before do
25
+ logger.post('example', {:foo => :bar})
26
+ io.rewind
27
+ end
28
+ its(:read) { should eq %Q!Sep 1 10:05:00 example: foo="bar"\n! }
29
+ end
30
+ end
31
+
32
+ context "Filename output" do
33
+ let(:path) {
34
+ @tmp = Tempfile.new('fluent-logger') # ref instance var because Tempfile.close(true) check GC
35
+ filename = @tmp.path
36
+ @tmp.close(true)
37
+ Pathname.new(filename)
38
+ }
39
+ let(:logger) { Fluent::Logger::ConsoleLogger.new(path.to_s) }
40
+
41
+ subject { path }
42
+ after { path.unlink }
43
+
44
+ context "post and read" do
45
+ before do
46
+ logger.post('example', {:foo => :bar})
47
+ logger.close
48
+ end
49
+ its(:read) { should eq %Q!Sep 1 10:05:00 example: foo="bar"\n! }
50
+ end
51
+
52
+ context "reopen" do
53
+ before do
54
+ logger.post('example', {:foo => :baz})
55
+ logger.close
56
+ logger.reopen!
57
+ end
58
+ its(:read) { should eq %Q!Sep 1 10:05:00 example: foo="baz"\n! }
59
+ end
60
+ end
61
+
62
+ context "Invalid output" do
63
+ it {
64
+ expect {
65
+ Fluent::Logger::ConsoleLogger.new(nil)
66
+ }.to raise_error
67
+ }
68
+ end
69
+ end
@@ -0,0 +1,297 @@
1
+
2
+ require 'spec_helper'
3
+ if RUBY_VERSION < "1.9.2"
4
+
5
+ describe Fluent::Logger::FluentLogger do
6
+ pending "fluentd don't work RUBY < 1.9.2"
7
+ end
8
+
9
+ else
10
+
11
+ require 'fluent/load'
12
+ require 'tempfile'
13
+ require 'logger'
14
+ require 'socket'
15
+ require 'stringio'
16
+ require 'fluent/logger/fluent_logger/cui'
17
+
18
+ $log = Fluent::Log.new(StringIO.new) # XXX should remove $log from fluentd
19
+
20
+ describe Fluent::Logger::FluentLogger do
21
+ WAIT = ENV['WAIT'] ? ENV['WAIT'].to_f : 0.1
22
+
23
+ let(:fluentd_port) {
24
+ port = 60001
25
+ loop do
26
+ begin
27
+ TCPServer.open('localhost', port).close
28
+ break
29
+ rescue Errno::EADDRINUSE
30
+ port += 1
31
+ end
32
+ end
33
+ port
34
+ }
35
+
36
+ let(:logger) {
37
+ @logger_io = StringIO.new
38
+ logger = ::Logger.new(@logger_io)
39
+ Fluent::Logger::FluentLogger.new('logger-test', {
40
+ :host => 'localhost',
41
+ :port => fluentd_port,
42
+ :logger => logger,
43
+ })
44
+ }
45
+
46
+ let(:logger_io) {
47
+ @logger_io
48
+ }
49
+
50
+ let(:output) {
51
+ sleep 0.0001 # next tick
52
+ Fluent::Engine.match('logger-test').output
53
+ }
54
+
55
+ let(:queue) {
56
+ queue = []
57
+ output.emits.each {|tag, time, record|
58
+ queue << [tag, record]
59
+ }
60
+ queue
61
+ }
62
+
63
+ let(:queue_with_time) {
64
+ queue = []
65
+ output.emits.each {|tag, time, record|
66
+ queue << [tag, time, record]
67
+ }
68
+ queue
69
+ }
70
+
71
+ after(:each) do
72
+ output.emits.clear rescue nil
73
+ end
74
+
75
+ def wait_transfer
76
+ sleep WAIT
77
+ end
78
+
79
+ context "running fluentd" do
80
+ before(:each) do
81
+ tmp = Tempfile.new('fluent-logger-config')
82
+ tmp.close(false)
83
+
84
+ File.open(tmp.path, 'w') {|f|
85
+ f.puts <<EOF
86
+ <source>
87
+ type tcp
88
+ port #{fluentd_port}
89
+ </source>
90
+ <match logger-test.**>
91
+ type test
92
+ </match>
93
+ EOF
94
+ }
95
+ Fluent::Test.setup
96
+ Fluent::Engine.read_config(tmp.path)
97
+ @coolio_default_loop = nil
98
+ @thread = Thread.new {
99
+ @coolio_default_loop = Coolio::Loop.default
100
+ Fluent::Engine.run
101
+ }
102
+ wait_transfer
103
+ end
104
+
105
+ after(:each) do
106
+ @coolio_default_loop.stop
107
+ Fluent::Engine.send :shutdown
108
+ @thread.join
109
+ end
110
+
111
+ context('Post by CUI') do
112
+ it('post') {
113
+ args = %W(-h localhost -p #{fluentd_port} -t logger-test.tag -v a=b -v foo=bar)
114
+ Fluent::Logger::FluentLogger::CUI.post(args)
115
+ wait_transfer
116
+ queue.last.should == ['logger-test.tag', {'a' => 'b', 'foo' => 'bar'}]
117
+ }
118
+ end
119
+
120
+ context('post') do
121
+ it ('success') {
122
+ logger.post('tag', {'a' => 'b'}).should be_true
123
+ wait_transfer
124
+ queue.last.should == ['logger-test.tag', {'a' => 'b'}]
125
+ }
126
+
127
+ it ('close after post') {
128
+ logger.should be_connect
129
+ logger.close
130
+ logger.should_not be_connect
131
+
132
+ logger.post('tag', {'b' => 'c'})
133
+ logger.should be_connect
134
+ wait_transfer
135
+ queue.last.should == ['logger-test.tag', {'b' => 'c'}]
136
+ }
137
+
138
+ it ('large data') {
139
+ data = {'a' => ('b' * 1000000)}
140
+ logger.post('tag', data)
141
+ wait_transfer
142
+ queue.last.should == ['logger-test.tag', data]
143
+ }
144
+
145
+ it ('msgpack unsupport data') {
146
+ data = {
147
+ 'time' => Time.utc(2008, 9, 1, 10, 5, 0),
148
+ 'object' => Object.new,
149
+ 'proc' => proc { 1 },
150
+ }
151
+ logger.post('tag', data)
152
+ wait_transfer
153
+ logger_data = queue.last.last
154
+ logger_data['time'].should == '2008-09-01 10:05:00 UTC'
155
+ logger_data['proc'].should be
156
+ logger_data['object'].should be
157
+ }
158
+
159
+ it ('msgpack and JSON unsupport data') {
160
+ data = {
161
+ 'time' => Time.utc(2008, 9, 1, 10, 5, 0),
162
+ 'object' => Object.new,
163
+ 'proc' => proc { 1 },
164
+ 'NaN' => (0.0/0.0) # JSON don't convert
165
+ }
166
+ logger.post('tag', data)
167
+ wait_transfer
168
+ queue.last.should be_nil
169
+ logger_io.rewind
170
+ logger_io.read =~ /FluentLogger: Can't convert to msgpack:/
171
+ }
172
+
173
+ it ('batch post') {
174
+ messages = [
175
+ ['logger-test.tag1', 'message 1', Time.utc(2008, 9, 1, 10, 5, 0)],
176
+ ['logger-test.tag2', 'message 2', Time.utc(2008, 9, 1, 10, 6, 0)],
177
+ ]
178
+
179
+ logger.batch_post_with_time(messages)
180
+ wait_transfer
181
+
182
+ queue_with_time.should have(2).items
183
+ queue_with_time.first[0].should eq('logger-test.tag1')
184
+ queue_with_time.first[1].should eq(1220263500)
185
+ queue_with_time.first[2].should eq('message 1')
186
+ queue_with_time.last[0].should eq('logger-test.tag2')
187
+ queue_with_time.last[1].should eq(1220263560)
188
+ queue_with_time.last[2].should eq('message 2')
189
+ }
190
+
191
+ it ('batches faster than single') {
192
+ require 'benchmark'
193
+ require 'digest/md5'
194
+
195
+ messages = 1000.times.map do |id|
196
+ ['logger-test.tag', Digest::MD5.hexdigest(id.to_s), Time.now]
197
+ end
198
+
199
+ single = Benchmark.realtime do
200
+ output.emits.clear
201
+ messages.each {|m| logger.post_with_time *m }
202
+ wait_transfer
203
+ queue.should have(1000).items
204
+ end
205
+
206
+ batch = Benchmark.realtime do
207
+ output.emits.clear
208
+ logger.batch_post_with_time messages
209
+ wait_transfer
210
+ queue.should have(1000).items
211
+ end
212
+
213
+ batch.should be < single
214
+ }
215
+
216
+ it ('should raise an error when second argument is non hash object') {
217
+ data = 'FooBar'
218
+ expect {
219
+ logger.post('tag', data)
220
+ }.to raise_error(ArgumentError)
221
+
222
+ data = nil
223
+ expect {
224
+ logger.post('tag', data)
225
+ }.to raise_error(ArgumentError)
226
+ }
227
+ end
228
+
229
+ context "initializer" do
230
+ it "backward compatible" do
231
+ port = fluentd_port
232
+ fluent_logger = Fluent::Logger::FluentLogger.new('logger-test', 'localhost', port)
233
+ fluent_logger.method_missing(:instance_eval) { # fluent_logger is delegetor
234
+ @host.should == 'localhost'
235
+ @port.should == port
236
+ }
237
+ end
238
+
239
+ it "hash argument" do
240
+ port = fluentd_port
241
+ fluent_logger = Fluent::Logger::FluentLogger.new('logger-test', {
242
+ :host => 'localhost',
243
+ :port => port
244
+ })
245
+ fluent_logger.method_missing(:instance_eval) { # fluent_logger is delegetor
246
+ @host.should == 'localhost'
247
+ @port.should == port
248
+ }
249
+ end
250
+ end
251
+ end
252
+
253
+ context "not running fluentd" do
254
+ context('fluent logger interface') do
255
+ it ('post & close') {
256
+ logger.post('tag', {'a' => 'b'}).should be_false
257
+ wait_transfer # even if wait
258
+ queue.last.should be_nil
259
+ logger.close
260
+ logger_io.rewind
261
+ log = logger_io.read
262
+ log.should =~ /Failed to connect/
263
+ log.should =~ /Can't send logs to/
264
+ }
265
+
266
+ it ('post limit over') do
267
+ logger.limit = 100
268
+ logger.post('tag', {'a' => 'b'})
269
+ wait_transfer # even if wait
270
+ queue.last.should be_nil
271
+
272
+ logger_io.rewind
273
+ logger_io.read.should_not =~ /Can't send logs to/
274
+
275
+ logger.post('tag', {'a' => ('c' * 1000)})
276
+ logger_io.rewind
277
+ logger_io.read.should =~ /Can't send logs to/
278
+ end
279
+
280
+ it ('log connect error once') do
281
+ Fluent::Logger::FluentLogger.any_instance.stub(:suppress_sec).and_return(-1)
282
+ logger.log_reconnect_error_threshold = 1
283
+ Fluent::Logger::FluentLogger.any_instance.should_receive(:log_reconnect_error).once.and_call_original
284
+
285
+ logger.post('tag', {'a' => 'b'})
286
+ wait_transfer # even if wait
287
+ logger.post('tag', {'a' => 'b'})
288
+ wait_transfer # even if wait
289
+ logger_io.rewind
290
+ logger_io.read.should =~ /Failed to connect/
291
+ end
292
+ end
293
+ end
294
+
295
+ end
296
+
297
+ end
@@ -0,0 +1,11 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Fluent::Logger::LoggerBase do
5
+ context "subclass" do
6
+ subject { Class.new(Fluent::Logger::LoggerBase) }
7
+ its(:open) {
8
+ should be_kind_of(Fluent::Logger::LoggerBase)
9
+ }
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+
2
+ require 'spec_helper'
3
+ require 'stringio'
4
+
5
+ describe Fluent::Logger do
6
+ context "default logger" do
7
+ let(:test_logger) {
8
+ Fluent::Logger::TestLogger.new
9
+ }
10
+ before(:each) do
11
+ Fluent::Logger.default = test_logger
12
+ end
13
+
14
+ it('post') {
15
+ test_logger.should_receive(:post).with('tag1', {:foo => :bar})
16
+ Fluent::Logger.post('tag1', {:foo => :bar})
17
+ }
18
+
19
+ it('close') {
20
+ test_logger.should_receive(:close)
21
+ Fluent::Logger.close
22
+ }
23
+
24
+ it('open') {
25
+ test_logger.should_receive(:close)
26
+ klass = Class.new(Fluent::Logger::LoggerBase)
27
+ fluent_logger_logger_io = StringIO.new
28
+ Fluent::Logger.open('tag-prefix', {
29
+ :logger => ::Logger.new(fluent_logger_logger_io)
30
+ })
31
+ # Fluent::Logger::FluentLogger is delegator
32
+ Fluent::Logger.default.method_missing(:kind_of?, Fluent::Logger::FluentLogger).should be_true
33
+ }
34
+
35
+ it('open with BaseLogger class') {
36
+ test_logger.should_receive(:close)
37
+ klass = Class.new(Fluent::Logger::LoggerBase)
38
+ Fluent::Logger.open(klass)
39
+ Fluent::Logger.default.class.should == klass
40
+ }
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+
2
+ require 'spec_helper'
3
+
4
+ describe Fluent::Logger::NullLogger do
5
+ context "logger method" do
6
+ let(:logger) { Fluent::Logger::NullLogger.new }
7
+
8
+ context "post" do
9
+ it('false') {
10
+ logger.post('tag1', {:foo => :bar}).should == false
11
+ logger.post('tag2', {:foo => :baz}).should == false
12
+ }
13
+ end
14
+ end
15
+ end