jmoses_fluent-logger 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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