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 +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +5 -0
- data/README.md +3 -1
- data/lib/fluent/plugin/in_opentelemetry.rb +11 -131
- data/lib/fluent/plugin/opentelemetry/grpc_input_handler.rb +85 -0
- data/lib/fluent/plugin/opentelemetry/grpc_output_handler.rb +76 -0
- data/lib/fluent/plugin/opentelemetry/http_input_handler.rb +89 -0
- data/lib/fluent/plugin/opentelemetry/http_output_handler.rb +100 -0
- data/lib/fluent/plugin/out_opentelemetry.rb +14 -129
- metadata +5 -17
- data/lib/fluent/plugin/opentelemetry/service_handler.rb +0 -37
- data/lib/fluent/plugin/opentelemetry/service_stub.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa3bba6bc0556816b7f7fba2e7be82ffc2d629e484778042c7aab5113df8f514
|
4
|
+
data.tar.gz: f6bf4718017e97e2976ba2f2d74dc60fd85b2c7806087300a1e61788578ad528
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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 | `
|
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/
|
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
|
-
|
9
|
+
begin
|
10
|
+
require "grpc"
|
12
11
|
|
13
|
-
|
14
|
-
|
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 =
|
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 =
|
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/
|
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
|
-
|
12
|
-
|
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 =
|
65
|
-
@grpc_handler =
|
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.
|
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
|