Skip to content

Commit

Permalink
opentelemetry tracer: add OTLP/HTTP exporter (#29207)
Browse files Browse the repository at this point in the history
* Refactor gRPC exporter to use general base class we can later reuse

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Update OpenTelemetryConfig with HTTP-related config

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Write and wire up HTTP trace exporter

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Code formatting

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Proto format

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Add basic tests

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* code format

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Add additional counters for http exporter and test

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Formatting

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Coverage improvements

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Formatting

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Add higher level Driver test for HTTP exporting

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* More formatting

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Clang tidy

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>

* Refactor config and add HTTP headers

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Modify tests

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Fix lint errors

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Revert oneof in proto config

Re-allow not having a grpc_service specified to keep the existing behavior. Added a condition to fail-fast when both exporters are configured.

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* PR suggestions

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Add HttpService config type

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Fix format issues

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Fix lint/format/docs issues

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Hostname -> Authority

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Use HttpUri type

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* PR suggestions

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Improve log messages

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Fix format

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* PR suggestions

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* PR suggestions

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Revert stats for http exporter

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Fix spelling

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Add changelog

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Properly manage in-flight http requests in the exporter

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Add proto migrate oneof annotation

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Clarify http headers do not support formatting

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Prepare headers when creating the HTTP exporter

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

* Revert test change

Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>

---------

Signed-off-by: Alex Ellis <ellisonjtk@gmail.com>
Signed-off-by: Joao Grassi <joao.grassi@dynatrace.com>
Co-authored-by: Alex Ellis <ellisonjtk@gmail.com>
  • Loading branch information
joaopgrassi and AlexanderEllis authored Oct 18, 2023
1 parent 525b1d2 commit 767cb40
Show file tree
Hide file tree
Showing 20 changed files with 566 additions and 26 deletions.
35 changes: 35 additions & 0 deletions api/envoy/config/core/v3/http_service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
syntax = "proto3";

package envoy.config.core.v3;

import "envoy/config/core/v3/base.proto";
import "envoy/config/core/v3/http_uri.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.config.core.v3";
option java_outer_classname = "HttpServiceProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/config/core/v3;corev3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: HTTP services]

// HTTP service configuration.
message HttpService {
// The service's HTTP URI. For example:
//
// .. code-block:: yaml
//
// http_uri:
// uri: https://www.myserviceapi.com/v1/data
// cluster: www.myserviceapi.com|443
//
HttpUri http_uri = 1;

// Specifies a list of HTTP headers that should be added to each request
// handled by this virtual host.
repeated HeaderValueOption request_headers_to_add = 2
[(validate.rules).repeated = {max_items: 1000}];
}
22 changes: 20 additions & 2 deletions api/envoy/config/trace/v3/opentelemetry.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ syntax = "proto3";
package envoy.config.trace.v3;

import "envoy/config/core/v3/grpc_service.proto";
import "envoy/config/core/v3/http_service.proto";

import "udpa/annotations/migrate.proto";
import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.config.trace.v3";
Expand All @@ -19,8 +21,24 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
message OpenTelemetryConfig {
// 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.
core.v3.GrpcService grpc_service = 1;
// This field can be left empty to disable reporting traces to the gRPC service.
// Only one of ``grpc_service``, ``http_service`` may be used.
core.v3.GrpcService grpc_service = 1
[(udpa.annotations.field_migrate).oneof_promotion = "otlp_exporter"];

// The upstream HTTP cluster that will receive OTLP traces.
// This field can be left empty to disable reporting traces to the HTTP service.
// Only one of ``grpc_service``, ``http_service`` may be used.
//
// .. note::
//
// Note: The ``request_headers_to_add`` property in the OTLP HTTP exporter service
// does not support the :ref:`format specifier <config_access_log_format>` as used for
// :ref:`HTTP access logging <config_access_log>`.
// The values configured are added as HTTP headers on the OTLP export request
// without any formatting applied.
core.v3.HttpService http_service = 3
[(udpa.annotations.field_migrate).oneof_promotion = "otlp_exporter"];

// 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".
Expand Down
3 changes: 3 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ new_features:
change: |
Added ``metadata`` support for :ref:`virtual host <envoy_v3_api_field_config.route.v3.VirtualHost.metadata>` and
:ref:`route configuration <envoy_v3_api_field_config.route.v3.RouteConfiguration.metadata>`.
- area: tracing
change: |
Added support for exporting spans via HTTP on the OpenTelemetry tracer.
deprecated:
- area: tracing
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v3/common_messages/common_messages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Common messages
../extensions/filters/common/dependency/v3/dependency.proto
../extensions/regex_engines/v3/google_re2.proto
../config/core/v3/grpc_method_list.proto
../config/core/v3/http_service.proto
../config/core/v3/grpc_service.proto
../extensions/key_value/file_based/v3/config.proto
../config/common/key_value/v3/config.proto
Expand Down
21 changes: 17 additions & 4 deletions source/extensions/tracers/opentelemetry/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ envoy_cc_library(
"tracer.h",
],
deps = [
":grpc_trace_exporter",
":trace_exporter",
"//envoy/thread_local:thread_local_interface",
"//source/common/config:utility_lib",
"//source/common/tracing:http_tracer_lib",
Expand All @@ -47,13 +47,26 @@ envoy_cc_library(
)

envoy_cc_library(
name = "grpc_trace_exporter",
srcs = ["grpc_trace_exporter.cc"],
hdrs = ["grpc_trace_exporter.h"],
name = "trace_exporter",
srcs = [
"grpc_trace_exporter.cc",
"http_trace_exporter.cc",
],
hdrs = [
"grpc_trace_exporter.h",
"http_trace_exporter.h",
"trace_exporter.h",
],
deps = [
"//envoy/grpc:async_client_manager_interface",
"//envoy/upstream:cluster_manager_interface",
"//source/common/grpc:typed_async_client_lib",
"//source/common/http:async_client_utility_lib",
"//source/common/http:header_map_lib",
"//source/common/http:message_lib",
"//source/common/http:utility_lib",
"//source/common/protobuf",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@opentelemetry_proto//:trace_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#include "grpc_trace_exporter.h"
#include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h"

#include "source/common/common/logger.h"
Expand Down
7 changes: 3 additions & 4 deletions source/extensions/tracers/opentelemetry/grpc_trace_exporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "source/common/common/logger.h"
#include "source/common/grpc/typed_async_client.h"
#include "source/extensions/tracers/opentelemetry/trace_exporter.h"

#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h"

Expand Down Expand Up @@ -80,18 +81,16 @@ class OpenTelemetryGrpcTraceExporterClient : Logger::Loggable<Logger::Id::tracin
const Protobuf::MethodDescriptor& service_method_;
};

class OpenTelemetryGrpcTraceExporter : Logger::Loggable<Logger::Id::tracing> {
class OpenTelemetryGrpcTraceExporter : public OpenTelemetryTraceExporter {
public:
OpenTelemetryGrpcTraceExporter(const Grpc::RawAsyncClientSharedPtr& client);

bool log(const ExportTraceServiceRequest& request);
bool log(const ExportTraceServiceRequest& request) override;

private:
OpenTelemetryGrpcTraceExporterClient client_;
};

using OpenTelemetryGrpcTraceExporterPtr = std::unique_ptr<OpenTelemetryGrpcTraceExporter>;

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
Expand Down
94 changes: 94 additions & 0 deletions source/extensions/tracers/opentelemetry/http_trace_exporter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "source/extensions/tracers/opentelemetry/http_trace_exporter.h"

#include <chrono>
#include <memory>
#include <string>
#include <vector>

#include "source/common/common/enum_to_int.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,
const envoy::config::core::v3::HttpService& http_service)
: cluster_manager_(cluster_manager), http_service_(http_service) {

// Prepare and store headers to be used later on each export request
for (const auto& header_value_option : http_service_.request_headers_to_add()) {
parsed_headers_to_add_.push_back({Http::LowerCaseString(header_value_option.header().key()),
header_value_option.header().value()});
}
}

bool OpenTelemetryHttpTraceExporter::log(const ExportTraceServiceRequest& request) {
std::string request_body;

const auto ok = request.SerializeToString(&request_body);
if (!ok) {
ENVOY_LOG(warn, "Error while serializing the binary proto ExportTraceServiceRequest.");
return false;
}

const auto thread_local_cluster =
cluster_manager_.getThreadLocalCluster(http_service_.http_uri().cluster());
if (thread_local_cluster == nullptr) {
ENVOY_LOG(error, "OTLP HTTP exporter failed: [cluster = {}] is not configured",
http_service_.http_uri().cluster());
return false;
}

Http::RequestMessagePtr message = Http::Utility::prepareHeaders(http_service_.http_uri());

// The request follows the OTLP HTTP specification:
// https://github.com/open-telemetry/opentelemetry-proto/blob/v1.0.0/docs/specification.md#otlphttp.
message->headers().setReferenceMethod(Http::Headers::get().MethodValues.Post);
message->headers().setReferenceContentType(Http::Headers::get().ContentTypeValues.Protobuf);

// Add all custom headers to the request.
for (const auto& header_pair : parsed_headers_to_add_) {
message->headers().setReference(header_pair.first, header_pair.second);
}
message->body().add(request_body);

const auto options = Http::AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds(
DurationUtil::durationToMilliseconds(http_service_.http_uri().timeout())));

Http::AsyncClient::Request* in_flight_request =
thread_local_cluster->httpAsyncClient().send(std::move(message), *this, options);

if (in_flight_request == nullptr) {
return false;
}

active_requests_.add(*in_flight_request);
return true;
}

void OpenTelemetryHttpTraceExporter::onSuccess(const Http::AsyncClient::Request& request,
Http::ResponseMessagePtr&& http_response) {
active_requests_.remove(request);
const auto response_code = Http::Utility::getResponseStatus(http_response->headers());
if (response_code != enumToInt(Http::Code::OK)) {
ENVOY_LOG(error,
"OTLP HTTP exporter received a non-success status code: {} while exporting the OTLP "
"message",
response_code);
}
}

void OpenTelemetryHttpTraceExporter::onFailure(const Http::AsyncClient::Request& request,
Http::AsyncClient::FailureReason reason) {
active_requests_.remove(request);
ENVOY_LOG(debug, "The OTLP export request failed. Reason {}", enumToInt(reason));
}

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
48 changes: 48 additions & 0 deletions source/extensions/tracers/opentelemetry/http_trace_exporter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include "envoy/config/core/v3/http_service.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 "source/extensions/tracers/opentelemetry/trace_exporter.h"

#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.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,
const envoy::config::core::v3::HttpService& http_service);

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::core::v3::HttpService http_service_;
// Track active HTTP requests to be able to cancel them on destruction.
Http::AsyncClientRequestTracker active_requests_;
std::vector<std::pair<const Http::LowerCaseString, const std::string>> parsed_headers_to_add_;
};

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
#include "source/common/common/logger.h"
#include "source/common/config/utility.h"
#include "source/common/tracing/http_tracer_impl.h"
#include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h"
#include "source/extensions/tracers/opentelemetry/http_trace_exporter.h"
#include "source/extensions/tracers/opentelemetry/span_context.h"
#include "source/extensions/tracers/opentelemetry/span_context_extractor.h"
#include "source/extensions/tracers/opentelemetry/trace_exporter.h"
#include "source/extensions/tracers/opentelemetry/tracer.h"

#include "opentelemetry/proto/collector/trace/v1/trace_service.pb.h"
#include "opentelemetry/proto/trace/v1/trace.pb.h"
#include "span_context.h"
#include "span_context_extractor.h"
#include "tracer.h"

namespace Envoy {
namespace Extensions {
Expand All @@ -26,16 +29,26 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr
tracing_stats_{OPENTELEMETRY_TRACER_STATS(
POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.opentelemetry"))} {
auto& factory_context = context.serverFactoryContext();

if (opentelemetry_config.has_grpc_service() && opentelemetry_config.has_http_service()) {
throw EnvoyException(
"OpenTelemetry Tracer cannot have both gRPC and HTTP exporters configured. "
"OpenTelemetry tracer will be disabled.");
}

// Create the tracer in Thread Local Storage.
tls_slot_ptr_->set([opentelemetry_config, &factory_context, this](Event::Dispatcher& dispatcher) {
OpenTelemetryGrpcTraceExporterPtr exporter;
OpenTelemetryTraceExporterPtr exporter;
if (opentelemetry_config.has_grpc_service()) {
Grpc::AsyncClientFactoryPtr&& factory =
factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService(
opentelemetry_config.grpc_service(), factory_context.scope(), true);
const Grpc::RawAsyncClientSharedPtr& async_client_shared_ptr =
factory->createUncachedRawAsyncClient();
exporter = std::make_unique<OpenTelemetryGrpcTraceExporter>(async_client_shared_ptr);
} else if (opentelemetry_config.has_http_service()) {
exporter = std::make_unique<OpenTelemetryHttpTraceExporter>(
factory_context.clusterManager(), opentelemetry_config.http_service());
}
TracerPtr tracer = std::make_unique<Tracer>(
std::move(exporter), factory_context.timeSource(), factory_context.api().randomGenerator(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "source/common/common/logger.h"
#include "source/common/singleton/const_singleton.h"
#include "source/extensions/tracers/common/factory_base.h"
#include "source/extensions/tracers/opentelemetry/grpc_trace_exporter.h"
#include "source/extensions/tracers/opentelemetry/tracer.h"

namespace Envoy {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "span_context_extractor.h"
#include "source/extensions/tracers/opentelemetry/span_context_extractor.h"

#include "envoy/tracing/tracer.h"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

#include "source/common/common/statusor.h"
#include "source/common/http/header_map_impl.h"

#include "span_context.h"
#include "source/extensions/tracers/opentelemetry/span_context.h"

namespace Envoy {
namespace Extensions {
Expand Down
Loading

0 comments on commit 767cb40

Please sign in to comment.