Skip to content

Commit 9e52dd3

Browse files
authored
Merge pull request #48 from DataDog/remeh/v2-routes
Support sending logs to Datadog v2 endpoints.
2 parents b6f8bf7 + 49f8e4e commit 9e52dd3

File tree

6 files changed

+108
-17
lines changed

6 files changed

+108
-17
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,4 @@ foo/
4040
*.iml
4141
.idea/
4242

43-
fluent/
4443
Gemfile.lock

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 0.14.0
2+
- Support Datadog v2 endpoints [#48](https://github.com/DataDog/fluent-plugin-datadog/pull/48)
3+
14
## 0.13.0
25
- Support HTTP proxies [#46](https://github.com/DataDog/fluent-plugin-datadog/pull/46)
36

fluent-plugin-datadog.gemspec

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
77
lib = File.expand_path('../lib', __FILE__)
88
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
99

10+
require "fluent/plugin/version.rb"
11+
1012
Gem::Specification.new do |spec|
1113
spec.name = "fluent-plugin-datadog"
12-
spec.version = "0.13.0"
14+
spec.version = DatadogFluentPlugin::VERSION
1315
spec.authors = ["Datadog Solutions Team"]
1416
spec.email = ["[email protected]"]
1517
spec.summary = "Datadog output plugin for Fluent event collector"
1618
spec.homepage = "http://datadoghq.com"
1719
spec.license = "Apache-2.0"
1820

19-
spec.files = [".gitignore", "Gemfile", "LICENSE", "README.md", "Rakefile", "fluent-plugin-datadog.gemspec", "lib/fluent/plugin/out_datadog.rb"]
21+
spec.files = [".gitignore", "Gemfile", "LICENSE", "README.md", "Rakefile", "fluent-plugin-datadog.gemspec", "lib/fluent/plugin/version.rb", "lib/fluent/plugin/out_datadog.rb"]
2022
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
2123
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
2224
spec.require_paths = ["lib"]
@@ -28,5 +30,5 @@ Gem::Specification.new do |spec|
2830
spec.add_development_dependency "test-unit", '~> 3.1'
2931
spec.add_development_dependency "rake", "~> 12.0"
3032
spec.add_development_dependency "yajl-ruby", "~> 1.2"
31-
spec.add_development_dependency 'webmock', "~> 3.5.0"
33+
spec.add_development_dependency 'webmock', "~> 3.6.0"
3234
end

lib/fluent/plugin/out_datadog.rb

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
require "zlib"
1010
require "fluent/plugin/output"
1111

12+
require_relative "version"
13+
1214
class Fluent::DatadogOutput < Fluent::Plugin::Output
1315
class RetryableError < StandardError;
1416
end
@@ -50,6 +52,7 @@ class RetryableError < StandardError;
5052
config_param :compression_level, :integer, :default => 6
5153
config_param :no_ssl_validation, :bool, :default => false
5254
config_param :http_proxy, :string, :default => nil
55+
config_param :force_v1_routes, :bool, :default => false
5356

5457
# Format settings
5558
config_param :use_json, :bool, :default => true
@@ -89,7 +92,7 @@ def formatted_to_msgpack_binary?
8992

9093
def start
9194
super
92-
@client = new_client(log, @api_key, @use_http, @use_ssl, @no_ssl_validation, @host, @ssl_port, @port, @http_proxy, @use_compression)
95+
@client = new_client(log, @api_key, @use_http, @use_ssl, @no_ssl_validation, @host, @ssl_port, @port, @http_proxy, @use_compression, @force_v1_routes)
9396
end
9497

9598
def shutdown
@@ -261,9 +264,9 @@ def gzip_compress(payload, compression_level)
261264
end
262265

263266
# Build a new transport client
264-
def new_client(logger, api_key, use_http, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression)
267+
def new_client(logger, api_key, use_http, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, force_v1_routes)
265268
if use_http
266-
DatadogHTTPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key
269+
DatadogHTTPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key, force_v1_routes
267270
else
268271
DatadogTCPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port
269272
end
@@ -301,20 +304,29 @@ class DatadogHTTPClient < DatadogClient
301304
require 'net/http'
302305
require 'net/http/persistent'
303306

304-
def initialize(logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key)
307+
def initialize(logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key, force_v1_routes = false)
305308
@logger = logger
306309
protocol = use_ssl ? "https" : "http"
307310
port = use_ssl ? ssl_port : port
308-
@uri = URI("#{protocol}://#{host}:#{port.to_s}/v1/input/#{api_key}")
311+
if force_v1_routes
312+
@uri = URI("#{protocol}://#{host}:#{port.to_s}/v1/input/#{api_key}")
313+
else
314+
@uri = URI("#{protocol}://#{host}:#{port.to_s}/api/v2/logs")
315+
end
309316
proxy_uri = :ENV
310317
if http_proxy
311318
proxy_uri = URI.parse(http_proxy)
312319
elsif ENV['HTTP_PROXY'] || ENV['http_proxy']
313320
logger.info("Using HTTP proxy defined in `HTTP_PROXY`/`http_proxy` env vars")
314321
end
315-
logger.info("Starting HTTP connection to #{protocol}://#{host}:#{port.to_s} with compression " + (use_compression ? "enabled" : "disabled"))
322+
logger.info("Starting HTTP connection to #{protocol}://#{host}:#{port.to_s} with compression " + (use_compression ? "enabled" : "disabled") + (force_v1_routes ? " using v1 routes" : " using v2 routes"))
316323
@client = Net::HTTP::Persistent.new name: "fluent-plugin-datadog-logcollector", proxy: proxy_uri
317324
@client.verify_mode = OpenSSL::SSL::VERIFY_NONE if no_ssl_validation
325+
unless force_v1_routes
326+
@client.override_headers["DD-API-KEY"] = api_key
327+
@client.override_headers["DD-EVP-ORIGIN"] = "fluent"
328+
@client.override_headers["DD-EVP-ORIGIN-VERSION"] = DatadogFluentPlugin::VERSION
329+
end
318330
@client.override_headers["Content-Type"] = "application/json"
319331
if use_compression
320332
@client.override_headers["Content-Encoding"] = "gzip"
@@ -330,7 +342,8 @@ def send(payload)
330342
request.body = payload
331343
response = @client.request @uri, request
332344
res_code = response.code.to_i
333-
if res_code >= 500
345+
# on a backend error or on an http 429, retry with backoff
346+
if res_code >= 500 || res_code == 429
334347
raise RetryableError.new "Unable to send payload: #{res_code} #{response.message}"
335348
end
336349
if res_code >= 400

lib/fluent/plugin/version.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
module DatadogFluentPlugin
4+
VERSION = '0.14.0'
5+
end

test/plugin/test_out_datadog.rb

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require "fluent/test/helpers"
33
require "fluent/test/driver/output"
44
require "fluent/plugin/out_datadog"
5+
require "fluent/plugin/version"
56
require 'webmock/test_unit'
67

78
class FluentDatadogTest < Test::Unit::TestCase
@@ -210,31 +211,82 @@ def create_valid_subject
210211
end
211212
end
212213

213-
sub_test_case "http connection errors" do
214+
# v1 routes
215+
sub_test_case "http connection errors (v1 routes)" do
214216
test "should retry when server is returning 5XX" do
215217
api_key = 'XXX'
216218
stub_dd_request_with_return_code(api_key, 500)
217219
payload = '{}'
218-
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key
220+
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key, true
219221
assert_raise(Fluent::DatadogOutput::RetryableError) do
220222
client.send(payload)
221223
end
222224
end
223225

224-
test "should not retry when server is returning 4XX" do
226+
# note that in theory, v1 routes should not return a 429 but still
227+
# we added this mechanism for v1 routes while implementing v2 ones
228+
test "should retry when server is returning 429 (v1 routes)" do
229+
api_key = 'XXX'
230+
stub_dd_request_with_return_code(api_key, 429)
231+
payload = '{}'
232+
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key, true
233+
assert_raise(Fluent::DatadogOutput::RetryableError) do
234+
client.send(payload)
235+
end
236+
end
237+
238+
test "should not retry when server is returning 4XX (v1 routes)" do
225239
api_key = 'XXX'
226240
stub_dd_request_with_return_code(api_key, 400)
227241
payload = '{}'
242+
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key, true
243+
assert_nothing_raised do
244+
client.send(payload)
245+
end
246+
end
247+
end
248+
249+
# v2 routes
250+
sub_test_case "http connection errors (v2 routes)" do
251+
test "should retry when server is returning 5XX" do
252+
api_key = 'XXX'
253+
stub_dd_request_with_return_code(api_key, 500, true)
254+
payload = '{}'
255+
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key
256+
assert_raise(Fluent::DatadogOutput::RetryableError) do
257+
client.send(payload)
258+
end
259+
end
260+
261+
test "should retry when server is returning 429 (v2 routes)" do
262+
api_key = 'XXX'
263+
stub_dd_request_with_return_code(api_key, 429, true)
264+
payload = '{}'
265+
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key
266+
assert_raise(Fluent::DatadogOutput::RetryableError) do
267+
client.send(payload)
268+
end
269+
end
270+
271+
test "should not retry when server is returning 4XX (v2 routes)" do
272+
api_key = 'XXX'
273+
stub_dd_request_with_return_code(api_key, 400, true)
274+
payload = '{}'
228275
client = Fluent::DatadogOutput::DatadogHTTPClient.new Logger.new(STDOUT), false, false, "datadog.com", 443, 80, nil, false, api_key
229276
assert_nothing_raised do
230277
client.send(payload)
231278
end
232279
end
233280
end
234281

235-
def stub_dd_request_with_return_code(api_key, return_code)
236-
stub_dd_request(api_key).
237-
to_return(status: return_code, body: "", headers: {})
282+
def stub_dd_request_with_return_code(api_key, return_code, v2_routes = false)
283+
if v2_routes
284+
stub_dd_request_v2_routes(api_key).
285+
to_return(status: return_code, body: "", headers: {})
286+
else
287+
stub_dd_request(api_key).
288+
to_return(status: return_code, body: "", headers: {})
289+
end
238290
end
239291

240292
def stub_dd_request_with_error(api_key, error)
@@ -255,4 +307,21 @@ def stub_dd_request(api_key)
255307
'User-Agent' => 'Ruby'
256308
})
257309
end
310+
311+
def stub_dd_request_v2_routes(api_key)
312+
stub_request(:post, "http://datadog.com/api/v2/logs").
313+
with(
314+
body: "{}",
315+
headers: {
316+
'Accept'=>'*/*',
317+
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
318+
'Connection'=>'keep-alive',
319+
'Content-Type'=>'application/json',
320+
'Dd-Api-Key'=> "#{api_key}",
321+
'Dd-Evp-Origin'=>'fluent',
322+
'Dd-Evp-Origin-Version'=> "#{DatadogFluentPlugin::VERSION}",
323+
'Keep-Alive'=>'30',
324+
'User-Agent'=>'Ruby'
325+
})
326+
end
258327
end

0 commit comments

Comments
 (0)