Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lightstep tracer cpp/ls tracer #65

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions ci/build_container/build_container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,3 @@ rm -fr googletest
wget -O gcovr-3.3.tar.gz https://github.com/gcovr/gcovr/archive/3.3.tar.gz
tar xf gcovr-3.3.tar.gz
rm gcovr-3.3.tar.gz


245 changes: 95 additions & 150 deletions source/common/tracing/http_tracer_impl.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <functional>

#include "http_tracer_impl.h"

#include "common/common/macros.h"
Expand Down Expand Up @@ -112,182 +114,125 @@ void HttpTracerImpl::populateStats(const Decision& decision) {
}
}

Http::MessagePtr LightStepUtility::buildHeaders(const std::string& access_token) {
Http::MessagePtr msg{new Http::RequestMessageImpl()};

msg->headers().addViaCopy(Http::Headers::get().Scheme, "http");
msg->headers().addViaCopy(Http::Headers::get().Method, "POST");
msg->headers().addViaCopy(Http::Headers::get().Path, "/api/v0/reports");
msg->headers().addViaCopy(Http::Headers::get().ContentType, "application/json");
msg->headers().addViaCopy(Http::Headers::get().Host, "collector.lightstep.com");
msg->headers().addViaCopy("LightStep-Access-Token", access_token);
namespace {

return msg;
}
class LightStepRecorder : public lightstep::Recorder {
public:
LightStepRecorder(LightStepSink *sink, const lightstep::TracerImpl& tracer)
: sink_(sink), builder_(tracer) { }

std::string LightStepUtility::buildJoiningIds(const Http::HeaderMap& request_headers) {
std::string join_ids;
// lightstep::Recorder
void RecordSpan(lightstep::collector::Span&& span) override {
// REVIEWER: This is unlocked, should be one tracer/recorder per thread.
builder_.addSpan(std::move(span));

// Always populate x-request-id as joining id.
static const std::string x_request_id_format = R"EOF(
{{
"TraceKey": "x-request-id",
"Value": "{}"
}})EOF";
join_ids += fmt::format(x_request_id_format, request_headers.get(Http::Headers::get().RequestId));
// When the buffer has accumulated N spans, send to LightStep.
const int N = 10;
if (builder_.pendingSpans() == N) {
lightstep::collector::ReportRequest request;
std::swap(request, builder_.pending());

// Optionally populate x-client-trace-id if present.
if (request_headers.has(Http::Headers::get().ClientTraceId)) {
static const std::string x_client_trace_id_format = R"EOF(
,{{
"TraceKey": "x-client-trace-id",
"Value": "{}"
}})EOF";
join_ids += fmt::format(x_client_trace_id_format,
request_headers.get(Http::Headers::get().ClientTraceId));
// REVIEWER: Here, call gRPC to collector-grpc.lightstep.com:443
// with std::move(request).
}
}

return join_ids;
}

std::string LightStepUtility::buildRequestLine(const Http::HeaderMap& request_headers,
const Http::AccessLog::RequestInfo& info) {
std::string method = request_headers.get(Http::Headers::get().Method);
std::string path = request_headers.has(Http::Headers::get().EnvoyOriginalPath)
? request_headers.get(Http::Headers::get().EnvoyOriginalPath)
: request_headers.get(Http::Headers::get().Path);
static const size_t max_path_length = 256;

if (path.length() > max_path_length) {
path = path.substr(0, max_path_length);
bool FlushWithTimeout(lightstep::Duration) override {
// Note: We don't expect this to be called, since the Tracer
// reference is private to its LightStepSink.
return true;
}

return fmt::format("{} {} {}", method, path, info.protocol());
}

std::string LightStepUtility::buildSpanAttributes(const Http::HeaderMap& request_headers,
const Http::AccessLog::RequestInfo& request_info,
const std::string& service_node) {
const std::string request_line = buildRequestLine(request_headers, request_info);
std::string downstream_cluster =
request_headers.get(Http::Headers::get().EnvoyDownstreamServiceCluster);
if (downstream_cluster.empty()) {
downstream_cluster = "-";
static std::unique_ptr<lightstep::Recorder> New(LightStepSink *sink, const lightstep::TracerImpl& tracer) {
return std::unique_ptr<lightstep::Recorder>(new LightStepRecorder(sink, tracer));
}

const std::string response_code = request_info.responseCode().valid()
? std::to_string(request_info.responseCode().value())
: "0";
std::string user_agent = request_headers.get(Http::Headers::get().UserAgent);
if (user_agent.empty()) {
user_agent = "-";
}
private:
LightStepSink *sink_;
lightstep::ReportBuilder builder_;
};

static const std::string attributes_format = R"EOF(
{{
"Key": "request line",
"Value": "{}"
}},
{{
"Key": "response code",
"Value": "{}"
}},
{{
"Key": "downstream cluster",
"Value": "{}"
}},
{{
"Key": "user agent",
"Value": "{}"
}},
{{
"Key": "node id",
"Value": "{}"
}})EOF";

return fmt::format(attributes_format, request_line, response_code, downstream_cluster, user_agent,
service_node);
const std::string& orDash(const std::string& s) {
if (s.empty()) {
static const std::string dash = "-";
return dash;
}
return s;
}

std::string LightStepUtility::buildJsonBody(const Http::HeaderMap& request_headers,
const Http::HeaderMap&,
const Http::AccessLog::RequestInfo& request_info,
Runtime::RandomGenerator& random,
const std::string& local_service_cluster,
const std::string& service_node) {
static const std::string json_format = R"EOF(
{{
"runtime": {{
"guid": "{}",
"group_name": "{}",
"start_micros": {}
}},
"span_records": [
{{
"span_guid": "{}",
"span_name": "{}",
"oldest_micros": {},
"youngest_micros": {},
"join_ids": [{}],
"attributes": [{}]
}}
]
}}
)EOF";

const std::string tracing_guid = random.uuid();
static const std::string group_name = "Envoy-Tracing";
uint64_t start_time = std::chrono::duration_cast<std::chrono::microseconds>(
request_info.startTime().time_since_epoch()).count();
const std::string start_micros = std::to_string(start_time);
const std::string span_guid = random.uuid();
const std::string& span_name = local_service_cluster;
const std::string oldest_micros = start_micros;
uint64_t end_time =
start_time +
std::chrono::duration_cast<std::chrono::microseconds>(request_info.duration()).count();
const std::string youngest_micros = std::to_string(end_time);
const std::string joining_ids = buildJoiningIds(request_headers);
const std::string annotations = buildSpanAttributes(request_headers, request_info, service_node);

return fmt::format(json_format, tracing_guid, group_name, start_micros, span_guid, span_name,
oldest_micros, youngest_micros, joining_ids, annotations);
}
} // namespace

// REVIEWER: Either this object or its tracer_ field should be
// thread-local, for this to work.
LightStepSink::LightStepSink(const Json::Object& config, Upstream::ClusterManager& cluster_manager,
const std::string& stat_prefix, Stats::Store& stats,
Runtime::RandomGenerator& random,
const std::string& local_service_cluster,
const std::string& service_node, const std::string& access_token)
const std::string& service_node, const lightstep::TracerOptions& options)
: collector_cluster_(config.getString("collector_cluster")), cm_(cluster_manager),
stats_{LIGHTSTEP_STATS(POOL_COUNTER_PREFIX(stats, stat_prefix + "tracing.lightstep."))},
random_(random), local_service_cluster_(local_service_cluster), service_node_(service_node),
access_token_(access_token) {
service_node_(service_node),
tracer_(lightstep::NewUserDefinedTransportLightStepTracer(
options,
std::bind(&LightStepRecorder::New, this, std::placeholders::_1))) {
if (!cm_.get(collector_cluster_)) {
throw EnvoyException(fmt::format("{} collector cluster is not defined on cluster manager level",
collector_cluster_));
collector_cluster_));
}
}

void LightStepSink::flushTrace(const Http::HeaderMap& request_headers,
const Http::HeaderMap& response_headers,
const Http::AccessLog::RequestInfo& request_info) {
Http::MessagePtr msg = LightStepUtility::buildHeaders(access_token_);
Buffer::InstancePtr buffer(new Buffer::OwnedImpl(
LightStepUtility::buildJsonBody(request_headers, response_headers, request_info, random_,
local_service_cluster_, service_node_)));

msg->body(std::move(buffer));
executeRequest(std::move(msg));
std::string LightStepSink::buildRequestLine(const Http::HeaderMap& request_headers,
const Http::AccessLog::RequestInfo& info) {
std::string method = request_headers.get(Http::Headers::get().Method);
std::string path = request_headers.has(Http::Headers::get().EnvoyOriginalPath)
? request_headers.get(Http::Headers::get().EnvoyOriginalPath)
: request_headers.get(Http::Headers::get().Path);
static const size_t max_path_length = 256;

if (path.length() > max_path_length) {
path = path.substr(0, max_path_length);
}

return fmt::format("{} {} {}", method, path, info.protocol());
}

void LightStepSink::executeRequest(Http::MessagePtr&& msg) {
cm_.httpAsyncClientForCluster(collector_cluster_)
.send(std::move(msg), *this, std::chrono::milliseconds(5000));
std::string LightStepSink::buildResponseCode(const Http::AccessLog::RequestInfo& info) {
return info.responseCode().valid()
? std::to_string(info.responseCode().value())
: "0";
}

void LightStepSink::onFailure(Http::AsyncClient::FailureReason) { stats_.collector_failed_.inc(); }
void LightStepSink::flushTrace(const Http::HeaderMap& request_headers,
const Http::HeaderMap& /*response_headers*/,
const Http::AccessLog::RequestInfo& request_info) {
// REVIEWER: Note that the span_id and trace_id are supplied
// automatically using the provided uuid generator.
lightstep::Span span =
tracer_.StartSpan("TODO:operation_name_goes_here",
{ lightstep::StartTimestamp(request_info.startTime()),
lightstep::SetTag("join:x-request-id",
request_headers.get(Http::Headers::get().RequestId)),
lightstep::SetTag("request line",
buildRequestLine(request_headers, request_info)),
lightstep::SetTag("response code",
buildResponseCode(request_info)),
lightstep::SetTag("downstream cluster",
orDash(request_headers.get(Http::Headers::get().
EnvoyDownstreamServiceCluster))),
lightstep::SetTag("user agent",
orDash(request_headers.get(Http::Headers::get().
UserAgent))),
lightstep::SetTag("node id", service_node_),
});

if (request_headers.has(Http::Headers::get().ClientTraceId)) {
span.SetTag("join:x-client-trace-id", request_headers.get(Http::Headers::get().ClientTraceId));
}

void LightStepSink::onSuccess(Http::MessagePtr&&) { stats_.collector_success_.inc(); }
// REVIEWER: The implementation of request_info.duration() uses the
// current system_time to compute a duration. Calling span.Finish()
// computes the same result by default (with less arithmetic),
// otherwise could pass the lightstep::FinishTimestamp() option to
// be explicit, here:
span.Finish();
}

} // Tracing
} // Tracing
63 changes: 10 additions & 53 deletions source/common/tracing/http_tracer_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "common/http/header_map_impl.h"
#include "common/json/json_loader.h"

#include "lightstep/tracer.h"

namespace Tracing {

#define LIGHTSTEP_STATS(COUNTER) \
Expand Down Expand Up @@ -88,78 +90,33 @@ class HttpTracerImpl : public HttpTracer {
std::vector<HttpSinkPtr> sinks_;
};

class LightStepUtility {
public:
/**
* Sample json body format.
*
* {"runtime": { "guid": "039e9da7-2a07-4a54-9440-eaee8f3887ea", "group_name": "Envoy-Test",
* "start_micros": 1466704630010000 }, "span_records": [ { "span_guid":
* "745bfab3-4ba4-4a36-9133-ecf76108feb5", "span_name": "front-envoy",
* "oldest_micros":1466704630010000, "youngest_micros": 1466704630500000, "join_ids": [ {
* "TraceKey": "x-request-id", "Value": "5463a0cc-e469-454c-a8ac-950dd1a87c66" }, {
* "TraceKey": "x-client-request-id", "Value": "fcd405fd-657e-40e7-adf3-27c306df60a3" }]}]}
**/
static std::string
buildJsonBody(const Http::HeaderMap& request_headers, const Http::HeaderMap& response_headers,
const Http::AccessLog::RequestInfo& request_info, Runtime::RandomGenerator& random,
const std::string& local_service_cluster, const std::string& service_node);

/**
* Create LightStep specific headers.
*
* Note: We temporary keep access token to LightStep here hardcoded.
* This needs to be retrieved from Confidant, but we only can do so when we move LightStep
* collectors to our internal service.
*
* @param access token for light step access.
*/
static Http::MessagePtr buildHeaders(const std::string& access_token);

private:
/**
* Build request line: Method Request-URI Protocol.
* Note: Request-URI will be truncated if it's longer than 256 chars.
*/
static std::string buildRequestLine(const Http::HeaderMap& request_headers,
const Http::AccessLog::RequestInfo& request_info);
static std::string buildJoiningIds(const Http::HeaderMap& request_headers);
static std::string buildSpanAttributes(const Http::HeaderMap& request_headers,
const Http::AccessLog::RequestInfo& request_info,
const std::string& service_node);
};

/**
* LightStep (http://lightstep.com/) provides tracing capabilities, aggregation, visualization of
* application trace data.
*
* LightStepSink is for flushing data to LightStep collectors.
*/
class LightStepSink : public HttpSink, public Http::AsyncClient::Callbacks {
class LightStepSink : public HttpSink {
public:
LightStepSink(const Json::Object& config, Upstream::ClusterManager& cluster_manager,
const std::string& stat_prefix, Stats::Store& stats,
Runtime::RandomGenerator& random, const std::string& local_service_cluster,
const std::string& service_node, const std::string& access_token);
const std::string& service_node, const lightstep::TracerOptions& options);

// Tracer::HttpSink
void flushTrace(const Http::HeaderMap& request_headers, const Http::HeaderMap& response_headers,
const Http::AccessLog::RequestInfo& request_info) override;

// Http::AsyncClient::Callbacks
void onSuccess(Http::MessagePtr&&) override;
void onFailure(Http::AsyncClient::FailureReason reason) override;

private:
void executeRequest(Http::MessagePtr&& msg);

std::string buildRequestLine(const Http::HeaderMap& request_headers,
const Http::AccessLog::RequestInfo& info);
std::string buildResponseCode(const Http::AccessLog::RequestInfo& info);

const std::string collector_cluster_;
Upstream::ClusterManager& cm_;
LightStepStats stats_;
Runtime::RandomGenerator& random_;
const std::string local_service_cluster_;
const std::string service_node_;
const std::string access_token_;
lightstep::Tracer tracer_;
};

} // Tracing
} // Tracing
Loading