fluent-plugin-datadog 0.11.1 → 0.14.0

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 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: []