fluent-plugin-datadog 0.14.4 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 926fa9b18f9ddafc71acdb33f6a35f9a8f5c8c349aa738c6772f37f4e678c27e
4
- data.tar.gz: 334c768913c3bde058cba88d88bd10e343ff92d9d98be29b34efabfb94a3caf3
3
+ metadata.gz: a972214999befc7ccaf2a687d6a5f08007b381c3722998523e58d1058515a802
4
+ data.tar.gz: 1be2db9c9b926019eb917aa4973ffde6243fb2d013072b74965c2e4d32dd21ab
5
5
  SHA512:
6
- metadata.gz: 8d9919de155b21d18ac85eac0479565fec145468fa169a41b56a12d974ce4326e689cd1794e32f24561639d30561d7c56ef45abd81559191c1d257ff16bab7f4
7
- data.tar.gz: 6449bf1033f5a217acf0598426e27aa82dbe546977eab926540bc82ff773a74a726ead6eaa0804b753ec26d42177c4cd843224bf160beca148cc653238b820e0
6
+ metadata.gz: 71b1a131838a953f3f352ee5132a4443b1a7f21f56c031d3f8e13c9409874e34a1c3a79c96e0a964bcb2c7b205d1f02ad2f3a22208087bf39358d456d8f7b8ad
7
+ data.tar.gz: 7082da59a80ea386ae4e4152755fc48fcdb5931dc53f80d9c41eecab2d4aa7f3a0f0e12428403dff48d5b91358b7d947b04cbc764c136561cb6abb9285118b09
data/README.md CHANGED
@@ -19,6 +19,7 @@ If you installed the td-agent instead
19
19
  /usr/sbin/td-agent-gem install fluent-plugin-datadog
20
20
 
21
21
  ## Usage
22
+
22
23
  ### Configure the output plugin
23
24
 
24
25
  To match events and send them to Datadog, simply add the following code to your configuration file.
@@ -74,10 +75,9 @@ Produces the following event:
74
75
  }
75
76
  ```
76
77
 
77
- ### fluent-plugin-datadog properties
78
- Let's go deeper on the plugin configuration.
78
+ ### Advanced configuration
79
79
 
80
- As fluent-plugin-datadog is an output_buffer, you can set all output_buffer properties like it's describe in the [fluentd documentation](http://docs.fluentd.org/articles/output-plugin-overview#buffered-output-parameters "documentation").
80
+ As `fluent-plugin-datadog` is a buffered output plugin, you can set all of the buffered output-specific properties as described in the [fluentd documentation](https://docs.fluentd.org/v/0.12/output#buffered-output-parameters).
81
81
 
82
82
  | Property | Description | Default value |
83
83
  |-------------|--------------------------------------------------------------------------|----------------|
@@ -100,8 +100,10 @@ As fluent-plugin-datadog is an output_buffer, you can set all output_buffer prop
100
100
  | **dd_hostname** | Used by Datadog to identify the host submitting the logs. | `hostname -f` |
101
101
  | **service** | Used by Datadog to correlate between logs, traces and metrics. | nil |
102
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 |
103
+ | **site** | The Datadog [site](https://docs.datadoghq.com/getting_started/site/) to send logs to. Used to derive the default `host` when no explicit `host` is provided. Valid values: `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `us5.datadoghq.com`, `ap1.datadoghq.com`, `ddog-gov.com`. | datadoghq.com |
104
+ | **host** | Proxy endpoint when logs are not directly forwarded to Datadog. When unset, the default is derived from `site` as `http-intake.logs.<site>`. An explicitly configured `host` always wins over `site`. | (derived from `site`) |
104
105
  | **http_proxy** | HTTP proxy, only takes effect if HTTP forwarding is enabled (`use_http`). Defaults to `HTTP_PROXY`/`http_proxy` env vars. | nil |
106
+ | **delete_extracted_tag_attributes** | When true, removes `kubernetes` and `docker` attributes from log records after extracting them as tags. Useful to avoid duplicate data in Datadog logs UI. | false |
105
107
 
106
108
  ### Docker and Kubernetes tags
107
109
 
@@ -115,6 +117,8 @@ If your logs contain any of the following attributes, it will automatically be a
115
117
  * kubernetes.pod_name
116
118
  * docker.container_id
117
119
 
120
+ **Note:** By default, these values will appear twice in the Datadog logs UI, once as tags (e.g., `container_name:myapp`) and once as attributes (e.g., `@kubernetes.container_name`). If you prefer to avoid this duplication, set `delete_extracted_tag_attributes` to `true` in your configuration. This will remove the `kubernetes` and `docker` attributes from the log record after the tags have been extracted.
121
+
118
122
  If the Datadog Agent collect them automatically, FluentD requires a plugin for this. We recommend using [fluent-plugin-kubernetes_metadata_filter](https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter) to collect Docker and Kubernetes metadata.
119
123
 
120
124
  Configuration example:
@@ -146,13 +150,29 @@ Configuration example:
146
150
 
147
151
  ## Build
148
152
 
149
- To build a new version of this plugin and push it to RubyGems:
153
+ To build the gem locally:
150
154
 
151
- - Update the version in the .gemspec file accordingly
152
155
  - `rake build` to build the gem file
153
- - `rake release` to push the new gem to RubyGems
154
156
 
155
- **Note**: The latest command will fail without appropriate credentials configured. You can set those credentials by running the following command:
157
+ ## Releasing
158
+
159
+ This gem is published to RubyGems via a GitHub Actions [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) workflow. No API keys or local credentials are needed.
160
+
161
+ To release a new version:
162
+
163
+ 1. Update the version in `lib/fluent/plugin/version.rb`
164
+ 2. Update `CHANGELOG.md`
165
+ 3. Merge to `master`
166
+ 4. Go to **Actions** > **Publish gem** > **Run workflow**
167
+ 5. Run with `push` unchecked first (dry run) to verify the build
168
+ 6. Run again with `push` checked to publish to RubyGems
169
+ 7. The `rubygems.org` environment gate will ask for approval before publishing
170
+
171
+ ## Development Environment
172
+
173
+ This repository includes the files to run it in a dev container in VS Code. To use it:
156
174
 
157
- `curl -u <USERNAME> https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials`, it will ask for your password.
175
+ 1. Install VS Code and Docker.
176
+ 2. Open the project in VS Code with the Dev Containers extension.
158
177
 
178
+ VS Code will build and start the container automatically.
@@ -28,9 +28,9 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 2.1"
30
30
  spec.add_development_dependency "test-unit", '~> 3.1'
31
- spec.add_development_dependency "rake", "~> 12.0"
32
- spec.add_development_dependency "yajl-ruby", "~> 1.2"
33
31
  spec.add_development_dependency 'webmock', "~> 3.6.0"
32
+ spec.add_development_dependency "ruby-debug-ide"
33
+ spec.add_development_dependency "rake", "~> 12.0"
34
34
 
35
35
  spec.metadata = {
36
36
  'bug_tracker_uri' => 'https://github.com/DataDog/fluent-plugin-datadog/issues',
@@ -5,7 +5,7 @@
5
5
 
6
6
  require "socket"
7
7
  require "openssl"
8
- require "yajl"
8
+ require "json"
9
9
  require "zlib"
10
10
  require "fluent/plugin/output"
11
11
 
@@ -24,7 +24,9 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
24
24
  DD_MAX_BATCH_SIZE = 5000000
25
25
  DD_TRUNCATION_SUFFIX = "...TRUNCATED..."
26
26
 
27
- DD_DEFAULT_HTTP_ENDPOINT = "http-intake.logs.datadoghq.com"
27
+ DD_DEFAULT_SITE = "datadoghq.com"
28
+ DD_DEFAULT_HTTP_HOST_PREFIX = "http-intake.logs."
29
+ DD_DEFAULT_HTTP_ENDPOINT = "#{DD_DEFAULT_HTTP_HOST_PREFIX}#{DD_DEFAULT_SITE}".freeze
28
30
  DD_DEFAULT_TCP_ENDPOINT = "intake.logs.datadoghq.com"
29
31
 
30
32
  helpers :compat_parameters
@@ -43,15 +45,25 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
43
45
  config_param :dd_source, :string, :default => nil
44
46
  config_param :dd_tags, :string, :default => nil
45
47
  config_param :dd_hostname, :string, :default => nil
48
+ config_param :delete_extracted_tag_attributes, :bool, :default => false
49
+
50
+ # Datadog site used to derive the default intake host. Valid values include:
51
+ # "datadoghq.com" (default), "datadoghq.eu", "us3.datadoghq.com",
52
+ # "us5.datadoghq.com", "ap1.datadoghq.com", "ddog-gov.com". Any value
53
+ # explicitly set for `host` takes precedence over the site-derived default.
54
+ config_param :site, :string, :default => DD_DEFAULT_SITE
46
55
 
47
56
  # Connection settings
48
- config_param :host, :string, :default => DD_DEFAULT_HTTP_ENDPOINT
57
+ # `host` defaults to nil so we can tell whether the user explicitly set it.
58
+ # When nil, the host is derived from `site` during `configure`.
59
+ config_param :host, :string, :default => nil
49
60
  config_param :use_ssl, :bool, :default => true
50
61
  config_param :port, :integer, :default => 80
51
62
  config_param :ssl_port, :integer, :default => 443
52
63
  config_param :max_retries, :integer, :default => -1
53
64
  config_param :max_backoff, :integer, :default => 30
54
65
  config_param :use_http, :bool, :default => true
66
+ config_param :custom_headers, :hash, :default => {}
55
67
  config_param :use_compression, :bool, :default => true
56
68
  config_param :compression_level, :integer, :default => 6
57
69
  config_param :no_ssl_validation, :bool, :default => false
@@ -75,12 +87,29 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
75
87
  def configure(conf)
76
88
  compat_parameters_convert(conf, :buffer)
77
89
  super
78
- return if @dd_hostname
79
90
 
80
- if not @use_http and @host == DD_DEFAULT_HTTP_ENDPOINT
81
- @host = DD_DEFAULT_TCP_ENDPOINT
91
+ # Derive default host from `site` only for HTTP transport.
92
+ # TCP users with a non-default site must set `host` explicitly;
93
+ # TCP users with no host and the default site get the legacy TCP endpoint.
94
+ if @host.nil? || @host.empty?
95
+ if @use_http
96
+ @host = "#{DD_DEFAULT_HTTP_HOST_PREFIX}#{@site}"
97
+ elsif @site == DD_DEFAULT_SITE
98
+ @host = DD_DEFAULT_TCP_ENDPOINT
99
+ else
100
+ # TCP + non-default site: we cannot safely derive a TCP intake hostname
101
+ # from `site` (the HTTP-intake prefix is HTTP-only), and leaving @host
102
+ # nil would surface as a cryptic connect-time error. Fail fast at
103
+ # configure time with an actionable message.
104
+ raise Fluent::ConfigError,
105
+ "`host` is required when `use_http false` is combined with a non-default `site` " \
106
+ "(`site #{@site.inspect}`). Set `host` explicitly to your TCP intake (e.g. " \
107
+ "`intake.logs.#{@site}`)."
108
+ end
82
109
  end
83
110
 
111
+ return if @dd_hostname
112
+
84
113
  # Set dd_hostname if not already set (can be set when using fluentd as aggregator)
85
114
  @dd_hostname = %x[hostname -f 2> /dev/null].strip
86
115
  @dd_hostname = Socket.gethostname if @dd_hostname.empty?
@@ -98,7 +127,7 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
98
127
 
99
128
  def start
100
129
  super
101
- @client = new_client(log, @api_key, @use_http, @use_ssl, @no_ssl_validation, @host, @ssl_port, @port, @http_proxy, @use_compression, @force_v1_routes)
130
+ @client = new_client(log, @api_key, @use_http, @use_ssl, @no_ssl_validation, @host, @ssl_port, @port, @http_proxy, @custom_headers, @use_compression, @force_v1_routes)
102
131
  end
103
132
 
104
133
  def shutdown
@@ -117,10 +146,10 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
117
146
  # is compatible with Time.at below.
118
147
  record = enrich_record(tag, time.to_f, record)
119
148
  if @use_http
120
- record = Yajl.dump(record)
149
+ record = JSON.generate(record)
121
150
  else
122
151
  if @use_json
123
- record = "#{api_key} #{Yajl.dump(record)}"
152
+ record = "#{api_key} #{JSON.generate(record)}"
124
153
  else
125
154
  record = "#{api_key} #{record}"
126
155
  end
@@ -146,8 +175,9 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
146
175
  process_tcp_event(record[0], @max_retries, @max_backoff, DD_MAX_BATCH_SIZE)
147
176
  end
148
177
  end
149
- rescue Exception => e
150
- log.error("Uncaught processing exception in datadog forwarder #{e.message}")
178
+ rescue StandardError => e
179
+ log.error("Processing exception in datadog forwarder #{e.class}: #{e.message}")
180
+ raise
151
181
  end
152
182
  end
153
183
 
@@ -253,6 +283,12 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
253
283
  record["ddtags"] = record["ddtags"] + "," + container_tags
254
284
  end
255
285
  end
286
+
287
+ if @delete_extracted_tag_attributes
288
+ record.delete('kubernetes')
289
+ record.delete('docker')
290
+ end
291
+
256
292
  record
257
293
  end
258
294
 
@@ -270,9 +306,9 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
270
306
  end
271
307
 
272
308
  # Build a new transport client
273
- def new_client(logger, api_key, use_http, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, force_v1_routes)
309
+ def new_client(logger, api_key, use_http, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, custom_headers, use_compression, force_v1_routes)
274
310
  if use_http
275
- DatadogHTTPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key, force_v1_routes
311
+ DatadogHTTPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, custom_headers, use_compression, api_key, force_v1_routes
276
312
  else
277
313
  DatadogTCPClient.new logger, use_ssl, no_ssl_validation, host, ssl_port, port
278
314
  end
@@ -293,6 +329,10 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
293
329
  retries += 1
294
330
  retry
295
331
  end
332
+ # Bounded retries exhausted: re-raise so the caller (and, ultimately,
333
+ # Fluentd core's buffer retry) is signalled instead of the failure being
334
+ # swallowed and the chunk silently dropped.
335
+ raise
296
336
  end
297
337
  end
298
338
 
@@ -310,7 +350,29 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
310
350
  require 'net/http'
311
351
  require 'net/http/persistent'
312
352
 
313
- def initialize(logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, use_compression, api_key, force_v1_routes = false)
353
+ # Transient network exceptions that warrant a retry. This mirrors the set
354
+ # Ruby's Net::HTTP retries for idempotent requests (see
355
+ # Net::HTTP#max_retries=), which notably does NOT include POST, plus the
356
+ # connection-establishment errors net-http-persistent wraps in its own
357
+ # Error. Because our log POSTs are non-idempotent, Net::HTTP will not retry
358
+ # them for us, so we classify these ourselves and route them through
359
+ # send_retries (and, on exhaustion, up to Fluentd core).
360
+ RETRYABLE_NETWORK_EXCEPTIONS = [
361
+ Net::OpenTimeout,
362
+ Net::ReadTimeout,
363
+ EOFError,
364
+ IOError,
365
+ SocketError,
366
+ Errno::ECONNRESET,
367
+ Errno::ECONNREFUSED,
368
+ Errno::ECONNABORTED,
369
+ Errno::EPIPE,
370
+ Errno::ETIMEDOUT,
371
+ OpenSSL::SSL::SSLError,
372
+ Net::HTTP::Persistent::Error,
373
+ ].freeze
374
+
375
+ def initialize(logger, use_ssl, no_ssl_validation, host, ssl_port, port, http_proxy, custom_headers, use_compression, api_key, force_v1_routes = false)
314
376
  @logger = logger
315
377
  protocol = use_ssl ? "https" : "http"
316
378
  port = use_ssl ? ssl_port : port
@@ -328,6 +390,9 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
328
390
  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"))
329
391
  @client = Net::HTTP::Persistent.new name: "fluent-plugin-datadog-logcollector", proxy: proxy_uri
330
392
  @client.verify_mode = OpenSSL::SSL::VERIFY_NONE if no_ssl_validation
393
+ custom_headers.each do |key, value|
394
+ @client.override_headers[key] = value
395
+ end
331
396
  unless force_v1_routes
332
397
  @client.override_headers["DD-API-KEY"] = api_key
333
398
  @client.override_headers["DD-EVP-ORIGIN"] = "fluent"
@@ -346,7 +411,13 @@ class Fluent::DatadogOutput < Fluent::Plugin::Output
346
411
  def send(payload)
347
412
  request = Net::HTTP::Post.new @uri.request_uri
348
413
  request.body = payload
349
- response = @client.request @uri, request
414
+ begin
415
+ response = @client.request @uri, request
416
+ rescue *RETRYABLE_NETWORK_EXCEPTIONS => e
417
+ # Transient network failure before we ever saw a response. Net::HTTP
418
+ # won't retry a POST for us, so surface it as retryable.
419
+ raise RetryableError.new "Unable to send payload, transient network error: #{e.class}: #{e.message}"
420
+ end
350
421
  res_code = response.code.to_i
351
422
  # on a backend error or on an http 429, retry with backoff
352
423
  if res_code >= 500 || res_code == 429
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatadogFluentPlugin
4
- VERSION = '0.14.4'
4
+ VERSION = '0.15.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.4
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog Solutions Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-15 00:00:00.000000000 Z
11
+ date: 2026-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -73,47 +73,47 @@ dependencies:
73
73
  - !ruby/object:Gem::Version
74
74
  version: '3.1'
75
75
  - !ruby/object:Gem::Dependency
76
- name: rake
76
+ name: webmock
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '12.0'
81
+ version: 3.6.0
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '12.0'
88
+ version: 3.6.0
89
89
  - !ruby/object:Gem::Dependency
90
- name: yajl-ruby
90
+ name: ruby-debug-ide
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "~>"
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: '1.2'
95
+ version: '0'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - "~>"
100
+ - - ">="
101
101
  - !ruby/object:Gem::Version
102
- version: '1.2'
102
+ version: '0'
103
103
  - !ruby/object:Gem::Dependency
104
- name: webmock
104
+ name: rake
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: 3.6.0
109
+ version: '12.0'
110
110
  type: :development
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: 3.6.0
116
+ version: '12.0'
117
117
  description:
118
118
  email:
119
119
  - support@datadoghq.com
@@ -152,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
154
  requirements: []
155
- rubygems_version: 3.3.15
155
+ rubygems_version: 3.5.22
156
156
  signing_key:
157
157
  specification_version: 4
158
158
  summary: Datadog output plugin for Fluent event collector