fluent-funplus-s3 0.3.6

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,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