fluent-plugin-out-kivera 1.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ce321115bd4b6032242005171702e374148359e62998ca3a4175ab6ae4aa8add
4
+ data.tar.gz: 5891e23d0f951706f17314e708fed643761901f60eafe6e046abddc5e9bed907
5
+ SHA512:
6
+ metadata.gz: 63dc2922e9e0771369c125b3e33105656a9237703e8b5cd0cded8f7808481707af395f3416e8abedb26ffec891a3fee262b6290a841112120d6341c1cd671afa
7
+ data.tar.gz: f47e150d7a7d920d5b4ada2e5d53a758efd39c4b3da4a896fedf7f42cc6027340837e9c77c350fd216fecf80145e5dcf5ec1e01f9fa443c8ebb4e72c12246ad5
@@ -0,0 +1,26 @@
1
+ name: Testing on Ubuntu
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.4', '2.5', '2.6', '2.7' ]
12
+ os:
13
+ - ubuntu-latest
14
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ - name: unit testing
21
+ env:
22
+ CI: true
23
+ run: |
24
+ gem install bundler rake
25
+ bundle install --jobs 4 --retry 3
26
+ bundle exec rake test
@@ -0,0 +1,26 @@
1
+ name: Testing on macOS
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.4', '2.5', '2.6', '2.7' ]
12
+ os:
13
+ - macOS-latest
14
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ - name: unit testing
21
+ env:
22
+ CI: true
23
+ run: |
24
+ gem install bundler rake
25
+ bundle install --jobs 4 --retry 3
26
+ bundle exec rake test
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *~
2
+ \#*
3
+ .\#*
4
+ *.gem
5
+ .bundle
6
+ .ruby-version
7
+ Gemfile.lock
8
+ vendor
data/CHANGELOG.md ADDED
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+ ## 1.3.3
3
+ * Revert x-ndjson format payload behavior
4
+
5
+ ## 1.3.2
6
+ * Fix invalid x-ndjson payload format
7
+
8
+ ## 1.3.1
9
+ * Support compression request
10
+
11
+ ## 1.3.0
12
+ * Support all private key types
13
+ * Recoverable error codes
14
+ * Bulk request with x-ndjson
15
+
16
+ ## 1.2.0
17
+ * Support mutual authentication
18
+
19
+ ## 1.1.7
20
+ * Fix dependent Fluentd version
21
+
22
+ ## 1.1.6
23
+ * Pass chunk directly info built-in placeholder instead of chunk.metadata
24
+
25
+ ## 1.1.5
26
+ * Add :raw serializer
27
+
28
+ ## 1.1.4
29
+ * Add custom formatter feature
30
+ * Tweak Travis CI tasks
31
+
32
+ ## 1.1.3
33
+ * Send query_string to endpoint_url
34
+
35
+ ## 1.1.2
36
+ * Added custom headers feature
37
+
38
+ ## 1.1.1
39
+ * Added plain text transport capability
40
+ * Added specify cacert file for ssl verify
41
+
42
+ ## 1.1.0
43
+ * Support for jwt token authentication
44
+
45
+ ## 1.0.1
46
+ * Added endpoint_url placeholder support
47
+
48
+ ## 1.0.0
49
+ * Use Fluentd v1 API
50
+
51
+ ## 0.2.0
52
+ ### Added
53
+ * SSL is now supported if `endpoint_url` uses the `https` scheme (uses ruby-2.1 syntax internally)
54
+ * New config: set `ssl_no_verify` to `true` to bypass SSL certificate verification.
55
+ Use at your own risk.
56
+ ### Changed
57
+ * Fixed tests:
58
+ * Removed some warnings
59
+ * Fixed failing binary test to use UTF-8
60
+ ### Removed
61
+ * Dropped support of Ruby 1.9-2.0
62
+
63
+ ## 0.1.4
64
+ * #11 Updated Fluentd dependency to: [">= 0.10.0", "< 2"]
65
+ * #10 `password` is now marked as a [secret option](https://github.com/fluent/fluentd/pull/604)
66
+
67
+ ## 0.1.3
68
+ * Added a new configuration option: `raise_on_error` (default: true)
69
+ * In order to let the plugin raise exceptions like it did in 0.1.1: keep using your configuration as-is
70
+ * In order to suppress all exceptions: add `raise_on_error false` to your configuration
71
+
72
+ ## 0.1.2
73
+ * #6 Catch all `StandardError`s during HTTP request to prevent td-agent from freezing
74
+
75
+ ## 0.1.1
76
+ * #2 Use yajl instead of json as json serializer
77
+ * #1 Fix a bug where a nil HTTP response caused the plugin to stop working
78
+
79
+ ## 0.1.0
80
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-out-kivera.gemspec
4
+ gemspec
data/ISSUE_TEMPLATE.md ADDED
@@ -0,0 +1,21 @@
1
+ #### Problem
2
+
3
+ ...
4
+
5
+ #### Steps to replicate
6
+
7
+ Provide example config and message
8
+
9
+ #### Expected Behavior or What you need to ask
10
+
11
+ ...
12
+
13
+ #### Using Fluentd and out_kivera plugin versions
14
+
15
+ * OS version
16
+ * Fluentd v0.12 or v0.14/v1.0
17
+ * paste result of ``fluentd --version`` or ``td-agent --version``
18
+ * out_kivera plugin 1.x.y or 0.x.y
19
+ * paste boot log of fluentd or td-agent
20
+ * paste result of ``fluent-gem list``, ``td-agent-gem list`` or your Gemfile.lock
21
+ * Bear Metal or Within Docker or Kubernetes or others? (optional)
data/LICENSE.txt ADDED
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # fluent-plugin-out-kivera, a plugin for [Fluentd](http://fluentd.org)
2
+
3
+ A generic [fluentd][1] output plugin for sending Kivera proxy logs to the Kivera log ingestion service.
4
+
5
+ ## Configuration options
6
+
7
+ You can specify the path to a config.json file which can contain the following parameters:
8
+
9
+ *config.json*
10
+ {
11
+ "client_id": "",
12
+ "client_secret": "",
13
+ "audience": "",
14
+ "auth0_domain": "",
15
+ "auth0_cert": "",
16
+ "auth0_cert_file": ""
17
+ }
18
+
19
+ Note that parameter specified within the config_file will take precendence over parameters specified in your fluent.conf.
20
+ Also note that the auth0_cert parameter will take precedence over the auth0_cert_file parameter.
21
+
22
+ *fluent.conf*
23
+ <match *>
24
+ @type kivera
25
+ endpoint_url http://localhost.local/api/
26
+ config_file /path/to/config_file.json
27
+ client_id abc123
28
+ client_secret def456
29
+ audience http://api.kivera.io
30
+ auth0_domain auth.nonp.kivera.io
31
+ auth0_cert -----BEGIN CERTIFICATE-----...
32
+ auth0_cert_file /path/to/auth0_cert_file
33
+ ssl_no_verify false # default: false
34
+ rate_limit_msec 100 # default: 0 = no rate limiting
35
+ raise_on_error false # default: true
36
+ recoverable_status_codes 503, 400 # default: 503
37
+ custom_headers {"token":"arbitrary"} # default: nil
38
+ buffered true # default: false. Switch non-buffered/buffered mode
39
+ bulk_request true # default: true. Send events as application/x-ndjson
40
+ compress_request true # default: false. Send compressed events
41
+ </match>
42
+
43
+ ## Usage notes
44
+
45
+ If you'd like to retry failed requests, consider using [fluent-plugin-bufferize][3].
46
+ Or, specify appropriate `recoverable_status_codes` parameter.
47
+
48
+ To send events with bulk_request, you should specify `bulk_request` as `true`
49
+ Note that when this parameter as `true`, Fluentd always send events as `application/x-ndjson`.
50
+ Currently, `application/x-ndjson` is only supported MIME type for bulk_request.
51
+
52
+ ----
53
+
54
+ Heavily based on [fluent-plugin-out-http][2]
55
+
56
+ [1]: http://fluentd.org/
57
+ [2]: https://github.com/fluent-plugins-nursery/fluent-plugin-out-http
58
+ [3]: https://github.com/sabottenda/fluent-plugin-bufferize
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "fluent-plugin-out-kivera"
5
+ gem.version = "1.0.1"
6
+ gem.authors = ["Tyler Matheson"]
7
+ gem.email = ["support@kivera.io"]
8
+ gem.summary = "Fluentd plugin for Kivera"
9
+ gem.description = "A Fluentd output plugin for sending Kivera proxy logs to the Kivera log ingestion service"
10
+ gem.homepage = "https://github.com/kivera-io/fluent-plugin-out-kivera"
11
+ gem.licenses = ["Apache-2.0"]
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = ["lib"]
17
+
18
+ gem.required_ruby_version = '>= 2.1.0'
19
+
20
+ gem.add_runtime_dependency "yajl-ruby", "~> 1.0"
21
+ gem.add_runtime_dependency "fluentd", [">= 0.14.22", "< 2"]
22
+ gem.add_runtime_dependency "jwt", '~> 2.2'
23
+ gem.add_development_dependency "bundler"
24
+ gem.add_development_dependency "rake"
25
+ gem.add_development_dependency "test-unit", ">= 3.1.0"
26
+ end
@@ -0,0 +1,378 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'yajl'
4
+ require 'fluent/plugin/output'
5
+ require 'tempfile'
6
+ require 'openssl'
7
+ require 'zlib'
8
+ require 'jwt'
9
+
10
+ class Fluent::Plugin::HTTPOutput < Fluent::Plugin::Output
11
+ Fluent::Plugin.register_output('kivera', self)
12
+
13
+ class RecoverableResponse < StandardError; end
14
+
15
+ helpers :compat_parameters, :formatter, :storage
16
+
17
+ DEFAULT_STORAGE_TYPE = "local"
18
+ DEFAULT_BUFFER_TYPE = "memory"
19
+ DEFAULT_FORMATTER = "json"
20
+ TOKEN_EXPIRY_OFFSET = 300
21
+
22
+ def initialize
23
+ super
24
+ end
25
+
26
+ # Endpoint URL ex. http://localhost.local/api/
27
+ config_param :endpoint_url, :string
28
+
29
+ # Set Net::HTTP.verify_mode to `OpenSSL::SSL::VERIFY_NONE`
30
+ config_param :ssl_no_verify, :bool, :default => false
31
+
32
+ # Simple rate limiting: ignore any records within `rate_limit_msec`
33
+ # since the last one.
34
+ config_param :rate_limit_msec, :integer, :default => 0
35
+
36
+ # Raise errors that were rescued during HTTP requests?
37
+ config_param :raise_on_error, :bool, :default => true
38
+
39
+ # Specify recoverable error codes
40
+ config_param :recoverable_status_codes, :array, value_type: :integer, default: [503]
41
+
42
+ # kivera proxy client config file
43
+ config_param :config_file, :string, default: ""
44
+
45
+ # kivera proxy client id
46
+ config_param :client_id, :string, default: ""
47
+
48
+ # kivera proxy client secret
49
+ config_param :client_secret, :string, default: ""
50
+
51
+ # kivera audience api
52
+ config_param :audience, :string, default: ""
53
+
54
+ # explicit Kivera auth0 certificate as a string
55
+ config_param :auth0_cert, :string, default: ""
56
+
57
+ # Kivera auth0 certificate file
58
+ config_param :auth0_cert_file, :string, default: ""
59
+
60
+ # Kivera auth0 domain
61
+ config_param :auth0_domain, :string, default: ""
62
+
63
+ # custom headers
64
+ config_param :custom_headers, :hash, :default => nil
65
+
66
+ # Switch non-buffered/buffered plugin
67
+ config_param :bulk_request, :bool, :default => true
68
+ config_param :buffered, :bool, :default => false
69
+ # Compress with gzip except for form serializer
70
+ config_param :compress_request, :bool, :default => false
71
+
72
+ config_section :buffer do
73
+ config_set_default :@type, DEFAULT_BUFFER_TYPE
74
+ config_set_default :chunk_keys, ['tag']
75
+ end
76
+
77
+ config_section :format do
78
+ config_set_default :@type, DEFAULT_FORMATTER
79
+ end
80
+
81
+ def configure(conf)
82
+ compat_parameters_convert(conf, :buffer, :formatter)
83
+ super
84
+
85
+ @ssl_verify_mode = if @ssl_no_verify
86
+ OpenSSL::SSL::VERIFY_NONE
87
+ else
88
+ OpenSSL::SSL::VERIFY_PEER
89
+ end
90
+
91
+ @last_request_time = nil
92
+ raise Fluent::ConfigError, "'tag' in chunk_keys is required." if !@chunk_key_tag && @buffered
93
+
94
+ if @formatter_config = conf.elements('format').first
95
+ @formatter = formatter_create
96
+ end
97
+
98
+ if @bulk_request
99
+ class << self
100
+ alias_method :format, :bulk_request_format
101
+ end
102
+ @formatter = formatter_create(type: :json)
103
+ @serializer = :x_ndjson # secret settings for bulk_request
104
+ else
105
+ class << self
106
+ alias_method :format, :split_request_format
107
+ end
108
+ @serializer = :json
109
+ end
110
+
111
+ # Create local storage for persisting JWT token
112
+ config = conf.elements(name: 'storage').first
113
+ @storage = storage_create(usage: 'jwt_token', conf: config, default_type: 'local')
114
+
115
+ if ! @config_file.empty?
116
+ creds = File.read(@config_file)
117
+ parsed = Yajl::Parser.new.parse(StringIO.new(creds))
118
+ @client_id = parsed.fetch("client_id", @client_id)
119
+ @client_secret = parsed.fetch("client_secret", @client_secret)
120
+ @audience = parsed.fetch("audience", @audience)
121
+ @auth0_cert = parsed.fetch("auth0_cert", @auth0_cert)
122
+ @auth0_cert_file = parsed.fetch("auth0_cert", @auth0_cert_file)
123
+ @auth0_domain = parsed.fetch("auth0_domain", @auth0_domain)
124
+ end
125
+
126
+ if @auth0_cert.empty? && ! @auth0_cert_file.empty?
127
+ @auth0_cert = File.read(@auth0_cert_file)
128
+ end
129
+
130
+ if @client_id.empty? &&
131
+ @client_secret.empty? &&
132
+ @audience.empty? &&
133
+ @auth0_cert.empty? &&
134
+ @auth0_domain.empty?
135
+ params = "client_id, client_secret, audience, auth0_cert and auth0_domain"
136
+ log.error "Missing configuration. Either specify a config_file or set the #{params} parameters"
137
+ end
138
+
139
+ end
140
+
141
+ def start
142
+ super
143
+ end
144
+
145
+ def shutdown
146
+ super
147
+ end
148
+
149
+ def format_url(tag, time, record)
150
+ @endpoint_url
151
+ end
152
+
153
+ def set_body(req, tag, time, record)
154
+ if @serializer == :json
155
+ set_json_body(req, record)
156
+ elsif @serializer == :x_ndjson
157
+ set_bulk_body(req, record)
158
+ else
159
+ req.set_form_data(record)
160
+ end
161
+ req
162
+ end
163
+
164
+ def set_header(req, tag, time, record)
165
+ if @custom_headers
166
+ @custom_headers.each do |k,v|
167
+ req[k] = v
168
+ end
169
+ req
170
+ else
171
+ req
172
+ end
173
+ end
174
+
175
+ def refresh_jwt_token
176
+ if @storage.get(:jwt_token)
177
+ if token_expired
178
+ @storage.put(:jwt_token, new_jwt_token)
179
+ end
180
+ else
181
+ @storage.put(:jwt_token, new_jwt_token)
182
+ end
183
+ end
184
+
185
+ def token_expired
186
+ x509 = OpenSSL::X509::Certificate.new(@auth0_cert)
187
+ begin
188
+ decoded_token = JWT.decode @storage.get(:jwt_token), x509.public_key, true, { algorithm: 'RS256' }
189
+ rescue => e
190
+ log.info 'JWT token expired'
191
+ return true
192
+ else
193
+ if decoded_token[0]['exp'] - Time.now.to_f < TOKEN_EXPIRY_OFFSET
194
+ log.info 'JWT token about to expire'
195
+ return true
196
+ end
197
+ return false
198
+ end
199
+ end
200
+
201
+ def new_jwt_token
202
+ url = "https://" + @auth0_domain + "/oauth/token"
203
+ uri = URI.parse(url)
204
+ req = Net::HTTP::Post.new(uri.to_s)
205
+ payload = {
206
+ "client_id" => @client_id,
207
+ "client_secret" => @client_secret,
208
+ "audience" => @audience,
209
+ "grant_type" => "client_credentials"
210
+ }
211
+ set_json_body(req, payload)
212
+ res = https(uri).request(req)
213
+ case res
214
+ when Net::HTTPSuccess then
215
+ parsed = Yajl::Parser.new.parse(StringIO.new(res.body))
216
+ log.info 'Generated new JWT token'
217
+ parsed['access_token']
218
+ else
219
+ log.warn "Failed to get token for client #{@client_id}"
220
+ end
221
+ end
222
+
223
+ def https(uri)
224
+ Net::HTTP.new(uri.host, uri.port).tap { |http|
225
+ http.use_ssl = true
226
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
227
+ }
228
+ end
229
+
230
+ def compress_body(req, data)
231
+ return unless @compress_request
232
+ gz = Zlib::GzipWriter.new(StringIO.new)
233
+ gz << data
234
+
235
+ req['Content-Encoding'] = "gzip"
236
+ req.body = gz.close.string
237
+ end
238
+
239
+ def set_json_body(req, data)
240
+ req.body = Yajl.dump(data)
241
+ req['Content-Type'] = 'application/json'
242
+ compress_body(req, req.body)
243
+ end
244
+
245
+ def set_bulk_body(req, data)
246
+ req.body = data.to_s
247
+ req['Content-Type'] = 'application/x-ndjson'
248
+ compress_body(req, req.body)
249
+ end
250
+
251
+ def set_jwt_auth(req)
252
+ refresh_jwt_token
253
+ req['Authorization'] = "Bearer #{@storage.get(:jwt_token)}"
254
+ end
255
+
256
+ def create_request(tag, time, record)
257
+ url = format_url(tag, time, record)
258
+ uri = URI.parse(url)
259
+ req = Net::HTTP::Put.new(uri.request_uri)
260
+ set_body(req, tag, time, record)
261
+ set_header(req, tag, time, record)
262
+ set_jwt_auth(req)
263
+ return req, uri
264
+ end
265
+
266
+ def http_opts(uri)
267
+ opts = {
268
+ :use_ssl => uri.scheme == 'https'
269
+ }
270
+ opts[:verify_mode] = @ssl_verify_mode if opts[:use_ssl]
271
+ opts
272
+ end
273
+
274
+ def proxies
275
+ ENV['HTTPS_PROXY'] || ENV['HTTP_PROXY'] || ENV['http_proxy'] || ENV['https_proxy']
276
+ end
277
+
278
+ def send_request(req, uri)
279
+ is_rate_limited = (@rate_limit_msec != 0 and not @last_request_time.nil?)
280
+ if is_rate_limited and ((Time.now.to_f - @last_request_time) * 1000.0 < @rate_limit_msec)
281
+ log.info('Dropped request due to rate limiting')
282
+ return
283
+ end
284
+
285
+ res = nil
286
+
287
+ begin
288
+
289
+ if proxy = proxies
290
+ proxy_uri = URI.parse(proxy)
291
+
292
+ res = Net::HTTP.start(uri.host, uri.port,
293
+ proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password,
294
+ **http_opts(uri)) {|http| http.request(req) }
295
+ else
296
+ res = Net::HTTP.start(uri.host, uri.port, **http_opts(uri)) {|http| http.request(req) }
297
+ end
298
+
299
+ rescue => e # rescue all StandardErrors
300
+ # server didn't respond
301
+ log.warn "Net::HTTP.#{req.method.capitalize} raises exception: #{e.class}, '#{e.message}'"
302
+ raise e if @raise_on_error
303
+ else
304
+ unless res and res.is_a?(Net::HTTPSuccess)
305
+ res_summary = if res
306
+ "#{res.code} #{res.message} #{res.body}"
307
+ else
308
+ "res=nil"
309
+ end
310
+ if @recoverable_status_codes.include?(res.code.to_i)
311
+ raise RecoverableResponse, res_summary
312
+ else
313
+ log.warn "failed to #{req.method} #{uri} (#{res_summary})"
314
+ end
315
+ end #end unless
316
+ end # end begin
317
+ end # end send_request
318
+
319
+ def handle_record(tag, time, record)
320
+ if @formatter_config
321
+ record = @formatter.format(tag, time, record)
322
+ end
323
+ req, uri = create_request(tag, time, record)
324
+ send_request(req, uri)
325
+ end
326
+
327
+ def handle_records(tag, time, chunk)
328
+ req, uri = create_request(tag, time, chunk.read)
329
+ send_request(req, uri)
330
+ end
331
+
332
+ def prefer_buffered_processing
333
+ @buffered
334
+ end
335
+
336
+ def format(tag, time, record)
337
+ # For safety.
338
+ end
339
+
340
+ def split_request_format(tag, time, record)
341
+ [time, record].to_msgpack
342
+ end
343
+
344
+ def bulk_request_format(tag, time, record)
345
+ @formatter.format(tag, time, record)
346
+ end
347
+
348
+ def formatted_to_msgpack_binary?
349
+ if @bulk_request
350
+ false
351
+ else
352
+ true
353
+ end
354
+ end
355
+
356
+ def multi_workers_ready?
357
+ true
358
+ end
359
+
360
+ def process(tag, es)
361
+ es.each do |time, record|
362
+ handle_record(tag, time, record)
363
+ end
364
+ end
365
+
366
+ def write(chunk)
367
+ tag = chunk.metadata.tag
368
+ @endpoint_url = extract_placeholders(@endpoint_url, chunk)
369
+ if @bulk_request
370
+ time = Fluent::Engine.now
371
+ handle_records(tag, time, chunk)
372
+ else
373
+ chunk.msgpack_each do |time, record|
374
+ handle_record(tag, time, record)
375
+ end
376
+ end
377
+ end
378
+ end