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