Coverage Report

Created: 2024-09-19 09:45

/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