fluent-plugin-opentelemetry 0.1.0 → 0.2.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: c8f0a6c1b314b0737a832920decef09f6066acd10d578271b65edc4bc535b540
4
- data.tar.gz: b5b1fd56d7c410ac7decfc50b67459277cb14d1a20b762bfdc414edaeaf1f4ef
3
+ metadata.gz: fa3bba6bc0556816b7f7fba2e7be82ffc2d629e484778042c7aab5113df8f514
4
+ data.tar.gz: f6bf4718017e97e2976ba2f2d74dc60fd85b2c7806087300a1e61788578ad528
5
5
  SHA512:
6
- metadata.gz: fe86042e9aa128f24f0479332aee03e753ef82e6e04fe34b6d49cc1226753f2ca26a775d5b938cce67cf02b710f5230c8106f278cb5afc19dc69faefc439a3c5
7
- data.tar.gz: 65c174366007116fd6b7fa2422914224414f5ec7c976732550d448fd219edd25c7b413e1a077ad243ab341d2e2fbd688369e1c525ba417c8418c32d5d17532b2
6
+ metadata.gz: 5a5b6d29a34477e583bcde8d81a174df7e8b55ec20ee93b7bf054882626621c2e800b610bd5025a0a8a96b80f6d29d4067a4f958f5f250e398ac2cf368e1439d
7
+ data.tar.gz: 8f514226a7c08fe6b890bae1a8004ec5f3cc58b267883339219937baa7f7c4aaade44fab72b791bcbcf246e83b5fdcf06544420371beb2b2521e3b6c437d9ae9
data/.rubocop.yml CHANGED
@@ -6,12 +6,18 @@ AllCops:
6
6
  SuggestExtensions: false
7
7
  TargetRubyVersion: 3.2
8
8
 
9
+ Gemspec/DevelopmentDependencies:
10
+ Enabled: false
11
+
9
12
  Layout/LineLength:
10
13
  Enabled: false
11
14
 
12
15
  Lint/UnusedMethodArgument:
13
16
  Enabled: false
14
17
 
18
+ Lint/SuppressedException:
19
+ Enabled: false
20
+
15
21
  Metrics:
16
22
  Enabled: false
17
23
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2025-06-26
4
+
5
+ - Make gRPC sending and receiving optional (#4)
6
+ - To use gRPC sending and receiving, you need to install `grpc` gem manually.
7
+
3
8
  ## [0.1.0] - 2025-03-31
4
9
 
5
10
  - Initial release
data/README.md CHANGED
@@ -46,6 +46,7 @@ This requires to receive data via HTTP/HTTPS.
46
46
  #### `<grpc>` section
47
47
 
48
48
  This requires to receive data via gRPC.
49
+ It needs to install `grpc` gem manually to use this feature.
49
50
 
50
51
  > [!WARNING]
51
52
  > Now, gRPC feature status is experimental.
@@ -53,7 +54,7 @@ This requires to receive data via gRPC.
53
54
  | parameter | type | description | default |
54
55
  |-----------|---------|------------------------|-----------|
55
56
  | bind | string | The address to bind to | `0.0.0.0` |
56
- | port | integer | The port to listen to | `4318` |
57
+ | port | integer | The port to listen to | `4317` |
57
58
 
58
59
  * `<transport>` section
59
60
 
@@ -103,6 +104,7 @@ This requires to send data via HTTP/HTTPS.
103
104
  #### `<grpc>` section
104
105
 
105
106
  This requires to send data via gRPC.
107
+ It needs to install `grpc` gem manually to use this feature.
106
108
 
107
109
  > [!WARNING]
108
110
  > Now, gRPC feature status is experimental.
@@ -2,29 +2,15 @@
2
2
 
3
3
  require "fluent/plugin/input"
4
4
  require "fluent/plugin/opentelemetry/constant"
5
- require "fluent/plugin/opentelemetry/request"
6
- require "fluent/plugin/opentelemetry/response"
7
- require "fluent/plugin/opentelemetry/service_handler"
5
+ require "fluent/plugin/opentelemetry/http_input_handler"
8
6
  require "fluent/plugin_helper/http_server"
9
7
  require "fluent/plugin_helper/thread"
10
8
 
11
- require "zlib"
9
+ begin
10
+ require "grpc"
12
11
 
13
- unless Fluent::PluginHelper::HttpServer::Request.method_defined?(:headers)
14
- # This API was introduced at fluentd v1.19.0.
15
- # Ref. https://github.com/fluent/fluentd/pull/4903
16
- # If we have supported v1.19.0+ only, we can remove this patch.
17
- module Fluent::PluginHelper::HttpServer
18
- module Extension
19
- refine Request do
20
- def headers
21
- @request.headers
22
- end
23
- end
24
- end
25
- end
26
-
27
- using Fluent::PluginHelper::HttpServer::Extension
12
+ require "fluent/plugin/opentelemetry/grpc_input_handler"
13
+ rescue LoadError
28
14
  end
29
15
 
30
16
  module Fluent::Plugin
@@ -57,6 +43,10 @@ module Fluent::Plugin
57
43
  def configure(conf)
58
44
  super
59
45
 
46
+ if @grpc_config && !defined?(GRPC)
47
+ raise Fluent::ConfigError, "To use gRPC feature, please install grpc gem such as 'fluent-gem install grpc'."
48
+ end
49
+
60
50
  unless [@http_config, @grpc_config].any?
61
51
  raise Fluent::ConfigError, "Please configure either <http> or <grpc> section, or both."
62
52
  end
@@ -66,7 +56,7 @@ module Fluent::Plugin
66
56
  super
67
57
 
68
58
  if @http_config
69
- http_handler = HttpHandler.new
59
+ http_handler = Opentelemetry::HttpInputHandler.new
70
60
  http_server_create_http_server(:in_opentelemetry_http_server, addr: @http_config.bind, port: @http_config.port, logger: log) do |serv|
71
61
  serv.post("/v1/logs") do |req|
72
62
  http_handler.logs(req) { |record| router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_LOGS, message: record }) }
@@ -82,7 +72,7 @@ module Fluent::Plugin
82
72
 
83
73
  if @grpc_config
84
74
  thread_create(:in_opentelemetry_grpc_server) do
85
- grpc_handler = GrpcHandler.new(@grpc_config, log)
75
+ grpc_handler = Opentelemetry::GrpcInputHandler.new(@grpc_config, log)
86
76
  grpc_handler.run(
87
77
  logs: lambda { |record|
88
78
  router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_LOGS, message: record })
@@ -97,115 +87,5 @@ module Fluent::Plugin
97
87
  end
98
88
  end
99
89
  end
100
-
101
- class HttpHandler
102
- def logs(req, &block)
103
- common(req, Opentelemetry::Request::Logs, Opentelemetry::Response::Logs, &block)
104
- end
105
-
106
- def metrics(req, &block)
107
- common(req, Opentelemetry::Request::Metrics, Opentelemetry::Response::Metrics, &block)
108
- end
109
-
110
- def traces(req, &block)
111
- common(req, Opentelemetry::Request::Traces, Opentelemetry::Response::Traces, &block)
112
- end
113
-
114
- private
115
-
116
- def common(req, request_class, response_class)
117
- content_type = req.headers["content-type"]
118
- content_encoding = req.headers["content-encoding"]&.first
119
- return response_unsupported_media_type unless valid_content_type?(content_type)
120
- return response_bad_request(content_type) unless valid_content_encoding?(content_encoding)
121
-
122
- body = req.body
123
- body = Zlib::GzipReader.new(StringIO.new(body)).read if content_encoding == Opentelemetry::CONTENT_ENCODING_GZIP
124
-
125
- begin
126
- record = request_class.new(body).record
127
- rescue Google::Protobuf::ParseError
128
- # The format in request body does not comply with the OpenTelemetry protocol.
129
- return response_bad_request(content_type)
130
- end
131
-
132
- yield record
133
-
134
- res = response_class.new
135
- response(200, content_type, res.body(type: Opentelemetry::Response.type(content_type)))
136
- end
137
-
138
- def valid_content_type?(content_type)
139
- case content_type
140
- when Opentelemetry::CONTENT_TYPE_PROTOBUF, Opentelemetry::CONTENT_TYPE_JSON
141
- true
142
- else
143
- false
144
- end
145
- end
146
-
147
- def valid_content_encoding?(content_encoding)
148
- return true if content_encoding.nil?
149
-
150
- content_encoding == Opentelemetry::CONTENT_ENCODING_GZIP
151
- end
152
-
153
- def response(code, content_type, body)
154
- [code, { Opentelemetry::CONTENT_TYPE => content_type }, body]
155
- end
156
-
157
- def response_unsupported_media_type
158
- response(415, Opentelemetry::CONTENT_TYPE_PAIN, "415 unsupported media type, supported: [application/json, application/x-protobuf]")
159
- end
160
-
161
- def response_bad_request(content_type)
162
- response(400, content_type, "") # TODO: fix body message
163
- end
164
- end
165
-
166
- class GrpcHandler
167
- class ExceptionInterceptor < GRPC::ServerInterceptor
168
- def request_response(request:, call:, method:)
169
- # call actual service
170
- yield
171
- rescue StandardError => e
172
- puts "[#{method}] Error: #{e.message}"
173
- raise
174
- end
175
- end
176
-
177
- def initialize(grpc_config, logger)
178
- @grpc_config = grpc_config
179
- @logger = logger
180
- end
181
-
182
- def run(logs:, metrics:, traces:)
183
- server = GRPC::RpcServer.new(interceptors: [ExceptionInterceptor.new])
184
- server.add_http2_port("#{@grpc_config.bind}:#{@grpc_config.port}", :this_port_is_insecure)
185
-
186
- logs_handler = Opentelemetry::ServiceHandler::Logs.new
187
- logs_handler.callback = lambda { |request|
188
- logs.call(request.to_json)
189
- Opentelemetry::Response::Logs.build
190
- }
191
- server.handle(logs_handler)
192
-
193
- metrics_handler = Opentelemetry::ServiceHandler::Metrics.new
194
- metrics_handler.callback = lambda { |request|
195
- metrics.call(request.to_json)
196
- Opentelemetry::Response::Metrics.build
197
- }
198
- server.handle(metrics_handler)
199
-
200
- traces_handler = Opentelemetry::ServiceHandler::Traces.new
201
- traces_handler.callback = lambda { |request|
202
- traces.call(request.to_json)
203
- Opentelemetry::Response::Traces.build
204
- }
205
- server.handle(traces_handler)
206
-
207
- server.run_till_terminated
208
- end
209
- end
210
90
  end
211
91
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/opentelemetry/response"
4
+ require "opentelemetry/proto/collector/logs/v1/logs_service_services_pb"
5
+ require "opentelemetry/proto/collector/metrics/v1/metrics_service_services_pb"
6
+ require "opentelemetry/proto/collector/trace/v1/trace_service_services_pb"
7
+
8
+ module Fluent::Plugin::Opentelemetry
9
+ class GrpcInputHandler
10
+ class ServiceHandler
11
+ class Logs < Opentelemetry::Proto::Collector::Logs::V1::LogsService::Service
12
+ def callback=(block)
13
+ @callback = block
14
+ end
15
+
16
+ def export(req, _call)
17
+ @callback.call(req)
18
+ end
19
+ end
20
+
21
+ class Metrics < Opentelemetry::Proto::Collector::Metrics::V1::MetricsService::Service
22
+ def callback=(block)
23
+ @callback = block
24
+ end
25
+
26
+ def export(req, _call)
27
+ @callback.call(req)
28
+ end
29
+ end
30
+
31
+ class Traces < Opentelemetry::Proto::Collector::Trace::V1::TraceService::Service
32
+ def callback=(block)
33
+ @callback = block
34
+ end
35
+
36
+ def export(req, _call)
37
+ @callback.call(req)
38
+ end
39
+ end
40
+ end
41
+
42
+ class ExceptionInterceptor < GRPC::ServerInterceptor
43
+ def request_response(request:, call:, method:)
44
+ # call actual service
45
+ yield
46
+ rescue StandardError => e
47
+ puts "[#{method}] Error: #{e.message}"
48
+ raise
49
+ end
50
+ end
51
+
52
+ def initialize(grpc_config, logger)
53
+ @grpc_config = grpc_config
54
+ @logger = logger
55
+ end
56
+
57
+ def run(logs:, metrics:, traces:)
58
+ server = GRPC::RpcServer.new(interceptors: [ExceptionInterceptor.new])
59
+ server.add_http2_port("#{@grpc_config.bind}:#{@grpc_config.port}", :this_port_is_insecure)
60
+
61
+ logs_handler = ServiceHandler::Logs.new
62
+ logs_handler.callback = lambda { |request|
63
+ logs.call(request.to_json)
64
+ Fluent::Plugin::Opentelemetry::Response::Logs.build
65
+ }
66
+ server.handle(logs_handler)
67
+
68
+ metrics_handler = ServiceHandler::Metrics.new
69
+ metrics_handler.callback = lambda { |request|
70
+ metrics.call(request.to_json)
71
+ Fluent::Plugin::Opentelemetry::Response::Metrics.build
72
+ }
73
+ server.handle(metrics_handler)
74
+
75
+ traces_handler = ServiceHandler::Traces.new
76
+ traces_handler.callback = lambda { |request|
77
+ traces.call(request.to_json)
78
+ Fluent::Plugin::Opentelemetry::Response::Traces.build
79
+ }
80
+ server.handle(traces_handler)
81
+
82
+ server.run_till_terminated
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opentelemetry/proto/collector/logs/v1/logs_service_pb"
4
+ require "opentelemetry/proto/collector/logs/v1/logs_service_services_pb"
5
+ require "opentelemetry/proto/collector/metrics/v1/metrics_service_pb"
6
+ require "opentelemetry/proto/collector/metrics/v1/metrics_service_services_pb"
7
+ require "opentelemetry/proto/collector/trace/v1/trace_service_pb"
8
+ require "opentelemetry/proto/collector/trace/v1/trace_service_services_pb"
9
+
10
+ require "fluent/plugin/opentelemetry/constant"
11
+ require "google/protobuf"
12
+
13
+ class Fluent::Plugin::Opentelemetry::GrpcOutputHandler
14
+ class ServiceStub
15
+ class Logs
16
+ def initialize(host, creds, **kw)
17
+ @stub = Opentelemetry::Proto::Collector::Logs::V1::LogsService::Stub.new(host, creds, **kw)
18
+ end
19
+
20
+ def export(json)
21
+ message = Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode_json(json)
22
+ @stub.export(message)
23
+ end
24
+ end
25
+
26
+ class Metrics
27
+ def initialize(host, creds, **kw)
28
+ @stub = Opentelemetry::Proto::Collector::Metrics::V1::MetricsService::Stub.new(host, creds, **kw)
29
+ end
30
+
31
+ def export(json)
32
+ message = Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.decode_json(json)
33
+ @stub.export(message)
34
+ end
35
+ end
36
+
37
+ class Traces
38
+ def initialize(host, creds, **kw)
39
+ @stub = Opentelemetry::Proto::Collector::Trace::V1::TraceService::Stub.new(host, creds, **kw)
40
+ end
41
+
42
+ def export(json)
43
+ message = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode_json(json)
44
+ @stub.export(message)
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize(grpc_config, transport_config, logger)
50
+ @grpc_config = grpc_config
51
+ @transport_config = transport_config
52
+ @logger = logger
53
+ end
54
+
55
+ def export(record)
56
+ msg = record["message"]
57
+
58
+ credential = :this_channel_is_insecure
59
+
60
+ case record["type"]
61
+ when Fluent::Plugin::Opentelemetry::RECORD_TYPE_LOGS
62
+ service = ServiceStub::Logs.new(@grpc_config.endpoint, credential)
63
+ when Fluent::Plugin::Opentelemetry::RECORD_TYPE_METRICS
64
+ service = ServiceStub::Metrics.new(@grpc_config.endpoint, credential)
65
+ when Fluent::Plugin::Opentelemetry::RECORD_TYPE_TRACES
66
+ service = ServiceStub::Traces.new(@grpc_config.endpoint, credential)
67
+ end
68
+
69
+ begin
70
+ service.export(msg)
71
+ rescue Google::Protobuf::ParseError => e
72
+ # The message format does not comply with the OpenTelemetry protocol.
73
+ raise ::Fluent::UnrecoverableError, e.message
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/opentelemetry/constant"
4
+ require "fluent/plugin/opentelemetry/request"
5
+ require "fluent/plugin/opentelemetry/response"
6
+ require "google/protobuf"
7
+ require "zlib"
8
+
9
+ unless Fluent::PluginHelper::HttpServer::Request.method_defined?(:headers)
10
+ # This API was introduced at fluentd v1.19.0.
11
+ # Ref. https://github.com/fluent/fluentd/pull/4903
12
+ # If we have supported v1.19.0+ only, we can remove this patch.
13
+ module Fluent::PluginHelper::HttpServer
14
+ module Extension
15
+ refine Request do
16
+ def headers
17
+ @request.headers
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ using Fluent::PluginHelper::HttpServer::Extension
24
+ end
25
+
26
+ class Fluent::Plugin::Opentelemetry::HttpInputHandler
27
+ def logs(req, &block)
28
+ common(req, Fluent::Plugin::Opentelemetry::Request::Logs, Fluent::Plugin::Opentelemetry::Response::Logs, &block)
29
+ end
30
+
31
+ def metrics(req, &block)
32
+ common(req, Fluent::Plugin::Opentelemetry::Request::Metrics, Fluent::Plugin::Opentelemetry::Response::Metrics, &block)
33
+ end
34
+
35
+ def traces(req, &block)
36
+ common(req, Fluent::Plugin::Opentelemetry::Request::Traces, Fluent::Plugin::Opentelemetry::Response::Traces, &block)
37
+ end
38
+
39
+ private
40
+
41
+ def common(req, request_class, response_class)
42
+ content_type = req.headers["content-type"]
43
+ content_encoding = req.headers["content-encoding"]&.first
44
+ return response_unsupported_media_type unless valid_content_type?(content_type)
45
+ return response_bad_request(content_type) unless valid_content_encoding?(content_encoding)
46
+
47
+ body = req.body
48
+ body = Zlib::GzipReader.new(StringIO.new(body)).read if content_encoding == Fluent::Plugin::Opentelemetry::CONTENT_ENCODING_GZIP
49
+
50
+ begin
51
+ record = request_class.new(body).record
52
+ rescue Google::Protobuf::ParseError
53
+ # The format in request body does not comply with the OpenTelemetry protocol.
54
+ return response_bad_request(content_type)
55
+ end
56
+
57
+ yield record
58
+
59
+ res = response_class.new
60
+ response(200, content_type, res.body(type: Fluent::Plugin::Opentelemetry::Response.type(content_type)))
61
+ end
62
+
63
+ def valid_content_type?(content_type)
64
+ case content_type
65
+ when Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PROTOBUF, Fluent::Plugin::Opentelemetry::CONTENT_TYPE_JSON
66
+ true
67
+ else
68
+ false
69
+ end
70
+ end
71
+
72
+ def valid_content_encoding?(content_encoding)
73
+ return true if content_encoding.nil?
74
+
75
+ content_encoding == Fluent::Plugin::Opentelemetry::CONTENT_ENCODING_GZIP
76
+ end
77
+
78
+ def response(code, content_type, body)
79
+ [code, { Fluent::Plugin::Opentelemetry::CONTENT_TYPE => content_type }, body]
80
+ end
81
+
82
+ def response_unsupported_media_type
83
+ response(415, Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PAIN, "415 unsupported media type, supported: [application/json, application/x-protobuf]")
84
+ end
85
+
86
+ def response_bad_request(content_type)
87
+ response(400, content_type, "") # TODO: fix body message
88
+ end
89
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/opentelemetry/constant"
4
+ require "fluent/plugin/opentelemetry/request"
5
+
6
+ require "excon"
7
+ require "google/protobuf"
8
+ require "stringio"
9
+ require "zlib"
10
+
11
+ class Fluent::Plugin::Opentelemetry::HttpOutputHandler
12
+ def initialize(http_config, transport_config, logger)
13
+ @http_config = http_config
14
+ @transport_config = transport_config
15
+ @logger = logger
16
+
17
+ @tls_settings = {}
18
+ if @transport_config.protocol == :tls
19
+ @tls_settings[:client_cert] = @transport_config.cert_path
20
+ @tls_settings[:client_key] = @transport_config.private_key_path
21
+ @tls_settings[:client_key_pass] = @transport_config.private_key_passphrase
22
+ @tls_settings[:ssl_min_version] = Fluent::Plugin::Opentelemetry::TLS_VERSIONS_MAP[@transport_config.min_version]
23
+ @tls_settings[:ssl_max_version] = Fluent::Plugin::Opentelemetry::TLS_VERSIONS_MAP[@transport_config.max_version]
24
+ end
25
+
26
+ @timeout_settings = {
27
+ read_timeout: http_config.read_timeout,
28
+ write_timeout: http_config.write_timeout,
29
+ connect_timeout: http_config.connect_timeout
30
+ }
31
+ end
32
+
33
+ def export(record)
34
+ uri, connection = create_http_connection(record)
35
+ response = connection.post
36
+
37
+ if response.status != 200
38
+ if response.status == 400
39
+ # The client MUST NOT retry the request when it receives HTTP 400 Bad Request response.
40
+ raise Fluent::UnrecoverableError, "got unrecoverable error response from '#{uri}', response code is #{response.status}"
41
+ end
42
+
43
+ if @http_config.retryable_response_codes&.include?(response.status)
44
+ raise Fluent::Plugin::OpentelemetryOutput::RetryableResponse, "got retryable error response from '#{uri}', response code is #{response.status}"
45
+ end
46
+ if @http_config.error_response_as_unrecoverable
47
+ raise Fluent::UnrecoverableError, "got unrecoverable error response from '#{uri}', response code is #{response.status}"
48
+ else
49
+ @logger.error "got error response from '#{uri}', response code is #{response.status}"
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def http_logs_endpoint
57
+ "#{@http_config.endpoint}/v1/logs"
58
+ end
59
+
60
+ def http_metrics_endpoint
61
+ "#{@http_config.endpoint}/v1/metrics"
62
+ end
63
+
64
+ def http_traces_endpoint
65
+ "#{@http_config.endpoint}/v1/traces"
66
+ end
67
+
68
+ def create_http_connection(record)
69
+ msg = record["message"]
70
+
71
+ begin
72
+ case record["type"]
73
+ when Fluent::Plugin::Opentelemetry::RECORD_TYPE_LOGS
74
+ uri = http_logs_endpoint
75
+ body = Fluent::Plugin::Opentelemetry::Request::Logs.new(msg).body
76
+ when Fluent::Plugin::Opentelemetry::RECORD_TYPE_METRICS
77
+ uri = http_metrics_endpoint
78
+ body = Fluent::Plugin::Opentelemetry::Request::Metrics.new(msg).body
79
+ when Fluent::Plugin::Opentelemetry::RECORD_TYPE_TRACES
80
+ uri = http_traces_endpoint
81
+ body = Fluent::Plugin::Opentelemetry::Request::Traces.new(msg).body
82
+ end
83
+ rescue Google::Protobuf::ParseError => e
84
+ # The message format does not comply with the OpenTelemetry protocol.
85
+ raise ::Fluent::UnrecoverableError, e.message
86
+ end
87
+
88
+ headers = { Fluent::Plugin::Opentelemetry::CONTENT_TYPE => Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PROTOBUF }
89
+ if @http_config.compress == :gzip
90
+ headers[Fluent::Plugin::Opentelemetry::CONTENT_ENCODING] = Fluent::Plugin::Opentelemetry::CONTENT_ENCODING_GZIP
91
+ gz = Zlib::GzipWriter.new(StringIO.new)
92
+ gz << body
93
+ body = gz.close.string
94
+ end
95
+
96
+ Excon.defaults[:ssl_verify_peer] = false if @transport_config.insecure
97
+ connection = Excon.new(uri, body: body, headers: headers, proxy: @http_config.proxy, persistent: true, **@tls_settings, **@timeout_settings)
98
+ [uri, connection]
99
+ end
100
+ end
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fluent/plugin/opentelemetry/constant"
4
- require "fluent/plugin/opentelemetry/request"
5
- require "fluent/plugin/opentelemetry/service_stub"
4
+ require "fluent/plugin/opentelemetry/http_output_handler"
6
5
  require "fluent/plugin/output"
7
6
 
8
- require "excon"
9
- require "grpc"
10
7
  require "json"
11
- require "stringio"
12
- require "zlib"
8
+
9
+ begin
10
+ require "grpc"
11
+
12
+ require "fluent/plugin/opentelemetry/grpc_output_handler"
13
+ rescue LoadError
14
+ end
13
15
 
14
16
  module Fluent::Plugin
15
17
  class OpentelemetryOutput < Output
@@ -57,12 +59,16 @@ module Fluent::Plugin
57
59
  def configure(conf)
58
60
  super
59
61
 
62
+ if @grpc_config && !defined?(GRPC)
63
+ raise Fluent::ConfigError, "To use gRPC feature, please install grpc gem such as 'fluent-gem install grpc'."
64
+ end
65
+
60
66
  unless [@http_config, @grpc_config].one?
61
67
  raise Fluent::ConfigError, "Please configure either <http> or <grpc> section."
62
68
  end
63
69
 
64
- @http_handler = HttpHandler.new(@http_config, @transport_config, log) if @http_config
65
- @grpc_handler = GrpcHandler.new(@grpc_config, @transport_config, log) if @grpc_config
70
+ @http_handler = Opentelemetry::HttpOutputHandler.new(@http_config, @transport_config, log) if @http_config
71
+ @grpc_handler = Opentelemetry::GrpcOutputHandler.new(@grpc_config, @transport_config, log) if @grpc_config
66
72
  end
67
73
 
68
74
  def multi_workers_ready?
@@ -82,126 +88,5 @@ module Fluent::Plugin
82
88
  @http_handler.export(record)
83
89
  end
84
90
  end
85
-
86
- class HttpHandler
87
- def initialize(http_config, transport_config, logger)
88
- @http_config = http_config
89
- @transport_config = transport_config
90
- @logger = logger
91
-
92
- @tls_settings = {}
93
- if @transport_config.protocol == :tls
94
- @tls_settings[:client_cert] = @transport_config.cert_path
95
- @tls_settings[:client_key] = @transport_config.private_key_path
96
- @tls_settings[:client_key_pass] = @transport_config.private_key_passphrase
97
- @tls_settings[:ssl_min_version] = Opentelemetry::TLS_VERSIONS_MAP[@transport_config.min_version]
98
- @tls_settings[:ssl_max_version] = Opentelemetry::TLS_VERSIONS_MAP[@transport_config.max_version]
99
- end
100
-
101
- @timeout_settings = {
102
- read_timeout: http_config.read_timeout,
103
- write_timeout: http_config.write_timeout,
104
- connect_timeout: http_config.connect_timeout
105
- }
106
- end
107
-
108
- def export(record)
109
- uri, connection = create_http_connection(record)
110
- response = connection.post
111
-
112
- if response.status != 200
113
- if response.status == 400
114
- # The client MUST NOT retry the request when it receives HTTP 400 Bad Request response.
115
- raise Fluent::UnrecoverableError, "got unrecoverable error response from '#{uri}', response code is #{response.status}"
116
- end
117
-
118
- if @http_config.retryable_response_codes&.include?(response.status)
119
- raise RetryableResponse, "got retryable error response from '#{uri}', response code is #{response.status}"
120
- end
121
- if @http_config.error_response_as_unrecoverable
122
- raise Fluent::UnrecoverableError, "got unrecoverable error response from '#{uri}', response code is #{response.status}"
123
- else
124
- @logger.error "got error response from '#{uri}', response code is #{response.status}"
125
- end
126
- end
127
- end
128
-
129
- private
130
-
131
- def http_logs_endpoint
132
- "#{@http_config.endpoint}/v1/logs"
133
- end
134
-
135
- def http_metrics_endpoint
136
- "#{@http_config.endpoint}/v1/metrics"
137
- end
138
-
139
- def http_traces_endpoint
140
- "#{@http_config.endpoint}/v1/traces"
141
- end
142
-
143
- def create_http_connection(record)
144
- msg = record["message"]
145
-
146
- begin
147
- case record["type"]
148
- when Opentelemetry::RECORD_TYPE_LOGS
149
- uri = http_logs_endpoint
150
- body = Opentelemetry::Request::Logs.new(msg).body
151
- when Opentelemetry::RECORD_TYPE_METRICS
152
- uri = http_metrics_endpoint
153
- body = Opentelemetry::Request::Metrics.new(msg).body
154
- when Opentelemetry::RECORD_TYPE_TRACES
155
- uri = http_traces_endpoint
156
- body = Opentelemetry::Request::Traces.new(msg).body
157
- end
158
- rescue Google::Protobuf::ParseError => e
159
- # The message format does not comply with the OpenTelemetry protocol.
160
- raise ::Fluent::UnrecoverableError, e.message
161
- end
162
-
163
- headers = { Opentelemetry::CONTENT_TYPE => Opentelemetry::CONTENT_TYPE_PROTOBUF }
164
- if @http_config.compress == :gzip
165
- headers[Opentelemetry::CONTENT_ENCODING] = Opentelemetry::CONTENT_ENCODING_GZIP
166
- gz = Zlib::GzipWriter.new(StringIO.new)
167
- gz << body
168
- body = gz.close.string
169
- end
170
-
171
- Excon.defaults[:ssl_verify_peer] = false if @transport_config.insecure
172
- connection = Excon.new(uri, body: body, headers: headers, proxy: @http_config.proxy, persistent: true, **@tls_settings, **@timeout_settings)
173
- [uri, connection]
174
- end
175
- end
176
-
177
- class GrpcHandler
178
- def initialize(grpc_config, transport_config, logger)
179
- @grpc_config = grpc_config
180
- @transport_config = transport_config
181
- @logger = logger
182
- end
183
-
184
- def export(record)
185
- msg = record["message"]
186
-
187
- credential = :this_channel_is_insecure
188
-
189
- case record["type"]
190
- when Opentelemetry::RECORD_TYPE_LOGS
191
- service = Opentelemetry::ServiceStub::Logs.new(@grpc_config.endpoint, credential)
192
- when Opentelemetry::RECORD_TYPE_METRICS
193
- service = Opentelemetry::ServiceStub::Metrics.new(@grpc_config.endpoint, credential)
194
- when Opentelemetry::RECORD_TYPE_TRACES
195
- service = Opentelemetry::ServiceStub::Traces.new(@grpc_config.endpoint, credential)
196
- end
197
-
198
- begin
199
- service.export(msg)
200
- rescue Google::Protobuf::ParseError => e
201
- # The message format does not comply with the OpenTelemetry protocol.
202
- raise ::Fluent::UnrecoverableError, e.message
203
- end
204
- end
205
- end
206
91
  end
207
92
  end
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shizuo Fujita
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '4.30'
68
- - !ruby/object:Gem::Dependency
69
- name: grpc
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '1.71'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '1.71'
82
68
  description: Fluentd input/output plugin to forward OpenTelemetry Protocol data.
83
69
  email:
84
70
  - fujita@clear-code.com
@@ -94,10 +80,12 @@ files:
94
80
  - TODO.md
95
81
  - lib/fluent/plugin/in_opentelemetry.rb
96
82
  - lib/fluent/plugin/opentelemetry/constant.rb
83
+ - lib/fluent/plugin/opentelemetry/grpc_input_handler.rb
84
+ - lib/fluent/plugin/opentelemetry/grpc_output_handler.rb
85
+ - lib/fluent/plugin/opentelemetry/http_input_handler.rb
86
+ - lib/fluent/plugin/opentelemetry/http_output_handler.rb
97
87
  - lib/fluent/plugin/opentelemetry/request.rb
98
88
  - lib/fluent/plugin/opentelemetry/response.rb
99
- - lib/fluent/plugin/opentelemetry/service_handler.rb
100
- - lib/fluent/plugin/opentelemetry/service_stub.rb
101
89
  - lib/fluent/plugin/out_opentelemetry.rb
102
90
  - lib/opentelemetry/proto/collector/logs/v1/logs_service_pb.rb
103
91
  - lib/opentelemetry/proto/collector/logs/v1/logs_service_services_pb.rb
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "opentelemetry/proto/collector/logs/v1/logs_service_services_pb"
4
- require "opentelemetry/proto/collector/metrics/v1/metrics_service_services_pb"
5
- require "opentelemetry/proto/collector/trace/v1/trace_service_services_pb"
6
-
7
- class Fluent::Plugin::Opentelemetry::ServiceHandler
8
- class Logs < Opentelemetry::Proto::Collector::Logs::V1::LogsService::Service
9
- def callback=(block)
10
- @callback = block
11
- end
12
-
13
- def export(req, _call)
14
- @callback.call(req)
15
- end
16
- end
17
-
18
- class Metrics < Opentelemetry::Proto::Collector::Metrics::V1::MetricsService::Service
19
- def callback=(block)
20
- @callback = block
21
- end
22
-
23
- def export(req, _call)
24
- @callback.call(req)
25
- end
26
- end
27
-
28
- class Traces < Opentelemetry::Proto::Collector::Trace::V1::TraceService::Service
29
- def callback=(block)
30
- @callback = block
31
- end
32
-
33
- def export(req, _call)
34
- @callback.call(req)
35
- end
36
- end
37
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "opentelemetry/proto/collector/logs/v1/logs_service_pb"
4
- require "opentelemetry/proto/collector/logs/v1/logs_service_services_pb"
5
- require "opentelemetry/proto/collector/metrics/v1/metrics_service_pb"
6
- require "opentelemetry/proto/collector/metrics/v1/metrics_service_services_pb"
7
- require "opentelemetry/proto/collector/trace/v1/trace_service_pb"
8
- require "opentelemetry/proto/collector/trace/v1/trace_service_services_pb"
9
-
10
- require "grpc"
11
-
12
- class Fluent::Plugin::Opentelemetry::ServiceStub
13
- class Logs
14
- def initialize(host, creds, **kw)
15
- @stub = Opentelemetry::Proto::Collector::Logs::V1::LogsService::Stub.new(host, creds, **kw)
16
- end
17
-
18
- def export(json)
19
- message = Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode_json(json)
20
- @stub.export(message)
21
- end
22
- end
23
-
24
- class Metrics
25
- def initialize(host, creds, **kw)
26
- @stub = Opentelemetry::Proto::Collector::Metrics::V1::MetricsService::Stub.new(host, creds, **kw)
27
- end
28
-
29
- def export(json)
30
- message = Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.decode_json(json)
31
- @stub.export(message)
32
- end
33
- end
34
-
35
- class Traces
36
- def initialize(host, creds, **kw)
37
- @stub = Opentelemetry::Proto::Collector::Trace::V1::TraceService::Stub.new(host, creds, **kw)
38
- end
39
-
40
- def export(json)
41
- message = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode_json(json)
42
- @stub.export(message)
43
- end
44
- end
45
- end