diff --git a/bazel/external/BUILD b/bazel/external/BUILD index 03a47a4107035..f686511e728b3 100644 --- a/bazel/external/BUILD +++ b/bazel/external/BUILD @@ -4,7 +4,8 @@ cc_library( name = "all_external", srcs = [":empty.cc"], deps = [ - "@com_github_lightstep_lightstep_tracer_cpp//:lightstep", + "@io_opentracing_cpp//:opentracing", + "@com_github_lightstep_lightstep_tracer_cpp//:lightstep_tracer", "@com_google_googletest//:gtest", ], ) diff --git a/bazel/external/lightstep.BUILD b/bazel/external/lightstep.BUILD deleted file mode 100644 index 6269c5cef7f3a..0000000000000 --- a/bazel/external/lightstep.BUILD +++ /dev/null @@ -1,50 +0,0 @@ -load("@envoy//bazel:genrule_repository.bzl", "genrule_cc_deps", "genrule_environment") -load(":genrule_cmd.bzl", "genrule_cmd") - -_HDRS = glob([ - "src/c++11/lightstep/**/*.h", - "src/c++11/mapbox_variant/**/*.hpp", -]) - -_PREFIX_HDRS = [hdr.replace("src/c++11/", "_prefix/include/") for hdr in _HDRS] + [ - "_prefix/include/collector.pb.h", - "_prefix/include/lightstep_carrier.pb.h", -] - -cc_library( - name = "lightstep", - srcs = [":_prefix/lib/liblightstep_core_cxx11.a"], - hdrs = [":" + hdr for hdr in _PREFIX_HDRS], - includes = ["_prefix/include"], - visibility = ["//visibility:public"], - deps = ["@com_google_protobuf//:protobuf"], -) - -genrule_environment( - name = "lightstep_compiler_flags", -) - -# This intermediate rule lets cc_library outputs be fed into a genrule. -# Normally a cc_library creates a CcSkylarkApiProvider, but no direct -# file outputs. -genrule_cc_deps( - name = "protobuf_deps", - deps = ["@com_google_protobuf//:protobuf"], -) - -genrule( - name = "install", - srcs = glob(["**"]) + [ - ":lightstep_compiler_flags", - ":protobuf_deps", - "@com_google_protobuf//:well_known_protos", - "@local_config_cc//:toolchain", - ], - outs = _PREFIX_HDRS + [ - "_prefix/lib/liblightstep_core_cxx11.a", - ], - cmd = genrule_cmd("@envoy//bazel/external:lightstep.genrule_cmd"), - tools = [ - "@com_google_protobuf//:protoc", - ], -) diff --git a/bazel/external/lightstep.genrule_cmd b/bazel/external/lightstep.genrule_cmd deleted file mode 100644 index 816d9061eafbf..0000000000000 --- a/bazel/external/lightstep.genrule_cmd +++ /dev/null @@ -1,23 +0,0 @@ -# This is a Bazel genrule script. It acts like Bash, but has some extra -# substitutions for locating dependencies. -# -# https://docs.bazel.build/versions/master/be/general.html#genrule - -cat "$(location :lightstep_compiler_flags)" -source "$(location :lightstep_compiler_flags)" - -CONFIGURE="$${PWD}/$(location :configure)" -PROTOC="$${PWD}/$(location @com_google_protobuf//:protoc)" -PROTOBUF_SRC="$${PWD}/external/com_google_protobuf/src/" -PROTOBUF_LIBDIR="$${PWD}/$(BINDIR)/external/com_google_protobuf/" -mkdir -p "$(@D)/build" -cd "$(@D)/build" -"$${CONFIGURE}" \ - --prefix="$${PWD}/../_prefix" \ - --disable-grpc \ - --enable-shared=no \ - protobuf_CFLAGS="-I$${PROTOBUF_SRC}" \ - protobuf_LIBS="-L$${PROTOBUF_LIBDIR} -lprotobuf -lprotobuf_lite" \ - PKG_CONFIG="true" \ - PROTOC="$${PROTOC} -I$${PROTOBUF_SRC}" -make V=1 install diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 9bfdadad67ea1..3985f81ef5736 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -238,6 +238,7 @@ def envoy_dependencies(path = "@envoy_deps//", skip_targets = []): _com_github_fmtlib_fmt() _com_github_gabime_spdlog() _com_github_gcovr_gcovr() + _io_opentracing_cpp() _com_github_lightstep_lightstep_tracer_cpp() _com_github_nodejs_http_parser() _com_github_tencent_rapidjson() @@ -312,23 +313,22 @@ def _com_github_gcovr_gcovr(): actual = "@com_github_gcovr_gcovr//:gcovr", ) +def _io_opentracing_cpp(): + _repository_impl("io_opentracing_cpp") + native.bind( + name = "opentracing", + actual = "@io_opentracing_cpp//:opentracing", + ) + def _com_github_lightstep_lightstep_tracer_cpp(): - location = REPOSITORY_LOCATIONS[ - "com_github_lightstep_lightstep_tracer_cpp"] - genrule_repository( - name = "com_github_lightstep_lightstep_tracer_cpp", - urls = location["urls"], - sha256 = location["sha256"], - strip_prefix = location["strip_prefix"], - patches = [ - "@envoy//bazel/external:lightstep-missing-header.patch", - ], - genrule_cmd_file = "@envoy//bazel/external:lightstep.genrule_cmd", - build_file = "@envoy//bazel/external:lightstep.BUILD", + _repository_impl("com_github_lightstep_lightstep_tracer_cpp") + _repository_impl( + name = "lightstep_vendored_googleapis", + build_file = "@com_github_lightstep_lightstep_tracer_cpp//:lightstep-tracer-common/third_party/googleapis/BUILD", ) native.bind( name = "lightstep", - actual = "@com_github_lightstep_lightstep_tracer_cpp//:lightstep", + actual = "@com_github_lightstep_lightstep_tracer_cpp//:lightstep_tracer", ) def _com_github_tencent_rapidjson(): diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 8c99aba0c028c..3b98b5cbe90fc 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -25,10 +25,17 @@ REPOSITORY_LOCATIONS = dict( commit = "c0d77201039c7b119b18bc7fb991564c602dd75d", remote = "https://github.com/gcovr/gcovr", ), + io_opentracing_cpp = dict( + commit = "550c686e0e174c845a034f432a6c31a808f5f994", + remote = "https://github.com/opentracing/opentracing-cpp", + ), com_github_lightstep_lightstep_tracer_cpp = dict( - sha256 = "f7477e67eca65f904c0b90a6bfec46d58cccfc998a8e75bc3259b6e93157ff84", - strip_prefix = "lightstep-tracer-cpp-0.36", - urls = ["https://github.com/lightstep/lightstep-tracer-cpp/releases/download/v0_36/lightstep-tracer-cpp-0.36.tar.gz"], + commit = "deb5284395075028c3e5b4eab1416fe1e597bdb7", + remote = "https://github.com/lightstep/lightstep-tracer-cpp", + ), + lightstep_vendored_googleapis = dict( + commit = "d6f78d948c53f3b400bb46996eb3084359914f9b", + remote = "https://github.com/google/googleapis", ), com_github_nodejs_http_parser = dict( commit = "feae95a3a69f111bc1897b9048d9acbc290992f9", # v2.7.1 diff --git a/ci/build_container/Makefile b/ci/build_container/Makefile index df388f0a2dd84..91932a4372c86 100644 --- a/ci/build_container/Makefile +++ b/ci/build_container/Makefile @@ -52,6 +52,3 @@ $(THIRDPARTY_DEPS)/%.dep: $(RECIPES)/%.sh # Special support for targets that need protobuf, and hence take a dependency on protobuf.dep. PROTOBUF_BUILD ?= $(THIRDPARTY_BUILD)/$(if $(BUILD_DISTINCT),protobuf.dep,) - -$(THIRDPARTY_DEPS)/lightstep.dep: $(RECIPES)/lightstep.sh $(THIRDPARTY_DEPS)/protobuf.dep - @+$(call build-recipe,PROTOBUF_BUILD=$(PROTOBUF_BUILD)) diff --git a/include/envoy/http/header_map.h b/include/envoy/http/header_map.h index 53aba737dcc7f..a888adc800f50 100644 --- a/include/envoy/http/header_map.h +++ b/include/envoy/http/header_map.h @@ -355,7 +355,7 @@ class HeaderMap { /** * Get a header by key. * @param key supplies the header key. - * @return the header entry if it exsits otherwise nullptr. + * @return the header entry if it exists otherwise nullptr. */ virtual const HeaderEntry* get(const LowerCaseString& key) const PURE; @@ -384,6 +384,18 @@ class HeaderMap { */ virtual void iterateReverse(ConstIterateCb cb, void* context) const PURE; + enum class Lookup { Found, NotFound, NotSupported }; + + /** + * Lookup one of the predefined inline headers (see ALL_INLINE_HEADERS below) by key. + * @param key supplies the header key. + * @param entry is set to the header entry if it exists and if key is one of the predefined inline + * headers; otherwise, nullptr. + * @return Lookup::Found if lookup was successful, Lookup::NotFound if the header entry doesn't + * exist, or Lookup::NotSupported if key is not one of the predefined inline headers. + */ + virtual Lookup lookup(const LowerCaseString& key, const HeaderEntry** entry) const PURE; + /** * Remove all instances of a header by key. * @param key supplies the header key to remove. diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 1fc7811b0ba97..720a5b47c0c21 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -38,6 +38,7 @@ namespace Logger { FUNCTION(router) \ FUNCTION(runtime) \ FUNCTION(testing) \ + FUNCTION(tracing) \ FUNCTION(upstream) enum class Id { diff --git a/source/common/common/utility.cc b/source/common/common/utility.cc index b6e11eac60329..7a295d891ef5d 100644 --- a/source/common/common/utility.cc +++ b/source/common/common/utility.cc @@ -32,6 +32,17 @@ std::string DateFormatter::now() { ProdSystemTimeSource ProdSystemTimeSource::instance_; ProdMonotonicTimeSource ProdMonotonicTimeSource::instance_; +ConstMemoryStreamBuffer::ConstMemoryStreamBuffer(const char* data, size_t size) { + // std::streambuf won't modify `data`, but the interface still requires a char* for convenience, + // so we need to const_cast. + char* ptr = const_cast(data); + + this->setg(ptr, ptr, ptr + size); +} + +InputConstMemoryStream::InputConstMemoryStream(const char* data, size_t size) + : ConstMemoryStreamBuffer{data, size}, std::istream{static_cast(this)} {} + bool DateUtil::timePointValid(SystemTime time_point) { return std::chrono::duration_cast(time_point.time_since_epoch()) .count() != 0; diff --git a/source/common/common/utility.h b/source/common/common/utility.h index 599ff334f0e93..91f46e1ac8594 100644 --- a/source/common/common/utility.h +++ b/source/common/common/utility.h @@ -64,6 +64,26 @@ class ProdMonotonicTimeSource : public MonotonicTimeSource { static ProdMonotonicTimeSource instance_; }; +/** + * Class used for creating non-copying std::istream's. See InputConstMemoryStream below. + */ +class ConstMemoryStreamBuffer : public std::streambuf { +public: + ConstMemoryStreamBuffer(const char* data, size_t size); +}; + +/** + * std::istream class similar to std::istringstream, except that it provides a view into a region of + * constant memory. It can be more efficient than std::istringstream because it doesn't copy the + * provided string. + * + * See https://stackoverflow.com/a/13059195/4447365. + */ +class InputConstMemoryStream : public virtual ConstMemoryStreamBuffer, public std::istream { +public: + InputConstMemoryStream(const char* data, size_t size); +}; + /** * Utility class for date/time helpers. */ diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc index d182d615306f4..48e8ab7b92a44 100644 --- a/source/common/http/header_map_impl.cc +++ b/source/common/http/header_map_impl.cc @@ -399,6 +399,29 @@ void HeaderMapImpl::iterateReverse(ConstIterateCb cb, void* context) const { } } +HeaderMap::Lookup HeaderMapImpl::lookup(const LowerCaseString& key, + const HeaderEntry** entry) const { + StaticLookupEntry::EntryCb cb = ConstSingleton::get().find(key.get().c_str()); + if (cb) { + // The accessor callbacks for predefined inline headers take a HeaderMapImpl& as an argument; + // even though we don't make any modifications, we need to cast_cast in order to use the + // accessor. + // + // Making this work without const_cast would require managing an additional const accessor + // callback for each predefined inline header and add to the complexity of the code. + StaticLookupResponse ref_lookup_response = cb(const_cast(*this)); + *entry = *ref_lookup_response.entry_; + if (*entry) { + return Lookup::Found; + } else { + return Lookup::NotFound; + } + } else { + *entry = nullptr; + return Lookup::NotSupported; + } +} + void HeaderMapImpl::remove(const LowerCaseString& key) { StaticLookupEntry::EntryCb cb = ConstSingleton::get().find(key.get().c_str()); if (cb) { diff --git a/source/common/http/header_map_impl.h b/source/common/http/header_map_impl.h index 07e75c946f814..a90f250971792 100644 --- a/source/common/http/header_map_impl.h +++ b/source/common/http/header_map_impl.h @@ -64,6 +64,7 @@ class HeaderMapImpl : public HeaderMap { const HeaderEntry* get(const LowerCaseString& key) const override; void iterate(ConstIterateCb cb, void* context) const override; void iterateReverse(ConstIterateCb cb, void* context) const override; + Lookup lookup(const LowerCaseString& key, const HeaderEntry** entry) const override; void remove(const LowerCaseString& key) override; size_t size() const override { return headers_.size(); } diff --git a/source/common/tracing/BUILD b/source/common/tracing/BUILD index 87f62a990778a..bf3ef043bccf7 100644 --- a/source/common/tracing/BUILD +++ b/source/common/tracing/BUILD @@ -12,10 +12,13 @@ envoy_cc_library( name = "http_tracer_lib", srcs = [ "http_tracer_impl.cc", + "opentracing_driver_impl.cc", ], hdrs = [ "http_tracer_impl.h", + "opentracing_driver_impl.h", ], + external_deps = ["opentracing"], deps = [ "//include/envoy/local_info:local_info_interface", "//include/envoy/runtime:runtime_interface", @@ -23,6 +26,7 @@ envoy_cc_library( "//include/envoy/tracing:http_tracer_interface", "//include/envoy/upstream:cluster_manager_interface", "//source/common/access_log:access_log_formatter_lib", + "//source/common/buffer:zero_copy_input_stream_lib", "//source/common/common:base64_lib", "//source/common/common:macros", "//source/common/common:utility_lib", diff --git a/source/common/tracing/lightstep_tracer_impl.cc b/source/common/tracing/lightstep_tracer_impl.cc index a280126351eaf..851a6b0eb1c92 100644 --- a/source/common/tracing/lightstep_tracer_impl.cc +++ b/source/common/tracing/lightstep_tracer_impl.cc @@ -5,6 +5,7 @@ #include #include +#include "common/buffer/zero_copy_input_stream_impl.h" #include "common/common/base64.h" #include "common/grpc/common.h" #include "common/http/message_impl.h" @@ -15,105 +16,112 @@ namespace Envoy { namespace Tracing { -LightStepSpan::LightStepSpan(lightstep::Span& span, lightstep::Tracer& tracer) - : span_(span), tracer_(tracer) {} +namespace { +class LightStepLogger : Logger::Loggable { +public: + void operator()(lightstep::LogLevel level, opentracing::string_view message) const { + const fmt::StringRef fmt_message{message.data(), message.size()}; + switch (level) { + case lightstep::LogLevel::debug: + ENVOY_LOG(debug, "{}", fmt_message); + break; + case lightstep::LogLevel::info: + ENVOY_LOG(info, "{}", fmt_message); + break; + default: + ENVOY_LOG(warn, "{}", fmt_message); + break; + } + } +}; +} // namespace + +LightStepDriver::LightStepTransporter::LightStepTransporter(LightStepDriver& driver) + : driver_(driver) {} + +void LightStepDriver::LightStepTransporter::Send(const Protobuf::Message& request, + Protobuf::Message& response, + lightstep::AsyncTransporter::Callback& callback) { + // TODO(rnburn): Update to use Grpc::AsyncClient when it supports abstract message classes. + active_callback_ = &callback; + active_response_ = &response; + + Http::MessagePtr message = + Grpc::Common::prepareHeaders(driver_.cluster()->name(), lightstep::CollectorServiceFullName(), + lightstep::CollectorMethodName()); + message->body() = Grpc::Common::serializeBody(request); + + const uint64_t timeout = + driver_.runtime().snapshot().getInteger("tracing.lightstep.request_timeout", 5000U); + driver_.clusterManager() + .httpAsyncClientForCluster(driver_.cluster()->name()) + .send(std::move(message), *this, std::chrono::milliseconds(timeout)); +} -void LightStepSpan::finishSpan() { span_.Finish(); } +void LightStepDriver::LightStepTransporter::onSuccess(Http::MessagePtr&& response) { + try { + Grpc::Common::validateResponse(*response); -void LightStepSpan::setOperation(const std::string& operation) { - span_.SetOperationName(operation); + Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), + lightstep::CollectorMethodName(), true); + // http://www.grpc.io/docs/guides/wire.html + // First 5 bytes contain the message header. + response->body()->drain(5); + Buffer::ZeroCopyInputStreamImpl stream{std::move(response->body())}; + if (!active_response_->ParseFromZeroCopyStream(&stream)) { + throw EnvoyException("Failed to parse LightStep collector response"); + } + active_callback_->OnSuccess(); + } catch (const Grpc::Exception& ex) { + Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), + lightstep::CollectorMethodName(), false); + active_callback_->OnFailure(std::make_error_code(std::errc::network_down)); + } } -void LightStepSpan::setTag(const std::string& name, const std::string& value) { - span_.SetTag(name, value); +void LightStepDriver::LightStepTransporter::onFailure(Http::AsyncClient::FailureReason) { + Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), + lightstep::CollectorMethodName(), false); + active_callback_->OnFailure(std::make_error_code(std::errc::network_down)); } -void LightStepSpan::injectContext(Http::HeaderMap& request_headers) { - lightstep::BinaryCarrier ctx; - tracer_.Inject(context(), lightstep::CarrierFormat::LightStepBinaryCarrier, - lightstep::ProtoWriter(&ctx)); - const std::string current_span_context = ctx.SerializeAsString(); - request_headers.insertOtSpanContext().value( - Base64::encode(current_span_context.c_str(), current_span_context.length())); -} +LightStepDriver::LightStepMetricsObserver::LightStepMetricsObserver(LightStepDriver& driver) + : driver_(driver) {} -SpanPtr LightStepSpan::spawnChild(const Config&, const std::string& name, SystemTime start_time) { - lightstep::Span ls_span = tracer_.StartSpan( - name, {lightstep::ChildOf(span_.context()), lightstep::StartTimestamp(start_time)}); - return SpanPtr{new LightStepSpan(ls_span, tracer_)}; +void LightStepDriver::LightStepMetricsObserver::OnSpansSent(int num_spans) { + driver_.tracerStats().spans_sent_.add(num_spans); } -LightStepRecorder::LightStepRecorder(const lightstep::TracerImpl& tracer, LightStepDriver& driver, - Event::Dispatcher& dispatcher) - : builder_(tracer), driver_(driver) { +LightStepDriver::TlsLightStepTracer::TlsLightStepTracer( + const std::shared_ptr& tracer, LightStepDriver& driver, + Event::Dispatcher& dispatcher) + : tracer_{tracer}, driver_{driver} { flush_timer_ = dispatcher.createTimer([this]() -> void { driver_.tracerStats().timer_flushed_.inc(); - flushSpans(); + tracer_->Flush(); enableTimer(); }); enableTimer(); } -void LightStepRecorder::RecordSpan(lightstep::collector::Span&& span) { - builder_.addSpan(std::move(span)); - - uint64_t min_flush_spans = - driver_.runtime().snapshot().getInteger("tracing.lightstep.min_flush_spans", 5U); - if (builder_.pendingSpans() == min_flush_spans) { - flushSpans(); - } -} - -bool LightStepRecorder::FlushWithTimeout(lightstep::Duration) { - // Note: We don't expect this to be called, since the Tracer - // reference is private to its LightStepSink. - return true; -} - -std::unique_ptr -LightStepRecorder::NewInstance(LightStepDriver& driver, Event::Dispatcher& dispatcher, - const lightstep::TracerImpl& tracer) { - return std::unique_ptr(new LightStepRecorder(tracer, driver, dispatcher)); -} +const opentracing::Tracer& LightStepDriver::TlsLightStepTracer::tracer() const { return *tracer_; } -void LightStepRecorder::enableTimer() { - uint64_t flush_interval = +void LightStepDriver::TlsLightStepTracer::enableTimer() { + const uint64_t flush_interval = driver_.runtime().snapshot().getInteger("tracing.lightstep.flush_interval_ms", 1000U); flush_timer_->enableTimer(std::chrono::milliseconds(flush_interval)); } -void LightStepRecorder::flushSpans() { - if (builder_.pendingSpans() != 0) { - driver_.tracerStats().spans_sent_.add(builder_.pendingSpans()); - lightstep::collector::ReportRequest request; - std::swap(request, builder_.pending()); - - Http::MessagePtr message = Grpc::Common::prepareHeaders(driver_.cluster()->name(), - lightstep::CollectorServiceFullName(), - lightstep::CollectorMethodName()); - - message->body() = Grpc::Common::serializeBody(std::move(request)); - - uint64_t timeout = - driver_.runtime().snapshot().getInteger("tracing.lightstep.request_timeout", 5000U); - driver_.clusterManager() - .httpAsyncClientForCluster(driver_.cluster()->name()) - .send(std::move(message), *this, std::chrono::milliseconds(timeout)); - } -} - -LightStepDriver::TlsLightStepTracer::TlsLightStepTracer(lightstep::Tracer tracer, - LightStepDriver& driver) - : tracer_(new lightstep::Tracer(tracer)), driver_(driver) {} - LightStepDriver::LightStepDriver(const Json::Object& config, Upstream::ClusterManager& cluster_manager, Stats::Store& stats, ThreadLocal::SlotAllocator& tls, Runtime::Loader& runtime, - std::unique_ptr options) - : cm_(cluster_manager), tracer_stats_{LIGHTSTEP_TRACER_STATS( - POOL_COUNTER_PREFIX(stats, "tracing.lightstep."))}, - tls_(tls.allocateSlot()), runtime_(runtime), options_(std::move(options)) { + std::unique_ptr&& options, + PropagationMode propagation_mode) + : OpenTracingDriver{stats}, cm_{cluster_manager}, + 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(config.getString("collector_cluster")); if (!cluster) { throw EnvoyException(fmt::format("{} collector cluster is not defined on cluster manager level", @@ -127,57 +135,26 @@ LightStepDriver::LightStepDriver(const Json::Object& config, } tls_->set([this](Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { - lightstep::Tracer tracer(lightstep::NewUserDefinedTransportLightStepTracer( - *options_, std::bind(&LightStepRecorder::NewInstance, std::ref(*this), std::ref(dispatcher), - std::placeholders::_1))); + lightstep::LightStepTracerOptions tls_options; + tls_options.access_token = options_->access_token; + tls_options.component_name = options_->component_name; + tls_options.use_thread = false; + tls_options.use_single_key_propagation = true; + tls_options.logger_sink = LightStepLogger{}; + tls_options.max_buffered_spans = std::function{ + [this] { return runtime_.snapshot().getInteger("tracing.lightstep.min_flush_spans", 5U); }}; + tls_options.metrics_observer.reset(new LightStepMetricsObserver{*this}); + tls_options.transporter.reset(new LightStepTransporter{*this}); + std::shared_ptr tracer = + lightstep::MakeLightStepTracer(std::move(tls_options)); return ThreadLocal::ThreadLocalObjectSharedPtr{ - new TlsLightStepTracer(std::move(tracer), *this)}; + new TlsLightStepTracer{tracer, *this, dispatcher}}; }); } -SpanPtr LightStepDriver::startSpan(const Config&, Http::HeaderMap& request_headers, - const std::string& operation_name, SystemTime start_time) { - lightstep::Tracer& tracer = *tls_->getTyped().tracer_; - LightStepSpanPtr active_span; - - if (request_headers.OtSpanContext()) { - // Extract downstream context from HTTP carrier. - // This code is safe even if decode returns empty string or data is malformed. - std::string parent_context = Base64::decode(request_headers.OtSpanContext()->value().c_str()); - lightstep::BinaryCarrier ctx; - ctx.ParseFromString(parent_context); - - lightstep::SpanContext parent_span_ctx = tracer.Extract( - lightstep::CarrierFormat::LightStepBinaryCarrier, lightstep::ProtoReader(ctx)); - lightstep::Span ls_span = - tracer.StartSpan(operation_name, {lightstep::ChildOf(parent_span_ctx), - lightstep::StartTimestamp(start_time)}); - active_span.reset(new LightStepSpan(ls_span, tracer)); - } else { - lightstep::Span ls_span = - tracer.StartSpan(operation_name, {lightstep::StartTimestamp(start_time)}); - active_span.reset(new LightStepSpan(ls_span, tracer)); - } - - return std::move(active_span); -} - -void LightStepRecorder::onFailure(Http::AsyncClient::FailureReason) { - Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), - lightstep::CollectorMethodName(), false); -} - -void LightStepRecorder::onSuccess(Http::MessagePtr&& msg) { - try { - Grpc::Common::validateResponse(*msg); - - Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), - lightstep::CollectorMethodName(), true); - } catch (const Grpc::Exception& ex) { - Grpc::Common::chargeStat(*driver_.cluster(), lightstep::CollectorServiceFullName(), - lightstep::CollectorMethodName(), false); - } +const opentracing::Tracer& LightStepDriver::tracer() const { + return tls_->getTyped().tracer(); } } // namespace Tracing diff --git a/source/common/tracing/lightstep_tracer_impl.h b/source/common/tracing/lightstep_tracer_impl.h index 903213fa6d0cc..62b91016bc3f6 100644 --- a/source/common/tracing/lightstep_tracer_impl.h +++ b/source/common/tracing/lightstep_tracer_impl.h @@ -12,9 +12,13 @@ #include "common/http/header_map_impl.h" #include "common/http/message_impl.h" #include "common/json/json_loader.h" +#include "common/protobuf/protobuf.h" +#include "common/tracing/opentracing_driver_impl.h" -#include "lightstep/carrier.h" #include "lightstep/tracer.h" +#include "lightstep/transporter.h" +#include "opentracing/noop.h" +#include "opentracing/tracer.h" namespace Envoy { namespace Tracing { @@ -27,87 +31,79 @@ struct LightstepTracerStats { LIGHTSTEP_TRACER_STATS(GENERATE_COUNTER_STRUCT) }; -class LightStepSpan : public Span { -public: - LightStepSpan(lightstep::Span& span, lightstep::Tracer& tracer); - - // Tracing::Span - void finishSpan() override; - void setOperation(const std::string& operation) override; - void setTag(const std::string& name, const std::string& value) override; - void injectContext(Http::HeaderMap& request_headers) override; - SpanPtr spawnChild(const Config& config, const std::string& name, SystemTime start_time) override; - - lightstep::SpanContext context() { return span_.context(); } - -private: - lightstep::Span span_; - lightstep::Tracer& tracer_; -}; - -typedef std::unique_ptr LightStepSpanPtr; - /** * LightStep (http://lightstep.com/) provides tracing capabilities, aggregation, visualization of * application trace data. * * LightStepSink is for flushing data to LightStep collectors. */ -class LightStepDriver : public Driver { +class LightStepDriver : public OpenTracingDriver { public: LightStepDriver(const Json::Object& config, Upstream::ClusterManager& cluster_manager, Stats::Store& stats, ThreadLocal::SlotAllocator& tls, Runtime::Loader& runtime, - std::unique_ptr options); - - // Tracer::TracingDriver - SpanPtr startSpan(const Config& config, Http::HeaderMap& request_headers, - const std::string& operation_name, SystemTime start_time) override; + std::unique_ptr&& options, + PropagationMode propagation_mode); Upstream::ClusterManager& clusterManager() { return cm_; } Upstream::ClusterInfoConstSharedPtr cluster() { return cluster_; } Runtime::Loader& runtime() { return runtime_; } LightstepTracerStats& tracerStats() { return tracer_stats_; } + // Tracer::OpenTracingDriver + const opentracing::Tracer& tracer() const override; + PropagationMode propagationMode() const override { return propagation_mode_; } + private: - struct TlsLightStepTracer : ThreadLocal::ThreadLocalObject { - TlsLightStepTracer(lightstep::Tracer tracer, LightStepDriver& driver); + class LightStepTransporter : public lightstep::AsyncTransporter, Http::AsyncClient::Callbacks { + public: + explicit LightStepTransporter(LightStepDriver& driver); + + // lightstep::AsyncTransporter + void Send(const Protobuf::Message& request, Protobuf::Message& response, + lightstep::AsyncTransporter::Callback& callback) override; + + // Http::AsyncClient::Callbacks + void onSuccess(Http::MessagePtr&& response) override; + void onFailure(Http::AsyncClient::FailureReason) override; - std::unique_ptr tracer_; + private: + lightstep::AsyncTransporter::Callback* active_callback_ = nullptr; + Protobuf::Message* active_response_ = nullptr; LightStepDriver& driver_; }; - Upstream::ClusterManager& cm_; - Upstream::ClusterInfoConstSharedPtr cluster_; - LightstepTracerStats tracer_stats_; - ThreadLocal::SlotPtr tls_; - Runtime::Loader& runtime_; - std::unique_ptr options_; -}; + class LightStepMetricsObserver : public ::lightstep::MetricsObserver { + public: + explicit LightStepMetricsObserver(LightStepDriver& driver); -class LightStepRecorder : public lightstep::Recorder, Http::AsyncClient::Callbacks { -public: - LightStepRecorder(const lightstep::TracerImpl& tracer, LightStepDriver& driver, - Event::Dispatcher& dispatcher); + void OnSpansSent(int num_spans) override; - // lightstep::Recorder - void RecordSpan(lightstep::collector::Span&& span) override; - bool FlushWithTimeout(lightstep::Duration) override; + private: + LightStepDriver& driver_; + }; - // Http::AsyncClient::Callbacks - void onSuccess(Http::MessagePtr&&) override; - void onFailure(Http::AsyncClient::FailureReason) override; + class TlsLightStepTracer : public ThreadLocal::ThreadLocalObject { + public: + TlsLightStepTracer(const std::shared_ptr& tracer, + LightStepDriver& driver, Event::Dispatcher& dispatcher); - static std::unique_ptr NewInstance(LightStepDriver& driver, - Event::Dispatcher& dispatcher, - const lightstep::TracerImpl& tracer); + const opentracing::Tracer& tracer() const; -private: - void enableTimer(); - void flushSpans(); + private: + void enableTimer(); - lightstep::ReportBuilder builder_; - LightStepDriver& driver_; - Event::TimerPtr flush_timer_; + std::shared_ptr tracer_; + LightStepDriver& driver_; + Event::TimerPtr flush_timer_; + }; + + Upstream::ClusterManager& cm_; + Upstream::ClusterInfoConstSharedPtr cluster_; + LightstepTracerStats tracer_stats_; + ThreadLocal::SlotPtr tls_; + Runtime::Loader& runtime_; + std::unique_ptr options_; + const PropagationMode propagation_mode_; }; } // Tracing diff --git a/source/common/tracing/opentracing_driver_impl.cc b/source/common/tracing/opentracing_driver_impl.cc new file mode 100644 index 0000000000000..5c54257f0b43a --- /dev/null +++ b/source/common/tracing/opentracing_driver_impl.cc @@ -0,0 +1,174 @@ +#include "common/tracing/opentracing_driver_impl.h" + +#include + +#include "common/common/assert.h" +#include "common/common/base64.h" +#include "common/common/utility.h" + +namespace Envoy { +namespace Tracing { + +namespace { +class OpenTracingHTTPHeadersWriter : public opentracing::HTTPHeadersWriter { +public: + explicit OpenTracingHTTPHeadersWriter(Http::HeaderMap& request_headers) + : request_headers_(request_headers) {} + + // opentracing::HTTPHeadersWriter + opentracing::expected Set(opentracing::string_view key, + opentracing::string_view value) const override { + Http::LowerCaseString lowercase_key{key}; + request_headers_.remove(lowercase_key); + request_headers_.addCopy(std::move(lowercase_key), value); + return {}; + } + +private: + Http::HeaderMap& request_headers_; +}; + +class OpenTracingHTTPHeadersReader : public opentracing::HTTPHeadersReader { +public: + explicit OpenTracingHTTPHeadersReader(const Http::HeaderMap& request_headers) + : request_headers_(request_headers) {} + + typedef std::function(opentracing::string_view, + opentracing::string_view)> + OpenTracingCb; + + // opentracing::HTTPHeadersReader + opentracing::expected + LookupKey(opentracing::string_view key) const override { + const Http::HeaderEntry* entry; + Http::HeaderMap::Lookup lookup_result = + request_headers_.lookup(Http::LowerCaseString{key}, &entry); + switch (lookup_result) { + case Http::HeaderMap::Lookup::Found: + return opentracing::string_view{entry->value().c_str(), entry->value().size()}; + case Http::HeaderMap::Lookup::NotFound: + return opentracing::make_unexpected(opentracing::key_not_found_error); + case Http::HeaderMap::Lookup::NotSupported: + return opentracing::make_unexpected(opentracing::lookup_key_not_supported_error); + } + NOT_REACHED; + } + + opentracing::expected ForeachKey(OpenTracingCb f) const override { + request_headers_.iterate(headerMapCallback, static_cast(&f)); + return {}; + } + +private: + const Http::HeaderMap& request_headers_; + + static Http::HeaderMap::Iterate headerMapCallback(const Http::HeaderEntry& header, + void* context) { + OpenTracingCb* callback = static_cast(context); + opentracing::string_view key{header.key().c_str(), header.key().size()}; + opentracing::string_view value{header.value().c_str(), header.value().size()}; + if ((*callback)(key, value)) { + return Http::HeaderMap::Iterate::Continue; + } else { + return Http::HeaderMap::Iterate::Break; + } + } +}; +} // namespace + +OpenTracingSpan::OpenTracingSpan(OpenTracingDriver& driver, + std::unique_ptr&& span) + : driver_{driver}, span_(std::move(span)) {} + +void OpenTracingSpan::finishSpan() { span_->Finish(); } + +void OpenTracingSpan::setOperation(const std::string& operation) { + span_->SetOperationName(operation); +} + +void OpenTracingSpan::setTag(const std::string& name, const std::string& value) { + span_->SetTag(name, value); +} + +void OpenTracingSpan::injectContext(Http::HeaderMap& request_headers) { + if (driver_.propagationMode() == OpenTracingDriver::PropagationMode::SingleHeader) { + // Inject the span context using Envoy's single-header format. + std::ostringstream oss; + const opentracing::expected was_successful = + span_->tracer().Inject(span_->context(), oss); + if (!was_successful) { + ENVOY_LOG(debug, "Failed to inject span context: {}", was_successful.error().message()); + driver_.tracerStats().span_context_injection_error_.inc(); + return; + } + const std::string current_span_context = oss.str(); + request_headers.insertOtSpanContext().value( + Base64::encode(current_span_context.c_str(), current_span_context.length())); + } else { + // Inject the context using the tracer's standard HTTP header format. + const OpenTracingHTTPHeadersWriter writer{request_headers}; + const opentracing::expected was_successful = + span_->tracer().Inject(span_->context(), writer); + if (!was_successful) { + ENVOY_LOG(debug, "Failed to inject span context: {}", was_successful.error().message()); + driver_.tracerStats().span_context_injection_error_.inc(); + return; + } + } +} + +SpanPtr OpenTracingSpan::spawnChild(const Config&, const std::string& name, SystemTime start_time) { + std::unique_ptr ot_span = span_->tracer().StartSpan( + name, {opentracing::ChildOf(&span_->context()), opentracing::StartTimestamp(start_time)}); + RELEASE_ASSERT(ot_span != nullptr); + return SpanPtr{new OpenTracingSpan{driver_, std::move(ot_span)}}; +} + +OpenTracingDriver::OpenTracingDriver(Stats::Store& stats) + : tracer_stats_{OPENTRACING_TRACER_STATS(POOL_COUNTER_PREFIX(stats, "tracing.opentracing."))} {} + +SpanPtr OpenTracingDriver::startSpan(const Config&, Http::HeaderMap& request_headers, + const std::string& operation_name, SystemTime start_time) { + const PropagationMode propagation_mode = this->propagationMode(); + const opentracing::Tracer& tracer = this->tracer(); + std::unique_ptr active_span; + std::unique_ptr parent_span_ctx; + if (propagation_mode == PropagationMode::SingleHeader && request_headers.OtSpanContext()) { + opentracing::expected> parent_span_ctx_maybe; + std::string parent_context = Base64::decode(request_headers.OtSpanContext()->value().c_str()); + + if (!parent_context.empty()) { + InputConstMemoryStream istream{parent_context.data(), parent_context.size()}; + parent_span_ctx_maybe = tracer.Extract(istream); + } else { + parent_span_ctx_maybe = + opentracing::make_unexpected(opentracing::span_context_corrupted_error); + } + + if (parent_span_ctx_maybe) { + parent_span_ctx = std::move(*parent_span_ctx_maybe); + } else { + ENVOY_LOG(debug, "Failed to extract span context: {}", + parent_span_ctx_maybe.error().message()); + tracerStats().span_context_extraction_error_.inc(); + } + } else if (propagation_mode == PropagationMode::TracerNative) { + const OpenTracingHTTPHeadersReader reader{request_headers}; + opentracing::expected> parent_span_ctx_maybe = + tracer.Extract(reader); + if (parent_span_ctx_maybe) { + parent_span_ctx = std::move(*parent_span_ctx_maybe); + } else { + ENVOY_LOG(debug, "Failed to extract span context: {}", + parent_span_ctx_maybe.error().message()); + tracerStats().span_context_extraction_error_.inc(); + } + } + active_span = tracer.StartSpan(operation_name, {opentracing::ChildOf(parent_span_ctx.get()), + opentracing::StartTimestamp(start_time)}); + RELEASE_ASSERT(active_span != nullptr); + return SpanPtr{new OpenTracingSpan{*this, std::move(active_span)}}; +} + +} // namespace Tracing +} // namespace Envoy diff --git a/source/common/tracing/opentracing_driver_impl.h b/source/common/tracing/opentracing_driver_impl.h new file mode 100644 index 0000000000000..f843eb868b7cb --- /dev/null +++ b/source/common/tracing/opentracing_driver_impl.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include "envoy/tracing/http_tracer.h" + +#include "common/common/logger.h" + +#include "opentracing/tracer.h" + +namespace Envoy { +namespace Tracing { + +#define OPENTRACING_TRACER_STATS(COUNTER) \ + COUNTER(span_context_extraction_error) \ + COUNTER(span_context_injection_error) + +struct OpenTracingTracerStats { + OPENTRACING_TRACER_STATS(GENERATE_COUNTER_STRUCT) +}; + +class OpenTracingDriver; + +class OpenTracingSpan : public Span, Logger::Loggable { +public: + OpenTracingSpan(OpenTracingDriver& driver, std::unique_ptr&& span); + + // Tracing::Span + void finishSpan() override; + void setOperation(const std::string& operation) override; + void setTag(const std::string& name, const std::string& value) override; + void injectContext(Http::HeaderMap& request_headers) override; + SpanPtr spawnChild(const Config& config, const std::string& name, SystemTime start_time) override; + +private: + OpenTracingDriver& driver_; + std::unique_ptr span_; +}; + +/** + * This driver can be used by tracing libraries implementing the OpenTracing API (see + * https://github.com/opentracing/opentracing-cpp) to hook into Envoy's tracing functionality with a + * minimal amount of effort. Libraries need only provide an opentracing::Tracer implementation; the + * rest of span creation is taken care of by this class. + */ +class OpenTracingDriver : public Driver, protected Logger::Loggable { +public: + explicit OpenTracingDriver(Stats::Store& stats); + + // Tracer::TracingDriver + SpanPtr startSpan(const Config& config, Http::HeaderMap& request_headers, + const std::string& operation_name, SystemTime start_time) override; + + virtual const opentracing::Tracer& tracer() const PURE; + + enum class PropagationMode { SingleHeader, TracerNative }; + + /** + * Controls how span context is propagated in HTTP hedaers. PropagationMode::SingleHeader will + * propagate span context as a single header within the inline header HeaderMap::OtSpanContext; + * otherwise, span context will be propagated using the native format of the tracing library. + */ + virtual PropagationMode propagationMode() const { return PropagationMode::SingleHeader; } + + OpenTracingTracerStats& tracerStats() { return tracer_stats_; } + +private: + OpenTracingTracerStats tracer_stats_; +}; + +} // namespace Tracing +} // namespace Envoy diff --git a/source/server/config/http/lightstep_http_tracer.cc b/source/server/config/http/lightstep_http_tracer.cc index fd1314e01be28..f9d167e1fec45 100644 --- a/source/server/config/http/lightstep_http_tracer.cc +++ b/source/server/config/http/lightstep_http_tracer.cc @@ -9,7 +9,6 @@ #include "common/tracing/http_tracer_impl.h" #include "common/tracing/lightstep_tracer_impl.h" -#include "lightstep/options.h" #include "lightstep/tracer.h" namespace Envoy { @@ -21,20 +20,16 @@ LightstepHttpTracerFactory::createHttpTracer(const Json::Object& json_config, Server::Instance& server, Upstream::ClusterManager& cluster_manager) { - Envoy::Runtime::RandomGenerator& rand = server.random(); - - std::unique_ptr opts(new lightstep::TracerOptions()); + std::unique_ptr opts(new lightstep::LightStepTracerOptions()); opts->access_token = server.api().fileReadToEnd(json_config.getString("access_token_file")); StringUtil::rtrim(opts->access_token); + opts->component_name = server.localInfo().clusterName(); - opts->tracer_attributes["lightstep.component_name"] = server.localInfo().clusterName(); - opts->guid_generator = [&rand]() { return rand.random(); }; - - Tracing::DriverPtr lightstep_driver( - new Tracing::LightStepDriver(json_config, cluster_manager, server.stats(), - server.threadLocal(), server.runtime(), std::move(opts))); - return Tracing::HttpTracerPtr( - new Tracing::HttpTracerImpl(std::move(lightstep_driver), server.localInfo())); + Tracing::DriverPtr lightstep_driver{new Tracing::LightStepDriver{ + json_config, cluster_manager, server.stats(), server.threadLocal(), server.runtime(), + std::move(opts), Tracing::OpenTracingDriver::PropagationMode::TracerNative}}; + return Tracing::HttpTracerPtr{ + new Tracing::HttpTracerImpl{std::move(lightstep_driver), server.localInfo()}}; } std::string LightstepHttpTracerFactory::name() { return Config::HttpTracerNames::get().LIGHTSTEP; } diff --git a/test/common/common/utility_test.cc b/test/common/common/utility_test.cc index 07c41a3f5d0fb..bec35a8f3c814 100644 --- a/test/common/common/utility_test.cc +++ b/test/common/common/utility_test.cc @@ -38,6 +38,25 @@ TEST(ProdSystemTimeSourceTest, All) { source.currentTime(); } +TEST(InputConstMemoryStream, All) { + { + InputConstMemoryStream istream{nullptr, 0}; + std::string s; + istream >> s; + EXPECT_TRUE(s.empty()); + EXPECT_TRUE(istream.eof()); + } + + { + std::string data{"123"}; + InputConstMemoryStream istream{data.data(), data.size()}; + int x; + istream >> x; + EXPECT_EQ(123, x); + EXPECT_TRUE(istream.eof()); + } +} + TEST(StringUtil, caseInsensitiveCompare) { EXPECT_EQ(0, StringUtil::caseInsensitiveCompare("CONTENT-LENGTH", "content-length")); EXPECT_LT(0, StringUtil::caseInsensitiveCompare("CONTENT-LENGTH", "blah")); diff --git a/test/common/http/header_map_impl_test.cc b/test/common/http/header_map_impl_test.cc index 1dff5cc738613..2e06089836b35 100644 --- a/test/common/http/header_map_impl_test.cc +++ b/test/common/http/header_map_impl_test.cc @@ -446,5 +446,31 @@ TEST(HeaderMapImplTest, IterateReverse) { &cb); } +TEST(HeaderMapImplTest, Lookup) { + TestHeaderMapImpl headers; + headers.addCopy("hello", "world"); + headers.insertContentLength().value(5); + + // Lookup is not supported for non predefined inline headers. + { + const HeaderEntry* entry; + EXPECT_EQ(HeaderMap::Lookup::NotSupported, headers.lookup(LowerCaseString{"hello"}, &entry)); + EXPECT_EQ(nullptr, entry); + } + + // Lookup returns the entry of a predefined inline header if it exists. + { + const HeaderEntry* entry; + EXPECT_EQ(HeaderMap::Lookup::Found, headers.lookup(Headers::get().ContentLength, &entry)); + EXPECT_STREQ("5", entry->value().c_str()); + } + + // Lookup returns HeaderMap::Lookup::NotFound if a predefined inline header does not exist. + { + const HeaderEntry* entry; + EXPECT_EQ(HeaderMap::Lookup::NotFound, headers.lookup(Headers::get().Host, &entry)); + EXPECT_EQ(nullptr, entry); + } +} } // namespace Http } // namespace Envoy diff --git a/test/common/tracing/lightstep_tracer_impl_test.cc b/test/common/tracing/lightstep_tracer_impl_test.cc index a3b6d18d24fee..4e02490900efe 100644 --- a/test/common/tracing/lightstep_tracer_impl_test.cc +++ b/test/common/tracing/lightstep_tracer_impl_test.cc @@ -1,8 +1,10 @@ #include #include +#include #include #include "common/common/base64.h" +#include "common/grpc/common.h" #include "common/http/header_map_impl.h" #include "common/http/headers.h" #include "common/http/message_impl.h" @@ -24,6 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::AtLeast; using testing::Invoke; using testing::NiceMock; using testing::Return; @@ -36,10 +39,13 @@ namespace Tracing { class LightStepDriverTest : public Test { public: - void setup(Json::Object& config, bool init_timer) { - std::unique_ptr opts(new lightstep::TracerOptions()); + void setup(Json::Object& config, bool init_timer, + OpenTracingDriver::PropagationMode propagation_mode = + OpenTracingDriver::PropagationMode::TracerNative) { + std::unique_ptr opts( + new lightstep::LightStepTracerOptions()); opts->access_token = "sample_token"; - opts->tracer_attributes["lightstep.component_name"] = "component"; + opts->component_name = "component"; ON_CALL(cm_, httpAsyncClientForCluster("fake_cluster")) .WillByDefault(ReturnRef(cm_.async_client_)); @@ -49,10 +55,12 @@ class LightStepDriverTest : public Test { EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000))); } - driver_.reset(new LightStepDriver(config, cm_, stats_, tls_, runtime_, std::move(opts))); + driver_.reset(new LightStepDriver{config, cm_, stats_, tls_, runtime_, std::move(opts), + propagation_mode}); } - void setupValidDriver() { + void setupValidDriver(OpenTracingDriver::PropagationMode propagation_mode = + OpenTracingDriver::PropagationMode::TracerNative) { 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)); @@ -62,7 +70,7 @@ class LightStepDriverTest : public Test { )EOF"; Json::ObjectSharedPtr loader = Json::Factory::loadFromString(valid_config); - setup(*loader, true); + setup(*loader, true, propagation_mode); } const std::string operation_name_{"test"}; @@ -162,7 +170,7 @@ TEST_F(LightStepDriverTest, FlushSeveralSpans) { })); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.min_flush_spans", 5)) - .Times(2) + .Times(AtLeast(1)) .WillRepeatedly(Return(2)); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.request_timeout", 5000U)) .WillOnce(Return(5000U)); @@ -181,6 +189,10 @@ TEST_F(LightStepDriverTest, FlushSeveralSpans) { Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); msg->trailers(Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{"grpc-status", "0"}}}); + std::unique_ptr collector_response = + lightstep::Transporter::MakeCollectorResponse(); + EXPECT_NE(collector_response, nullptr); + msg->body() = Grpc::Common::serializeBody(*collector_response); callback->onSuccess(std::move(msg)); @@ -188,15 +200,56 @@ TEST_F(LightStepDriverTest, FlushSeveralSpans) { .counter("grpc.lightstep.collector.CollectorService.Report.success") .value()); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("grpc.lightstep.collector.CollectorService.Report.total") + .value()); + EXPECT_EQ(2U, stats_.counter("tracing.lightstep.spans_sent").value()); +} + +TEST_F(LightStepDriverTest, FlushOneFailure) { + setupValidDriver(); + + Http::MockAsyncClientRequest request(&cm_.async_client_); + Http::AsyncClient::Callbacks* callback; + const Optional timeout(std::chrono::seconds(5)); + + EXPECT_CALL(cm_.async_client_, send_(_, _, timeout)) + .WillOnce( + Invoke([&](Http::MessagePtr& message, Http::AsyncClient::Callbacks& callbacks, + const Optional&) -> Http::AsyncClient::Request* { + callback = &callbacks; + + EXPECT_STREQ("/lightstep.collector.CollectorService/Report", + message->headers().Path()->value().c_str()); + EXPECT_STREQ("fake_cluster", message->headers().Host()->value().c_str()); + EXPECT_STREQ("application/grpc", message->headers().ContentType()->value().c_str()); + + return &request; + })); + + EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.min_flush_spans", 5)) + .WillOnce(Return(1)); + EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.request_timeout", 5000U)) + .WillOnce(Return(5000U)); + + SpanPtr first_span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + + first_span->finishSpan(); + + Http::MessagePtr msg(new Http::ResponseMessageImpl( + Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); + + msg->trailers(Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{"grpc-status", "0"}}}); + callback->onFailure(Http::AsyncClient::FailureReason::Reset); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("grpc.lightstep.collector.CollectorService.Report.failure") .value()); - EXPECT_EQ(2U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("grpc.lightstep.collector.CollectorService.Report.total") .value()); - EXPECT_EQ(2U, stats_.counter("tracing.lightstep.spans_sent").value()); + EXPECT_EQ(1U, stats_.counter("tracing.lightstep.spans_sent").value()); } TEST_F(LightStepDriverTest, FlushSpansTimer) { @@ -268,38 +321,45 @@ TEST_F(LightStepDriverTest, FlushOneSpanGrpcFailure) { } TEST_F(LightStepDriverTest, SerializeAndDeserializeContext) { - setupValidDriver(); - - // Supply bogus context, that will be simply ignored. - const std::string invalid_context = "notvalidcontext"; - request_headers_.insertOtSpanContext().value(invalid_context); - driver_->startSpan(config_, request_headers_, operation_name_, start_time_); - - std::string injected_ctx = request_headers_.OtSpanContext()->value().c_str(); - EXPECT_FALSE(injected_ctx.empty()); - - // Supply empty context. - request_headers_.removeOtSpanContext(); - SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); - - EXPECT_EQ(nullptr, request_headers_.OtSpanContext()); - span->injectContext(request_headers_); - - injected_ctx = request_headers_.OtSpanContext()->value().c_str(); - EXPECT_FALSE(injected_ctx.empty()); - - // Context can be parsed fine. - lightstep::BinaryCarrier ctx; - std::string context = Base64::decode(injected_ctx); - ctx.ParseFromString(context); - - // Supply parent context, request_headers has properly populated x-ot-span-context. - SpanPtr span_with_parent = - driver_->startSpan(config_, request_headers_, operation_name_, start_time_); - request_headers_.removeOtSpanContext(); - span_with_parent->injectContext(request_headers_); - injected_ctx = request_headers_.OtSpanContext()->value().c_str(); - EXPECT_FALSE(injected_ctx.empty()); + for (OpenTracingDriver::PropagationMode propagation_mode : + {OpenTracingDriver::PropagationMode::SingleHeader, + OpenTracingDriver::PropagationMode::TracerNative}) { + setupValidDriver(propagation_mode); + + // Supply bogus context, that will be simply ignored. + const std::string invalid_context = "notvalidcontext"; + request_headers_.insertOtSpanContext().value(invalid_context); + stats_.counter("tracing.opentracing.span_context_extraction_error").reset(); + driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + EXPECT_EQ(1U, stats_.counter("tracing.opentracing.span_context_extraction_error").value()); + + std::string injected_ctx = request_headers_.OtSpanContext()->value().c_str(); + EXPECT_FALSE(injected_ctx.empty()); + + // Supply empty context. + request_headers_.removeOtSpanContext(); + SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + + EXPECT_EQ(nullptr, request_headers_.OtSpanContext()); + span->injectContext(request_headers_); + + injected_ctx = request_headers_.OtSpanContext()->value().c_str(); + EXPECT_FALSE(injected_ctx.empty()); + + // Context can be parsed fine. + const opentracing::Tracer& tracer = driver_->tracer(); + std::string context = Base64::decode(injected_ctx); + std::istringstream iss{context, std::ios::binary}; + EXPECT_TRUE(tracer.Extract(iss)); + + // Supply parent context, request_headers has properly populated x-ot-span-context. + SpanPtr span_with_parent = + driver_->startSpan(config_, request_headers_, operation_name_, start_time_); + request_headers_.removeOtSpanContext(); + span_with_parent->injectContext(request_headers_); + injected_ctx = request_headers_.OtSpanContext()->value().c_str(); + EXPECT_FALSE(injected_ctx.empty()); + } } TEST_F(LightStepDriverTest, SpawnChild) {