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

tracing: Add support for sending data in Zipkin v2 format #6985

Merged
merged 55 commits into from
Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d445cc8
Prepare sending proto3 to zipkin
dio May 16, 2019
ac13b27
Generate ids
dio May 16, 2019
7fdbeb9
Add newline
dio May 16, 2019
07dfb1d
Add test for endpoint proto
dio May 16, 2019
07c897b
Add test for zipkin_core_types
dio May 17, 2019
9aafd95
Add span buffer test
dio May 17, 2019
8685f63
Fix
dio May 17, 2019
4c58100
Fix
dio May 17, 2019
c6f86c8
Rename bytesOf to toByteString
dio May 17, 2019
c775be0
Add changelog
dio May 18, 2019
d60b37f
Review comments
dio May 27, 2019
d03f5f4
Fix
dio May 27, 2019
a75668b
Fix format
dio May 27, 2019
544522c
Merge remote-tracking branch 'upstream/master'
dio May 27, 2019
7c925bc
Merge remote-tracking branch 'upstream/master'
dio Aug 15, 2019
53d0ded
Fix tests
dio Aug 15, 2019
12015fe
Fix format
dio Aug 15, 2019
4c7542f
Fix format
dio Aug 16, 2019
f7601d0
apache -> openzipkin
dio Aug 16, 2019
2a2746a
Fix sha256
dio Aug 16, 2019
e4cf785
Fix format
dio Aug 17, 2019
66ea6dd
Change approach to use serializer
dio Aug 20, 2019
240c9a1
Comments and add tests
dio Aug 21, 2019
8c0480d
Fix docs
dio Aug 21, 2019
ee21280
Merge remote-tracking branch 'upstream/master'
dio Aug 21, 2019
acfa29d
Fix
dio Aug 21, 2019
50aec33
absl::StrAppend and absl::StrCat
dio Aug 21, 2019
bd462dc
Remove wrong entry
dio Aug 21, 2019
273c92c
Fix clang-tidy
dio Aug 21, 2019
756261b
Add missing comments
dio Aug 21, 2019
49f6bf5
Remove unused headers
dio Aug 21, 2019
3d3fcae
Fix clang-tidy again
dio Aug 21, 2019
336fe07
Merge remote-tracking branch 'upstream/master'
dio Aug 21, 2019
57625de
Fix alpha ordering later
dio Aug 21, 2019
f303859
Comments
dio Aug 22, 2019
93cbf23
Comments
dio Aug 22, 2019
19f3bb4
Use vector
dio Aug 23, 2019
ba7bf9c
Use 0.2.2 release
dio Aug 23, 2019
199d28e
Fix
dio Aug 23, 2019
10f220d
Cleanup
dio Aug 23, 2019
7971c73
Fix
dio Aug 23, 2019
f21f54e
Merge remote-tracking branch 'upstream/master'
dio Aug 23, 2019
6a39da4
Comments
dio Aug 23, 2019
d91e756
Fix
dio Aug 23, 2019
b00b470
Comments
dio Aug 23, 2019
7c3a278
Merge remote-tracking branch 'upstream/master'
dio Aug 26, 2019
a1c8cc7
Add comment on immediate deprecation
dio Aug 27, 2019
f413994
Fix
dio Aug 27, 2019
17e24d8
Comments
dio Aug 28, 2019
e602b50
Fix
dio Aug 28, 2019
3f4c927
More const-ness
dio Aug 28, 2019
527f62c
make_unique and make_shared
dio Aug 28, 2019
c409f91
Comments
dio Aug 29, 2019
3976724
Remove TODOs
dio Aug 29, 2019
ec4ca84
Merge remote-tracking branch 'upstream/master'
dio Aug 30, 2019
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
25 changes: 25 additions & 0 deletions api/bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ def api_dependencies():
locations = REPOSITORY_LOCATIONS,
build_file_content = KAFKASOURCE_BUILD_CONTENT,
)
envoy_http_archive(
name = "com_github_openzipkin_zipkinapi",
locations = REPOSITORY_LOCATIONS,
build_file_content = ZIPKINAPI_BUILD_CONTENT,
)

GOGOPROTO_BUILD_CONTENT = """
load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library", "py_proto_library")
Expand Down Expand Up @@ -153,3 +158,23 @@ filegroup(
)

"""

ZIPKINAPI_BUILD_CONTENT = """

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_go_proto_library")
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")

api_proto_library(
name = "zipkin",
srcs = [
"zipkin-jsonv2.proto",
"zipkin.proto",
],
visibility = ["//visibility:public"],
)

api_go_proto_library(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this will race with #8003. CC @kyessenov

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After #8003 is merged what should we use here?

name = "zipkin",
proto = ":zipkin",
)
"""
8 changes: 8 additions & 0 deletions api/bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ KAFKA_SOURCE_SHA = "ae7a1696c0a0302b43c5b21e515c37e6ecd365941f68a510a7e442eebddf
UDPA_GIT_SHA = "4cbdcb9931ca743a915a7c5fda51b2ee793ed157" # Aug 22, 2019
UDPA_SHA256 = "6291d0c0e3a4d5f08057ea7a00ed0b0ec3dd4e5a3b1cf20f803774680b5a806f"

ZIPKINAPI_RELEASE = "0.2.2" # Aug 23, 2019
ZIPKINAPI_SHA256 = "688c4fe170821dd589f36ec45aaadc03a618a40283bc1f97da8fa11686fc816b"

REPOSITORY_LOCATIONS = dict(
bazel_skylib = dict(
sha256 = BAZEL_SKYLIB_SHA256,
Expand Down Expand Up @@ -62,4 +65,9 @@ REPOSITORY_LOCATIONS = dict(
strip_prefix = "kafka-2.2.0-rc2/clients/src/main/resources/common/message",
urls = ["https://github.com/apache/kafka/archive/2.2.0-rc2.zip"],
),
com_github_openzipkin_zipkinapi = dict(
sha256 = ZIPKINAPI_SHA256,
strip_prefix = "zipkin-api-" + ZIPKINAPI_RELEASE,
urls = ["https://github.com/openzipkin/zipkin-api/archive/" + ZIPKINAPI_RELEASE + ".tar.gz"],
),
)
28 changes: 27 additions & 1 deletion api/envoy/config/trace/v2/trace.proto
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ message LightstepConfig {
string access_token_file = 2 [(validate.rules).string.min_bytes = 1];
}

// Configuration for the Zipkin tracer.
message ZipkinConfig {
// The cluster manager cluster that hosts the Zipkin collectors. Note that the
// Zipkin cluster must be defined in the :ref:`Bootstrap static cluster
Expand All @@ -80,9 +81,34 @@ message ZipkinConfig {
// trace instance. The default value is false, which will result in a 64 bit trace id being used.
bool trace_id_128bit = 3;

// Determines whether client and server spans will shared the same span id.
// Determines whether client and server spans will share the same span context.
// The default value is true.
google.protobuf.BoolValue shared_span_context = 4;

// Available Zipkin collector endpoint versions.
enum CollectorEndpointVersion {
// Zipkin API v1, JSON over HTTP.
// [#comment: The default implementation of Zipkin client before this field is added was only v1
// and the way user configure this was by not explicitly specifying the version. Consequently,
// before this is added, the corresponding Zipkin collector expected to receive v1 payload.
// Hence the motivation of adding HTTP_JSON_V1 as the default is to avoid a breaking change when
// user upgrading Envoy with this change. Furthermore, we also immediately deprecate this field,
// since in Zipkin realm this v1 version is considered to be not preferable anymore.]
HTTP_JSON_V1 = 0 [deprecated = true];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'm even more confused now :) Why are we adding any field that is immediately deprecated? Can the default be the long-term default that you desire?

Copy link
Member Author

@dio dio Aug 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@htuch I feel like we need to maintain the backward compatibility with the existing installation, i.e. not specifying it and default to v1 JSON. But, if you think this can be removed, I'll be happy to do it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So some context from the Zipkin side of things...

In Zipkin pretty much everyone has long switched over to V2 model. Especially since the information stored by Zipkin for the last couple of years has been V2 and thus doing a V1 -> V2 conversion at ingestion.

My preference would be to say that the 0 value defaults to V2 and we explicitly list the various options. We can then use the deprecated label on the explicit value used for HTTP_JSON_V1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@htuch as discussed offline, I've added a comment block.


// Zipkin API v2, JSON over HTTP.
HTTP_JSON = 1;

// Zipkin API v2, protobuf over HTTP.
HTTP_PROTO = 2;

// [#not-implemented-hide:]
GRPC = 3;
}

// Determines the selected collector endpoint version. By default, the ``HTTP_JSON_V1`` will be
// used.
CollectorEndpointVersion collector_endpoint_version = 5;
}

// DynamicOtConfig is used to dynamically load a tracer from a shared library
Expand Down
3 changes: 3 additions & 0 deletions docs/root/intro/deprecated.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Version 1.12.0 (pending)
and `present_match` fields.
* The :option:`--allow-unknown-fields` command-line option,
use :option:`--allow-unknown-static-fields` instead.
* The use of HTTP_JSON_V1 :ref:`Zipkin collector endpoint version
<envoy_api_field_config.trace.v2.ZipkinConfig.collector_endpoint_version>` or not explicitly
specifying it is deprecated, use HTTP_JSON or HTTP_PROTO instead.

Version 1.11.0 (July 11, 2019)
==============================
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Version history
* router: added :ref:`rq_retry_skipped_request_not_complete <config_http_filters_router_stats>` counter stat to router stats.
* router check tool: add coverage reporting & enforcement.
* router check tool: add comprehensive coverage reporting.
* tracing: added support to the Zipkin reporter for sending list of spans as Zipkin JSON v2 and protobuf message over HTTP.
* tls: added verification of IP address SAN fields in certificates against configured SANs in the
certificate validation context.
* upstream: added network filter chains to upstream connections, see :ref:`filters<envoy_api_field_Cluster.filters>`.
Expand Down
1 change: 1 addition & 0 deletions source/common/http/headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class HeaderValues {
const std::string GrpcWebText{"application/grpc-web-text"};
const std::string GrpcWebTextProto{"application/grpc-web-text+proto"};
const std::string Json{"application/json"};
const std::string Protobuf{"application/x-protobuf"};
const std::string FormUrlEncoded{"application/x-www-form-urlencoded"};
} ContentTypeValues;

Expand Down
1 change: 1 addition & 0 deletions source/extensions/tracers/zipkin/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ envoy_cc_library(
"//source/common/singleton:const_singleton",
"//source/common/tracing:http_tracer_lib",
"//source/extensions/tracers:well_known_names",
"@com_github_openzipkin_zipkinapi//:zipkin_cc",
],
)

Expand Down
216 changes: 203 additions & 13 deletions source/extensions/tracers/zipkin/span_buffer.cc
Original file line number Diff line number Diff line change
@@ -1,35 +1,225 @@
#include "extensions/tracers/zipkin/span_buffer.h"

#include "common/protobuf/protobuf.h"

#include "extensions/tracers/zipkin/util.h"
#include "extensions/tracers/zipkin/zipkin_core_constants.h"

#include "absl/strings/str_join.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace Zipkin {

SpanBuffer::SpanBuffer(
const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version,
const bool shared_span_context)
: serializer_{makeSerializer(version, shared_span_context)} {}

SpanBuffer::SpanBuffer(
const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version,
const bool shared_span_context, uint64_t size)
: serializer_{makeSerializer(version, shared_span_context)} {
allocateBuffer(size);
}

// TODO(fabolive): Need to avoid the copy to improve performance.
dio marked this conversation as resolved.
Show resolved Hide resolved
bool SpanBuffer::addSpan(const Span& span) {
if (span_buffer_.size() == span_buffer_.capacity()) {
// Buffer full
bool SpanBuffer::addSpan(Span&& span) {
const auto& annotations = span.annotations();
if (span_buffer_.size() == span_buffer_.capacity() || annotations.empty() ||
annotations.end() ==
std::find_if(annotations.begin(), annotations.end(), [](const auto& annotation) {
return annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND ||
annotation.value() == ZipkinCoreConstants::get().SERVER_RECV;
})) {

// Buffer full or invalid span.
return false;
}

span_buffer_.push_back(std::move(span));

return true;
}

std::string SpanBuffer::toStringifiedJsonArray() {
std::string stringified_json_array = "[";
SerializerPtr SpanBuffer::makeSerializer(
const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version,
const bool shared_span_context) {
switch (version) {
case envoy::config::trace::v2::ZipkinConfig::HTTP_JSON_V1:
return std::make_unique<JsonV1Serializer>();
case envoy::config::trace::v2::ZipkinConfig::HTTP_JSON:
return std::make_unique<JsonV2Serializer>(shared_span_context);
case envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO:
return std::make_unique<ProtobufSerializer>(shared_span_context);
default:
NOT_REACHED_GCOVR_EXCL_LINE;
}
}

std::string JsonV1Serializer::serialize(std::vector<Span>&& zipkin_spans) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I like this better :)

const std::string serialized_elements =
absl::StrJoin(zipkin_spans, ",", [](std::string* element, Span zipkin_span) {
absl::StrAppend(element, zipkin_span.toJson());
});
return absl::StrCat("[", serialized_elements, "]");
}

JsonV2Serializer::JsonV2Serializer(const bool shared_span_context)
Copy link
Contributor

@jcchavezs jcchavezs Aug 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

: shared_span_context_{shared_span_context} {}

std::string JsonV2Serializer::serialize(std::vector<Span>&& zipkin_spans) {
const std::string serialized_elements =
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absl::StrJoin(zipkin_spans, ",", [this](std::string* out, const Span& zipkin_span) {
absl::StrAppend(out,
absl::StrJoin(toListOfSpans(zipkin_span), ",",
[](std::string* element, const zipkin::jsonv2::Span& span) {
std::string entry;
Protobuf::util::MessageToJsonString(span, &entry);
absl::StrAppend(element, entry);
}));
});
return absl::StrCat("[", serialized_elements, "]");
}

if (pendingSpans()) {
stringified_json_array += span_buffer_[0].toJson();
const uint64_t size = span_buffer_.size();
for (uint64_t i = 1; i < size; i++) {
stringified_json_array += ",";
stringified_json_array += span_buffer_[i].toJson();
const std::vector<zipkin::jsonv2::Span>
JsonV2Serializer::toListOfSpans(const Span& zipkin_span) const {
std::vector<zipkin::jsonv2::Span> spans;
spans.reserve(zipkin_span.annotations().size());
for (const auto& annotation : zipkin_span.annotations()) {
zipkin::jsonv2::Span span;

if (annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND) {
span.set_kind(ZipkinCoreConstants::get().KIND_CLIENT);
} else if (annotation.value() == ZipkinCoreConstants::get().SERVER_RECV) {
span.set_shared(shared_span_context_ && zipkin_span.annotations().size() > 1);
span.set_kind(ZipkinCoreConstants::get().KIND_SERVER);
} else {
continue;
}

if (annotation.isSetEndpoint()) {
span.set_timestamp(annotation.timestamp());
span.mutable_local_endpoint()->MergeFrom(toProtoEndpoint(annotation.endpoint()));
}

span.set_trace_id(zipkin_span.traceIdAsHexString());
if (zipkin_span.isSetParentId()) {
span.set_parent_id(zipkin_span.parentIdAsHexString());
}

span.set_id(zipkin_span.idAsHexString());
span.set_name(zipkin_span.name());

if (zipkin_span.isSetDuration()) {
span.set_duration(zipkin_span.duration());
}

auto& tags = *span.mutable_tags();
for (const auto& binary_annotation : zipkin_span.binaryAnnotations()) {
tags[binary_annotation.key()] = binary_annotation.value();
}

spans.push_back(span);
dio marked this conversation as resolved.
Show resolved Hide resolved
}
return spans;
}

const zipkin::jsonv2::Endpoint
JsonV2Serializer::toProtoEndpoint(const Endpoint& zipkin_endpoint) const {
zipkin::jsonv2::Endpoint endpoint;
Network::Address::InstanceConstSharedPtr address = zipkin_endpoint.address();
if (address) {
if (address->ip()->version() == Network::Address::IpVersion::v4) {
endpoint.set_ipv4(address->ip()->addressAsString());
} else {
endpoint.set_ipv6(address->ip()->addressAsString());
}
endpoint.set_port(address->ip()->port());
}

const std::string& service_name = zipkin_endpoint.serviceName();
if (!service_name.empty()) {
endpoint.set_service_name(service_name);
}

return endpoint;
}

ProtobufSerializer::ProtobufSerializer(const bool shared_span_context)
: shared_span_context_{shared_span_context} {}

std::string ProtobufSerializer::serialize(std::vector<Span>&& zipkin_spans) {
zipkin::proto3::ListOfSpans spans;
for (const Span& zipkin_span : zipkin_spans) {
spans.MergeFrom(toListOfSpans(zipkin_span));
}
std::string serialized;
spans.SerializeToString(&serialized);
return serialized;
}

const zipkin::proto3::ListOfSpans ProtobufSerializer::toListOfSpans(const Span& zipkin_span) const {
zipkin::proto3::ListOfSpans spans;
for (const auto& annotation : zipkin_span.annotations()) {
zipkin::proto3::Span span;
if (annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND) {
span.set_kind(zipkin::proto3::Span::CLIENT);
} else if (annotation.value() == ZipkinCoreConstants::get().SERVER_RECV) {
span.set_shared(shared_span_context_ && zipkin_span.annotations().size() > 1);
span.set_kind(zipkin::proto3::Span::SERVER);
} else {
continue;
}

if (annotation.isSetEndpoint()) {
span.set_timestamp(annotation.timestamp());
span.mutable_local_endpoint()->MergeFrom(toProtoEndpoint(annotation.endpoint()));
}

span.set_trace_id(zipkin_span.traceIdAsByteString());
if (zipkin_span.isSetParentId()) {
span.set_parent_id(zipkin_span.parentIdAsByteString());
}

span.set_id(zipkin_span.idAsByteString());
span.set_name(zipkin_span.name());

if (zipkin_span.isSetDuration()) {
span.set_duration(zipkin_span.duration());
}

auto& tags = *span.mutable_tags();
for (const auto& binary_annotation : zipkin_span.binaryAnnotations()) {
tags[binary_annotation.key()] = binary_annotation.value();
}

auto* mutable_span = spans.add_spans();
mutable_span->MergeFrom(span);
}
return spans;
}

const zipkin::proto3::Endpoint
ProtobufSerializer::toProtoEndpoint(const Endpoint& zipkin_endpoint) const {
zipkin::proto3::Endpoint endpoint;
Network::Address::InstanceConstSharedPtr address = zipkin_endpoint.address();
if (address) {
if (address->ip()->version() == Network::Address::IpVersion::v4) {
endpoint.set_ipv4(Util::toByteString(address->ip()->ipv4()->address()));
} else {
endpoint.set_ipv6(Util::toByteString(address->ip()->ipv6()->address()));
}
endpoint.set_port(address->ip()->port());
}

const std::string& service_name = zipkin_endpoint.serviceName();
if (!service_name.empty()) {
endpoint.set_service_name(service_name);
}
stringified_json_array += "]";

return stringified_json_array;
return endpoint;
}

} // namespace Zipkin
Expand Down
Loading