fluent-plugin-opentelemetry 0.5.2 → 0.5.4
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +41 -5
- data/TODO.md +15 -8
- data/lib/fluent/plugin/in_opentelemetry.rb +10 -2
- data/lib/fluent/plugin/opentelemetry/constant.rb +1 -1
- data/lib/fluent/plugin/opentelemetry/grpc_input_handler.rb +4 -2
- data/lib/fluent/plugin/opentelemetry/grpc_output_handler.rb +5 -3
- data/lib/fluent/plugin/opentelemetry/grpc_tls.rb +71 -0
- data/lib/fluent/plugin/opentelemetry/http_input_handler.rb +82 -4
- data/lib/fluent/plugin/opentelemetry/http_output_handler.rb +2 -1
- data/lib/fluent/plugin/opentelemetry/version.rb +1 -1
- data/lib/fluent/plugin/out_opentelemetry.rb +55 -5
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dad225b751029746a86f4bdeae0b9f415d0756c25d9c560e7606ac11c13ade0b
|
|
4
|
+
data.tar.gz: '091d4d6113b42822cc47e6cd386aa10e2044f9a146d401e437eefe21b11df10e'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d5919a65f7d4adf3491c16cc3a098283bb69f7fd84a09e88730d15df7919c878838eee465d3315f5f762acd323f2a0983c2445f74b36384526ea1a1dc2e04f51
|
|
7
|
+
data.tar.gz: fe3517def463d5c8ae2c559df0e42438d518ae4d74f0ccfc6e900c570ca74b1cca2d1b31a97da33e79d566ba304884c5646f13a46869d3ea8147ba209143d1c9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.4] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
Fixes:
|
|
6
|
+
|
|
7
|
+
- Apply <transport tls> ca_path to HTTP connections (#55)
|
|
8
|
+
- Apply <transport tls> settings to gRPC connections (#54)
|
|
9
|
+
- out_opentelemetry: skip invalid records instead of failing the chunk (#53)
|
|
10
|
+
|
|
11
|
+
## [0.5.3] - 2026-06-25
|
|
12
|
+
|
|
13
|
+
Fixes:
|
|
14
|
+
|
|
15
|
+
- in_opentelemetry: enforce size limits on incoming payloads
|
|
16
|
+
|
|
3
17
|
## [0.5.2] - 2026-03-15
|
|
4
18
|
|
|
5
19
|
Improvements:
|
data/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# fluent-plugin-opentelemetry
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/fluent-plugin-opentelemetry)
|
|
4
|
+
[](https://github.com/fluent-plugins-nursery/fluent-plugin-opentelemetry/actions/workflows/test.yml)
|
|
5
|
+
|
|
3
6
|
[Fluentd](https://fluentd.org/) input/output plugin to forward [OpenTelemetry Protocol](https://github.com/open-telemetry/opentelemetry-proto) data.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
@@ -65,10 +68,12 @@ Example:
|
|
|
65
68
|
|
|
66
69
|
This requires to receive data via HTTP/HTTPS.
|
|
67
70
|
|
|
68
|
-
| parameter
|
|
69
|
-
|
|
70
|
-
| bind
|
|
71
|
-
| port
|
|
71
|
+
| parameter | type | description | default |
|
|
72
|
+
|--------------------------|---------|---------------------------------------------------------------------------------------------------------------------------|-----------|
|
|
73
|
+
| bind | string | The address to bind to | `0.0.0.0` |
|
|
74
|
+
| port | integer | The port to listen to | `4318` |
|
|
75
|
+
| body_size_limit | size | The size limit of the POSTed element. This value should be larger than the 'chunk_limit_size' in out_opentelemetry plugin | `32M` (32MiB) |
|
|
76
|
+
| decompression_size_limit | size | The size limit of the decompressed element | `256M` (256MiB) |
|
|
72
77
|
|
|
73
78
|
#### `<grpc>` section
|
|
74
79
|
|
|
@@ -87,6 +92,11 @@ It needs to install `grpc` gem manually to use this feature.
|
|
|
87
92
|
|
|
88
93
|
Refer [Config: Transport Section](https://docs.fluentd.org/configuration/transport-section)
|
|
89
94
|
|
|
95
|
+
When the `<grpc>` section is configured, `<transport tls>` applies to the gRPC listener as well. gRPC uses `ca_path`, `cert_path`, `private_key_path`, `private_key_passphrase` and `client_cert_auth`, and the other TLS parameters have no effect on the gRPC listener. `client_cert_auth` requires `ca_path`, because gRPC has no default trust store for client certificates.
|
|
96
|
+
|
|
97
|
+
> [!WARNING]
|
|
98
|
+
> This changes the behavior of an existing configuration that has both `<transport tls>` and `<grpc>`. The gRPC listener used to accept plaintext regardless of `<transport tls>`, and it now requires TLS. Plaintext OTLP/gRPC clients have to be switched to TLS.
|
|
99
|
+
|
|
90
100
|
#### Example
|
|
91
101
|
|
|
92
102
|
```
|
|
@@ -144,6 +154,19 @@ To output the data, it requires to use output `opentelemetry` plugin.
|
|
|
144
154
|
>
|
|
145
155
|
> **PromQL Example:** `rate(fluentd_process_cpu_time_seconds_total[$__rate_interval])`
|
|
146
156
|
|
|
157
|
+
> [!NOTE]
|
|
158
|
+
> **Process Metrics (CPU / Memory)**
|
|
159
|
+
>
|
|
160
|
+
> Fluentd operates on a multi-process architecture consisting of a Supervisor process and one or more Worker processes.
|
|
161
|
+
>
|
|
162
|
+
> Because this plugin runs inside a specific worker process, the process-level metrics it collects (such as CPU time and memory usage) represent **only the footprint of that single worker process**, not the total resource consumption of the entire Fluentd instance.
|
|
163
|
+
>
|
|
164
|
+
> If you are using a multi-worker configuration (`<system> workers N </system>`), be aware that the actual total CPU and memory usage of Fluentd will be higher than what this plugin reports.
|
|
165
|
+
>
|
|
166
|
+
> For accurate, instance-wide resource monitoring, we strongly recommend using external OS-level monitoring tools rather than relying on application-internal metrics:
|
|
167
|
+
> * Container Environments (Kubernetes/Docker): Use tools like `cAdvisor` or `kube-state-metrics`, etc.
|
|
168
|
+
> * VM/Bare-metal Environments: Use `node_exporter` combined with `process-exporter`, etc.
|
|
169
|
+
|
|
147
170
|
### Output `opentelemetry` plugin
|
|
148
171
|
|
|
149
172
|
To send data, this plugin requires `<http>` or `<grpc>` section.
|
|
@@ -189,9 +212,11 @@ It needs to install `grpc` gem manually to use this feature.
|
|
|
189
212
|
|
|
190
213
|
| parameter | type | description | default |
|
|
191
214
|
|------------------------|---------|------------------------------------------------|---------|
|
|
215
|
+
| ca_path | string | Specifies the path of CA Certificate file used to verify the server | `nil` |
|
|
192
216
|
| cert_path | string | Specifies the path of Certificate file | `nil` |
|
|
193
217
|
| private_key_path | string | Specifies the path of Private Key file | `nil` |
|
|
194
218
|
| private_key_passphrase | string | Specifies the public CA private key passphrase | `nil` |
|
|
219
|
+
| insecure | bool | Skips the verification of the server certificate | `false` |
|
|
195
220
|
|
|
196
221
|
| parameter | type | description | available values | default |
|
|
197
222
|
|-------------|------|-------------------------------------------------------------|--------------------------------|---------|
|
|
@@ -200,6 +225,18 @@ It needs to install `grpc` gem manually to use this feature.
|
|
|
200
225
|
|
|
201
226
|
Refer [Config: Transport Section](https://docs.fluentd.org/configuration/transport-section)
|
|
202
227
|
|
|
228
|
+
The HTTP connection uses `ca_path`, `cert_path`, `private_key_path`, `private_key_passphrase`, `min_version`, `max_version` and `insecure`, and the other TLS parameters have no effect on it. `ca_path` is added to the default trusted CAs of the system instead of replacing them.
|
|
229
|
+
|
|
230
|
+
Fluentd rejects a `<transport tls>` section that configures no certificate at all, so pair `ca_path` with `insecure true` to verify the server without presenting a client certificate. On the HTTP connection, `insecure true` skips the verification only when `ca_path` is omitted.
|
|
231
|
+
|
|
232
|
+
> [!WARNING]
|
|
233
|
+
> This changes the behavior of an existing configuration that has both `ca_path` and `insecure true`. The HTTP connection used to skip the verification, and it now verifies the server certificate and its hostname. Drop `ca_path` to keep the previous behavior.
|
|
234
|
+
|
|
235
|
+
When the `<grpc>` section is configured, `<transport tls>` applies to the gRPC connection as well. gRPC uses `ca_path`, `cert_path`, `private_key_path` and `private_key_passphrase`, and the other TLS parameters have no effect on the gRPC connection. `insecure` does not weaken the gRPC connection: the server certificate is verified against `ca_path`, or against the default root certificates of gRPC when `ca_path` is omitted.
|
|
236
|
+
|
|
237
|
+
> [!WARNING]
|
|
238
|
+
> This changes the behavior of an existing configuration that has both `<transport tls>` and `<grpc>`. The gRPC connection used to be plaintext regardless of `<transport tls>`, and it now uses TLS, so the collector has to accept it.
|
|
239
|
+
|
|
203
240
|
#### `<buffer>` section
|
|
204
241
|
|
|
205
242
|
| parameter | type | description | default |
|
|
@@ -231,4 +268,3 @@ Refer [Config: Buffer Section](https://docs.fluentd.org/configuration/buffer-sec
|
|
|
231
268
|
* Copyright(c) 2025- Shizuo Fujita
|
|
232
269
|
* License
|
|
233
270
|
* Apache License, Version 2.0
|
|
234
|
-
|
data/TODO.md
CHANGED
|
@@ -8,10 +8,13 @@ Ref. https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specif
|
|
|
8
8
|
- [x] [MUST] Gzip compression, denoted by gzip.
|
|
9
9
|
|
|
10
10
|
### OTLP/gRPC Concurrent Requests
|
|
11
|
-
- [
|
|
11
|
+
- [x] [SHOULD] The implementations that need to achieve high throughput SHOULD support concurrent Unary calls to achieve higher throughput.
|
|
12
|
+
* NOTE: Supported by `flush_thread_count` settings in `<buffer>`.
|
|
12
13
|
- [ ] [SHOULD] The client SHOULD send new requests without waiting for the response to the earlier sent requests, essentially creating a pipeline of requests that are currently in flight that are not acknowledged.
|
|
13
|
-
- [
|
|
14
|
-
|
|
14
|
+
- [x] [SHOULD] The number of concurrent requests SHOULD be configurable.
|
|
15
|
+
* NOTE: Supported by `flush_thread_count` settings in `<buffer>`.
|
|
16
|
+
- [x] [SHOULD] The client implementation SHOULD expose an option to turn on and off the waiting during a shutdown.
|
|
17
|
+
* NOTE: Supported by `flush_at_shutdown` settings in `<buffer>`.
|
|
15
18
|
|
|
16
19
|
### OTLP/gRPC Response
|
|
17
20
|
- Full Success
|
|
@@ -30,7 +33,8 @@ Ref. https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specif
|
|
|
30
33
|
- [ ] [SHOULD] Retryable errors indicate that telemetry data processing failed, and the client SHOULD record the error and may retry exporting the same data.
|
|
31
34
|
- [ ] [SHOULD] The client SHOULD maintain a counter of such dropped data.
|
|
32
35
|
- [ ] [SHOULD] The client SHOULD interpret gRPC status codes as retryable or not-retryable according to the following table.
|
|
33
|
-
- [
|
|
36
|
+
- [x] [SHOULD] When retrying, the client SHOULD implement an exponential backoff strategy.
|
|
37
|
+
* NOTE: Fluentd's retry mechanism has `retry_type: exponential_backoff` by default.
|
|
34
38
|
- [ ] [SHOULD] The client SHOULD interpret RESOURCE_EXHAUSTED code as retryable only if the server signals that the recovery from resource exhaustion is possible.
|
|
35
39
|
|
|
36
40
|
### OTLP/gRPC Throttling
|
|
@@ -91,18 +95,21 @@ Ref. https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specif
|
|
|
91
95
|
- OTLP/HTTP Throttling
|
|
92
96
|
- [ ] [SHOULD] If the server receives more requests than the client is allowed or the server is overloaded, the server SHOULD respond with HTTP 429 Too Many Requests or HTTP 503 Service Unavailable.
|
|
93
97
|
- [ ] [SHOULD] The client SHOULD honour the waiting interval specified in the "Retry-After" header if it is present.
|
|
94
|
-
- [
|
|
98
|
+
- [x] [SHOULD] The "Retry-After" header is not present in the response, then the client SHOULD implement an exponential backoff strategy between retries.
|
|
99
|
+
* NOTE: Fluentd's retry mechanism has `retry_type: exponential_backoff` by default.
|
|
95
100
|
- All Other Responses
|
|
96
101
|
- [x] [SHOULD] If the server disconnects without returning a response, the client SHOULD retry and send the same request.
|
|
97
102
|
- [x] [SHOULD] The client SHOULD implement an exponential backoff strategy between retries to avoid overwhelming the server.
|
|
98
103
|
|
|
99
104
|
### OTLP/HTTP Connection
|
|
100
|
-
- [
|
|
101
|
-
|
|
105
|
+
- [x] [SHOULD] If the client cannot connect to the server, the client SHOULD retry the connection using an exponential backoff strategy between retries.
|
|
106
|
+
* NOTE: Fluentd's retry mechanism has `retry_type: exponential_backoff` by default.
|
|
107
|
+
- [x] [SHOULD] The client SHOULD keep the connection alive between requests.
|
|
102
108
|
- [ ] [SHOULD] Server implementations SHOULD accept OTLP/HTTP with binary-encoded Protobuf payload and OTLP/HTTP with JSON-encoded Protobuf payload requests on the same port and multiplex the requests to the corresponding payload decoder based on the "Content-Type" request header.
|
|
103
109
|
|
|
104
110
|
### OTLP/HTTP Concurrent Requests
|
|
105
|
-
- [
|
|
111
|
+
- [x] [SHOULD] To achieve higher total throughput, the client MAY send requests using several parallel HTTP connections. In that case, the maximum number of parallel connections SHOULD be configurable.
|
|
112
|
+
* NOTE: Supported by `flush_thread_count` settings in `<buffer>`.
|
|
106
113
|
|
|
107
114
|
# Future Versions and Interoperability
|
|
108
115
|
- [ ] [MUST] When possible, the interoperability MUST be ensured between all versions of OTLP that are not declared obsolete.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "fluent/plugin/input"
|
|
4
4
|
require "fluent/plugin/opentelemetry/constant"
|
|
5
|
+
require "fluent/plugin/opentelemetry/grpc_tls"
|
|
5
6
|
require "fluent/plugin/opentelemetry/http_input_handler"
|
|
6
7
|
require "fluent/plugin_helper/http_server"
|
|
7
8
|
require "fluent/plugin_helper/thread"
|
|
@@ -27,6 +28,11 @@ module Fluent::Plugin
|
|
|
27
28
|
config_param :bind, :string, default: "0.0.0.0"
|
|
28
29
|
desc "The port to listen to."
|
|
29
30
|
config_param :port, :integer, default: 4318
|
|
31
|
+
|
|
32
|
+
desc "The size limit of the POSTed element. This value should be larger than the 'chunk_limit_size' in out_opentelemetry plugin."
|
|
33
|
+
config_param :body_size_limit, :size, default: 32 * 1024 * 1024
|
|
34
|
+
desc "The size limit of the decompressed element."
|
|
35
|
+
config_param :decompression_size_limit, :size, default: 256 * 1024 * 1024
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
config_section :grpc, required: false, multi: false, init: false, param_name: :grpc_config do
|
|
@@ -52,13 +58,15 @@ module Fluent::Plugin
|
|
|
52
58
|
unless [@http_config, @grpc_config].any?
|
|
53
59
|
raise Fluent::ConfigError, "Please configure either <http> or <grpc> section, or both."
|
|
54
60
|
end
|
|
61
|
+
|
|
62
|
+
Opentelemetry::GrpcTLS.validate_server!(@transport_config) if @grpc_config
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
def start
|
|
58
66
|
super
|
|
59
67
|
|
|
60
68
|
if @http_config
|
|
61
|
-
http_handler = Opentelemetry::HttpInputHandler.new
|
|
69
|
+
http_handler = Opentelemetry::HttpInputHandler.new(@http_config, log)
|
|
62
70
|
http_server_create_http_server(:in_opentelemetry_http_server, addr: @http_config.bind, port: @http_config.port, logger: log) do |serv|
|
|
63
71
|
serv.post("/v1/logs") do |req|
|
|
64
72
|
http_handler.logs(req) { |record| router.emit(tag_for(Opentelemetry::RECORD_TYPE_LOGS), Fluent::EventTime.now, { "type" => Opentelemetry::RECORD_TYPE_LOGS, "message" => record }) }
|
|
@@ -73,8 +81,8 @@ module Fluent::Plugin
|
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
if @grpc_config
|
|
84
|
+
@grpc_handler = Opentelemetry::GrpcInputHandler.new(@grpc_config, @transport_config, log)
|
|
76
85
|
thread_create(:in_opentelemetry_grpc_server) do
|
|
77
|
-
@grpc_handler = Opentelemetry::GrpcInputHandler.new(@grpc_config, log)
|
|
78
86
|
@grpc_handler.run(
|
|
79
87
|
logs: lambda { |record|
|
|
80
88
|
router.emit(tag_for(Opentelemetry::RECORD_TYPE_LOGS), Fluent::EventTime.now, { "type" => Opentelemetry::RECORD_TYPE_LOGS, "message" => record })
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fluent/plugin/opentelemetry/grpc_tls"
|
|
3
4
|
require "fluent/plugin/opentelemetry/response"
|
|
4
5
|
require "opentelemetry/proto/collector/logs/v1/logs_service_services_pb"
|
|
5
6
|
require "opentelemetry/proto/collector/metrics/v1/metrics_service_services_pb"
|
|
@@ -49,14 +50,15 @@ module Fluent::Plugin::Opentelemetry
|
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
def initialize(grpc_config, logger)
|
|
53
|
+
def initialize(grpc_config, transport_config, logger)
|
|
53
54
|
@grpc_config = grpc_config
|
|
54
55
|
@logger = logger
|
|
56
|
+
@credentials = Fluent::Plugin::Opentelemetry::GrpcTLS.server_credentials(transport_config)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def run(logs:, metrics:, traces:)
|
|
58
60
|
@server = GRPC::RpcServer.new(interceptors: [ExceptionInterceptor.new])
|
|
59
|
-
@server.add_http2_port("#{@grpc_config.bind}:#{@grpc_config.port}",
|
|
61
|
+
@server.add_http2_port("#{@grpc_config.bind}:#{@grpc_config.port}", @credentials)
|
|
60
62
|
|
|
61
63
|
logs_handler = ServiceHandler::Logs.new
|
|
62
64
|
logs_handler.callback = lambda { |request|
|
|
@@ -8,6 +8,7 @@ require "opentelemetry/proto/collector/trace/v1/trace_service_pb"
|
|
|
8
8
|
require "opentelemetry/proto/collector/trace/v1/trace_service_services_pb"
|
|
9
9
|
|
|
10
10
|
require "fluent/plugin/opentelemetry/constant"
|
|
11
|
+
require "fluent/plugin/opentelemetry/grpc_tls"
|
|
11
12
|
require "google/protobuf"
|
|
12
13
|
|
|
13
14
|
class Fluent::Plugin::Opentelemetry::GrpcOutputHandler
|
|
@@ -56,10 +57,11 @@ class Fluent::Plugin::Opentelemetry::GrpcOutputHandler
|
|
|
56
57
|
channel_args["grpc.keepalive_time_ms"] = grpc_config.keepalive_time * 1000
|
|
57
58
|
channel_args["grpc.keepalive_timeout_ms"] = grpc_config.keepalive_timeout * 1000
|
|
58
59
|
channel_args["grpc.keepalive_permit_without_calls"] = 1
|
|
60
|
+
credentials = Fluent::Plugin::Opentelemetry::GrpcTLS.channel_credentials(@transport_config)
|
|
59
61
|
@services = {
|
|
60
|
-
Fluent::Plugin::Opentelemetry::RECORD_TYPE_LOGS => ServiceStub::Logs.new(@grpc_config.endpoint,
|
|
61
|
-
Fluent::Plugin::Opentelemetry::RECORD_TYPE_METRICS => ServiceStub::Metrics.new(@grpc_config.endpoint,
|
|
62
|
-
Fluent::Plugin::Opentelemetry::RECORD_TYPE_TRACES => ServiceStub::Traces.new(@grpc_config.endpoint,
|
|
62
|
+
Fluent::Plugin::Opentelemetry::RECORD_TYPE_LOGS => ServiceStub::Logs.new(@grpc_config.endpoint, credentials, channel_args: channel_args),
|
|
63
|
+
Fluent::Plugin::Opentelemetry::RECORD_TYPE_METRICS => ServiceStub::Metrics.new(@grpc_config.endpoint, credentials, channel_args: channel_args),
|
|
64
|
+
Fluent::Plugin::Opentelemetry::RECORD_TYPE_TRACES => ServiceStub::Traces.new(@grpc_config.endpoint, credentials, channel_args: channel_args)
|
|
63
65
|
}
|
|
64
66
|
end
|
|
65
67
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fluent/plugin/opentelemetry/constant"
|
|
4
|
+
|
|
5
|
+
require "openssl"
|
|
6
|
+
|
|
7
|
+
module Fluent::Plugin::Opentelemetry
|
|
8
|
+
module GrpcTLS
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def validate_server!(transport_config)
|
|
12
|
+
return unless tls?(transport_config)
|
|
13
|
+
|
|
14
|
+
if transport_config.cert_path.nil? || transport_config.private_key_path.nil?
|
|
15
|
+
raise Fluent::ConfigError, "<transport tls> cert_path and private_key_path are required when <grpc> is used"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if transport_config.client_cert_auth && transport_config.ca_path.nil?
|
|
19
|
+
raise Fluent::ConfigError, "<transport tls> client_cert_auth requires ca_path when <grpc> is used"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
[transport_config.ca_path, transport_config.cert_path, transport_config.private_key_path].compact.each do |path|
|
|
23
|
+
raise Fluent::ConfigError, "<transport tls> cannot read '#{path}'" unless File.readable?(path)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def channel_credentials(transport_config)
|
|
28
|
+
return :this_channel_is_insecure unless tls?(transport_config)
|
|
29
|
+
|
|
30
|
+
GRPC::Core::ChannelCredentials.new(
|
|
31
|
+
read_pem(transport_config.ca_path),
|
|
32
|
+
read_private_key(transport_config),
|
|
33
|
+
read_pem(transport_config.cert_path)
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def server_credentials(transport_config)
|
|
38
|
+
return :this_port_is_insecure unless tls?(transport_config)
|
|
39
|
+
|
|
40
|
+
GRPC::Core::ServerCredentials.new(
|
|
41
|
+
read_pem(transport_config.ca_path),
|
|
42
|
+
[{ private_key: read_private_key(transport_config), cert_chain: read_pem(transport_config.cert_path) }],
|
|
43
|
+
transport_config.client_cert_auth
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def tls?(transport_config)
|
|
48
|
+
transport_config.protocol == :tls
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read_pem(path)
|
|
52
|
+
return nil unless path
|
|
53
|
+
|
|
54
|
+
File.read(path)
|
|
55
|
+
rescue SystemCallError, IOError => e
|
|
56
|
+
raise Fluent::ConfigError, "<transport tls> failed to read '#{path}': #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def read_private_key(transport_config)
|
|
60
|
+
pem = read_pem(transport_config.private_key_path)
|
|
61
|
+
return nil unless pem
|
|
62
|
+
|
|
63
|
+
OpenSSL::PKey.read(pem, transport_config.private_key_passphrase.to_s).to_pem
|
|
64
|
+
rescue OpenSSL::PKey::PKeyError => e
|
|
65
|
+
raise Fluent::ConfigError,
|
|
66
|
+
"<transport tls> failed to load private_key_path '#{transport_config.private_key_path}': #{e.message}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private_class_method :tls?, :read_pem, :read_private_key
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -25,13 +25,26 @@ module Fluent::PluginHelper::HttpServer
|
|
|
25
25
|
@request.body&.close
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
unless method_defined?(:body_stream)
|
|
30
|
+
def body_stream
|
|
31
|
+
@request.body
|
|
32
|
+
end
|
|
33
|
+
end
|
|
28
34
|
end
|
|
29
35
|
end
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
class Fluent::Plugin::Opentelemetry::HttpInputHandler
|
|
39
|
+
class SizeLimitError < StandardError; end
|
|
40
|
+
|
|
33
41
|
using Fluent::PluginHelper::HttpServer::Extension
|
|
34
42
|
|
|
43
|
+
def initialize(http_config, logger)
|
|
44
|
+
@http_config = http_config
|
|
45
|
+
@logger = logger
|
|
46
|
+
end
|
|
47
|
+
|
|
35
48
|
def logs(req, &block)
|
|
36
49
|
common(req, Fluent::Plugin::Opentelemetry::Request::Logs, Fluent::Plugin::Opentelemetry::Response::Logs, &block)
|
|
37
50
|
end
|
|
@@ -49,16 +62,40 @@ class Fluent::Plugin::Opentelemetry::HttpInputHandler
|
|
|
49
62
|
def common(req, request_class, response_class)
|
|
50
63
|
content_type = req.headers["content-type"]
|
|
51
64
|
content_encoding = req.headers["content-encoding"]&.first
|
|
52
|
-
|
|
65
|
+
|
|
66
|
+
content_length = req.headers["content-length"]&.first&.to_i
|
|
67
|
+
if content_length && content_length >= @http_config.body_size_limit
|
|
68
|
+
@logger.warn { "Received too big content length: #{content_length}" }
|
|
69
|
+
return response_payload_too_large
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
body = read_body(req, limit: @http_config.body_size_limit)
|
|
74
|
+
rescue SizeLimitError
|
|
75
|
+
@logger.warn { "Received payload exceeding body_size_limit" }
|
|
76
|
+
return response_payload_too_large
|
|
77
|
+
end
|
|
78
|
+
|
|
53
79
|
return response_unsupported_media_type unless valid_content_type?(content_type)
|
|
54
80
|
return response_bad_request(content_type) unless valid_content_encoding?(content_encoding)
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
if content_encoding == Fluent::Plugin::Opentelemetry::CONTENT_ENCODING_GZIP
|
|
83
|
+
begin
|
|
84
|
+
body = decompress(body, limit: @http_config.decompression_size_limit)
|
|
85
|
+
rescue SizeLimitError
|
|
86
|
+
@logger.warn { "Decompressed payload exceeding decompression_size_limit" }
|
|
87
|
+
return response_payload_too_large
|
|
88
|
+
rescue Zlib::Error => e
|
|
89
|
+
@logger.warn { "Failed to decompress gzip payload: #{e.message}" }
|
|
90
|
+
return response_bad_request(content_type)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
57
93
|
|
|
58
94
|
begin
|
|
59
95
|
record = request_class.new(body).record
|
|
60
|
-
rescue Google::Protobuf::ParseError
|
|
96
|
+
rescue Google::Protobuf::ParseError => e
|
|
61
97
|
# The format in request body does not comply with the OpenTelemetry protocol.
|
|
98
|
+
@logger.warn { "Failed to parse OpenTelemetry payload: #{e.message}" }
|
|
62
99
|
return response_bad_request(content_type)
|
|
63
100
|
end
|
|
64
101
|
|
|
@@ -70,6 +107,43 @@ class Fluent::Plugin::Opentelemetry::HttpInputHandler
|
|
|
70
107
|
req.close
|
|
71
108
|
end
|
|
72
109
|
|
|
110
|
+
def read_body(request, limit:)
|
|
111
|
+
body = +""
|
|
112
|
+
while (chunk = request.body_stream&.read)
|
|
113
|
+
body << chunk
|
|
114
|
+
if body.bytesize > limit
|
|
115
|
+
raise SizeLimitError, "Too large payload"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
body
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
BYTES_TO_READ = 64 * 1024
|
|
122
|
+
private_constant :BYTES_TO_READ
|
|
123
|
+
|
|
124
|
+
def decompress(compressed_data, limit:)
|
|
125
|
+
io = StringIO.new(compressed_data)
|
|
126
|
+
out = +""
|
|
127
|
+
loop do
|
|
128
|
+
reader = Zlib::GzipReader.new(io)
|
|
129
|
+
while (chunk = reader.read(BYTES_TO_READ))
|
|
130
|
+
out << chunk
|
|
131
|
+
if out.bytesize > limit
|
|
132
|
+
raise SizeLimitError, "Decompressed data exceeds limit of #{limit} bytes"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
unused = reader.unused
|
|
137
|
+
reader.finish
|
|
138
|
+
unless unused.nil?
|
|
139
|
+
adjust = unused.length
|
|
140
|
+
io.pos -= adjust
|
|
141
|
+
end
|
|
142
|
+
break if io.eof?
|
|
143
|
+
end
|
|
144
|
+
out
|
|
145
|
+
end
|
|
146
|
+
|
|
73
147
|
def valid_content_type?(content_type)
|
|
74
148
|
case content_type
|
|
75
149
|
when Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PROTOBUF, Fluent::Plugin::Opentelemetry::CONTENT_TYPE_JSON
|
|
@@ -90,7 +164,11 @@ class Fluent::Plugin::Opentelemetry::HttpInputHandler
|
|
|
90
164
|
end
|
|
91
165
|
|
|
92
166
|
def response_unsupported_media_type
|
|
93
|
-
response(415, Fluent::Plugin::Opentelemetry::
|
|
167
|
+
response(415, Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PLAIN, "415 unsupported media type, supported: [application/json, application/x-protobuf]")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def response_payload_too_large
|
|
171
|
+
response(413, Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PLAIN, "413 Payload Too Large")
|
|
94
172
|
end
|
|
95
173
|
|
|
96
174
|
def response_bad_request(content_type)
|
|
@@ -19,6 +19,7 @@ class Fluent::Plugin::Opentelemetry::HttpOutputHandler
|
|
|
19
19
|
tls_settings[:client_cert] = @transport_config.cert_path
|
|
20
20
|
tls_settings[:client_key] = @transport_config.private_key_path
|
|
21
21
|
tls_settings[:client_key_pass] = @transport_config.private_key_passphrase
|
|
22
|
+
tls_settings[:ssl_ca_file] = @transport_config.ca_path unless @transport_config.ca_path.to_s.empty?
|
|
22
23
|
tls_settings[:ssl_min_version] = Fluent::Plugin::Opentelemetry::TLS_VERSIONS_MAP[@transport_config.min_version]
|
|
23
24
|
tls_settings[:ssl_max_version] = Fluent::Plugin::Opentelemetry::TLS_VERSIONS_MAP[@transport_config.max_version]
|
|
24
25
|
end
|
|
@@ -29,7 +30,7 @@ class Fluent::Plugin::Opentelemetry::HttpOutputHandler
|
|
|
29
30
|
connect_timeout: http_config.connect_timeout
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
tls_settings[:ssl_verify_peer] = false if @transport_config.insecure && tls_settings[:ssl_ca_file].nil?
|
|
33
34
|
@connections = {
|
|
34
35
|
Fluent::Plugin::Opentelemetry::RECORD_TYPE_LOGS => Excon.new(http_logs_endpoint, proxy: @http_config.proxy, persistent: true, **tls_settings, **timeout_settings),
|
|
35
36
|
Fluent::Plugin::Opentelemetry::RECORD_TYPE_METRICS => Excon.new(http_metrics_endpoint, proxy: @http_config.proxy, persistent: true, **tls_settings, **timeout_settings),
|
|
@@ -90,7 +90,7 @@ module Fluent::Plugin
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def write(chunk)
|
|
93
|
-
BatchProcessor.build_export_requests(chunk).each do |export_request|
|
|
93
|
+
BatchProcessor.build_export_requests(chunk, log).each do |export_request|
|
|
94
94
|
if @grpc_handler
|
|
95
95
|
@grpc_handler.export(export_request)
|
|
96
96
|
else
|
|
@@ -106,22 +106,72 @@ module Fluent::Plugin
|
|
|
106
106
|
Opentelemetry::RECORD_TYPE_TRACES => "resourceSpans"
|
|
107
107
|
}.freeze
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
class InvalidRecords
|
|
110
|
+
MAX_SAMPLE_BYTESIZE = 100
|
|
111
|
+
MAX_SAMPLES = 3
|
|
112
|
+
private_constant :MAX_SAMPLE_BYTESIZE, :MAX_SAMPLES
|
|
113
|
+
|
|
114
|
+
attr_reader :count
|
|
115
|
+
|
|
116
|
+
def initialize
|
|
117
|
+
@count = 0
|
|
118
|
+
@samples = []
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def add(sample = nil)
|
|
122
|
+
@count += 1
|
|
123
|
+
return if sample.nil? || @samples.size >= MAX_SAMPLES
|
|
124
|
+
|
|
125
|
+
text = sample.byteslice(0, MAX_SAMPLE_BYTESIZE).scrub("")
|
|
126
|
+
@samples << text unless @samples.include?(text)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def to_s
|
|
130
|
+
@samples.empty? ? @count.to_s : "#{@count} (#{@samples.join(' | ')})"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.build_export_requests(chunk, logger)
|
|
110
135
|
requests = {
|
|
111
136
|
Opentelemetry::RECORD_TYPE_LOGS => {},
|
|
112
137
|
Opentelemetry::RECORD_TYPE_METRICS => {},
|
|
113
138
|
Opentelemetry::RECORD_TYPE_TRACES => {}
|
|
114
139
|
}
|
|
140
|
+
invalid = Hash.new { |hash, key| hash[key] = InvalidRecords.new }
|
|
115
141
|
|
|
116
142
|
chunk.each do |_, record| # rubocop:disable Style/HashEachMethods
|
|
117
143
|
record_type = record["type"]
|
|
118
144
|
resource_key = RESOURCE_KEY_MAP[record_type]
|
|
119
|
-
|
|
120
|
-
|
|
145
|
+
unless resource_key
|
|
146
|
+
invalid["unknown type"].add(record_type.inspect)
|
|
147
|
+
next
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
begin
|
|
151
|
+
record["message"] = JSON.parse(record["message"])
|
|
152
|
+
rescue JSON::ParserError, TypeError => e
|
|
153
|
+
invalid["broken message"].add(e.message)
|
|
154
|
+
next
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
resources = record["message"][resource_key] if record["message"].is_a?(Hash)
|
|
158
|
+
unless resources.is_a?(Array) && resources.first.is_a?(Hash)
|
|
159
|
+
invalid["no #{resource_key}"].add
|
|
160
|
+
next
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
resource_hash = resources.first["resource"].hash
|
|
121
164
|
if requests[record_type][resource_hash].nil?
|
|
122
165
|
requests[record_type][resource_hash] = record
|
|
123
166
|
else
|
|
124
|
-
requests[record_type][resource_hash]["message"][resource_key].concat(
|
|
167
|
+
requests[record_type][resource_hash]["message"][resource_key].concat(resources)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
unless invalid.empty?
|
|
172
|
+
logger.warn do
|
|
173
|
+
details = invalid.map { |reason, records| "#{reason}=#{records}" }
|
|
174
|
+
"Skipped invalid records (total: #{invalid.values.sum(&:count)}): #{details.join(', ')}"
|
|
125
175
|
end
|
|
126
176
|
end
|
|
127
177
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluent-plugin-opentelemetry
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shizuo Fujita
|
|
@@ -112,6 +112,7 @@ files:
|
|
|
112
112
|
- lib/fluent/plugin/opentelemetry/constant.rb
|
|
113
113
|
- lib/fluent/plugin/opentelemetry/grpc_input_handler.rb
|
|
114
114
|
- lib/fluent/plugin/opentelemetry/grpc_output_handler.rb
|
|
115
|
+
- lib/fluent/plugin/opentelemetry/grpc_tls.rb
|
|
115
116
|
- lib/fluent/plugin/opentelemetry/http_input_handler.rb
|
|
116
117
|
- lib/fluent/plugin/opentelemetry/http_output_handler.rb
|
|
117
118
|
- lib/fluent/plugin/opentelemetry/request.rb
|
|
@@ -138,6 +139,7 @@ licenses:
|
|
|
138
139
|
metadata:
|
|
139
140
|
homepage_uri: https://github.com/fluent-plugins-nursery/fluent-plugin-opentelemetry
|
|
140
141
|
source_code_uri: https://github.com/fluent-plugins-nursery/fluent-plugin-opentelemetry
|
|
142
|
+
changelog_uri: https://github.com/fluent-plugins-nursery/fluent-plugin-opentelemetry/blob/main/CHANGELOG.md
|
|
141
143
|
rubygems_mfa_required: 'true'
|
|
142
144
|
rdoc_options: []
|
|
143
145
|
require_paths:
|
|
@@ -153,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
153
155
|
- !ruby/object:Gem::Version
|
|
154
156
|
version: '0'
|
|
155
157
|
requirements: []
|
|
156
|
-
rubygems_version: 4.0.
|
|
158
|
+
rubygems_version: 4.0.18
|
|
157
159
|
specification_version: 4
|
|
158
160
|
summary: Fluentd input/output plugin to forward OpenTelemetry Protocol data.
|
|
159
161
|
test_files: []
|