-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add HTTP exporting to OpenTelemetry Tracer #28454
Changes from all commits
59868bf
2ee33ca
a0a2129
1455975
a7fa79b
b707eae
a530005
85b0607
0952ef9
82f9426
66a10c3
e3be1ff
85e7428
2553904
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ syntax = "proto3"; | |
package envoy.config.trace.v3; | ||
|
||
import "envoy/config/core/v3/grpc_service.proto"; | ||
import "envoy/config/core/v3/http_uri.proto"; | ||
|
||
import "udpa/annotations/status.proto"; | ||
|
||
|
@@ -17,6 +18,31 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; | |
// Configuration for the OpenTelemetry tracer. | ||
// [#extension: envoy.tracers.opentelemetry] | ||
message OpenTelemetryConfig { | ||
// Configuration for sending traces to the collector over HTTP. Note that | ||
// this will only be used if the grpc_service is not set above. | ||
message HttpConfig { | ||
enum HttpFormat { | ||
// Binary Protobuf Encoding. | ||
BINARY_PROTOBUF = 0; | ||
|
||
// JSON Protobuf Encoding. | ||
JSON_PROTOBUF = 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mark this not implemented as it is not in the implementation? |
||
} | ||
|
||
// The upstream cluster that will receive OTLP traces over HTTP. | ||
core.v3.HttpUri http_uri = 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is appropriate for this PR, but the OTEL spec defines default URL/Port for gRPC and HTTP that targets these endpoints in the OTel collector. https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#otlphttp-default-port. OTel users using these exporters in Envoy might be surprised they have to configure these, since in other "OTel components" they are set OOTB. |
||
|
||
// OTLP/HTTP uses Protobuf payloads encoded either in binary format or in JSON format. | ||
// See https://opentelemetry.io/docs/specs/otlp/#otlphttp. | ||
HttpFormat http_format = 2; | ||
|
||
// Optional path. If omitted, the default path of "/v1/traces" will be used. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The HttpUri contains path and host as part of URI, so just derive from there? |
||
string collector_path = 3; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure it helps with the design/impl of the HTTP headers configuration for the HTTP exporter here, but the OTel C++ defines it as this https://github.com/open-telemetry/opentelemetry-cpp/blob/0c5f90dea5147461df4ff7d32cbb5d42e5011daa/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h#L141 which is part of this struct https://github.com/open-telemetry/opentelemetry-cpp/blob/main/exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_exporter_options.h#L49 |
||
// Hostname to include with the request to the collector in the Host header. | ||
string collector_hostname = 4; | ||
} | ||
|
||
// The upstream gRPC cluster that will receive OTLP traces. | ||
// Note that the tracer drops traces if the server does not read data fast enough. | ||
// This field can be left empty to disable reporting traces to the collector. | ||
|
@@ -25,4 +51,7 @@ message OpenTelemetryConfig { | |
// The name for the service. This will be populated in the ResourceSpan Resource attributes. | ||
// If it is not provided, it will default to "unknown_service:envoy". | ||
string service_name = 2; | ||
|
||
// This field can be also left empty to disable reporting traces to the collector. | ||
HttpConfig http_config = 3; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#include "http_trace_exporter.h" | ||
|
||
#include "source/common/common/logger.h" | ||
#include "source/common/protobuf/protobuf.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Tracers { | ||
namespace OpenTelemetry { | ||
|
||
OpenTelemetryHttpTraceExporter::OpenTelemetryHttpTraceExporter( | ||
Upstream::ClusterManager& cluster_manager, | ||
envoy::config::trace::v3::OpenTelemetryConfig::HttpConfig http_config, | ||
OpenTelemetryTracerStats& tracing_stats) | ||
: cluster_manager_(cluster_manager), http_config_(http_config), tracing_stats_(tracing_stats) {} | ||
|
||
bool OpenTelemetryHttpTraceExporter::log(const ExportTraceServiceRequest& request) { | ||
|
||
std::string request_body; | ||
|
||
// TODO: add JSON option as well (though it may need custom serializing, see | ||
// https://opentelemetry.io/docs/specs/otlp/#json-protobuf-encoding) | ||
if (http_config_.http_format() == | ||
envoy::config::trace::v3::OpenTelemetryConfig::HttpConfig::BINARY_PROTOBUF) { | ||
const auto ok = request.SerializeToString(&request_body); | ||
if (!ok) { | ||
ENVOY_LOG(warn, "Error during Span proto serialization."); | ||
return false; | ||
} | ||
} | ||
|
||
Http::RequestMessagePtr message = std::make_unique<Http::RequestMessageImpl>(); | ||
message->headers().setReferenceMethod(Http::Headers::get().MethodValues.Post); | ||
message->headers().setPath(http_config_.collector_path()); | ||
message->headers().setHost(http_config_.collector_hostname()); | ||
message->headers().setReferenceContentType(Http::Headers::get().ContentTypeValues.Protobuf); | ||
message->body().add(request_body); | ||
|
||
const auto thread_local_cluster = | ||
cluster_manager_.getThreadLocalCluster(http_config_.http_uri().cluster()); | ||
if (thread_local_cluster == nullptr) { | ||
ENVOY_LOG(warn, "Thread local cluster not found for collector."); | ||
return false; | ||
} | ||
|
||
std::chrono::milliseconds timeout = std::chrono::duration_cast<std::chrono::milliseconds>( | ||
std::chrono::nanoseconds(http_config_.http_uri().timeout().nanos())); | ||
Http::AsyncClient::Request* http_request = thread_local_cluster->httpAsyncClient().send( | ||
std::move(message), *this, Http::AsyncClient::RequestOptions().setTimeout(timeout)); | ||
tracing_stats_.http_reports_sent_.inc(); | ||
|
||
return http_request; | ||
} | ||
|
||
void OpenTelemetryHttpTraceExporter::onSuccess(const Http::AsyncClient::Request&, | ||
Http::ResponseMessagePtr&& message) { | ||
tracing_stats_.http_reports_success_.inc(); | ||
const auto response_code = message->headers().Status()->value().getStringView(); | ||
if (response_code != "200") { | ||
ENVOY_LOG(warn, "response code: {}", response_code); | ||
} | ||
} | ||
|
||
void OpenTelemetryHttpTraceExporter::onFailure(const Http::AsyncClient::Request&, | ||
Http::AsyncClient::FailureReason) { | ||
ENVOY_LOG(debug, "Request failed."); | ||
tracing_stats_.http_reports_failed_.inc(); | ||
} | ||
|
||
} // namespace OpenTelemetry | ||
} // namespace Tracers | ||
} // namespace Extensions | ||
} // namespace Envoy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
|
||
#pragma once | ||
|
||
#include "envoy/config/core/v3/http_uri.pb.h" | ||
#include "envoy/config/trace/v3/opentelemetry.pb.h" | ||
#include "envoy/upstream/cluster_manager.h" | ||
|
||
#include "source/common/common/logger.h" | ||
#include "source/common/http/async_client_impl.h" | ||
#include "source/common/http/async_client_utility.h" | ||
#include "source/common/http/headers.h" | ||
#include "source/common/http/message_impl.h" | ||
#include "source/common/http/utility.h" | ||
|
||
#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" | ||
#include "trace_exporter.h" | ||
#include "tracer_stats.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Tracers { | ||
namespace OpenTelemetry { | ||
|
||
/** | ||
* Exporter for OTLP traces over HTTP. | ||
*/ | ||
class OpenTelemetryHttpTraceExporter : public OpenTelemetryTraceExporter, | ||
public Http::AsyncClient::Callbacks { | ||
public: | ||
OpenTelemetryHttpTraceExporter( | ||
Upstream::ClusterManager& cluster_manager, | ||
envoy::config::trace::v3::OpenTelemetryConfig::HttpConfig http_config, | ||
OpenTelemetryTracerStats& tracing_stats); | ||
|
||
bool log(const ExportTraceServiceRequest& request) override; | ||
|
||
// Http::AsyncClient::Callbacks. | ||
void onSuccess(const Http::AsyncClient::Request&, Http::ResponseMessagePtr&&) override; | ||
void onFailure(const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason) override; | ||
void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} | ||
|
||
private: | ||
Upstream::ClusterManager& cluster_manager_; | ||
envoy::config::trace::v3::OpenTelemetryConfig::HttpConfig http_config_; | ||
OpenTelemetryTracerStats& tracing_stats_; | ||
}; | ||
|
||
} // namespace OpenTelemetry | ||
} // namespace Tracers | ||
} // namespace Extensions | ||
} // namespace Envoy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#pragma once | ||
|
||
#include "source/common/common/logger.h" | ||
|
||
#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h" | ||
|
||
using opentelemetry::proto::collector::trace::v1::ExportTraceServiceRequest; | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Tracers { | ||
namespace OpenTelemetry { | ||
|
||
class OpenTelemetryTraceExporter : public Logger::Loggable<Logger::Id::tracing> { | ||
public: | ||
virtual ~OpenTelemetryTraceExporter() = default; | ||
|
||
virtual bool log(const ExportTraceServiceRequest& request) = 0; | ||
}; | ||
|
||
using OpenTelemetryTraceExporterPtr = std::unique_ptr<OpenTelemetryTraceExporter>; | ||
|
||
} // namespace OpenTelemetry | ||
} // namespace Tracers | ||
} // namespace Extensions | ||
} // namespace Envoy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#pragma once | ||
|
||
#include "envoy/stats/stats_macros.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Tracers { | ||
namespace OpenTelemetry { | ||
|
||
#define OPENTELEMETRY_TRACER_STATS(COUNTER) \ | ||
COUNTER(http_reports_failed) \ | ||
COUNTER(http_reports_sent) \ | ||
COUNTER(http_reports_success) \ | ||
COUNTER(spans_sent) \ | ||
COUNTER(timer_flushed) | ||
|
||
struct OpenTelemetryTracerStats { | ||
OPENTELEMETRY_TRACER_STATS(GENERATE_COUNTER_STRUCT) | ||
}; | ||
|
||
} // namespace OpenTelemetry | ||
} // namespace Tracers | ||
} // namespace Extensions | ||
} // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to also refactor the config in the same way done for the exporters? For example, keeping the base config in here, but then adding new types for
OtlpHttpExporterConfig
andOtlpGrpcExporterConfig
?That's also what OTel C++ does Http gRPC
Now we will have a config with HTTP-related things (HTTP Format, HTTP headers) that are not relevant for gRPC. Same would apply for possible future gRPC config only.