Line data Source code
1 : #include "source/extensions/tracers/xray/tracer.h"
2 :
3 : #include <algorithm>
4 : #include <chrono>
5 : #include <string>
6 :
7 : #include "envoy/http/header_map.h"
8 : #include "envoy/network/listener.h"
9 :
10 : #include "source/common/common/assert.h"
11 : #include "source/common/common/fmt.h"
12 : #include "source/common/protobuf/utility.h"
13 : #include "source/common/tracing/http_tracer_impl.h"
14 : #include "source/extensions/tracers/xray/daemon.pb.validate.h"
15 :
16 : namespace Envoy {
17 : namespace Extensions {
18 : namespace Tracers {
19 : namespace XRay {
20 :
21 : namespace {
22 : constexpr absl::string_view XRaySerializationVersion = "1";
23 : constexpr absl::string_view DirectionKey = "direction";
24 :
25 : // X-Ray Trace ID Format
26 : //
27 : // A trace_id consists of three parts separated by hyphens.
28 : // For example, 1-58406cf0-a006649127e371903a2de979.
29 : // This includes:
30 : //
31 : // - The version number, that is, 1.
32 : // - The time of the original request, in Unix epoch time, in 8 hexadecimal digits.
33 : // - A 96-bit unique identifier in 24 hexadecimal digits.
34 : //
35 : // For more details see:
36 : // https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-fields
37 0 : std::string generateTraceId(SystemTime point_in_time, Random::RandomGenerator& random) {
38 0 : using std::chrono::seconds;
39 0 : using std::chrono::time_point_cast;
40 : // epoch in seconds represented as 8 hexadecimal characters
41 0 : const auto epoch = time_point_cast<seconds>(point_in_time).time_since_epoch().count();
42 0 : std::string uuid = random.uuid();
43 : // unique id represented as 24 hexadecimal digits and no dashes
44 0 : uuid.erase(std::remove(uuid.begin(), uuid.end(), '-'), uuid.end());
45 0 : ASSERT(uuid.length() >= 24);
46 0 : const std::string out =
47 0 : absl::StrCat(XRaySerializationVersion, "-", Hex::uint32ToHex(epoch), "-", uuid.substr(0, 24));
48 0 : return out;
49 0 : }
50 :
51 : } // namespace
52 :
53 0 : void Span::finishSpan() {
54 0 : using std::chrono::time_point_cast;
55 0 : using namespace source::extensions::tracers::xray;
56 : // X-Ray expects timestamps to be in epoch seconds with milli/micro-second precision as a fraction
57 0 : using SecondsWithFraction = std::chrono::duration<double>;
58 0 : if (!sampled()) {
59 0 : return;
60 0 : }
61 :
62 0 : daemon::Segment s;
63 0 : s.set_name(name());
64 0 : s.set_id(id());
65 0 : s.set_trace_id(traceId());
66 0 : s.set_start_time(time_point_cast<SecondsWithFraction>(startTime()).time_since_epoch().count());
67 0 : s.set_end_time(
68 0 : time_point_cast<SecondsWithFraction>(time_source_.systemTime()).time_since_epoch().count());
69 0 : s.set_origin(origin());
70 0 : s.set_parent_id(parentId());
71 0 : s.set_error(clientError());
72 0 : s.set_fault(serverError());
73 0 : s.set_throttle(isThrottled());
74 0 : if (type() == Subsegment) {
75 0 : s.set_type(std::string(Subsegment));
76 0 : }
77 0 : auto* aws = s.mutable_aws()->mutable_fields();
78 0 : for (const auto& field : aws_metadata_) {
79 0 : aws->insert({field.first, field.second});
80 0 : }
81 :
82 0 : auto* request_fields = s.mutable_http()->mutable_request()->mutable_fields();
83 0 : for (const auto& field : http_request_annotations_) {
84 0 : request_fields->insert({field.first, field.second});
85 0 : }
86 :
87 0 : auto* response_fields = s.mutable_http()->mutable_response()->mutable_fields();
88 0 : for (const auto& field : http_response_annotations_) {
89 0 : response_fields->insert({field.first, field.second});
90 0 : }
91 :
92 0 : for (const auto& item : custom_annotations_) {
93 0 : s.mutable_annotations()->insert({item.first, item.second});
94 0 : }
95 : // `direction` will be either "ingress" or "egress"
96 0 : s.mutable_annotations()->insert({std::string(DirectionKey), direction()});
97 :
98 0 : const std::string json = MessageUtil::getJsonStringFromMessageOrError(
99 0 : s, false /* pretty_print */, false /* always_print_primitive_fields */);
100 :
101 0 : broker_.send(json);
102 0 : } // namespace XRay
103 :
104 0 : const Tracing::TraceContextHandler& xRayTraceHeader() {
105 0 : CONSTRUCT_ON_FIRST_USE(Tracing::TraceContextHandler, "x-amzn-trace-id");
106 0 : }
107 :
108 0 : const Tracing::TraceContextHandler& xForwardedForHeader() {
109 0 : CONSTRUCT_ON_FIRST_USE(Tracing::TraceContextHandler, "x-forwarded-for");
110 0 : }
111 :
112 : void Span::injectContext(Tracing::TraceContext& trace_context,
113 0 : const Upstream::HostDescriptionConstSharedPtr&) {
114 0 : const std::string xray_header_value =
115 0 : fmt::format("Root={};Parent={};Sampled={}", traceId(), id(), sampled() ? "1" : "0");
116 0 : xRayTraceHeader().setRefKey(trace_context, xray_header_value);
117 0 : }
118 :
119 : Tracing::SpanPtr Span::spawnChild(const Tracing::Config& config, const std::string& operation_name,
120 0 : Envoy::SystemTime start_time) {
121 0 : auto child_span = std::make_unique<XRay::Span>(time_source_, random_, broker_);
122 0 : child_span->setName(operation_name);
123 0 : child_span->setOperation(operation_name);
124 0 : child_span->setDirection(Tracing::TracerUtility::toString(config.operationName()));
125 0 : child_span->setStartTime(start_time);
126 0 : child_span->setParentId(id());
127 0 : child_span->setTraceId(traceId());
128 0 : child_span->setSampled(sampled());
129 0 : child_span->setType(Subsegment);
130 0 : return child_span;
131 0 : }
132 :
133 : Tracing::SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& operation_name,
134 : Envoy::SystemTime start_time,
135 : const absl::optional<XRayHeader>& xray_header,
136 0 : const absl::optional<absl::string_view> client_ip) {
137 :
138 0 : auto span_ptr = std::make_unique<XRay::Span>(time_source_, random_, *daemon_broker_);
139 0 : span_ptr->setName(segment_name_);
140 0 : span_ptr->setOperation(operation_name);
141 0 : span_ptr->setDirection(Tracing::TracerUtility::toString(config.operationName()));
142 : // Even though we have a TimeSource member in the tracer, we assume the start_time argument has a
143 : // more precise value than calling the systemTime() at this point in time.
144 0 : span_ptr->setStartTime(start_time);
145 0 : span_ptr->setOrigin(origin_);
146 0 : span_ptr->setAwsMetadata(aws_metadata_);
147 0 : if (client_ip) {
148 0 : span_ptr->addToHttpRequestAnnotations(SpanClientIp,
149 0 : ValueUtil::stringValue(std::string(*client_ip)));
150 : // The `client_ip` is the address specified in the HTTP X-Forwarded-For header.
151 0 : span_ptr->addToHttpRequestAnnotations(SpanXForwardedFor, ValueUtil::boolValue(true));
152 0 : }
153 :
154 0 : if (xray_header) {
155 : // There's a previous span that this span should be based-on.
156 0 : span_ptr->setParentId(xray_header->parent_id_);
157 0 : span_ptr->setTraceId(xray_header->trace_id_);
158 0 : switch (xray_header->sample_decision_) {
159 0 : case SamplingDecision::Sampled:
160 0 : span_ptr->setSampled(true);
161 0 : break;
162 0 : case SamplingDecision::NotSampled:
163 : // should never get here. If the header has Sampled=0 then we never call startSpan().
164 0 : IS_ENVOY_BUG("unexpected code path hit");
165 0 : default:
166 0 : break;
167 0 : }
168 0 : } else {
169 0 : span_ptr->setTraceId(generateTraceId(time_source_.systemTime(), random_));
170 0 : }
171 0 : return span_ptr;
172 0 : }
173 :
174 0 : XRay::SpanPtr Tracer::createNonSampledSpan(const absl::optional<XRayHeader>& xray_header) const {
175 0 : auto span_ptr = std::make_unique<XRay::Span>(time_source_, random_, *daemon_broker_);
176 0 : if (xray_header) {
177 : // There's a previous span that this span should be based-on.
178 0 : span_ptr->setParentId(xray_header->parent_id_);
179 0 : span_ptr->setTraceId(xray_header->trace_id_);
180 0 : } else {
181 0 : span_ptr->setTraceId(generateTraceId(time_source_.systemTime(), random_));
182 0 : }
183 0 : span_ptr->setSampled(false);
184 0 : return span_ptr;
185 0 : }
186 :
187 0 : void Span::setTag(absl::string_view name, absl::string_view value) {
188 : // For the full set of values see:
189 : // https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-http
190 0 : constexpr auto SpanContentLength = "content_length";
191 0 : constexpr auto SpanMethod = "method";
192 0 : constexpr auto SpanUrl = "url";
193 :
194 0 : if (name.empty() || value.empty()) {
195 0 : return;
196 0 : }
197 :
198 0 : if (name == Tracing::Tags::get().HttpUrl) {
199 0 : addToHttpRequestAnnotations(SpanUrl, ValueUtil::stringValue(std::string(value)));
200 0 : } else if (name == Tracing::Tags::get().HttpMethod) {
201 0 : addToHttpRequestAnnotations(SpanMethod, ValueUtil::stringValue(std::string(value)));
202 0 : } else if (name == Tracing::Tags::get().UserAgent) {
203 0 : addToHttpRequestAnnotations(Tracing::Tags::get().UserAgent,
204 0 : ValueUtil::stringValue(std::string(value)));
205 0 : } else if (name == Tracing::Tags::get().HttpStatusCode) {
206 0 : uint64_t status_code;
207 0 : if (!absl::SimpleAtoi(value, &status_code)) {
208 0 : ENVOY_LOG(debug, "{} must be a number, given: {}", Tracing::Tags::get().HttpStatusCode,
209 0 : value);
210 0 : return;
211 0 : }
212 0 : setResponseStatusCode(status_code);
213 0 : addToHttpResponseAnnotations(Tracing::Tags::get().Status, ValueUtil::numberValue(status_code));
214 0 : } else if (name == Tracing::Tags::get().ResponseSize) {
215 0 : uint64_t response_size;
216 0 : if (!absl::SimpleAtoi(value, &response_size)) {
217 0 : ENVOY_LOG(debug, "{} must be a number, given: {}", Tracing::Tags::get().ResponseSize, value);
218 0 : return;
219 0 : }
220 0 : addToHttpResponseAnnotations(SpanContentLength, ValueUtil::numberValue(response_size));
221 0 : } else if (name == Tracing::Tags::get().PeerAddress) {
222 : // Use PeerAddress if client_ip is not already set from the header.
223 0 : if (!hasKeyInHttpRequestAnnotations(SpanClientIp)) {
224 0 : addToHttpRequestAnnotations(SpanClientIp, ValueUtil::stringValue(std::string(value)));
225 : // In this case, PeerAddress refers to the client's actual IP address, not
226 : // the address specified in the HTTP X-Forwarded-For header.
227 0 : addToHttpRequestAnnotations(SpanXForwardedFor, ValueUtil::boolValue(false));
228 0 : }
229 0 : } else if (name == Tracing::Tags::get().Error && value == Tracing::Tags::get().True) {
230 0 : setServerError();
231 0 : } else {
232 0 : custom_annotations_.emplace(name, value);
233 0 : }
234 0 : }
235 :
236 : } // namespace XRay
237 : } // namespace Tracers
238 : } // namespace Extensions
239 : } // namespace Envoy
|