fluent-plugin-opentelemetry 0.1.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.
Files changed (29) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +75 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE +202 -0
  5. data/README.md +154 -0
  6. data/Rakefile +35 -0
  7. data/TODO.md +108 -0
  8. data/lib/fluent/plugin/in_opentelemetry.rb +211 -0
  9. data/lib/fluent/plugin/opentelemetry/constant.rb +29 -0
  10. data/lib/fluent/plugin/opentelemetry/request.rb +67 -0
  11. data/lib/fluent/plugin/opentelemetry/response.rb +90 -0
  12. data/lib/fluent/plugin/opentelemetry/service_handler.rb +37 -0
  13. data/lib/fluent/plugin/opentelemetry/service_stub.rb +45 -0
  14. data/lib/fluent/plugin/out_opentelemetry.rb +207 -0
  15. data/lib/opentelemetry/proto/collector/logs/v1/logs_service_pb.rb +27 -0
  16. data/lib/opentelemetry/proto/collector/logs/v1/logs_service_services_pb.rb +48 -0
  17. data/lib/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.rb +27 -0
  18. data/lib/opentelemetry/proto/collector/metrics/v1/metrics_service_services_pb.rb +48 -0
  19. data/lib/opentelemetry/proto/collector/profiles/v1development/profiles_service_pb.rb +27 -0
  20. data/lib/opentelemetry/proto/collector/profiles/v1development/profiles_service_services_pb.rb +47 -0
  21. data/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb +27 -0
  22. data/lib/opentelemetry/proto/collector/trace/v1/trace_service_services_pb.rb +48 -0
  23. data/lib/opentelemetry/proto/common/v1/common_pb.rb +26 -0
  24. data/lib/opentelemetry/proto/logs/v1/logs_pb.rb +29 -0
  25. data/lib/opentelemetry/proto/metrics/v1/metrics_pb.rb +41 -0
  26. data/lib/opentelemetry/proto/profiles/v1development/profiles_pb.rb +36 -0
  27. data/lib/opentelemetry/proto/resource/v1/resource_pb.rb +23 -0
  28. data/lib/opentelemetry/proto/trace/v1/trace_pb.rb +33 -0
  29. metadata +140 -0
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/input"
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"
8
+ require "fluent/plugin_helper/http_server"
9
+ require "fluent/plugin_helper/thread"
10
+
11
+ require "zlib"
12
+
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
28
+ end
29
+
30
+ module Fluent::Plugin
31
+ class OpentelemetryInput < Input
32
+ Fluent::Plugin.register_input("opentelemetry", self)
33
+
34
+ helpers :thread, :http_server
35
+
36
+ desc "The tag of the event."
37
+ config_param :tag, :string
38
+
39
+ config_section :http, required: false, multi: false, init: false, param_name: :http_config do
40
+ desc "The address to bind to."
41
+ config_param :bind, :string, default: "0.0.0.0"
42
+ desc "The port to listen to."
43
+ config_param :port, :integer, default: 4318
44
+ end
45
+
46
+ config_section :grpc, required: false, multi: false, init: false, param_name: :grpc_config do
47
+ desc "The address to bind to."
48
+ config_param :bind, :string, default: "0.0.0.0"
49
+ desc "The port to listen to."
50
+ config_param :port, :integer, default: 4317
51
+ end
52
+
53
+ config_section :transport, required: false, multi: false, init: false, param_name: :transport_config do
54
+ config_argument :protocol, :enum, list: [:tls], default: nil
55
+ end
56
+
57
+ def configure(conf)
58
+ super
59
+
60
+ unless [@http_config, @grpc_config].any?
61
+ raise Fluent::ConfigError, "Please configure either <http> or <grpc> section, or both."
62
+ end
63
+ end
64
+
65
+ def start
66
+ super
67
+
68
+ if @http_config
69
+ http_handler = HttpHandler.new
70
+ http_server_create_http_server(:in_opentelemetry_http_server, addr: @http_config.bind, port: @http_config.port, logger: log) do |serv|
71
+ serv.post("/v1/logs") do |req|
72
+ http_handler.logs(req) { |record| router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_LOGS, message: record }) }
73
+ end
74
+ serv.post("/v1/metrics") do |req|
75
+ http_handler.metrics(req) { |record| router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_METRICS, message: record }) }
76
+ end
77
+ serv.post("/v1/traces") do |req|
78
+ http_handler.traces(req) { |record| router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_TRACES, message: record }) }
79
+ end
80
+ end
81
+ end
82
+
83
+ if @grpc_config
84
+ thread_create(:in_opentelemetry_grpc_server) do
85
+ grpc_handler = GrpcHandler.new(@grpc_config, log)
86
+ grpc_handler.run(
87
+ logs: lambda { |record|
88
+ router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_LOGS, message: record })
89
+ },
90
+ metrics: lambda { |record|
91
+ router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_METRICS, message: record })
92
+ },
93
+ traces: lambda { |record|
94
+ router.emit(@tag, Fluent::EventTime.now, { type: Opentelemetry::RECORD_TYPE_TRACES, message: record })
95
+ }
96
+ )
97
+ end
98
+ end
99
+ 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
+ end
211
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin"
4
+ require "openssl"
5
+
6
+ module Fluent::Plugin::Opentelemetry
7
+ CONTENT_TYPE = "Content-Type"
8
+ CONTENT_TYPE_PAIN = "text/plain"
9
+ CONTENT_TYPE_PROTOBUF = "application/x-protobuf"
10
+ CONTENT_TYPE_JSON = "application/json"
11
+
12
+ CONTENT_ENCODING = "Content-Encoding"
13
+ CONTENT_ENCODING_GZIP = "gzip"
14
+
15
+ RECORD_TYPE_LOGS = "opentelemetry_logs"
16
+ RECORD_TYPE_METRICS = "opentelemetry_metrics"
17
+ RECORD_TYPE_TRACES = "opentelemetry_traces"
18
+
19
+ TLS_VERSIONS_MAP =
20
+ begin
21
+ map = {
22
+ TLSv1: OpenSSL::SSL::TLS1_VERSION,
23
+ TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION,
24
+ TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION
25
+ }
26
+ map[:TLSv1_3] = OpenSSL::SSL::TLS1_3_VERSION if defined?(OpenSSL::SSL::TLS1_3_VERSION)
27
+ map.freeze
28
+ end
29
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/opentelemetry/constant"
4
+ require "opentelemetry/proto/collector/logs/v1/logs_service_pb"
5
+ require "opentelemetry/proto/collector/metrics/v1/metrics_service_pb"
6
+ require "opentelemetry/proto/collector/trace/v1/trace_service_pb"
7
+
8
+ require "google/protobuf"
9
+
10
+ class Fluent::Plugin::Opentelemetry::Request
11
+ class Logs
12
+ def initialize(body)
13
+ @request =
14
+ if body.start_with?("{")
15
+ Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode_json(body, ignore_unknown_fields: true)
16
+ else
17
+ Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode(body)
18
+ end
19
+ end
20
+
21
+ def body
22
+ @request.to_proto
23
+ end
24
+
25
+ def record
26
+ @request.to_json
27
+ end
28
+ end
29
+
30
+ class Metrics
31
+ def initialize(body)
32
+ @request =
33
+ if body.start_with?("{")
34
+ Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.decode_json(body, ignore_unknown_fields: true)
35
+ else
36
+ Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.decode(body)
37
+ end
38
+ end
39
+
40
+ def body
41
+ @request.to_proto
42
+ end
43
+
44
+ def record
45
+ @request.to_json
46
+ end
47
+ end
48
+
49
+ class Traces
50
+ def initialize(body)
51
+ @request =
52
+ if body.start_with?("{")
53
+ Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode_json(body, ignore_unknown_fields: true)
54
+ else
55
+ Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode(body)
56
+ end
57
+ end
58
+
59
+ def body
60
+ @request.to_proto
61
+ end
62
+
63
+ def record
64
+ @request.to_json
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/opentelemetry/constant"
4
+ require "opentelemetry/proto/collector/logs/v1/logs_service_pb"
5
+ require "opentelemetry/proto/collector/metrics/v1/metrics_service_pb"
6
+ require "opentelemetry/proto/collector/trace/v1/trace_service_pb"
7
+
8
+ require "google/protobuf"
9
+
10
+ module Fluent::Plugin::Opentelemetry::Response
11
+ def self.type(content_type)
12
+ case content_type
13
+ when Fluent::Plugin::Opentelemetry::CONTENT_TYPE_PROTOBUF
14
+ :protobuf
15
+ when Fluent::Plugin::Opentelemetry::CONTENT_TYPE_JSON
16
+ :json
17
+ else
18
+ raise "unknown content-type: #{content_type}"
19
+ end
20
+ end
21
+
22
+ class Logs
23
+ def self.build(rejected: 0, error: "")
24
+ Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceResponse.new(
25
+ partial_success: Opentelemetry::Proto::Collector::Logs::V1::ExportLogsPartialSuccess.new(
26
+ rejected_log_records: rejected,
27
+ error_message: error
28
+ )
29
+ )
30
+ end
31
+
32
+ def initialize(rejected: 0, error: "")
33
+ @response = Logs.build(rejected: rejected, error: error)
34
+ end
35
+
36
+ def body(type:)
37
+ if type == :protobuf
38
+ @response.to_proto
39
+ else
40
+ @response.to_json
41
+ end
42
+ end
43
+ end
44
+
45
+ class Metrics
46
+ def self.build(rejected: 0, error: "")
47
+ Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceResponse.new(
48
+ partial_success: Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsPartialSuccess.new(
49
+ rejected_data_points: rejected,
50
+ error_message: error
51
+ )
52
+ )
53
+ end
54
+
55
+ def initialize(rejected: 0, error: "")
56
+ @response = Metrics.build(rejected: rejected, error: error)
57
+ end
58
+
59
+ def body(type:)
60
+ if type == :protobuf
61
+ @response.to_proto
62
+ else
63
+ @response.to_json
64
+ end
65
+ end
66
+ end
67
+
68
+ class Traces
69
+ def self.build(rejected: 0, error: "")
70
+ Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceResponse.new(
71
+ partial_success: Opentelemetry::Proto::Collector::Trace::V1::ExportTracePartialSuccess.new(
72
+ rejected_spans: rejected,
73
+ error_message: error
74
+ )
75
+ )
76
+ end
77
+
78
+ def initialize(rejected: 0, error: "")
79
+ @response = Traces.build(rejected: rejected, error: error)
80
+ end
81
+
82
+ def body(type:)
83
+ if type == :protobuf
84
+ @response.to_proto
85
+ else
86
+ @response.to_json
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,37 @@
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
@@ -0,0 +1,45 @@
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
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fluent/plugin/opentelemetry/constant"
4
+ require "fluent/plugin/opentelemetry/request"
5
+ require "fluent/plugin/opentelemetry/service_stub"
6
+ require "fluent/plugin/output"
7
+
8
+ require "excon"
9
+ require "grpc"
10
+ require "json"
11
+ require "stringio"
12
+ require "zlib"
13
+
14
+ module Fluent::Plugin
15
+ class OpentelemetryOutput < Output
16
+ class RetryableResponse < StandardError; end
17
+
18
+ Fluent::Plugin.register_output("opentelemetry", self)
19
+
20
+ helpers :server
21
+
22
+ config_section :buffer do
23
+ config_set_default :chunk_keys, ["tag"]
24
+ end
25
+
26
+ config_section :http, required: false, multi: false, init: false, param_name: :http_config do
27
+ desc "The endpoint"
28
+ config_param :endpoint, :string, default: "http://127.0.0.1:4318"
29
+ desc "The proxy for HTTP request"
30
+ config_param :proxy, :string, default: ENV["HTTP_PROXY"] || ENV["http_proxy"]
31
+
32
+ desc "Raise UnrecoverableError when the response is non success, 4xx/5xx"
33
+ config_param :error_response_as_unrecoverable, :bool, default: true
34
+ desc "The list of retryable response code"
35
+ config_param :retryable_response_codes, :array, value_type: :integer, default: [429, 502, 503, 504]
36
+
37
+ desc "Compress request body"
38
+ config_param :compress, :enum, list: %i[text gzip], default: :text
39
+
40
+ desc "The read timeout in seconds"
41
+ config_param :read_timeout, :integer, default: 60
42
+ desc "The write timeout in seconds"
43
+ config_param :write_timeout, :integer, default: 60
44
+ desc "The connect timeout in seconds"
45
+ config_param :connect_timeout, :integer, default: 60
46
+ end
47
+
48
+ config_section :grpc, required: false, multi: false, init: false, param_name: :grpc_config do
49
+ desc "The endpoint"
50
+ config_param :endpoint, :string, default: "127.0.0.1:4317"
51
+ end
52
+
53
+ config_section :transport, required: false, multi: false, init: false, param_name: :transport_config do
54
+ config_argument :protocol, :enum, list: [:tls], default: nil
55
+ end
56
+
57
+ def configure(conf)
58
+ super
59
+
60
+ unless [@http_config, @grpc_config].one?
61
+ raise Fluent::ConfigError, "Please configure either <http> or <grpc> section."
62
+ end
63
+
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
66
+ end
67
+
68
+ def multi_workers_ready?
69
+ true
70
+ end
71
+
72
+ def format(tag, time, record)
73
+ JSON.generate(record)
74
+ end
75
+
76
+ def write(chunk)
77
+ record = JSON.parse(chunk.read)
78
+
79
+ if @grpc_handler
80
+ @grpc_handler.export(record)
81
+ else
82
+ @http_handler.export(record)
83
+ end
84
+ 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
+ end
207
+ end