fluent-plugin-azurestorage 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 776476b29eb416de1bf483aaf7f2a7a864ed10fe
4
- data.tar.gz: 95cdf6fa62d1f4e772179694a1a3f7a611ee3149
3
+ metadata.gz: 19fc61140e33ed5db2e6f41736d8a41515fd2eab
4
+ data.tar.gz: c83005cb236f4b19a8c547945930e4783bd02366
5
5
  SHA512:
6
- metadata.gz: cec4a7f65d722e23bb49e46211c482b10f7c3e9f15e0ed286b960599aac7034a5d110db122ab072ace1ea056a820b68d7e2f55a5b9033f6252a6ba71710718cc
7
- data.tar.gz: b21756a7d13066e1155853ee3ed2bd9262ef24304483f0306c509f34538ae4ee9029efd0a1d161432f65520785c30e7033f35aeee9e10a68acaca710bc0cd6ad
6
+ metadata.gz: ca632bb038f91cf996ff63bb171bdda0e67baee3c6911513ef4f884862cb2fd0ea17161273518134eb952eb406b2561ae1b07968466e1cb190b112c7a7bbd65a
7
+ data.tar.gz: 12c019aa3ef994cd7089f5fe419e5ffd301226429054a85f18b94e3abf24b157c2427854755f772fa27fd726d70632fdbf21178c61fce7950253fb20bd36309f
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  Gemfile.lock
10
10
  vendor
11
11
  .ruby-version
12
+ *.komodoproject
data/README.md CHANGED
@@ -16,6 +16,48 @@ $ gem install fluent-plugin-azurestorage
16
16
 
17
17
  ## Configuration
18
18
 
19
+ ### v0.14 style
20
+
21
+ ```
22
+ <match pattern>
23
+ type azurestorage
24
+
25
+ azure_storage_account <your azure storage account>
26
+ azure_storage_access_key <your azure storage access key>
27
+ azure_container <your azure storage container>
28
+ azure_storage_type blob
29
+ store_as gzip
30
+ auto_create_container true
31
+ path logs/
32
+ azure_object_key_format %{path}%{time_slice}_%{index}.%{file_extension}
33
+ time_slice_format %Y%m%d-%H
34
+ # if you want to use ${tag} or %Y/%m/%d/ like syntax in path / s3_object_key_format,
35
+ # need to specify tag for ${tag} and time for %Y/%m/%d in <buffer> argument.
36
+ <buffer tag,time>
37
+ @type file
38
+ path /var/log/fluent/azurestorage
39
+ timekey 3600 # 1 hour partition
40
+ timekey_wait 10m
41
+ timekey_use_utc true # use utc
42
+ </buffer>
43
+ </match>
44
+ ```
45
+
46
+ For `<buffer>`, you can use any record field in `path` / `azure_object_key_format`.
47
+
48
+ ```
49
+ path logs/${tag}/${foo}
50
+ <buffer tag,foo>
51
+ # parameters...
52
+ </buffer>
53
+ ```
54
+
55
+ See official article for more detail: Buffer section configurations
56
+
57
+ Note that this configuration doesn't work with fluentd v0.12.
58
+
59
+ ### v0.12 style
60
+
19
61
  ```
20
62
  <match pattern>
21
63
  type azurestorage
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.1.0
@@ -17,9 +17,9 @@ Gem::Specification.new do |gem|
17
17
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
18
  gem.require_paths = ['lib']
19
19
 
20
- gem.add_dependency "fluentd", [">= 0.12.0", "< 2"]
20
+ gem.add_dependency "fluentd", [">= 0.14.0", "< 2"]
21
21
  gem.add_dependency "azure", [">= 0.7.1", "<= 0.7.7"]
22
- gem.add_dependency "fluent-mixin-config-placeholders", ">= 0.3.0"
22
+ gem.add_dependency "uuidtools", ">= 2.1.5"
23
23
  gem.add_development_dependency "rake", ">= 0.9.2"
24
24
  gem.add_development_dependency "test-unit", ">= 3.0.8"
25
25
  gem.add_development_dependency "test-unit-rr", ">= 1.0.3"
@@ -1,16 +1,18 @@
1
- module Fluent
2
- require 'fluent/mixin/config_placeholders'
3
-
4
- class AzureStorageOutput < Fluent::TimeSlicedOutput
1
+ require 'azure'
2
+ require 'fluent/plugin/upload_service'
3
+ require 'zlib'
4
+ require 'time'
5
+ require 'tempfile'
6
+ require 'fluent/plugin/output'
7
+
8
+ module Fluent::Plugin
9
+ class AzureStorageOutput < Fluent::Plugin::Output
5
10
  Fluent::Plugin.register_output('azurestorage', self)
6
11
 
12
+ helpers :compat_parameters, :formatter, :inject
13
+
7
14
  def initialize
8
15
  super
9
- require 'azure'
10
- require 'fluent/plugin/upload_service'
11
- require 'zlib'
12
- require 'time'
13
- require 'tempfile'
14
16
 
15
17
  @compressor = nil
16
18
  end
@@ -26,27 +28,32 @@ module Fluent
26
28
  config_param :format, :string, :default => "out_file"
27
29
  config_param :command_parameter, :string, :default => nil
28
30
 
29
- attr_reader :bs
31
+ DEFAULT_FORMAT_TYPE = "out_file"
30
32
 
31
- include Fluent::Mixin::ConfigPlaceholders
33
+ config_section :format do
34
+ config_set_default :@type, DEFAULT_FORMAT_TYPE
35
+ end
32
36
 
33
- def placeholders
34
- [:percent]
37
+ config_section :buffer do
38
+ config_set_default :chunk_keys, ['time']
39
+ config_set_default :timekey, (60 * 60 * 24)
35
40
  end
36
41
 
42
+ attr_reader :bs
43
+
37
44
  def configure(conf)
45
+ compat_parameters_convert(conf, :buffer, :formatter, :inject)
38
46
  super
39
47
 
40
48
  begin
41
49
  @compressor = COMPRESSOR_REGISTRY.lookup(@store_as).new(:buffer_type => @buffer_type, :log => log)
42
50
  rescue => e
43
- $log.warn "#{@store_as} not found. Use 'text' instead"
51
+ log.warn "#{@store_as} not found. Use 'text' instead"
44
52
  @compressor = TextCompressor.new
45
53
  end
46
54
  @compressor.configure(conf)
47
55
 
48
- @formatter = Plugin.new_formatter(@format)
49
- @formatter.configure(conf)
56
+ @formatter = formatter_create
50
57
 
51
58
  if @localtime
52
59
  @path_slicer = Proc.new {|path|
@@ -70,6 +77,13 @@ module Fluent
70
77
  else
71
78
  'blob'
72
79
  end
80
+ # For backward compatibility
81
+ # TODO: Remove time_slice_format when end of support compat_parameters
82
+ @configured_time_slice_format = conf['time_slice_format']
83
+ end
84
+
85
+ def multi_workers_ready?
86
+ true
73
87
  end
74
88
 
75
89
  def start
@@ -88,25 +102,32 @@ module Fluent
88
102
  end
89
103
 
90
104
  def format(tag, time, record)
91
- @formatter.format(tag, time, record)
105
+ r = inject_values_to_record(tag, time, record)
106
+ @formatter.format(tag, time, r)
92
107
  end
93
108
 
94
109
  def write(chunk)
95
110
  i = 0
111
+ metadata = chunk.metadata
96
112
  previous_path = nil
113
+ time_slice_format = @configured_time_slice_format || timekey_to_timeformat(@buffer_config['timekey'])
114
+ time_slice = if metadata.timekey.nil?
115
+ ''.freeze
116
+ else
117
+ Time.at(metadata.timekey).utc.strftime(time_slice_format)
118
+ end
97
119
 
98
120
  begin
99
121
  path = @path_slicer.call(@path)
100
122
  values_for_object_key = {
101
- "path" => path,
102
- "time_slice" => chunk.key,
103
- "file_extension" => @compressor.ext,
104
- "index" => i,
105
- "uuid_flush" => uuid_random
106
- }
107
- storage_path = @azure_object_key_format.gsub(%r(%{[^}]+})) { |expr|
108
- values_for_object_key[expr[2...expr.size-1]]
123
+ "%{path}" => path,
124
+ "%{time_slice}" => time_slice,
125
+ "%{file_extension}" => @compressor.ext,
126
+ "%{index}" => i,
127
+ "%{uuid_flush}" => uuid_random
109
128
  }
129
+ storage_path = @azure_object_key_format.gsub(%r(%{[^}]+}), values_for_object_key)
130
+ storage_path = extract_placeholders(storage_path, metadata)
110
131
  if (i > 0) && (storage_path == previous_path)
111
132
  raise "duplicated path is generated. use %{index} in azure_object_key_format: path = #{storage_path}"
112
133
  end
@@ -140,8 +161,24 @@ module Fluent
140
161
  end
141
162
  end
142
163
 
164
+ def uuid_random
165
+ require 'uuidtools'
166
+ ::UUIDTools::UUID.random_create.to_s
167
+ end
168
+
169
+ # This is stolen from Fluentd
170
+ def timekey_to_timeformat(timekey)
171
+ case timekey
172
+ when nil then ''
173
+ when 0...60 then '%Y%m%d%H%M%S' # 60 exclusive
174
+ when 60...3600 then '%Y%m%d%H%M'
175
+ when 3600...86400 then '%Y%m%d%H'
176
+ else '%Y%m%d'
177
+ end
178
+ end
179
+
143
180
  class Compressor
144
- include Configurable
181
+ include Fluent::Configurable
145
182
 
146
183
  def initialize(opts = {})
147
184
  super()
@@ -220,7 +257,7 @@ module Fluent
220
257
  end
221
258
  end
222
259
 
223
- COMPRESSOR_REGISTRY = Registry.new(:azurestorage_compressor_type, 'fluent/plugin/azurestorage_compressor_')
260
+ COMPRESSOR_REGISTRY = Fluent::Registry.new(:azurestorage_compressor_type, 'fluent/plugin/azurestorage_compressor_')
224
261
  {
225
262
  'gzip' => GzipCompressor,
226
263
  'json' => JsonCompressor,
@@ -1,10 +1,14 @@
1
1
  require 'fluent/test'
2
+ require 'fluent/test/driver/output'
3
+ require 'fluent/test/helpers'
2
4
  require 'fluent/plugin/out_azurestorage'
3
5
 
4
6
  require 'test/unit/rr'
5
7
  require 'zlib'
6
8
  require 'fileutils'
7
9
 
10
+ include Fluent::Test::Helpers
11
+
8
12
  class AzureStorageOutputTest < Test::Unit::TestCase
9
13
  def setup
10
14
  require 'azure'
@@ -21,9 +25,16 @@ class AzureStorageOutputTest < Test::Unit::TestCase
21
25
  ]
22
26
 
23
27
  def create_driver(conf = CONFIG)
24
- Fluent::Test::TimeSlicedOutputTestDriver.new(Fluent::AzureStorageOutput) do
28
+ Fluent::Test::Driver::Output.new(Fluent::Plugin::AzureStorageOutput) do
29
+ # for testing.
30
+ def contents
31
+ @emit_streams
32
+ end
33
+
25
34
  def write(chunk)
26
- chunk.read
35
+ @emit_streams = []
36
+ event = chunk.read
37
+ @emit_streams << event
27
38
  end
28
39
 
29
40
  private
@@ -64,11 +75,9 @@ class AzureStorageOutputTest < Test::Unit::TestCase
64
75
  conf = CONFIG.clone
65
76
  conf << "\nstore_as lzo\n"
66
77
  d = create_driver(conf)
67
- assert_equal 'lzo', d.instance.instance_variable_get(:@compressor).ext
68
- assert_equal 'application/x-lzop', d.instance.instance_variable_get(:@compressor).content_type
69
- rescue => e
70
- # TODO: replace code with disable lzop command
71
- assert(e.is_a?(Fluent::ConfigError))
78
+ # Fallback to text/plain.
79
+ assert_equal 'txt', d.instance.instance_variable_get(:@compressor).ext
80
+ assert_equal 'text/plain', d.instance.instance_variable_get(:@compressor).content_type
72
81
  end
73
82
 
74
83
  def test_path_slicing
@@ -93,109 +102,118 @@ class AzureStorageOutputTest < Test::Unit::TestCase
93
102
  def test_format
94
103
  d = create_driver
95
104
 
96
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
97
- d.emit({"a"=>1}, time)
98
- d.emit({"a"=>2}, time)
99
-
100
- d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n]
101
- d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]
105
+ time = event_time("2011-01-02 13:14:15 UTC")
106
+ d.run(default_tag: "test") do
107
+ d.feed(time, {"a"=>1})
108
+ d.feed(time, {"a"=>2})
109
+ end
110
+ formatted = d.formatted
102
111
 
103
- d.run
112
+ assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n], formatted[0]
113
+ assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n], formatted[1]
104
114
  end
105
115
 
106
116
  def test_format_included_tag_and_time
107
117
  config = [CONFIG, 'include_tag_key true', 'include_time_key true'].join("\n")
108
118
  d = create_driver(config)
109
119
 
110
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
111
- d.emit({"a"=>1}, time)
112
- d.emit({"a"=>2}, time)
120
+ time = event_time("2011-01-02 13:14:15 UTC")
121
+ d.run(default_tag: "test") do
122
+ d.feed(time, {"a"=>1})
123
+ d.feed(time, {"a"=>2})
124
+ end
125
+ formatted = d.formatted
113
126
 
114
- d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":1,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
115
- d.expect_format %[2011-01-02T13:14:15Z\ttest\t{"a":2,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
116
-
117
- d.run
127
+ assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1,"tag":"test","time":"2011-01-02T13:14:15Z"}\n], formatted[0]
128
+ assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":2,"tag":"test","time":"2011-01-02T13:14:15Z"}\n], d.formatted[1]
118
129
  end
119
130
 
120
131
  def test_format_with_format_ltsv
121
132
  config = [CONFIG, 'format ltsv'].join("\n")
122
133
  d = create_driver(config)
123
134
 
124
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
125
- d.emit({"a"=>1, "b"=>1}, time)
126
- d.emit({"a"=>2, "b"=>2}, time)
127
-
128
- d.expect_format %[a:1\tb:1\n]
129
- d.expect_format %[a:2\tb:2\n]
135
+ time = event_time("2011-01-02 13:14:15 UTC")
136
+ d.run(default_tag: "test") do
137
+ d.feed(time, {"a"=>1, "b"=>1})
138
+ d.feed(time, {"a"=>2, "b"=>2})
139
+ end
140
+ formatted = d.formatted
130
141
 
131
- d.run
142
+ assert_equal %[a:1\tb:1\n], formatted[0]
143
+ assert_equal %[a:2\tb:2\n], formatted[1]
132
144
  end
133
145
 
134
146
  def test_format_with_format_json
135
147
  config = [CONFIG, 'format json'].join("\n")
136
148
  d = create_driver(config)
137
149
 
138
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
139
- d.emit({"a"=>1}, time)
140
- d.emit({"a"=>2}, time)
141
-
142
- d.expect_format %[{"a":1}\n]
143
- d.expect_format %[{"a":2}\n]
150
+ time = event_time("2011-01-02 13:14:15 UTC")
151
+ d.run(default_tag: "test") do
152
+ d.feed(time, {"a"=>1})
153
+ d.feed(time, {"a"=>2})
154
+ end
155
+ formatted = d.formatted
144
156
 
145
- d.run
157
+ assert_equal %[{"a":1}\n], formatted[0]
158
+ assert_equal %[{"a":2}\n], formatted[1]
146
159
  end
147
160
 
148
161
  def test_format_with_format_json_included_tag
149
162
  config = [CONFIG, 'format json', 'include_tag_key true'].join("\n")
150
163
  d = create_driver(config)
151
164
 
152
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
153
- d.emit({"a"=>1}, time)
154
- d.emit({"a"=>2}, time)
165
+ time = event_time("2011-01-02 13:14:15 UTC")
166
+ d.run(default_tag: "test") do
167
+ d.feed(time, {"a"=>1})
168
+ d.feed(time, {"a"=>2})
169
+ end
170
+ formatted = d.formatted
155
171
 
156
- d.expect_format %[{"a":1,"tag":"test"}\n]
157
- d.expect_format %[{"a":2,"tag":"test"}\n]
158
-
159
- d.run
172
+ assert_equal %[{"a":1,"tag":"test"}\n], formatted[0]
173
+ assert_equal %[{"a":2,"tag":"test"}\n], formatted[1]
160
174
  end
161
175
 
162
176
  def test_format_with_format_json_included_time
163
177
  config = [CONFIG, 'format json', 'include_time_key true'].join("\n")
164
178
  d = create_driver(config)
165
179
 
166
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
167
- d.emit({"a"=>1}, time)
168
- d.emit({"a"=>2}, time)
169
-
170
- d.expect_format %[{"a":1,"time":"2011-01-02T13:14:15Z"}\n]
171
- d.expect_format %[{"a":2,"time":"2011-01-02T13:14:15Z"}\n]
180
+ time = event_time("2011-01-02 13:14:15 UTC")
181
+ d.run(default_tag: "test") do
182
+ d.feed(time, {"a"=>1})
183
+ d.feed(time, {"a"=>2})
184
+ end
185
+ formatted = d.formatted
172
186
 
173
- d.run
187
+ assert_equal %[{"a":1,"time":"2011-01-02T13:14:15Z"}\n], formatted[0]
188
+ assert_equal %[{"a":2,"time":"2011-01-02T13:14:15Z"}\n], formatted[1]
174
189
  end
175
190
 
176
191
  def test_format_with_format_json_included_tag_and_time
177
192
  config = [CONFIG, 'format json', 'include_tag_key true', 'include_time_key true'].join("\n")
178
193
  d = create_driver(config)
179
194
 
180
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
181
- d.emit({"a"=>1}, time)
182
- d.emit({"a"=>2}, time)
183
-
184
- d.expect_format %[{"a":1,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
185
- d.expect_format %[{"a":2,"tag":"test","time":"2011-01-02T13:14:15Z"}\n]
195
+ time = event_time("2011-01-02 13:14:15 UTC")
196
+ d.run(default_tag: "test") do
197
+ d.feed(time, {"a"=>1})
198
+ d.feed(time, {"a"=>2})
199
+ end
200
+ formatted = d.formatted
186
201
 
187
- d.run
202
+ assert_equal %[{"a":1,"tag":"test","time":"2011-01-02T13:14:15Z"}\n], formatted[0]
203
+ assert_equal %[{"a":2,"tag":"test","time":"2011-01-02T13:14:15Z"}\n], formatted[1]
188
204
  end
189
205
 
190
206
  def test_chunk_to_write
191
207
  d = create_driver
192
208
 
193
- time = Time.parse("2011-01-02 13:14:15 UTC").to_i
194
- d.emit({"a"=>1}, time)
195
- d.emit({"a"=>2}, time)
209
+ time = event_time("2011-01-02 13:14:15 UTC")
210
+ d.run(default_tag: "test") do
211
+ d.feed(time, {"a"=>1})
212
+ d.feed(time, {"a"=>2})
213
+ end
196
214
 
197
- # AzureStorageOutputTest#write returns chunk.read
198
- data = d.run
215
+ # Stubbed #write and #emit_streams returns chunk.read result.
216
+ data = d.instance.contents
199
217
 
200
218
  assert_equal [%[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
201
219
  %[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]],
@@ -216,7 +234,7 @@ class AzureStorageOutputTest < Test::Unit::TestCase
216
234
  ]
217
235
 
218
236
  def create_time_sliced_driver(conf = CONFIG_TIME_SLICE)
219
- d = Fluent::Test::TimeSlicedOutputTestDriver.new(Fluent::AzureStorageOutput) do
237
+ d = Fluent::Test::Driver::Output.new(Fluent::Plugin::AzureStorageOutput) do
220
238
  end.configure(conf)
221
239
  d
222
240
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-azurestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hidemasa Togashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-23 00:00:00.000000000 Z
11
+ date: 2017-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.12.0
19
+ version: 0.14.0
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '2'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 0.12.0
29
+ version: 0.14.0
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2'
@@ -51,19 +51,19 @@ dependencies:
51
51
  - !ruby/object:Gem::Version
52
52
  version: 0.7.7
53
53
  - !ruby/object:Gem::Dependency
54
- name: fluent-mixin-config-placeholders
54
+ name: uuidtools
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: 0.3.0
59
+ version: 2.1.5
60
60
  type: :runtime
61
61
  prerelease: false
62
62
  version_requirements: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
- version: 0.3.0
66
+ version: 2.1.5
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: rake
69
69
  requirement: !ruby/object:Gem::Requirement