diff --git a/api/envoy/config/trace/v2/trace.proto b/api/envoy/config/trace/v2/trace.proto index 16b06f4df908b..7aac3277fee9b 100644 --- a/api/envoy/config/trace/v2/trace.proto +++ b/api/envoy/config/trace/v2/trace.proto @@ -23,15 +23,21 @@ import "validate/validate.proto"; message Tracing { message Http { // The name of the HTTP trace driver to instantiate. The name must match a - // supported HTTP trace driver. *envoy.lightstep*, *envoy.zipkin*, and - // *envoy.dynamic.ot* are built-in trace drivers. + // supported HTTP trace driver. Built-in trace drivers: + // + // - *envoy.lightstep* + // - *envoy.zipkin* + // - *envoy.dynamic.ot* + // - *envoy.tracers.datadog* string name = 1 [(validate.rules).string.min_bytes = 1]; // Trace driver specific configuration which depends on the driver being instantiated. - // See the :ref:`LightstepConfig - // `, :ref:`ZipkinConfig - // `, and :ref:`DynamicOtConfig - // ` trace drivers for examples. + // See the trace drivers for examples: + // + // - :ref:`LightstepConfig ` + // - :ref:`ZipkinConfig ` + // - :ref:`DynamicOtConfig ` + // - :ref:`DatadogConfig ` oneof config_type { google.protobuf.Struct config = 2; @@ -86,6 +92,14 @@ message DynamicOtConfig { google.protobuf.Struct config = 2; } +// Configuration for the Datadog tracer. +message DatadogConfig { + // The cluster to use for submitting traces to the Datadog agent. + string collector_cluster = 1 [(validate.rules).string.min_bytes = 1]; + // The name used for the service when traces are generated by envoy. + string service_name = 2 [(validate.rules).string.min_bytes = 1]; +} + // Configuration structure. message TraceServiceConfig { // The upstream gRPC cluster that hosts the metrics service. diff --git a/bazel/external/BUILD b/bazel/external/BUILD index 63a81dd5e12b1..f2111ab6b8843 100644 --- a/bazel/external/BUILD +++ b/bazel/external/BUILD @@ -6,6 +6,7 @@ cc_library( deps = [ "@com_google_googletest//:gtest", "@com_lightstep_tracer_cpp//:lightstep_tracer", + "@com_github_datadog_dd_opentracing_cpp//:dd_opentracing_cpp", "@io_opentracing_cpp//:opentracing", ], ) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 466378fa00ad8..4c32dab853eac 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -280,6 +280,7 @@ def envoy_dependencies(path = "@envoy_deps//", skip_targets = []): _com_github_google_libprotobuf_mutator() _io_opentracing_cpp() _com_lightstep_tracer_cpp() + _com_github_datadog_dd_opentracing_cpp() _com_github_grpc_grpc() _com_github_google_jwt_verify() _com_github_nanopb_nanopb() @@ -401,6 +402,17 @@ def _com_lightstep_tracer_cpp(): actual = "@com_lightstep_tracer_cpp//:lightstep_tracer", ) +def _com_github_datadog_dd_opentracing_cpp(): + _repository_impl("com_github_datadog_dd_opentracing_cpp") + _repository_impl( + name = "com_github_msgpack_msgpack_c", + build_file = "@com_github_datadog_dd_opentracing_cpp//:bazel/external/msgpack.BUILD", + ) + native.bind( + name = "dd_opentracing_cpp", + actual = "@com_github_datadog_dd_opentracing_cpp//:dd_opentracing_cpp", + ) + def _com_github_tencent_rapidjson(): _repository_impl( name = "com_github_tencent_rapidjson", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index b9ac6b90816ee..fb2b58dd02019 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -90,6 +90,16 @@ REPOSITORY_LOCATIONS = dict( # From: https://github.com/lightstep/lightstep-tracer-cpp/blob/v0.8.0/lightstep-tracer-common/third_party/googleapis/README.lightstep-tracer-common#L6 urls = ["https://github.com/googleapis/googleapis/archive/d6f78d948c53f3b400bb46996eb3084359914f9b.tar.gz"], ), + com_github_datadog_dd_opentracing_cpp = dict( + sha256 = "733e9b698a232cfd3aa35b4e27c59641bf1fa78e52e71d29e230af4f2070cdf5", + strip_prefix = "dd-opentracing-cpp-0.3.5", + urls = ["https://github.com/DataDog/dd-opentracing-cpp/archive/v0.3.5.tar.gz"], + ), + com_github_msgpack_msgpack_c = dict( + sha256 = "bda49f996a73d2c6080ff0523e7b535917cd28c8a79c3a5da54fc29332d61d1e", + strip_prefix = "msgpack-c-cpp-3.1.1", + urls = ["https://github.com/msgpack/msgpack-c/archive/cpp-3.1.1.tar.gz"], + ), com_github_google_jwt_verify = dict( sha256 = "499f1e145c19f33031eb8fc6452d5d391b4cecfdeda23e2055386a3b33be4d41", strip_prefix = "jwt_verify_lib-66792a057ec54e4b75c6a2eeda4e98220bd12a9a", diff --git a/docs/root/configuration/http_conn_man/headers.rst b/docs/root/configuration/http_conn_man/headers.rst index 555cb3904eff4..ea46fb8ae9d75 100644 --- a/docs/root/configuration/http_conn_man/headers.rst +++ b/docs/root/configuration/http_conn_man/headers.rst @@ -437,6 +437,34 @@ The *b3* HTTP header is used by the Zipkin tracer in Envoy. Is a more compressed header format. See more on zipkin tracing `here `. +.. _config_http_conn_man_headers_x-datadog-trace-id: + +x-datadog-trace-id +------------------ + +The *x-datadog-trace-id* HTTP header is used by the Datadog tracer in Envoy. +The 64-bit value represents the ID of the overall trace, and is used to correlate +the spans. + +.. _config_http_conn_man_headers_x-datadog-parent-id: + +x-datadog-parent-id +------------------- + +The *x-datadog-parent-id* HTTP header is used by the Datadog tracer in Envoy. +The 64-bit value uniquely identifies the span within the trace, and is used to +create parent-child relationships between spans. + +.. _config_http_conn_man_headers_x-datadog-sampling-priority: + +x-datadog-sampling-priority +--------------------------- + +The *x-datadog-sampling-priority* HTTP header is used by the Datadog tracer in Envoy. +The integer value indicates the sampling decision that has been made for this trace. +A value of 0 indicates that the trace should not be collected, and a value of 1 +requests that spans are sampled and reported. + .. _config_http_conn_man_headers_custom_request_headers: Custom request/response headers diff --git a/docs/root/install/ref_configs.rst b/docs/root/install/ref_configs.rst index 6ae32e62cbda7..35fccc5b66c23 100644 --- a/docs/root/install/ref_configs.rst +++ b/docs/root/install/ref_configs.rst @@ -47,7 +47,7 @@ A few notes about the example configurations: * DNS for `yourcompany.net` is assumed to be setup for various things. Search the configuration templates for different instances of this. * Tracing is configured for `LightStep `_. To - disable this or enable `Zipkin ` tracing, delete or + disable this or enable `Zipkin ` or `Datadog ` tracing, delete or change the :ref:`tracing configuration ` accordingly. * The configuration demonstrates the use of a :ref:`global rate limiting service `. To disable this delete the :ref:`rate limit configuration diff --git a/docs/root/intro/arch_overview/tracing.rst b/docs/root/intro/arch_overview/tracing.rst index 633a64919cc64..91407ae4d01c3 100644 --- a/docs/root/intro/arch_overview/tracing.rst +++ b/docs/root/intro/arch_overview/tracing.rst @@ -14,7 +14,8 @@ sources of latency. Envoy supports three features related to system wide tracing x-request-id header for unified logging as well as tracing. * **External trace service integration**: Envoy supports pluggable external trace visualization providers. Currently Envoy supports `LightStep `_, `Zipkin `_ - or any Zipkin compatible backends (e.g. `Jaeger `_). + or any Zipkin compatible backends (e.g. `Jaeger `_), and + `Datadog `_. However, support for other tracing providers would not be difficult to add. * **Client trace ID joining**: The :ref:`config_http_conn_man_headers_x-client-trace-id` header can be used to join untrusted request IDs to the trusted internal @@ -71,6 +72,12 @@ Alternatively the trace context can be manually propagated by the service: request. In addition, the single :ref:`config_http_conn_man_headers_b3` header propagation format is supported, which is a more compressed format. +* When using the Datadog tracer, Envoy relies on the service to propagate the + Datadog-specific HTTP headers ( + :ref:`config_http_conn_man_headers_x-datadog-trace-id`, + :ref:`config_http_conn_man_headers_x-datadog-parent-id`, + :ref:`config_http_conn_man_headers_x-datadog-sampling-priority`). + What data each trace contains ----------------------------- An end-to-end trace is comprised of one or more spans. A @@ -95,7 +102,7 @@ the route. The name can also be overridden using the Envoy automatically sends spans to tracing collectors. Depending on the tracing collector, multiple spans are stitched together using common information such as the globally unique request ID :ref:`config_http_conn_man_headers_x-request-id` (LightStep) or -the trace ID configuration (Zipkin). See +the trace ID configuration (Zipkin and Datadog). See * :ref:`v2 API reference ` diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index e5a6d9a5ff1bd..8cef57f59259d 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -41,6 +41,7 @@ Version history * thrift_proxy: introduced thrift rate limiter filter * tls: add support for CRLs in :ref:`trusted_ca `. * tracing: added support to the Zipkin tracer for the :ref:`b3 ` single header format. +* tracing: added support for :ref:`Datadog ` tracer. * upstream: changed how load calculation for :ref:`priority levels` and :ref:`panic thresholds` interact. As long as normalized total health is 100% panic thresholds are disregarded. * upstream: changed the default hash for :ref:`ring hash ` from std::hash to `xxHash `_. diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 1a2a243e5f535..5fb01c97c821c 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -102,6 +102,7 @@ EXTENSIONS = { "envoy.tracers.dynamic_ot": "//source/extensions/tracers/dynamic_ot:config", "envoy.tracers.lightstep": "//source/extensions/tracers/lightstep:config", + "envoy.tracers.datadog": "//source/extensions/tracers/datadog:config", "envoy.tracers.zipkin": "//source/extensions/tracers/zipkin:config", # diff --git a/source/extensions/tracers/datadog/BUILD b/source/extensions/tracers/datadog/BUILD new file mode 100644 index 0000000000000..afcc3c7b7f88a --- /dev/null +++ b/source/extensions/tracers/datadog/BUILD @@ -0,0 +1,39 @@ +licenses(["notice"]) # Apache 2 + +# Trace driver for Datadog (https://datadoghq.com/) + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "datadog_tracer_lib", + srcs = [ + "datadog_tracer_impl.cc", + ], + hdrs = [ + "datadog_tracer_impl.h", + ], + external_deps = ["dd_opentracing_cpp"], + deps = [ + "//source/common/tracing:http_tracer_lib", + "//source/extensions/tracers:well_known_names", + "//source/extensions/tracers/common/ot:opentracing_driver_lib", + "//source/server:configuration_lib", + ], +) + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":datadog_tracer_lib", + "//source/extensions/tracers:well_known_names", + "//source/server:configuration_lib", + ], +) diff --git a/source/extensions/tracers/datadog/config.cc b/source/extensions/tracers/datadog/config.cc new file mode 100644 index 0000000000000..0d0e74577810b --- /dev/null +++ b/source/extensions/tracers/datadog/config.cc @@ -0,0 +1,48 @@ +#include "extensions/tracers/datadog/config.h" + +#include "envoy/registry/registry.h" + +#include "common/common/utility.h" +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/datadog/datadog_tracer_impl.h" +#include "extensions/tracers/well_known_names.h" + +#include "datadog/opentracing.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { + +Tracing::HttpTracerPtr +DatadogTracerFactory::createHttpTracer(const envoy::config::trace::v2::Tracing& configuration, + Server::Instance& server) { + + ProtobufTypes::MessagePtr config_ptr = createEmptyConfigProto(); + + if (configuration.http().has_config()) { + MessageUtil::jsonConvert(configuration.http().config(), *config_ptr); + } + + const auto& datadog_config = + dynamic_cast(*config_ptr); + + Tracing::DriverPtr datadog_driver{ + std::make_unique(datadog_config, server.clusterManager(), server.stats(), + server.threadLocal(), server.runtime())}; + return std::make_unique(std::move(datadog_driver), server.localInfo()); +} + +std::string DatadogTracerFactory::name() { return TracerNames::get().Datadog; } + +/** + * Static registration for the Datadog tracer. @see RegisterFactory. + */ +static Registry::RegisterFactory + register_; + +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/datadog/config.h b/source/extensions/tracers/datadog/config.h new file mode 100644 index 0000000000000..a257faf8c6f94 --- /dev/null +++ b/source/extensions/tracers/datadog/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "envoy/server/instance.h" + +#include "server/configuration_impl.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { + +/** + * Config registration for the Datadog tracer. @see TracerFactory. + */ +class DatadogTracerFactory : public Server::Configuration::TracerFactory { +public: + // TracerFactory + Tracing::HttpTracerPtr createHttpTracer(const envoy::config::trace::v2::Tracing& configuration, + Server::Instance& server) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override; +}; + +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/datadog/datadog_tracer_impl.cc b/source/extensions/tracers/datadog/datadog_tracer_impl.cc new file mode 100644 index 0000000000000..81feec2ad9f8d --- /dev/null +++ b/source/extensions/tracers/datadog/datadog_tracer_impl.cc @@ -0,0 +1,120 @@ +#include "extensions/tracers/datadog/datadog_tracer_impl.h" + +#include "common/common/enum_to_int.h" +#include "common/common/fmt.h" +#include "common/common/utility.h" +#include "common/config/utility.h" +#include "common/http/message_impl.h" +#include "common/http/utility.h" +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { + +Driver::TlsTracer::TlsTracer(const std::shared_ptr& tracer, + TraceReporterPtr&& reporter, Driver& driver) + : tracer_(tracer), reporter_(std::move(reporter)), driver_(driver) {} + +Driver::Driver(const envoy::config::trace::v2::DatadogConfig& datadog_config, + Upstream::ClusterManager& cluster_manager, Stats::Store& stats, + ThreadLocal::SlotAllocator& tls, Runtime::Loader& runtime) + : OpenTracingDriver{stats}, + cm_(cluster_manager), tracer_stats_{DATADOG_TRACER_STATS( + POOL_COUNTER_PREFIX(stats, "tracing.datadog."))}, + tls_(tls.allocateSlot()), runtime_(runtime) { + + Config::Utility::checkCluster(TracerNames::get().Datadog, datadog_config.collector_cluster(), + cm_); + cluster_ = cm_.get(datadog_config.collector_cluster())->info(); + + // Default tracer options. + tracer_options_.operation_name_override = "envoy.proxy"; + tracer_options_.service = "envoy"; + + // Configuration overrides for tracer options. + if (!datadog_config.service_name().empty()) { + tracer_options_.service = datadog_config.service_name(); + } + + tls_->set([this](Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { + auto tp = datadog::opentracing::makeTracerAndEncoder(tracer_options_); + auto tracer = std::get<0>(tp); + auto encoder = std::get<1>(tp); + TraceReporterPtr reporter(new TraceReporter(encoder, *this, dispatcher)); + return ThreadLocal::ThreadLocalObjectSharedPtr{ + new TlsTracer(tracer, std::move(reporter), *this)}; + }); +} + +opentracing::Tracer& Driver::tracer() { return *tls_->getTyped().tracer_; } + +TraceReporter::TraceReporter(TraceEncoderSharedPtr encoder, Driver& driver, + Event::Dispatcher& dispatcher) + : driver_(driver), encoder_(encoder) { + flush_timer_ = dispatcher.createTimer([this]() -> void { + for (auto& h : encoder_->headers()) { + lower_case_headers_.emplace(h.first, Http::LowerCaseString{h.first}); + } + driver_.tracerStats().timer_flushed_.inc(); + flushTraces(); + enableTimer(); + }); + + enableTimer(); +} + +void TraceReporter::enableTimer() { flush_timer_->enableTimer(std::chrono::milliseconds(1000U)); } + +void TraceReporter::flushTraces() { + auto pendingTraces = encoder_->pendingTraces(); + ENVOY_LOG(debug, "flushing traces: {} traces", pendingTraces); + if (pendingTraces) { + driver_.tracerStats().traces_sent_.add(pendingTraces); + + Http::MessagePtr message(new Http::RequestMessageImpl()); + message->headers().insertMethod().value().setReference(Http::Headers::get().MethodValues.Post); + message->headers().insertPath().value().setReference(encoder_->path()); + message->headers().insertHost().value().setReference(driver_.cluster()->name()); + for (auto& h : encoder_->headers()) { + message->headers().setReferenceKey(lower_case_headers_.at(h.first), h.second); + } + + Buffer::InstancePtr body(new Buffer::OwnedImpl()); + body->add(encoder_->payload()); + message->body() = std::move(body); + ENVOY_LOG(debug, "submitting {} trace(s) to {} with payload {}", pendingTraces, + encoder_->path(), encoder_->payload().size()); + + driver_.clusterManager() + .httpAsyncClientForCluster(driver_.cluster()->name()) + .send(std::move(message), *this, std::chrono::milliseconds(1000U)); + + encoder_->clearTraces(); + } +} + +void TraceReporter::onFailure(Http::AsyncClient::FailureReason) { + ENVOY_LOG(debug, "failure submitting traces to datadog agent"); + driver_.tracerStats().reports_failed_.inc(); +} + +void TraceReporter::onSuccess(Http::MessagePtr&& http_response) { + uint64_t responseStatus = Http::Utility::getResponseStatus(http_response->headers()); + if (responseStatus != enumToInt(Http::Code::OK)) { + ENVOY_LOG(debug, "unexpected HTTP response code from datadog agent: {}", responseStatus); + driver_.tracerStats().reports_dropped_.inc(); + } else { + ENVOY_LOG(debug, "traces successfully submitted to datadog agent"); + driver_.tracerStats().reports_sent_.inc(); + encoder_->handleResponse(http_response->body()->toString()); + } +} + +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/datadog/datadog_tracer_impl.h b/source/extensions/tracers/datadog/datadog_tracer_impl.h new file mode 100644 index 0000000000000..54f8379b84da5 --- /dev/null +++ b/source/extensions/tracers/datadog/datadog_tracer_impl.h @@ -0,0 +1,133 @@ +#pragma once + +#include + +#include "envoy/local_info/local_info.h" +#include "envoy/runtime/runtime.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/tracing/http_tracer.h" +#include "envoy/upstream/cluster_manager.h" + +#include "common/http/header_map_impl.h" +#include "common/json/json_loader.h" + +#include "extensions/tracers/common/ot/opentracing_driver_impl.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { + +#define DATADOG_TRACER_STATS(COUNTER) \ + COUNTER(traces_sent) \ + COUNTER(timer_flushed) \ + COUNTER(reports_sent) \ + COUNTER(reports_dropped) \ + COUNTER(reports_failed) + +struct DatadogTracerStats { + DATADOG_TRACER_STATS(GENERATE_COUNTER_STRUCT) +}; + +class TraceReporter; +typedef std::unique_ptr TraceReporterPtr; +typedef std::shared_ptr TraceEncoderSharedPtr; + +/** + * Class for a Datadog-specific Driver. + */ +class Driver : public Common::Ot::OpenTracingDriver { +public: + /** + * Constructor. It adds itself and a newly-created Datadog::Tracer object to a thread-local store. + */ + Driver(const envoy::config::trace::v2::DatadogConfig& datadog_config, + Upstream::ClusterManager& cluster_manager, Stats::Store& stats, + ThreadLocal::SlotAllocator& tls, Runtime::Loader& runtime); + + // Getters to return the DatadogDriver's key members. + Upstream::ClusterManager& clusterManager() { return cm_; } + Upstream::ClusterInfoConstSharedPtr cluster() { return cluster_; } + Runtime::Loader& runtime() { return runtime_; } + DatadogTracerStats& tracerStats() { return tracer_stats_; } + const datadog::opentracing::TracerOptions& tracerOptions() { return tracer_options_; } + + // Tracer::OpenTracingDriver + opentracing::Tracer& tracer() override; + PropagationMode propagationMode() const override { + return Common::Ot::OpenTracingDriver::PropagationMode::TracerNative; + } + +private: + /** + * Thread-local store containing DatadogDriver and Datadog::Tracer objects. + */ + struct TlsTracer : ThreadLocal::ThreadLocalObject { + TlsTracer(const std::shared_ptr& tracer, TraceReporterPtr&& reporter, + Driver& driver); + + std::shared_ptr tracer_; + TraceReporterPtr reporter_; + Driver& driver_; + }; + + Upstream::ClusterManager& cm_; + Upstream::ClusterInfoConstSharedPtr cluster_; + DatadogTracerStats tracer_stats_; + datadog::opentracing::TracerOptions tracer_options_; + ThreadLocal::SlotPtr tls_; + Runtime::Loader& runtime_; +}; + +/** + * This class wraps the encoder provided with the tracer at initialization + * and uses Http::AsyncClient to send completed traces to the Datadog Agent. + * + * The cluster to use for submitting traces to the agent is controlled with + * the setting tracing.datadog.collector_cluster, which is mandatory and must + * refer to a cluster in the active configuration. + * + * An internal timer is used to control how often traces are submitted. + * If zero traces have completed in the interval between timer events, + * no action is taken. + * The timer interval can be controlled with the setting + * tracing.datadog.flush_interval_ms, and defaults to 2000ms. + */ +class TraceReporter : public Http::AsyncClient::Callbacks, + protected Logger::Loggable { +public: + /** + * Constructor. + * + * @param encoder Provides methods to retrieve data for publishing traces. + * @param driver The driver to be associated with the reporter. + * @param dispatcher Controls the timer used to flush buffered traces. + */ + TraceReporter(TraceEncoderSharedPtr encoder, Driver& driver, Event::Dispatcher& dispatcher); + + // Http::AsyncClient::Callbacks. + void onSuccess(Http::MessagePtr&&) override; + void onFailure(Http::AsyncClient::FailureReason) override; + +private: + /** + * Enables the trace-flushing timer. + */ + void enableTimer(); + + /** + * Removes all traces from the trace buffer and sends them to a Datadog Agent using + * Http::AsyncClient. + */ + void flushTraces(); + + Driver& driver_; + Event::TimerPtr flush_timer_; + TraceEncoderSharedPtr encoder_; + + std::map lower_case_headers_; +}; +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/lightstep/BUILD b/source/extensions/tracers/lightstep/BUILD index 7a30babd54671..60009ac3648e7 100644 --- a/source/extensions/tracers/lightstep/BUILD +++ b/source/extensions/tracers/lightstep/BUILD @@ -21,7 +21,9 @@ envoy_cc_library( external_deps = ["lightstep"], deps = [ "//source/common/tracing:http_tracer_lib", + "//source/extensions/tracers:well_known_names", "//source/extensions/tracers/common/ot:opentracing_driver_lib", + "//source/server:configuration_lib", ], ) diff --git a/source/extensions/tracers/lightstep/lightstep_tracer_impl.cc b/source/extensions/tracers/lightstep/lightstep_tracer_impl.cc index 99ea5d8175868..1309cb05b9c75 100644 --- a/source/extensions/tracers/lightstep/lightstep_tracer_impl.cc +++ b/source/extensions/tracers/lightstep/lightstep_tracer_impl.cc @@ -8,10 +8,13 @@ #include "common/buffer/zero_copy_input_stream_impl.h" #include "common/common/base64.h" #include "common/common/fmt.h" +#include "common/config/utility.h" #include "common/grpc/common.h" #include "common/http/message_impl.h" #include "common/tracing/http_tracer_impl.h" +#include "extensions/tracers/well_known_names.h" + namespace Envoy { namespace Extensions { namespace Tracers { @@ -138,12 +141,9 @@ LightStepDriver::LightStepDriver(const envoy::config::trace::v2::LightstepConfig tracer_stats_{LIGHTSTEP_TRACER_STATS(POOL_COUNTER_PREFIX(stats, "tracing.lightstep."))}, tls_{tls.allocateSlot()}, runtime_{runtime}, options_{std::move(options)}, propagation_mode_{propagation_mode} { - Upstream::ThreadLocalCluster* cluster = cm_.get(lightstep_config.collector_cluster()); - if (!cluster) { - throw EnvoyException(fmt::format("{} collector cluster is not defined on cluster manager level", - lightstep_config.collector_cluster())); - } - cluster_ = cluster->info(); + Config::Utility::checkCluster(TracerNames::get().Lightstep, lightstep_config.collector_cluster(), + cm_); + cluster_ = cm_.get(lightstep_config.collector_cluster())->info(); if (!(cluster_->features() & Upstream::ClusterInfo::Features::HTTP2)) { throw EnvoyException( diff --git a/source/extensions/tracers/well_known_names.h b/source/extensions/tracers/well_known_names.h index a7366b0fdc31e..2b141f61eda4a 100644 --- a/source/extensions/tracers/well_known_names.h +++ b/source/extensions/tracers/well_known_names.h @@ -19,6 +19,8 @@ class TracerNameValues { const std::string Zipkin = "envoy.zipkin"; // Dynamic tracer const std::string DynamicOt = "envoy.dynamic.ot"; + // Datadog tracer + const std::string Datadog = "envoy.tracers.datadog"; }; typedef ConstSingleton TracerNames; diff --git a/source/extensions/tracers/zipkin/BUILD b/source/extensions/tracers/zipkin/BUILD index c418211e213fd..2c76c3d8bba53 100644 --- a/source/extensions/tracers/zipkin/BUILD +++ b/source/extensions/tracers/zipkin/BUILD @@ -55,6 +55,8 @@ envoy_cc_library( "//source/common/network:address_lib", "//source/common/singleton:const_singleton", "//source/common/tracing:http_tracer_lib", + "//source/extensions/tracers:well_known_names", + "//source/server:configuration_lib", ], ) diff --git a/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc b/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc index 3852d572cef34..043f965f27e48 100644 --- a/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc +++ b/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc @@ -3,11 +3,13 @@ #include "common/common/enum_to_int.h" #include "common/common/fmt.h" #include "common/common/utility.h" +#include "common/config/utility.h" #include "common/http/headers.h" #include "common/http/message_impl.h" #include "common/http/utility.h" #include "common/tracing/http_tracer_impl.h" +#include "extensions/tracers/well_known_names.h" #include "extensions/tracers/zipkin/span_context_extractor.h" #include "extensions/tracers/zipkin/zipkin_core_constants.h" @@ -65,13 +67,8 @@ Driver::Driver(const envoy::config::trace::v2::ZipkinConfig& zipkin_config, POOL_COUNTER_PREFIX(stats, "tracing.zipkin."))}, tls_(tls.allocateSlot()), runtime_(runtime), local_info_(local_info), time_source_(time_source) { - - Upstream::ThreadLocalCluster* cluster = cm_.get(zipkin_config.collector_cluster()); - if (!cluster) { - throw EnvoyException(fmt::format("{} collector cluster is not defined on cluster manager level", - zipkin_config.collector_cluster())); - } - cluster_ = cluster->info(); + Config::Utility::checkCluster(TracerNames::get().Zipkin, zipkin_config.collector_cluster(), cm_); + cluster_ = cm_.get(zipkin_config.collector_cluster())->info(); std::string collector_endpoint = ZipkinCoreConstants::get().DEFAULT_COLLECTOR_ENDPOINT; if (zipkin_config.collector_endpoint().size() > 0) { diff --git a/test/extensions/tracers/datadog/BUILD b/test/extensions/tracers/datadog/BUILD new file mode 100644 index 0000000000000..714c3091bad41 --- /dev/null +++ b/test/extensions/tracers/datadog/BUILD @@ -0,0 +1,48 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "datadog_tracer_impl_test", + srcs = [ + "datadog_tracer_impl_test.cc", + ], + extension_name = "envoy.tracers.datadog", + deps = [ + "//source/common/common:base64_lib", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/http:message_lib", + "//source/common/runtime:runtime_lib", + "//source/common/runtime:uuid_util_lib", + "//source/extensions/tracers/datadog:datadog_tracer_lib", + "//test/mocks/http:http_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/stats:stats_mocks", + "//test/mocks/thread_local:thread_local_mocks", + "//test/mocks/tracing:tracing_mocks", + "//test/mocks/upstream:upstream_mocks", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.tracers.datadog", + deps = [ + "//source/extensions/tracers/datadog:config", + "//test/mocks/server:server_mocks", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc new file mode 100644 index 0000000000000..750333b57f6c5 --- /dev/null +++ b/test/extensions/tracers/datadog/config_test.cc @@ -0,0 +1,42 @@ +#include "extensions/tracers/datadog/config.h" + +#include "test/mocks/server/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { + +TEST(DatadogTracerConfigTest, DatadogHttpTracer) { + NiceMock server; + EXPECT_CALL(server.cluster_manager_, get("fake_cluster")) + .WillRepeatedly(Return(&server.cluster_manager_.thread_local_cluster_)); + ON_CALL(*server.cluster_manager_.thread_local_cluster_.cluster_.info_, features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + http: + name: envoy.tracers.datadog + config: + collector_cluster: fake_cluster + service_name: fake_file + )EOF"; + envoy::config::trace::v2::Tracing configuration; + MessageUtil::loadFromYaml(yaml_string, configuration); + + DatadogTracerFactory factory; + Tracing::HttpTracerPtr datadog_tracer = factory.createHttpTracer(configuration, server); + EXPECT_NE(nullptr, datadog_tracer); +} + +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc b/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc new file mode 100644 index 0000000000000..6900e56b3ed81 --- /dev/null +++ b/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc @@ -0,0 +1,167 @@ +#include +#include +#include +#include + +#include "common/common/base64.h" +#include "common/http/header_map_impl.h" +#include "common/http/headers.h" +#include "common/http/message_impl.h" +#include "common/runtime/runtime_impl.h" +#include "common/runtime/uuid_util.h" +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/datadog/datadog_tracer_impl.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/thread_local/mocks.h" +#include "test/mocks/tracing/mocks.h" +#include "test/mocks/upstream/mocks.h" +#include "test/test_common/printers.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::AtLeast; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; +using testing::Test; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace Datadog { + +class DatadogDriverTest : public Test { +public: + void setup(envoy::config::trace::v2::DatadogConfig& datadog_config, bool init_timer) { + ON_CALL(cm_, httpAsyncClientForCluster("fake_cluster")) + .WillByDefault(ReturnRef(cm_.async_client_)); + + if (init_timer) { + timer_ = new NiceMock(&tls_.dispatcher_); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000))); + } + + driver_ = std::make_unique(datadog_config, cm_, stats_, tls_, runtime_); + } + + void setupValidDriver() { + EXPECT_CALL(cm_, get("fake_cluster")).WillRepeatedly(Return(&cm_.thread_local_cluster_)); + ON_CALL(*cm_.thread_local_cluster_.cluster_.info_, features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + collector_cluster: fake_cluster + )EOF"; + envoy::config::trace::v2::DatadogConfig datadog_config; + MessageUtil::loadFromYaml(yaml_string, datadog_config); + + setup(datadog_config, true); + } + + const std::string operation_name_{"test"}; + Http::TestHeaderMapImpl request_headers_{ + {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}}; + const Http::TestHeaderMapImpl response_headers_{{":status", "500"}}; + SystemTime start_time_; + + NiceMock tls_; + std::unique_ptr driver_; + NiceMock* timer_; + Stats::IsolatedStoreImpl stats_; + NiceMock cm_; + NiceMock random_; + NiceMock runtime_; + NiceMock local_info_; + + NiceMock config_; +}; + +TEST_F(DatadogDriverTest, InitializeDriver) { + { + envoy::config::trace::v2::DatadogConfig datadog_config; + + EXPECT_THROW(setup(datadog_config, false), EnvoyException); + } + + { + // Valid config but not valid cluster. + EXPECT_CALL(cm_, get("fake_cluster")).WillOnce(Return(nullptr)); + + const std::string yaml_string = R"EOF( + collector_cluster: fake_cluster + )EOF"; + envoy::config::trace::v2::DatadogConfig datadog_config; + MessageUtil::loadFromYaml(yaml_string, datadog_config); + + EXPECT_THROW(setup(datadog_config, false), EnvoyException); + } + + { + EXPECT_CALL(cm_, get("fake_cluster")).WillRepeatedly(Return(&cm_.thread_local_cluster_)); + ON_CALL(*cm_.thread_local_cluster_.cluster_.info_, features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + collector_cluster: fake_cluster + )EOF"; + envoy::config::trace::v2::DatadogConfig datadog_config; + MessageUtil::loadFromYaml(yaml_string, datadog_config); + + setup(datadog_config, true); + } +} + +TEST_F(DatadogDriverTest, FlushSpansTimer) { + setupValidDriver(); + + Http::MockAsyncClientRequest request(&cm_.async_client_); + Http::AsyncClient::Callbacks* callback; + const absl::optional timeout(std::chrono::seconds(1)); + EXPECT_CALL(cm_.async_client_, send_(_, _, timeout)) + .WillOnce(Invoke( + [&](Http::MessagePtr& message, Http::AsyncClient::Callbacks& callbacks, + const absl::optional&) -> Http::AsyncClient::Request* { + callback = &callbacks; + + EXPECT_STREQ("fake_cluster", message->headers().Host()->value().c_str()); + EXPECT_STREQ("application/msgpack", message->headers().ContentType()->value().c_str()); + + return &request; + })); + + Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, + start_time_, {Tracing::Reason::Sampling, true}); + span->finishSpan(); + + // Timer should be re-enabled. + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000))); + + timer_->callback_(); + + EXPECT_EQ(1U, stats_.counter("tracing.datadog.timer_flushed").value()); + EXPECT_EQ(1U, stats_.counter("tracing.datadog.traces_sent").value()); + + Http::MessagePtr msg(new Http::ResponseMessageImpl( + Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); + msg->body() = std::make_unique(""); + + callback->onSuccess(std::move(msg)); + + EXPECT_EQ(1U, stats_.counter("tracing.datadog.reports_sent").value()); + EXPECT_EQ(0U, stats_.counter("tracing.datadog.reports_dropped").value()); + EXPECT_EQ(0U, stats_.counter("tracing.datadog.reports_failed").value()); +} + +} // namespace Datadog +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy