fluent-funplus-s3 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea37f19916d904dc2e085f55d4f34b077909fdc5
4
+ data.tar.gz: 34e39dc79062fdab3af1f3747a1c85168cfd2021
5
+ SHA512:
6
+ metadata.gz: cd83b78d9b901871d06ad07a3166fbb3ecc1bff5dea479b70448cb20633cd7f1b91b80f85def159a84b2546dc7d1d3ed09388ae36f4f8fd38d343c877391384d
7
+ data.tar.gz: 6171413759442726c3ed7a6b2b1480c535d47b3ebb59aef094c7cc2f491659d07f78cec8068230283b4d6888a6a57c0fe02e7b0ce4ed636cf200c2a39f859d97
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - rbx-19mode
8
+
9
+ branches:
10
+ only:
11
+ - master
12
+
13
+ script: bundle exec rake test
14
+
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: rbx-19mode
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ FURUHASHI Sadayuki <frsyuki _at_ gmail.com>
@@ -0,0 +1,91 @@
1
+ Release 0.3.5 - 2013/12/05
2
+
3
+ * Add 'reduced_redundancy' option to store logs in reduced redundancy
4
+ https://github.com/fluent/fluent-plugin-s3/pull/33
5
+
6
+
7
+ Release 0.3.4 - 2013/07/31
8
+
9
+ * Add dynamic path slicing by time formatted string
10
+ https://github.com/fluent/fluent-plugin-s3/pull/24
11
+
12
+
13
+ Release 0.3.3 - 2013/06/18
14
+
15
+ * Fix require bug on case-sensitive environment
16
+
17
+
18
+ Release 0.3.2 - 2013/06/18
19
+
20
+ * Support lzo mime-type
21
+ https://github.com/fluent/fluent-plugin-s3/pull/29
22
+ * Add proxy_uri option
23
+ https://github.com/fluent/fluent-plugin-s3/issues/25
24
+ * Add check_apikey_on_start option
25
+ https://github.com/fluent/fluent-plugin-s3/pull/28
26
+
27
+
28
+ Release 0.3.1 - 2013/03/28
29
+
30
+ * Support json and text mime-types
31
+ https://github.com/fluent/fluent-plugin-s3/pull/20
32
+
33
+
34
+ Release 0.3.0 - 2013/02/19
35
+
36
+ * Enable dynamic and configurable S3 object kyes
37
+ https://github.com/fluent/fluent-plugin-s3/pull/12
38
+ * Fix a lot of temporary files were left on /tmp when the plugin failed to write to S3
39
+ https://github.com/fluent/fluent-plugin-s3/pull/15
40
+ * Enable fluent-mixin-config-placeholders to support hostname, uuid and other parameters in configuration
41
+ https://github.com/fluent/fluent-plugin-s3/pull/19
42
+ * Update 'aws-sdk' version requirement to '~> 1.8.2'
43
+ https://github.com/fluent/fluent-plugin-s3/pull/21
44
+ * Create new S3 bucket if not exists
45
+ https://github.com/fluent/fluent-plugin-s3/pull/22
46
+ * Check the permission and bucket existence at start method, not write method.
47
+
48
+
49
+ Release 0.2.6 - 2013/01/15
50
+
51
+ * Add use_ssl option
52
+
53
+
54
+ Release 0.2.5 - 2012/12/06
55
+
56
+ * Add format_json and time/tag mixin options [#9]
57
+
58
+
59
+ Release 0.2.4 - 2012/11/21
60
+
61
+ * Set content type when writing file to s3
62
+
63
+
64
+ Release 0.2.3 - 2012/11/19
65
+
66
+ * Loosen 'aws-sdk' version requirement from "~> 1.1.3" to "~> 1.1"
67
+ * Support aws-sdk facility to load credentials from ENV vars or IAM Instance Profile by making the credentials non-mandatory
68
+ * Use Yajl instead of to_json not to raise exceptions when it got invalid bytes as UTF-8.
69
+
70
+
71
+ Release 0.2.2 - 2011/12/15
72
+
73
+ * Add s3_endpoint option
74
+
75
+
76
+ Release 0.2.1 - 2011/10/24
77
+
78
+ * Add sequential number to the file to avoid overwriting
79
+ * Use bundler instead of jeweler for packaging
80
+ * Updated README
81
+
82
+
83
+ Release 0.2.0 - 2011/10/16
84
+
85
+ * Updated to fluentd-0.10.0
86
+
87
+
88
+ Release 0.1.1 - 2011/09/27
89
+
90
+ * First release
91
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,110 @@
1
+ = Amazon S3 output plugin for Fluent event collector
2
+
3
+ == Overview
4
+
5
+ *s3* output plugin buffers event logs in local file and upload it to S3 periodically.
6
+
7
+ This plugin splits files exactly by using the time of event logs (not the time when the logs are received). For example, a log '2011-01-02 message B' is reached, and then another log '2011-01-03 message B' is reached in this order, the former one is stored in "20110102.gz" file, and latter one in "20110103.gz" file.
8
+
9
+
10
+ == Installation
11
+
12
+ Simply use RubyGems:
13
+
14
+ gem install fluent-plugin-s3
15
+
16
+ == Configuration
17
+
18
+ <match pattern>
19
+ type s3
20
+
21
+ aws_key_id YOUR_AWS_KEY_ID
22
+ aws_sec_key YOUR_AWS_SECRET/KEY
23
+ s3_bucket YOUR_S3_BUCKET_NAME
24
+ s3_endpoint s3-ap-northeast-1.amazonaws.com
25
+ s3_object_key_format %{path}%{time_slice}_%{index}.%{file_extension}
26
+ path logs/
27
+ buffer_path /var/log/fluent/s3
28
+
29
+ time_slice_format %Y%m%d-%H
30
+ time_slice_wait 10m
31
+ utc
32
+ </match>
33
+
34
+ [aws_key_id] AWS access key id. This parameter is required when your agent is not running on EC2 instance with an IAM Instance Profile.
35
+
36
+ [aws_sec_key] AWS secret key. This parameter is required when your agent is not running on EC2 instance with an IAM Instance Profile.
37
+
38
+ [s3_bucket (required)] S3 bucket name.
39
+
40
+ [s3_endpoint] s3 endpoint name. For example, US West (Oregon) Region is "s3-us-west-2.amazonaws.com". The full list of endpoints are available here. > http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
41
+
42
+ [s3_object_key_format] The format of S3 object keys. You can use several built-in variables:
43
+
44
+ - %{path}
45
+ - %{time_slice}
46
+ - %{index}
47
+ - %{file_extension}
48
+
49
+ to decide keys dynamically.
50
+
51
+ %{path} is exactly the value of *path* configured in the configuration file. E.g., "logs/" in the example configuration above.
52
+ %{time_slice} is the time-slice in text that are formatted with *time_slice_format*.
53
+ %{index} is the sequential number starts from 0, increments when multiple files are uploaded to S3 in the same time slice.
54
+ %{file_extention} is always "gz" for now.
55
+
56
+ The default format is "%{path}%{time_slice}_%{index}.%{file_extension}".
57
+
58
+ For instance, using the example configuration above, actual object keys on S3 will be something like:
59
+
60
+ "logs/20130111-22_0.gz"
61
+ "logs/20130111-23_0.gz"
62
+ "logs/20130111-23_1.gz"
63
+ "logs/20130112-00_0.gz"
64
+
65
+ With the configuration:
66
+
67
+ s3_object_key_format %{path}/events/ts=%{time_slice}/events_%{index}.%{file_extension}
68
+ path log
69
+ time_slice_format %Y%m%d-%H
70
+
71
+ You get:
72
+
73
+ "log/events/ts=20130111-22/events_0.gz"
74
+ "log/events/ts=20130111-23/events_0.gz"
75
+ "log/events/ts=20130111-23/events_1.gz"
76
+ "log/events/ts=20130112-00/events_0.gz"
77
+
78
+ The {fluent-mixin-config-placeholders}[https://github.com/tagomoris/fluent-mixin-config-placeholders] mixin is also incorporated, so additional variables such as %{hostname}, %{uuid}, etc. can be used in the s3_object_key_format. This could prove useful in preventing filename conflicts when writing from multiple servers.
79
+
80
+ s3_object_key_format %{path}/events/ts=%{time_slice}/events_%{index}-%{hostname}.%{file_extension}
81
+
82
+ [store_as] archive format on S3. You can use serveral format:
83
+
84
+ - gzip (default)
85
+ - json
86
+ - text
87
+ - lzo (Need lzop command)
88
+
89
+ [auto_create_bucket] Create S3 bucket if it does not exists. Default is true.
90
+
91
+ [check_apikey_on_start] Check AWS key on start. Default is true.
92
+
93
+ [proxy_uri] uri of proxy environment.
94
+
95
+ [path] path prefix of the files on S3. Default is "" (no prefix).
96
+
97
+ [buffer_path (required)] path prefix of the files to buffer logs.
98
+
99
+ [time_slice_format] Format of the time used as the file name. Default is '%Y%m%d'. Use '%Y%m%d%H' to split files hourly.
100
+
101
+ [time_slice_wait] The time to wait old logs. Default is 10 minutes. Specify larger value if old logs may reache.
102
+
103
+ [utc] Use UTC instead of local time.
104
+
105
+
106
+ == Copyright
107
+
108
+ Copyright:: Copyright (c) 2011 Sadayuki Furuhashi
109
+ License:: Apache License, Version 2.0
110
+
@@ -0,0 +1,14 @@
1
+
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'lib' << 'test'
9
+ test.test_files = FileList['test/*.rb']
10
+ test.verbose = true
11
+ end
12
+
13
+ task :default => [:build]
14
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.6
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "fluent-funplus-s3"
6
+ gem.description = "Amazon S3 output plugin for Fluentd event collector, comma seperated and timestamp format"
7
+ gem.homepage = "https://github.com/funplus/fluent-funplus-s3"
8
+ gem.summary = gem.description
9
+ gem.version = File.read("VERSION").strip
10
+ gem.authors = ["Sadayuki Furuhashi"]
11
+ gem.email = "frsyuki@gmail.com"
12
+ gem.has_rdoc = false
13
+ #gem.platform = Gem::Platform::RUBY
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ gem.require_paths = ['lib']
18
+
19
+ gem.add_dependency "fluentd", "~> 0.10.0"
20
+ gem.add_dependency "aws-sdk", "~> 1.8.2"
21
+ gem.add_dependency "yajl-ruby", "~> 1.0"
22
+ gem.add_dependency "fluent-mixin-config-placeholders", "~> 0.2.0"
23
+ gem.add_development_dependency "rake", ">= 0.9.2"
24
+ gem.add_development_dependency "flexmock", ">= 1.2.0"
25
+ end
@@ -0,0 +1,202 @@
1
+ module Fluent
2
+
3
+ require 'fluent/mixin/config_placeholders'
4
+
5
+ class S3Output < Fluent::TimeSlicedOutput
6
+ Fluent::Plugin.register_output('s3', self)
7
+
8
+ def initialize
9
+ super
10
+ require 'aws-sdk'
11
+ require 'zlib'
12
+ require 'time'
13
+ require 'tempfile'
14
+ require 'open3'
15
+
16
+ @use_ssl = true
17
+ end
18
+
19
+ config_param :path, :string, :default => ""
20
+ config_param :time_format, :string, :default => nil
21
+
22
+ include SetTagKeyMixin
23
+ config_set_default :include_tag_key, false
24
+
25
+ include SetTimeKeyMixin
26
+ config_set_default :include_time_key, false
27
+
28
+ config_param :aws_key_id, :string, :default => nil
29
+ config_param :aws_sec_key, :string, :default => nil
30
+ config_param :s3_bucket, :string
31
+ config_param :s3_endpoint, :string, :default => nil
32
+ config_param :s3_object_key_format, :string, :default => "%{path}%{time_slice}_%{index}.%{file_extension}"
33
+ config_param :store_as, :string, :default => "gzip"
34
+ config_param :auto_create_bucket, :bool, :default => true
35
+ config_param :check_apikey_on_start, :bool, :default => true
36
+ config_param :proxy_uri, :string, :default => nil
37
+ config_param :reduced_redundancy, :bool, :default => false
38
+ config_param :data_separator, :string, :default => ","
39
+ config_param :use_timestamp, :bool, :default => true
40
+
41
+ attr_reader :bucket
42
+
43
+ include Fluent::Mixin::ConfigPlaceholders
44
+
45
+ def placeholders
46
+ [:percent]
47
+ end
48
+
49
+ def configure(conf)
50
+ super
51
+
52
+ if format_json = conf['format_json']
53
+ @format_json = true
54
+ else
55
+ @format_json = false
56
+ end
57
+
58
+ if use_ssl = conf['use_ssl']
59
+ if use_ssl.empty?
60
+ @use_ssl = true
61
+ else
62
+ @use_ssl = Config.bool_value(use_ssl)
63
+ if @use_ssl.nil?
64
+ raise ConfigError, "'true' or 'false' is required for use_ssl option on s3 output"
65
+ end
66
+ end
67
+ end
68
+
69
+ @ext, @mime_type = case @store_as
70
+ when 'gzip' then ['gz', 'application/x-gzip']
71
+ when 'lzo' then
72
+ begin
73
+ Open3.capture3('lzop -V')
74
+ rescue Errno::ENOENT
75
+ raise ConfigError, "'lzop' utility must be in PATH for LZO compression"
76
+ end
77
+ ['lzo', 'application/x-lzop']
78
+ when 'json' then ['json', 'application/json']
79
+ else ['txt', 'text/plain']
80
+ end
81
+
82
+ @timef = TimeFormatter.new(@time_format, @localtime)
83
+
84
+ if @localtime
85
+ @path_slicer = Proc.new {|path|
86
+ Time.now.strftime(path)
87
+ }
88
+ else
89
+ @path_slicer = Proc.new {|path|
90
+ Time.now.utc.strftime(path)
91
+ }
92
+ end
93
+
94
+ end
95
+
96
+ def start
97
+ super
98
+ options = {}
99
+ if @aws_key_id && @aws_sec_key
100
+ options[:access_key_id] = @aws_key_id
101
+ options[:secret_access_key] = @aws_sec_key
102
+ end
103
+ options[:s3_endpoint] = @s3_endpoint if @s3_endpoint
104
+ options[:proxy_uri] = @proxy_uri if @proxy_uri
105
+ options[:use_ssl] = @use_ssl
106
+
107
+ @s3 = AWS::S3.new(options)
108
+ @bucket = @s3.buckets[@s3_bucket]
109
+
110
+ ensure_bucket
111
+ check_apikeys if @check_apikey_on_start
112
+ end
113
+
114
+ def format(tag, time, record)
115
+ if @include_time_key || !@format_json
116
+ if @use_timestamp
117
+ time_str = time.to_i
118
+ else
119
+ time_str = @timef.format(time)
120
+ end
121
+ end
122
+
123
+ # copied from each mixin because current TimeSlicedOutput can't support mixins.
124
+ if @include_tag_key
125
+ record[@tag_key] = tag
126
+ end
127
+ if @include_time_key
128
+ record[@time_key] = time_str
129
+ end
130
+
131
+ if @format_json
132
+ Yajl.dump(record) + "\n"
133
+ else
134
+ "#{time_str}#{@data_separator}#{tag}#{@data_separator}#{Yajl.dump(record)}\n"
135
+ end
136
+ end
137
+
138
+ def write(chunk)
139
+ i = 0
140
+
141
+ begin
142
+ path = @path_slicer.call(@path)
143
+ values_for_s3_object_key = {
144
+ "path" => path,
145
+ "time_slice" => chunk.key,
146
+ "file_extension" => @ext,
147
+ "index" => i
148
+ }
149
+ s3path = @s3_object_key_format.gsub(%r(%{[^}]+})) { |expr|
150
+ values_for_s3_object_key[expr[2...expr.size-1]]
151
+ }
152
+ i += 1
153
+ end while @bucket.objects[s3path].exists?
154
+
155
+ tmp = Tempfile.new("s3-")
156
+ begin
157
+ if @store_as == "gzip"
158
+ w = Zlib::GzipWriter.new(tmp)
159
+ chunk.write_to(w)
160
+ w.close
161
+ elsif @store_as == "lzo"
162
+ w = Tempfile.new("chunk-tmp")
163
+ chunk.write_to(w)
164
+ w.close
165
+ tmp.close
166
+ # We don't check the return code because we can't recover lzop failure.
167
+ system "lzop -qf1 -o #{tmp.path} #{w.path}"
168
+ else
169
+ chunk.write_to(tmp)
170
+ tmp.close
171
+ end
172
+ @bucket.objects[s3path].write(Pathname.new(tmp.path), {:content_type => @mime_type,
173
+ :reduced_redundancy => @reduced_redundancy})
174
+ ensure
175
+ tmp.close(true) rescue nil
176
+ w.close rescue nil
177
+ w.unlink rescue nil
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def ensure_bucket
184
+ if !@bucket.exists?
185
+ if @auto_create_bucket
186
+ $log.info "Creating bucket #{@s3_bucket} on #{@s3_endpoint}"
187
+ @s3.buckets.create(@s3_bucket)
188
+ else
189
+ raise "The specified bucket does not exist: bucket = #{@s3_bucket}"
190
+ end
191
+ end
192
+ end
193
+
194
+ def check_apikeys
195
+ @bucket.empty?
196
+ rescue
197
+ raise "aws_key_id or aws_sec_key is invalid. Please check your configuration"
198
+ end
199
+ end
200
+
201
+
202
+ end
@@ -0,0 +1,295 @@
1
+ require 'fluent/test'
2
+ require 'fluent/plugin/out_s3'
3
+
4
+ require 'flexmock/test_unit'
5
+ require 'zlib'
6
+
7
+ class S3OutputTest < Test::Unit::TestCase
8
+ def setup
9
+ require 'aws-sdk'
10
+ Fluent::Test.setup
11
+ end
12
+
13
+ CONFIG = %[
14
+ aws_key_id test_key_id
15
+ aws_sec_key test_sec_key
16
+ s3_bucket test_bucket
17
+ path log
18
+ utc
19
+ buffer_type memory
20
+ ]
21
+
22
+ def create_driver(conf = CONFIG)
23
+ Fluent::Test::BufferedOutputTestDriver.new(Fluent::S3Output) do
24
+ def write(chunk)
25
+ chunk.read
26
+ end
27
+
28
+ private
29
+
30
+ def check_apikeys
31
+ end
32
+ end.configure(conf)
33
+ end
34
+
35
+ def test_configure
36
+ d = create_driver
37
+ assert_equal 'test_key_id', d.instance.aws_key_id
38
+ assert_equal 'test_sec_key', d.instance.aws_sec_key
39
+ assert_equal 'test_bucket', d.instance.s3_bucket
40
+ assert_equal 'log', d.instance.path
41
+ assert d.instance.instance_variable_get(:@use_ssl)
42
+ assert_equal 'gz', d.instance.instance_variable_get(:@ext)
43
+ assert_equal 'application/x-gzip', d.instance.instance_variable_get(:@mime_type)
44
+ end
45
+
46
+ def test_configure_with_mime_type_json
47
+ conf = CONFIG.clone
48
+ conf << "\nstore_as json\n"
49
+ d = create_driver(conf)
50
+ assert_equal 'json', d.instance.instance_variable_get(:@ext)
51
+ assert_equal 'application/json', d.instance.instance_variable_get(:@mime_type)
52
+ end
53
+
54
+ def test_configure_with_mime_type_text
55
+ conf = CONFIG.clone
56
+ conf << "\nstore_as text\n"
57
+ d = create_driver(conf)
58
+ assert_equal 'txt', d.instance.instance_variable_get(:@ext)
59
+ assert_equal 'text/plain', d.instance.instance_variable_get(:@mime_type)
60
+ end
61
+
62
+ def test_configure_with_mime_type_lzo
63
+ conf = CONFIG.clone
64
+ conf << "\nstore_as lzo\n"
65
+ d = create_driver(conf)
66
+ assert_equal 'lzo', d.instance.instance_variable_get(:@ext)
67
+ assert_equal 'application/x-lzop', d.instance.instance_variable_get(:@mime_type)
68
+ rescue => e
69
+ # TODO: replace code with disable lzop command
70
+ assert(e.is_a?(Fluent::ConfigError))
71
+ end
72
+
73
+ def test_path_slicing
74
+ config = CONFIG.clone.gsub(/path\slog/, "path log/%Y/%m/%d")
75
+ d = create_driver(config)
76
+ path_slicer = d.instance.instance_variable_get(:@path_slicer)
77
+ path = d.instance.instance_variable_get(:@path)
78
+ slice = path_slicer.call(path)
79
+ assert_equal slice, Time.now.strftime("log/%Y/%m/%d")
80
+ end
81
+
82
+ def test_path_slicing_utc
83
+ config = CONFIG.clone.gsub(/path\slog/, "path log/%Y/%m/%d")
84
+ config << "\nutc\n"
85
+ d = create_driver(config)
86
+ path_slicer = d.instance.instance_variable_get(:@path_slicer)
87
+ path = d.instance.instance_variable_get(:@path)
88
+ slice = path_slicer.call(path)
89
+ assert_equal slice, Time.now.utc.strftime("log/%Y/%m/%d")
90
+ end
91
+
92
+ def test_format
93
+ d = create_driver
94
+
95
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
96
+ d.emit({"a"=>1}, time)
97
+ d.emit({"a"=>2}, time)
98
+
99
+ d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n]
100
+ d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]
101
+
102
+ d.run
103
+ end
104
+
105
+ def test_format_included_tag_and_time
106
+ config = [CONFIG, 'include_tag_key true', 'include_time_key true'].join("\n")
107
+ d = create_driver(config)
108
+
109
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
110
+ d.emit({"a"=>1}, time)
111
+ d.emit({"a"=>2}, time)
112
+
113
+ d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":1,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
114
+ d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":2,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
115
+
116
+ d.run
117
+ end
118
+
119
+ def test_format_with_format_json
120
+ config = [CONFIG, 'format_json true'].join("\n")
121
+ d = create_driver(config)
122
+
123
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
124
+ d.emit({"a"=>1}, time)
125
+ d.emit({"a"=>2}, time)
126
+
127
+ d.expect_format %[{"a":1}\n]
128
+ d.expect_format %[{"a":2}\n]
129
+
130
+ d.run
131
+ end
132
+
133
+ def test_format_with_format_json_included_tag
134
+ config = [CONFIG, 'format_json true', 'include_tag_key true'].join("\n")
135
+ d = create_driver(config)
136
+
137
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
138
+ d.emit({"a"=>1}, time)
139
+ d.emit({"a"=>2}, time)
140
+
141
+ d.expect_format %[{"a":1,"tag":"test"}\n]
142
+ d.expect_format %[{"a":2,"tag":"test"}\n]
143
+
144
+ d.run
145
+ end
146
+
147
+ def test_format_with_format_json_included_time
148
+ config = [CONFIG, 'format_json true', 'include_time_key true'].join("\n")
149
+ d = create_driver(config)
150
+
151
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
152
+ d.emit({"a"=>1}, time)
153
+ d.emit({"a"=>2}, time)
154
+
155
+ d.expect_format %[{"a":1,"time":"2011-01-02T13:14:15Z"}\n]
156
+ d.expect_format %[{"a":2,"time":"2011-01-02T13:14:15Z"}\n]
157
+
158
+ d.run
159
+ end
160
+
161
+ def test_format_with_format_json_included_tag_and_time
162
+ config = [CONFIG, 'format_json true', 'include_tag_key true', 'include_time_key true'].join("\n")
163
+ d = create_driver(config)
164
+
165
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
166
+ d.emit({"a"=>1}, time)
167
+ d.emit({"a"=>2}, time)
168
+
169
+ d.expect_format %[{"a":1,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
170
+ d.expect_format %[{"a":2,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
171
+
172
+ d.run
173
+ end
174
+
175
+ def test_chunk_to_write
176
+ d = create_driver
177
+
178
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
179
+ d.emit({"a"=>1}, time)
180
+ d.emit({"a"=>2}, time)
181
+
182
+ # S3OutputTest#write returns chunk.read
183
+ data = d.run
184
+
185
+ assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
186
+ %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n],
187
+ data
188
+ end
189
+
190
+ CONFIG2 = %[
191
+ hostname testing.node.local
192
+ aws_key_id test_key_id
193
+ aws_sec_key test_sec_key
194
+ s3_bucket test_bucket
195
+ s3_object_key_format %{path}/events/ts=%{time_slice}/events_%{index}-%{hostname}.%{file_extension}
196
+ time_slice_format %Y%m%d-%H
197
+ path log
198
+ utc
199
+ buffer_type memory
200
+ auto_create_bucket false
201
+ ]
202
+
203
+ def create_time_sliced_driver(additional_conf = '')
204
+ d = Fluent::Test::TimeSlicedOutputTestDriver.new(Fluent::S3Output) do
205
+ private
206
+
207
+ def check_apikeys
208
+ end
209
+ end.configure([CONFIG2, additional_conf].join("\n"))
210
+ d
211
+ end
212
+
213
+ def test_write_with_custom_s3_object_key_format
214
+ # Assert content of event logs which are being sent to S3
215
+ s3obj = flexmock(AWS::S3::S3Object)
216
+ s3obj.should_receive(:exists?).with_any_args.
217
+ and_return { false }
218
+ s3obj.should_receive(:write).with(
219
+ on { |pathname|
220
+ data = nil
221
+ # Event logs are compressed in GZip
222
+ pathname.open { |f|
223
+ gz = Zlib::GzipReader.new(f)
224
+ data = gz.read
225
+ gz.close
226
+ }
227
+ assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
228
+ %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n],
229
+ data
230
+
231
+ pathname.to_s.match(%r|s3-|)
232
+ },
233
+ {:content_type => "application/x-gzip", :reduced_redundancy => false})
234
+
235
+ # Assert the key of S3Object, which event logs are stored in
236
+ s3obj_col = flexmock(AWS::S3::ObjectCollection)
237
+ s3obj_col.should_receive(:[]).with(
238
+ on { |key|
239
+ key == "log/events/ts=20110102-13/events_0-testing.node.local.gz"
240
+ }).
241
+ and_return {
242
+ s3obj
243
+ }
244
+
245
+ # Partial mock the S3Bucket, not to make an actual connection to Amazon S3
246
+ flexmock(AWS::S3::Bucket).new_instances do |bucket|
247
+ bucket.should_receive(:objects).with_any_args.
248
+ and_return {
249
+ s3obj_col
250
+ }
251
+ end
252
+
253
+ # We must use TimeSlicedOutputTestDriver instead of BufferedOutputTestDriver,
254
+ # to make assertions on chunks' keys
255
+ d = create_time_sliced_driver
256
+
257
+ time = Time.parse("2011-01-02 13:14:15 UTC").to_i
258
+ d.emit({"a"=>1}, time)
259
+ d.emit({"a"=>2}, time)
260
+
261
+ # Finally, the instance of S3Output is initialized and then invoked
262
+ d.run
263
+ end
264
+
265
+ def setup_mocks
266
+ s3bucket = flexmock(AWS::S3::Bucket)
267
+ s3bucket.should_receive(:exists?).with_any_args.and_return { false }
268
+ s3bucket_col = flexmock(AWS::S3::BucketCollection)
269
+ s3bucket_col.should_receive(:[]).with_any_args.and_return { s3bucket }
270
+ flexmock(AWS::S3).new_instances do |bucket|
271
+ bucket.should_receive(:buckets).with_any_args.and_return { s3bucket_col }
272
+ end
273
+
274
+ return s3bucket, s3bucket_col
275
+ end
276
+
277
+ def test_auto_create_bucket_false_with_non_existence_bucket
278
+ s3bucket, s3bucket_col = setup_mocks
279
+
280
+ d = create_time_sliced_driver('auto_create_bucket false')
281
+ assert_raise(RuntimeError, "The specified bucket does not exist: bucket = test_bucket") {
282
+ d.run
283
+ }
284
+ end
285
+
286
+ def test_auto_create_bucket_true_with_non_existence_bucket
287
+ s3bucket, s3bucket_col = setup_mocks
288
+ s3bucket_col.should_receive(:create).with_any_args.and_return { true }
289
+
290
+ d = create_time_sliced_driver('auto_create_bucket true')
291
+ assert_nothing_raised {
292
+ d.run
293
+ }
294
+ end
295
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-funplus-s3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.6
5
+ platform: ruby
6
+ authors:
7
+ - Sadayuki Furuhashi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.8.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: yajl-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fluent-mixin-config-placeholders
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.2.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.2.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.9.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.9.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: flexmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.2.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 1.2.0
97
+ description: Amazon S3 output plugin for Fluentd event collector, comma seperated
98
+ and timestamp format
99
+ email: frsyuki@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .travis.yml
105
+ - AUTHORS
106
+ - ChangeLog
107
+ - Gemfile
108
+ - README.rdoc
109
+ - Rakefile
110
+ - VERSION
111
+ - fluent-funplus-s3.gemspec
112
+ - lib/fluent/plugin/out_s3.rb
113
+ - test/out_s3.rb
114
+ homepage: https://github.com/funplus/fluent-funplus-s3
115
+ licenses: []
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.0.3
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: Amazon S3 output plugin for Fluentd event collector, comma seperated and
137
+ timestamp format
138
+ test_files:
139
+ - test/out_s3.rb