fluent-plugin-datadog 0.11.1 → 0.14.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
  SHA256:
3
- metadata.gz: b5d03f1a431fabff4b1578b16530b66af5c4ed9108fafd3149ebff3c3aaeae97
4
- data.tar.gz: b0de09ecbf8cd35ffb0b8e5135dc9b362e9a34f8ebec7af1574c54fb331f2e2d
3
+ metadata.gz: 8907f2de3502bd5d7e4ee138dba441f1d5d0197537b8a9b246d46038f12fa045
4
+ data.tar.gz: '09e4e69d440b126a68f54bc535c2c2bba8ab1b199e70e306fd0e08ae9fbc5a37'
5
5
  SHA512:
6
- metadata.gz: 99e3edfafaa3917b04bba0152462cbbafcba16c71b16ea828b46ec3d526d2808c066474eaba0044f37f0af04f94e59a178b35a8a6c8954a59ecd6132f97dfcb7
7
- data.tar.gz: 8fb07f8e11898794ef21881694f3f9501d95a8dd80b375fe899ed959b81faa6aa1bd523fe792b315f762c32249b8952e26e332c79f0d240d4ecc9f79f56b12b3
6
+ metadata.gz: f895096ecb110d7d9a269e882f02c5ba844a4b6593163dcfd82f81f570eb4a3979ffa2f0fedf398f02d22445651005fe6cd9b98172fa91e1ef49b5f55d8c9d11
7
+ data.tar.gz: ac780383e151377090900d904dc855a161c2c6abaff43bf7ac7b27140fecb1ee461e0cd4bdbcdbc39c9cdf28d569eede8a30634094e7516f0b60d899246846a8
data/.gitignore CHANGED
@@ -40,5 +40,4 @@ foo/
40
40
  *.iml
41
41
  .idea/
42
42
 
43
- fluent/
44
43
  Gemfile.lock
data/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # Fluentd output plugin for Datadog
2
2
 
3
- It mainly contains a proper JSON formatter and a socket handler that
4
- streams logs directly to Datadog - so no need to use a log shipper
3
+ This output plugin allows sending logs directly from Fluentd to Datadog - so you don't have to use a separate log shipper
5
4
  if you don't wan't to.
6
5
 
7
6
  ## Pre-requirements
8
7
 
8
+ | fluent-plugin-datadog | Fluentd | Ruby |
9
+ |:--------------------------|:-----------|:-------|
10
+ | \>= 0.12.0 | \>= v1 | \>= 2.4 |
11
+ | < 0.12.0 | \>= v0.12.0 | \>= 2.1 |
12
+
9
13
  To add the plugin to your fluentd agent, use the following command:
10
14
 
11
15
  gem install fluent-plugin-datadog
@@ -19,7 +23,7 @@ If you installed the td-agent instead
19
23
 
20
24
  To match events and send them to Datadog, simply add the following code to your configuration file.
21
25
 
22
- TCP example:
26
+ HTTP example:
23
27
 
24
28
  ```xml
25
29
  # Match events tagged with "datadog.**" and
@@ -35,10 +39,21 @@ TCP example:
35
39
  tag_key 'tag'
36
40
 
37
41
  # Optional parameters
38
- dd_source '<INTEGRATION_NAME>'
39
- dd_tags '<KEY1:VALU1>,<KEY2:VALUE2>'
42
+ dd_source '<INTEGRATION_NAME>'
43
+ dd_tags '<KEY1:VALUE1>,<KEY2:VALUE2>'
40
44
  dd_sourcecategory '<MY_SOURCE_CATEGORY>'
41
45
 
46
+ # Optional http proxy
47
+ http_proxy 'http://my-proxy.example'
48
+
49
+ <buffer>
50
+ @type memory
51
+ flush_thread_count 4
52
+ flush_interval 3s
53
+ chunk_limit_size 5m
54
+ chunk_limit_records 500
55
+ </buffer>
56
+
42
57
  </match>
43
58
  ```
44
59
 
@@ -72,15 +87,21 @@ As fluent-plugin-datadog is an output_buffer, you can set all output_buffer prop
72
87
  | **tag_key** | Where to store the Fluentd tag. | "tag" |
73
88
  | **timestamp_key** | Name of the attribute which will contain timestamp of the log event. If nil, timestamp attribute is not added. | "@timestamp" |
74
89
  | **use_ssl** | If true, the agent initializes a secure connection to Datadog. In clear TCP otherwise. | true |
75
- | **ssl_port** | Port used to send logs over a SSL encripted connection to Datadog (use 443 for the EU region) | 10516 |
90
+ | **no_ssl_validation** | Disable SSL validation (useful for proxy forwarding) | false |
91
+ | **ssl_port** | Port used to send logs over a SSL encrypted connection to Datadog. If use_http is disabled, use 10516 for the US region and 443 for the EU region. | 443 |
76
92
  | **max_retries** | The number of retries before the output plugin stops. Set to -1 for unlimited retries | -1 |
93
+ | **max_backoff** | The maximum time waited between each retry in seconds | 30 |
94
+ | **use_http** | Enable HTTP forwarding. If you disable it, make sure to change the port to 10514 or ssl_port to 10516 | true |
95
+ | **use_compression** | Enable log compression for HTTP | true |
96
+ | **compression_level** | Set the log compression level for HTTP (1 to 9, 9 being the best ratio) | 6 |
77
97
  | **dd_source** | This tells Datadog what integration it is | nil |
78
98
  | **dd_sourcecategory** | Multiple value attribute. Can be used to refine the source attribute | nil |
79
99
  | **dd_tags** | Custom tags with the following format "key1:value1, key2:value2" | nil |
80
100
  | **dd_hostname** | Used by Datadog to identify the host submitting the logs. | `hostname -f` |
81
101
  | **service** | Used by Datadog to correlate between logs, traces and metrics. | nil |
82
- | **port** | Proxy port when logs are not directly forwarded to Datadog and ssl is not used | 10514 |
83
- | **host** | Proxy endpoint when logs are not directly forwarded to Datadog | intake.logs.datadoghq.com |
102
+ | **port** | Proxy port when logs are not directly forwarded to Datadog and ssl is not used | 80 |
103
+ | **host** | Proxy endpoint when logs are not directly forwarded to Datadog | http-intake.logs.datadoghq.com |
104
+ | **http_proxy** | HTTP proxy, only takes effect if HTTP forwarding is enabled (`use_http`). Defaults to `HTTP_PROXY`/`http_proxy` env vars. | nil |
84
105
 
85
106
  ### Docker and Kubernetes tags
86
107
 
@@ -105,6 +126,24 @@ Configuration example:
105
126
  </filter>
106
127
  ```
107
128
 
129
+ ### Encoding
130
+
131
+ Datadog's API expects log messages to be encoded in UTF-8.
132
+ If some of your logs are encoded with a different encoding, we recommend using the [`record_modifier` filter plugin](https://github.com/repeatedly/fluent-plugin-record-modifier#char_encoding)
133
+ to encode these logs to UTF-8.
134
+
135
+ Configuration example:
136
+
137
+ ```
138
+ # Change encoding of logs tagged with "datadog.**"
139
+ <filter datadog.**>
140
+ @type record_modifier
141
+
142
+ # change the encoding from the '<SOURCE_ENCODING>' of your logs to 'utf-8'
143
+ char_encoding <SOURCE_ENCODING>:utf-8
144
+ </filter>
145
+ ```
146
+
108
147
  ## Build
109
148
 
110
149
  To build a new version of this plugin and push it to RubyGems:
@@ -7,21 +7,28 @@
7
7
  lib = File.expand_path('../lib', __FILE__)
8
8
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
9
9
 
10
+ require "fluent/plugin/version.rb"
11
+
10
12
  Gem::Specification.new do |spec|
11
13
  spec.name = "fluent-plugin-datadog"
12
- spec.version = "0.11.1"
14
+ spec.version = DatadogFluentPlugin::VERSION
13
15
  spec.authors = ["Datadog Solutions Team"]
14
16
  spec.email = ["support@datadoghq.com"]
15
17
  spec.summary = "Datadog output plugin for Fluent event collector"
16
18
  spec.homepage = "http://datadoghq.com"
17
- spec.license = "Apache License 2.0"
19
+ spec.license = "Apache-2.0"
18
20
 
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"]
20
22
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
23
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
24
  spec.require_paths = ["lib"]
23
25
 
24
- spec.add_development_dependency "bundler", "~> 1.5"
25
- spec.add_development_dependency "rake"
26
+ spec.add_runtime_dependency "fluentd", [">= 1", "< 2"]
27
+ spec.add_runtime_dependency "net-http-persistent", '~> 3.1'
28
+
29
+ spec.add_development_dependency "bundler", "~> 2.1"
30
+ spec.add_development_dependency "test-unit", '~> 3.1'
31
+ spec.add_development_dependency "rake", "~> 12.0"
26
32
  spec.add_development_dependency "yajl-ruby", "~> 1.2"
33
+ spec.add_development_dependency 'webmock', "~> 3.6.0"
27
34
  end
@@ -3,51 +3,81 @@
3
3
  # This product includes software developed at Datadog (https://www.datadoghq.com/).
4
4
  # Copyright 2018 Datadog, Inc.
5
5
 
6
- require 'socket'
7
- require 'openssl'
8
- require 'yajl'
6
+ require "socket"
7
+ require "openssl"
8
+ require "yajl"
9
+ require "zlib"
10
+ require "fluent/plugin/output"
9
11
 
10
- class Fluent::DatadogOutput < Fluent::BufferedOutput
11
- class ConnectionFailure < StandardError; end
12
+ require_relative "version"
13
+
14
+ class Fluent::DatadogOutput < Fluent::Plugin::Output
15
+ class RetryableError < StandardError;
16
+ end
17
+
18
+ # Max limits for transport regardless of Fluentd buffer, respecting https://docs.datadoghq.com/api/?lang=bash#logs
19
+ DD_MAX_BATCH_LENGTH = 500
20
+ DD_MAX_BATCH_SIZE = 5000000
21
+ DD_TRUNCATION_SUFFIX = "...TRUNCATED..."
22
+
23
+ DD_DEFAULT_HTTP_ENDPOINT = "http-intake.logs.datadoghq.com"
24
+ DD_DEFAULT_TCP_ENDPOINT = "intake.logs.datadoghq.com"
25
+
26
+ helpers :compat_parameters
27
+
28
+ DEFAULT_BUFFER_TYPE = "memory"
12
29
 
13
30
  # Register the plugin
14
31
  Fluent::Plugin.register_output('datadog', self)
15
32
 
16
33
  # Output settings
17
- config_param :use_json, :bool, :default => true
18
- config_param :include_tag_key, :bool, :default => false
19
- config_param :tag_key, :string, :default => 'tag'
20
- config_param :timestamp_key, :string, :default => '@timestamp'
21
- config_param :service, :string, :default => nil
22
- config_param :dd_sourcecategory, :string, :default => nil
23
- config_param :dd_source, :string, :default => nil
24
- config_param :dd_tags, :string, :default => nil
25
- config_param :dd_hostname, :string, :default => nil
34
+ config_param :include_tag_key, :bool, :default => false
35
+ config_param :tag_key, :string, :default => 'tag'
36
+ config_param :timestamp_key, :string, :default => '@timestamp'
37
+ config_param :service, :string, :default => nil
38
+ config_param :dd_sourcecategory, :string, :default => nil
39
+ config_param :dd_source, :string, :default => nil
40
+ config_param :dd_tags, :string, :default => nil
41
+ config_param :dd_hostname, :string, :default => nil
26
42
 
27
43
  # Connection settings
28
- config_param :host, :string, :default => 'intake.logs.datadoghq.com'
29
- config_param :use_ssl, :bool, :default => true
30
- config_param :port, :integer, :default => 10514
31
- config_param :ssl_port, :integer, :default => 10516
32
- config_param :max_retries, :integer, :default => -1
33
- config_param :tcp_ping_rate, :integer, :default => 10
44
+ config_param :host, :string, :default => DD_DEFAULT_HTTP_ENDPOINT
45
+ config_param :use_ssl, :bool, :default => true
46
+ config_param :port, :integer, :default => 80
47
+ config_param :ssl_port, :integer, :default => 443
48
+ config_param :max_retries, :integer, :default => -1
49
+ config_param :max_backoff, :integer, :default => 30
50
+ config_param :use_http, :bool, :default => true
51
+ config_param :use_compression, :bool, :default => true
52
+ config_param :compression_level, :integer, :default => 6
53
+ config_param :no_ssl_validation, :bool, :default => false
54
+ config_param :http_proxy, :string, :default => nil
55
+ config_param :force_v1_routes, :bool, :default => false
56
+
57
+ # Format settings
58
+ config_param :use_json, :bool, :default => true
34
59
 
35
60
  # API Settings
36
- config_param :api_key, :string
61
+ config_param :api_key, :string, secret: true
37
62
 
38
- def initialize
39
- super
63
+ config_section :buffer do
64
+ config_set_default :@type, DEFAULT_BUFFER_TYPE
40
65
  end
41
66
 
42
- # Define `log` method for v0.10.42 or earlier
43
- unless method_defined?(:log)
44
- define_method("log") { $log }
67
+ def initialize
68
+ super
45
69
  end
46
70
 
47
71
  def configure(conf)
72
+ compat_parameters_convert(conf, :buffer)
48
73
  super
49
74
  return if @dd_hostname
50
75
 
76
+ if not @use_http and @host == DD_DEFAULT_HTTP_ENDPOINT
77
+ @host = DD_DEFAULT_TCP_ENDPOINT
78
+ end
79
+
80
+ # Set dd_hostname if not already set (can be set when using fluentd as aggregator)
51
81
  @dd_hostname = %x[hostname -f 2> /dev/null].strip
52
82
  @dd_hostname = Socket.gethostname if @dd_hostname.empty?
53
83
  end
@@ -56,41 +86,22 @@ class Fluent::DatadogOutput < Fluent::BufferedOutput
56
86
  true
57
87
  end
58
88
 
59
- def new_client
60
- if @use_ssl
61
- context = OpenSSL::SSL::SSLContext.new
62
- socket = TCPSocket.new @host, @ssl_port
63
- ssl_client = OpenSSL::SSL::SSLSocket.new socket, context
64
- ssl_client.connect
65
- return ssl_client
66
- else
67
- return TCPSocket.new @host, @port
68
- end
89
+ def formatted_to_msgpack_binary?
90
+ true
69
91
  end
70
92
 
71
93
  def start
72
94
  super
73
- @my_mutex = Mutex.new
74
- @running = true
75
-
76
- if @tcp_ping_rate > 0
77
- @timer = Thread.new do
78
- while @running do
79
- messages = Array.new
80
- messages.push("fp\n")
81
- send_to_datadog(messages)
82
- sleep(@tcp_ping_rate)
83
- end
84
- end
85
- end
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)
86
96
  end
87
97
 
88
98
  def shutdown
89
99
  super
90
- @running = false
91
- if @client
92
- @client.close
93
- end
100
+ end
101
+
102
+ def terminate
103
+ super
104
+ @client.close if @client
94
105
  end
95
106
 
96
107
  # This method is called when an event reaches Fluentd.
@@ -98,92 +109,296 @@ class Fluent::DatadogOutput < Fluent::BufferedOutput
98
109
  # When Fluent::EventTime is msgpack'ed it gets converted to int with seconds
99
110
  # precision only. We explicitly convert it to floating point number, which
100
111
  # is compatible with Time.at below.
101
- return [tag, time.to_f, record].to_msgpack
112
+ record = enrich_record(tag, time.to_f, record)
113
+ if @use_http
114
+ record = Yajl.dump(record)
115
+ else
116
+ if @use_json
117
+ record = "#{api_key} #{Yajl.dump(record)}"
118
+ else
119
+ record = "#{api_key} #{record}"
120
+ end
121
+ end
122
+ [record].to_msgpack
102
123
  end
103
124
 
125
+
104
126
  # NOTE! This method is called by internal thread, not Fluentd's main thread.
105
127
  # 'chunk' is a buffer chunk that includes multiple formatted events.
106
128
  def write(chunk)
107
- messages = Array.new
129
+ begin
130
+ if @use_http
131
+ events = Array.new
132
+ chunk.msgpack_each do |record|
133
+ next if record.empty?
134
+ events.push record[0]
135
+ end
136
+ process_http_events(events, @use_compression, @compression_level, @max_retries, @max_backoff, DD_MAX_BATCH_LENGTH, DD_MAX_BATCH_SIZE)
137
+ else
138
+ chunk.msgpack_each do |record|
139
+ next if record.empty?
140
+ process_tcp_event(record[0], @max_retries, @max_backoff, DD_MAX_BATCH_SIZE)
141
+ end
142
+ end
143
+ rescue Exception => e
144
+ @logger.error("Uncaught processing exception in datadog forwarder #{e.message}")
145
+ end
146
+ end
147
+
148
+ # Process and send a set of http events. Potentially break down this set of http events in smaller batches
149
+ def process_http_events(events, use_compression, compression_level, max_retries, max_backoff, max_batch_length, max_batch_size)
150
+ batches = batch_http_events(events, max_batch_length, max_batch_size)
151
+ batches.each do |batched_event|
152
+ formatted_events = format_http_event_batch(batched_event)
153
+ if use_compression
154
+ formatted_events = gzip_compress(formatted_events, compression_level)
155
+ end
156
+ @client.send_retries(formatted_events, max_retries, max_backoff)
157
+ end
158
+ end
108
159
 
109
- chunk.msgpack_each do |tag, time, record|
110
- next unless record.is_a? Hash
111
- next if record.empty?
160
+ # Process and send a single tcp event
161
+ def process_tcp_event(event, max_retries, max_backoff, max_batch_size)
162
+ if event.bytesize > max_batch_size
163
+ event = truncate(event, max_batch_size)
164
+ end
165
+ @client.send_retries(event, max_retries, max_backoff)
166
+ end
112
167
 
113
- if @dd_sourcecategory
114
- record["ddsourcecategory"] ||= @dd_sourcecategory
168
+ # Group HTTP events in batches
169
+ def batch_http_events(encoded_events, max_batch_length, max_request_size)
170
+ batches = []
171
+ current_batch = []
172
+ current_batch_size = 0
173
+ encoded_events.each_with_index do |encoded_event, i|
174
+ current_event_size = encoded_event.bytesize
175
+ # If this unique log size is bigger than the request size, truncate it
176
+ if current_event_size > max_request_size
177
+ encoded_event = truncate(encoded_event, max_request_size)
178
+ current_event_size = encoded_event.bytesize
115
179
  end
116
- if @dd_source
117
- record["ddsource"] ||= @dd_source
180
+
181
+ if (i > 0 and i % max_batch_length == 0) or (current_batch_size + current_event_size > max_request_size)
182
+ batches << current_batch
183
+ current_batch = []
184
+ current_batch_size = 0
118
185
  end
119
- if @dd_tags
120
- record["ddtags"] ||= @dd_tags
186
+
187
+ current_batch_size += encoded_event.bytesize
188
+ current_batch << encoded_event
189
+ end
190
+ batches << current_batch
191
+ batches
192
+ end
193
+
194
+ # Truncate events over the provided max length, appending a marker when truncated
195
+ def truncate(event, max_length)
196
+ if event.length > max_length
197
+ event = event[0..max_length - 1]
198
+ event[max(0, max_length - DD_TRUNCATION_SUFFIX.length)..max_length - 1] = DD_TRUNCATION_SUFFIX
199
+ return event
200
+ end
201
+ event
202
+ end
203
+
204
+ def max(a, b)
205
+ a > b ? a : b
206
+ end
207
+
208
+ # Format batch of http events
209
+ def format_http_event_batch(events)
210
+ "[#{events.join(',')}]"
211
+ end
212
+
213
+ # Enrich records with metadata such as service, tags or source
214
+ def enrich_record(tag, time, record)
215
+ if @dd_sourcecategory
216
+ record["ddsourcecategory"] ||= @dd_sourcecategory
217
+ end
218
+ if @dd_source
219
+ record["ddsource"] ||= @dd_source
220
+ end
221
+ if @dd_tags
222
+ record["ddtags"] ||= @dd_tags
223
+ end
224
+ if @service
225
+ record["service"] ||= @service
226
+ end
227
+ if @dd_hostname
228
+ # set the record hostname to the configured dd_hostname only
229
+ # if the record hostname is empty, ensuring having a hostname set
230
+ # even if the record doesn't contain any.
231
+ record["hostname"] ||= @dd_hostname
232
+ end
233
+
234
+ if @include_tag_key
235
+ record[@tag_key] = tag
236
+ end
237
+ # If @timestamp_key already exists, we don't overwrite it.
238
+ if @timestamp_key and record[@timestamp_key].nil? and time
239
+ record[@timestamp_key] = Time.at(time).utc.iso8601(3)
240
+ end
241
+
242
+ container_tags = get_container_tags(record)
243
+ unless container_tags.empty?
244
+ if record["ddtags"].nil? || record["ddtags"].empty?
245
+ record["ddtags"] = container_tags
246
+ else
247
+ record["ddtags"] = record["ddtags"] + "," + container_tags
121
248
  end
122
- if @service
123
- record["service"] ||= @service
249
+ end
250
+ record
251
+ end
252
+
253
+ # Compress logs with GZIP
254
+ def gzip_compress(payload, compression_level)
255
+ gz = StringIO.new
256
+ gz.set_encoding("BINARY")
257
+ z = Zlib::GzipWriter.new(gz, compression_level)
258
+ begin
259
+ z.write(payload)
260
+ ensure
261
+ z.close
262
+ end
263
+ gz.string
264
+ end
265
+
266
+ # Build a new transport client
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)
268
+ if use_http
269
+ DatadogHTTPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key, force_v1_routes
270
+ else
271
+ DatadogTCPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port
272
+ end
273
+ end
274
+
275
+ # Top level class for datadog transport clients, managing retries and backoff
276
+ class DatadogClient
277
+ def send_retries(payload, max_retries, max_backoff)
278
+ backoff = 1
279
+ retries = 0
280
+ begin
281
+ send(payload)
282
+ rescue RetryableError => e
283
+ if retries < max_retries || max_retries < 0
284
+ @logger.warn("Retrying ", :exception => e, :backtrace => e.backtrace)
285
+ sleep backoff
286
+ backoff = 2 * backoff unless backoff > max_backoff
287
+ retries += 1
288
+ retry
289
+ end
124
290
  end
125
- if @dd_hostname
126
- record["hostname"] ||= @dd_hostname
291
+ end
292
+
293
+ def send(payload)
294
+ raise NotImplementedError, "Datadog transport client should implement the send method"
295
+ end
296
+
297
+ def close
298
+ raise NotImplementedError, "Datadog transport client should implement the close method"
299
+ end
300
+ end
301
+
302
+ # HTTP datadog client
303
+ class DatadogHTTPClient < DatadogClient
304
+ require 'net/http'
305
+ require 'net/http/persistent'
306
+
307
+ def initialize(logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key, force_v1_routes = false)
308
+ @logger = logger
309
+ protocol = use_ssl ? "https" : "http"
310
+ port = use_ssl ? ssl_port : port
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
316
+ proxy_uri = :ENV
317
+ if http_proxy
318
+ proxy_uri = URI.parse(http_proxy)
319
+ elsif ENV['HTTP_PROXY'] || ENV['http_proxy']
320
+ logger.info("Using HTTP proxy defined in `HTTP_PROXY`/`http_proxy` env vars")
321
+ end
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"))
323
+ @client = Net::HTTP::Persistent.new name: "fluent-plugin-datadog-logcollector", proxy: proxy_uri
324
+ @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
127
329
  end
330
+ @client.override_headers["Content-Type"] = "application/json"
331
+ if use_compression
332
+ @client.override_headers["Content-Encoding"] = "gzip"
333
+ end
334
+ if !@client.proxy_uri.nil?
335
+ # Log the proxy settings as resolved by the HTTP client
336
+ logger.info("Using HTTP proxy #{@client.proxy_uri.scheme}://#{@client.proxy_uri.host}:#{@client.proxy_uri.port} username: #{@client.proxy_uri.user ? "set" : "unset"}, password: #{@client.proxy_uri.password ? "set" : "unset"}")
337
+ end
338
+ end
128
339
 
129
- if @include_tag_key
130
- record[@tag_key] = tag
340
+ def send(payload)
341
+ request = Net::HTTP::Post.new @uri.request_uri
342
+ request.body = payload
343
+ response = @client.request @uri, request
344
+ res_code = response.code.to_i
345
+ # on a backend error or on an http 429, retry with backoff
346
+ if res_code >= 500 || res_code == 429
347
+ raise RetryableError.new "Unable to send payload: #{res_code} #{response.message}"
131
348
  end
132
- # If @timestamp_key already exists, we don't overwrite it.
133
- if @timestamp_key and record[@timestamp_key].nil? and time
134
- record[@timestamp_key] = Time.at(time).utc.iso8601(3)
349
+ if res_code >= 400
350
+ @logger.error("Unable to send payload due to client error: #{res_code} #{response.message}")
135
351
  end
352
+ end
136
353
 
137
- container_tags = get_container_tags(record)
138
- if not container_tags.empty?
139
- if record["ddtags"].nil? || record["ddtags"].empty?
140
- record["ddtags"] = container_tags
141
- else
142
- record["ddtags"] = record["ddtags"] + "," + container_tags
354
+ def close
355
+ @client.shutdown
356
+ end
357
+ end
358
+
359
+ # TCP Datadog client
360
+ class DatadogTCPClient < DatadogClient
361
+ require "socket"
362
+
363
+ def initialize(logger, use_ssl, no_ssl_validation, host, ssl_port, port)
364
+ @logger = logger
365
+ @use_ssl = use_ssl
366
+ @no_ssl_validation = no_ssl_validation
367
+ @host = host
368
+ @port = use_ssl ? ssl_port : port
369
+ end
370
+
371
+ def connect
372
+ if @use_ssl
373
+ @logger.info("Starting SSL connection #{@host} #{@port}")
374
+ socket = TCPSocket.new @host, @port
375
+ ssl_context = OpenSSL::SSL::SSLContext.new
376
+ if @no_ssl_validation
377
+ ssl_context.set_params({:verify_mode => OpenSSL::SSL::VERIFY_NONE})
143
378
  end
379
+ ssl_context = OpenSSL::SSL::SSLSocket.new socket, ssl_context
380
+ ssl_context.connect
381
+ ssl_context
382
+ else
383
+ @logger.info("Starting plaintext connection #{@host} #{@port}")
384
+ TCPSocket.new @host, @port
144
385
  end
386
+ end
145
387
 
146
- if @use_json
147
- messages.push "#{api_key} " + Yajl.dump(record) + "\n"
148
- else
149
- next unless record.has_key? "message"
150
- messages.push "#{api_key} " + record["message"].strip + "\n"
151
- end
152
- end
153
- send_to_datadog(messages)
154
- end
155
-
156
- def send_to_datadog(events)
157
- @my_mutex.synchronize do
158
- events.each do |event|
159
- log.trace "Datadog plugin: about to send event=#{event}"
160
- retries = 0
161
- begin
162
- log.info "New attempt to Datadog attempt=#{retries}" if retries > 1
163
- @client ||= new_client
164
- @client.write(event)
165
- rescue => e
166
- @client.close rescue nil
167
- @client = nil
168
-
169
- if retries == 0
170
- # immediately retry, in case it's just a server-side close
171
- retries += 1
172
- retry
173
- end
174
-
175
- if retries < @max_retries || @max_retries == -1
176
- a_couple_of_seconds = retries ** 2
177
- a_couple_of_seconds = 30 unless a_couple_of_seconds < 30
178
- retries += 1
179
- log.warn "Could not push event to Datadog, attempt=#{retries} max_attempts=#{max_retries} wait=#{a_couple_of_seconds}s error=#{e}"
180
- sleep a_couple_of_seconds
181
- retry
182
- end
183
- raise ConnectionFailure, "Could not push event to Datadog after #{retries} retries, #{e}"
184
- end
388
+ def send(payload)
389
+ begin
390
+ @socket ||= connect
391
+ @socket.puts(payload)
392
+ rescue => e
393
+ @socket.close rescue nil
394
+ @socket = nil
395
+ raise RetryableError.new "Unable to send payload: #{e.message}."
185
396
  end
186
397
  end
398
+
399
+ def close
400
+ @socket.close rescue nil
401
+ end
187
402
  end
188
403
 
189
404
  # Collect docker and kubernetes tags for your logs using `filter_kubernetes_metadata` plugin,
@@ -191,9 +406,9 @@ class Fluent::DatadogOutput < Fluent::BufferedOutput
191
406
  # https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter/blob/master/lib/fluent/plugin/filter_kubernetes_metadata.rb#L265
192
407
 
193
408
  def get_container_tags(record)
194
- return [
195
- get_kubernetes_tags(record),
196
- get_docker_tags(record)
409
+ [
410
+ get_kubernetes_tags(record),
411
+ get_docker_tags(record)
197
412
  ].compact.join(",")
198
413
  end
199
414
 
@@ -207,7 +422,7 @@ class Fluent::DatadogOutput < Fluent::BufferedOutput
207
422
  tags.push("pod_name:" + kubernetes['pod_name']) unless kubernetes['pod_name'].nil?
208
423
  return tags.join(",")
209
424
  end
210
- return nil
425
+ nil
211
426
  end
212
427
 
213
428
  def get_docker_tags(record)
@@ -217,7 +432,6 @@ class Fluent::DatadogOutput < Fluent::BufferedOutput
217
432
  tags.push("container_id:" + docker['container_id']) unless docker['container_id'].nil?
218
433
  return tags.join(",")
219
434
  end
220
- return nil
435
+ nil
221
436
  end
222
-
223
437
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DatadogFluentPlugin
4
+ VERSION = '0.14.0'
5
+ end
metadata CHANGED
@@ -1,43 +1,91 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog Solutions Team
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-22 00:00:00.000000000 Z
11
+ date: 2021-10-18 00:00:00.000000000 Z
12
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: '1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: net-http-persistent
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.1'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.1'
13
47
  - !ruby/object:Gem::Dependency
14
48
  name: bundler
15
49
  requirement: !ruby/object:Gem::Requirement
16
50
  requirements:
17
51
  - - "~>"
18
52
  - !ruby/object:Gem::Version
19
- version: '1.5'
53
+ version: '2.1'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.1'
61
+ - !ruby/object:Gem::Dependency
62
+ name: test-unit
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.1'
20
68
  type: :development
21
69
  prerelease: false
22
70
  version_requirements: !ruby/object:Gem::Requirement
23
71
  requirements:
24
72
  - - "~>"
25
73
  - !ruby/object:Gem::Version
26
- version: '1.5'
74
+ version: '3.1'
27
75
  - !ruby/object:Gem::Dependency
28
76
  name: rake
29
77
  requirement: !ruby/object:Gem::Requirement
30
78
  requirements:
31
- - - ">="
79
+ - - "~>"
32
80
  - !ruby/object:Gem::Version
33
- version: '0'
81
+ version: '12.0'
34
82
  type: :development
35
83
  prerelease: false
36
84
  version_requirements: !ruby/object:Gem::Requirement
37
85
  requirements:
38
- - - ">="
86
+ - - "~>"
39
87
  - !ruby/object:Gem::Version
40
- version: '0'
88
+ version: '12.0'
41
89
  - !ruby/object:Gem::Dependency
42
90
  name: yajl-ruby
43
91
  requirement: !ruby/object:Gem::Requirement
@@ -52,7 +100,21 @@ dependencies:
52
100
  - - "~>"
53
101
  - !ruby/object:Gem::Version
54
102
  version: '1.2'
55
- description:
103
+ - !ruby/object:Gem::Dependency
104
+ name: webmock
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 3.6.0
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 3.6.0
117
+ description:
56
118
  email:
57
119
  - support@datadoghq.com
58
120
  executables: []
@@ -66,11 +128,12 @@ files:
66
128
  - Rakefile
67
129
  - fluent-plugin-datadog.gemspec
68
130
  - lib/fluent/plugin/out_datadog.rb
131
+ - lib/fluent/plugin/version.rb
69
132
  homepage: http://datadoghq.com
70
133
  licenses:
71
- - Apache License 2.0
134
+ - Apache-2.0
72
135
  metadata: {}
73
- post_install_message:
136
+ post_install_message:
74
137
  rdoc_options: []
75
138
  require_paths:
76
139
  - lib
@@ -85,9 +148,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
148
  - !ruby/object:Gem::Version
86
149
  version: '0'
87
150
  requirements: []
88
- rubyforge_project:
151
+ rubyforge_project:
89
152
  rubygems_version: 2.7.10
90
- signing_key:
153
+ signing_key:
91
154
  specification_version: 4
92
155
  summary: Datadog output plugin for Fluent event collector
93
156
  test_files: []