/proc/self/cwd/source/extensions/tracers/xray/tracer.cc
Line | Count | Source (jump to first uncovered line) |
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 | 0 | void Span::injectContext(Tracing::TraceContext& trace_context, const Tracing::UpstreamContext&) { |
113 | 0 | const std::string xray_header_value = |
114 | 0 | fmt::format("Root={};Parent={};Sampled={}", traceId(), id(), sampled() ? "1" : "0"); |
115 | 0 | xRayTraceHeader().setRefKey(trace_context, xray_header_value); |
116 | 0 | } |
117 | | |
118 | | Tracing::SpanPtr Span::spawnChild(const Tracing::Config& config, const std::string& operation_name, |
119 | 0 | Envoy::SystemTime start_time) { |
120 | 0 | auto child_span = std::make_unique<XRay::Span>(time_source_, random_, broker_); |
121 | 0 | child_span->setName(operation_name); |
122 | 0 | child_span->setOperation(operation_name); |
123 | 0 | child_span->setDirection(Tracing::TracerUtility::toString(config.operationName())); |
124 | 0 | child_span->setStartTime(start_time); |
125 | 0 | child_span->setParentId(id()); |
126 | 0 | child_span->setTraceId(traceId()); |
127 | 0 | child_span->setSampled(sampled()); |
128 | 0 | child_span->setType(Subsegment); |
129 | 0 | return child_span; |
130 | 0 | } |
131 | | |
132 | | Tracing::SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& operation_name, |
133 | | Envoy::SystemTime start_time, |
134 | | const absl::optional<XRayHeader>& xray_header, |
135 | 0 | const absl::optional<absl::string_view> client_ip) { |
136 | |
|
137 | 0 | auto span_ptr = std::make_unique<XRay::Span>(time_source_, random_, *daemon_broker_); |
138 | 0 | span_ptr->setName(segment_name_); |
139 | 0 | span_ptr->setOperation(operation_name); |
140 | 0 | span_ptr->setDirection(Tracing::TracerUtility::toString(config.operationName())); |
141 | | // Even though we have a TimeSource member in the tracer, we assume the start_time argument has a |
142 | | // more precise value than calling the systemTime() at this point in time. |
143 | 0 | span_ptr->setStartTime(start_time); |
144 | 0 | span_ptr->setOrigin(origin_); |
145 | 0 | span_ptr->setAwsMetadata(aws_metadata_); |
146 | 0 | if (client_ip) { |
147 | 0 | span_ptr->addToHttpRequestAnnotations(SpanClientIp, |
148 | 0 | ValueUtil::stringValue(std::string(*client_ip))); |
149 | | // The `client_ip` is the address specified in the HTTP X-Forwarded-For header. |
150 | 0 | span_ptr->addToHttpRequestAnnotations(SpanXForwardedFor, ValueUtil::boolValue(true)); |
151 | 0 | } |
152 | |
|
153 | 0 | if (xray_header) { |
154 | | // There's a previous span that this span should be based-on. |
155 | 0 | span_ptr->setParentId(xray_header->parent_id_); |
156 | 0 | span_ptr->setTraceId(xray_header->trace_id_); |
157 | 0 | switch (xray_header->sample_decision_) { |
158 | 0 | case SamplingDecision::Sampled: |
159 | 0 | span_ptr->setSampled(true); |
160 | 0 | break; |
161 | 0 | case SamplingDecision::NotSampled: |
162 | | // should never get here. If the header has Sampled=0 then we never call startSpan(). |
163 | 0 | IS_ENVOY_BUG("unexpected code path hit"); |
164 | 0 | default: |
165 | 0 | break; |
166 | 0 | } |
167 | 0 | } else { |
168 | 0 | span_ptr->setTraceId(generateTraceId(time_source_.systemTime(), random_)); |
169 | 0 | } |
170 | 0 | return span_ptr; |
171 | 0 | } |
172 | | |
173 | 0 | XRay::SpanPtr Tracer::createNonSampledSpan(const absl::optional<XRayHeader>& xray_header) const { |
174 | 0 | auto span_ptr = std::make_unique<XRay::Span>(time_source_, random_, *daemon_broker_); |
175 | 0 | if (xray_header) { |
176 | | // There's a previous span that this span should be based-on. |
177 | 0 | span_ptr->setParentId(xray_header->parent_id_); |
178 | 0 | span_ptr->setTraceId(xray_header->trace_id_); |
179 | 0 | } else { |
180 | 0 | span_ptr->setTraceId(generateTraceId(time_source_.systemTime(), random_)); |
181 | 0 | } |
182 | 0 | span_ptr->setSampled(false); |
183 | 0 | return span_ptr; |
184 | 0 | } |
185 | | |
186 | 0 | void Span::setTag(absl::string_view name, absl::string_view value) { |
187 | | // For the full set of values see: |
188 | | // https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html#api-segmentdocuments-http |
189 | 0 | constexpr auto SpanContentLength = "content_length"; |
190 | 0 | constexpr auto SpanMethod = "method"; |
191 | 0 | constexpr auto SpanUrl = "url"; |
192 | |
|
193 | 0 | if (name.empty() || value.empty()) { |
194 | 0 | return; |
195 | 0 | } |
196 | | |
197 | 0 | if (name == Tracing::Tags::get().HttpUrl) { |
198 | 0 | addToHttpRequestAnnotations(SpanUrl, ValueUtil::stringValue(std::string(value))); |
199 | 0 | } else if (name == Tracing::Tags::get().HttpMethod) { |
200 | 0 | addToHttpRequestAnnotations(SpanMethod, ValueUtil::stringValue(std::string(value))); |
201 | 0 | } else if (name == Tracing::Tags::get().UserAgent) { |
202 | 0 | addToHttpRequestAnnotations(Tracing::Tags::get().UserAgent, |
203 | 0 | ValueUtil::stringValue(std::string(value))); |
204 | 0 | } else if (name == Tracing::Tags::get().HttpStatusCode) { |
205 | 0 | uint64_t status_code; |
206 | 0 | if (!absl::SimpleAtoi(value, &status_code)) { |
207 | 0 | ENVOY_LOG(debug, "{} must be a number, given: {}", Tracing::Tags::get().HttpStatusCode, |
208 | 0 | value); |
209 | 0 | return; |
210 | 0 | } |
211 | 0 | setResponseStatusCode(status_code); |
212 | 0 | addToHttpResponseAnnotations(Tracing::Tags::get().Status, ValueUtil::numberValue(status_code)); |
213 | 0 | } else if (name == Tracing::Tags::get().ResponseSize) { |
214 | 0 | uint64_t response_size; |
215 | 0 | if (!absl::SimpleAtoi(value, &response_size)) { |
216 | 0 | ENVOY_LOG(debug, "{} must be a number, given: {}", Tracing::Tags::get().ResponseSize, value); |
217 | 0 | return; |
218 | 0 | } |
219 | 0 | addToHttpResponseAnnotations(SpanContentLength, ValueUtil::numberValue(response_size)); |
220 | 0 | } else if (name == Tracing::Tags::get().PeerAddress) { |
221 | | // Use PeerAddress if client_ip is not already set from the header. |
222 | 0 | if (!hasKeyInHttpRequestAnnotations(SpanClientIp)) { |
223 | 0 | addToHttpRequestAnnotations(SpanClientIp, ValueUtil::stringValue(std::string(value))); |
224 | | // In this case, PeerAddress refers to the client's actual IP address, not |
225 | | // the address specified in the HTTP X-Forwarded-For header. |
226 | 0 | addToHttpRequestAnnotations(SpanXForwardedFor, ValueUtil::boolValue(false)); |
227 | 0 | } |
228 | 0 | } else if (name == Tracing::Tags::get().Error && value == Tracing::Tags::get().True) { |
229 | 0 | setServerError(); |
230 | 0 | } else { |
231 | 0 | custom_annotations_.emplace(name, value); |
232 | 0 | } |
233 | 0 | } |
234 | | |
235 | | } // namespace XRay |
236 | | } // namespace Tracers |
237 | | } // namespace Extensions |
238 | | } // namespace Envoy |