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
19
std::string generateTraceId(SystemTime point_in_time, Random::RandomGenerator& random) {
38
19
  using std::chrono::seconds;
39
19
  using std::chrono::time_point_cast;
40
  // epoch in seconds represented as 8 hexadecimal characters
41
19
  const auto epoch = time_point_cast<seconds>(point_in_time).time_since_epoch().count();
42
19
  std::string uuid = random.uuid();
43
  // unique id represented as 24 hexadecimal digits and no dashes
44
19
  uuid.erase(std::remove(uuid.begin(), uuid.end(), '-'), uuid.end());
45
19
  ASSERT(uuid.length() >= 24);
46
19
  const std::string out =
47
19
      absl::StrCat(XRaySerializationVersion, "-", Hex::uint32ToHex(epoch), "-", uuid.substr(0, 24));
48
19
  return out;
49
19
}
50

            
51
} // namespace
52

            
53
13
void Span::finishSpan() {
54
13
  using std::chrono::time_point_cast;
55
13
  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
13
  using SecondsWithFraction = std::chrono::duration<double>;
58
13
  if (!sampled()) {
59
4
    return;
60
4
  }
61

            
62
9
  daemon::Segment s;
63
9
  s.set_name(name());
64
9
  s.set_id(id());
65
9
  s.set_trace_id(traceId());
66
9
  s.set_start_time(time_point_cast<SecondsWithFraction>(startTime()).time_since_epoch().count());
67
9
  s.set_end_time(
68
9
      time_point_cast<SecondsWithFraction>(time_source_.systemTime()).time_since_epoch().count());
69
9
  s.set_origin(origin());
70
9
  s.set_parent_id(parentId());
71
9
  s.set_error(clientError());
72
9
  s.set_fault(serverError());
73
9
  s.set_throttle(isThrottled());
74
9
  if (type() == Subsegment) {
75
1
    s.set_type(std::string(Subsegment));
76
1
  }
77
9
  auto* aws = s.mutable_aws()->mutable_fields();
78
9
  for (const auto& field : aws_metadata_) {
79
7
    aws->insert({field.first, field.second});
80
7
  }
81

            
82
9
  auto* request_fields = s.mutable_http()->mutable_request()->mutable_fields();
83
25
  for (const auto& field : http_request_annotations_) {
84
25
    request_fields->insert({field.first, field.second});
85
25
  }
86

            
87
9
  auto* response_fields = s.mutable_http()->mutable_response()->mutable_fields();
88
9
  for (const auto& field : http_response_annotations_) {
89
8
    response_fields->insert({field.first, field.second});
90
8
  }
91

            
92
9
  for (const auto& item : custom_annotations_) {
93
2
    s.mutable_annotations()->insert({item.first, item.second});
94
2
  }
95
  // `direction` will be either "ingress" or "egress"
96
9
  s.mutable_annotations()->insert({std::string(DirectionKey), direction()});
97

            
98
9
  const std::string json = MessageUtil::getJsonStringFromMessageOrError(
99
9
      s, false /* pretty_print  */, false /* always_print_primitive_fields */);
100

            
101
9
  broker_.send(json);
102
9
} // namespace XRay
103

            
104
15
const Tracing::TraceContextHandler& xRayTraceHeader() {
105
15
  CONSTRUCT_ON_FIRST_USE(Tracing::TraceContextHandler, "x-amzn-trace-id");
106
15
}
107

            
108
6
const Tracing::TraceContextHandler& xForwardedForHeader() {
109
6
  CONSTRUCT_ON_FIRST_USE(Tracing::TraceContextHandler, "x-forwarded-for");
110
6
}
111

            
112
2
void Span::injectContext(Tracing::TraceContext& trace_context, const Tracing::UpstreamContext&) {
113
2
  const std::string xray_header_value =
114
2
      fmt::format("Root={};Parent={};Sampled={}", traceId(), id(), sampled() ? "1" : "0");
115
2
  xRayTraceHeader().setRefKey(trace_context, xray_header_value);
116
2
}
117

            
118
Tracing::SpanPtr Span::spawnChild(const Tracing::Config& config, const std::string& operation_name,
119
1
                                  Envoy::SystemTime start_time) {
120
1
  auto child_span = std::make_unique<XRay::Span>(time_source_, random_, broker_);
121
1
  child_span->setName(operation_name);
122
1
  child_span->setOperation(operation_name);
123
1
  child_span->setDirection(Tracing::TracerUtility::toString(config.operationName()));
124
1
  child_span->setStartTime(start_time);
125
1
  child_span->setParentId(id());
126
1
  child_span->setTraceId(traceId());
127
1
  child_span->setSampled(sampled());
128
1
  child_span->setType(Subsegment);
129
1
  return child_span;
130
1
}
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
18
                                   const absl::optional<absl::string_view> client_ip) {
136

            
137
18
  auto span_ptr = std::make_unique<XRay::Span>(time_source_, random_, *daemon_broker_);
138
18
  span_ptr->setName(segment_name_);
139
18
  span_ptr->setOperation(operation_name);
140
18
  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
18
  span_ptr->setStartTime(start_time);
144
18
  span_ptr->setOrigin(origin_);
145
18
  span_ptr->setAwsMetadata(aws_metadata_);
146
18
  if (client_ip) {
147
2
    span_ptr->addToHttpRequestAnnotations(SpanClientIp,
148
2
                                          ValueUtil::stringValue(std::string(*client_ip)));
149
    // The `client_ip` is the address specified in the HTTP X-Forwarded-For header.
150
2
    span_ptr->addToHttpRequestAnnotations(SpanXForwardedFor, ValueUtil::boolValue(true));
151
2
  }
152

            
153
18
  if (xray_header) {
154
    // There's a previous span that this span should be based-on.
155
5
    span_ptr->setParentId(xray_header->parent_id_);
156
5
    span_ptr->setTraceId(xray_header->trace_id_);
157
5
    switch (xray_header->sample_decision_) {
158
1
    case SamplingDecision::Sampled:
159
1
      span_ptr->setSampled(true);
160
1
      break;
161
1
    case SamplingDecision::NotSampled:
162
      // should never get here. If the header has Sampled=0 then we never call startSpan().
163
1
      IS_ENVOY_BUG("unexpected code path hit");
164
4
    default:
165
4
      break;
166
5
    }
167
13
  } else {
168
13
    span_ptr->setTraceId(generateTraceId(time_source_.systemTime(), random_));
169
13
  }
170
18
  return span_ptr;
171
18
}
172

            
173
9
XRay::SpanPtr Tracer::createNonSampledSpan(const absl::optional<XRayHeader>& xray_header) const {
174
9
  auto span_ptr = std::make_unique<XRay::Span>(time_source_, random_, *daemon_broker_);
175
9
  if (xray_header) {
176
    // There's a previous span that this span should be based-on.
177
3
    span_ptr->setParentId(xray_header->parent_id_);
178
3
    span_ptr->setTraceId(xray_header->trace_id_);
179
6
  } else {
180
6
    span_ptr->setTraceId(generateTraceId(time_source_.systemTime(), random_));
181
6
  }
182
9
  span_ptr->setSampled(false);
183
9
  return span_ptr;
184
9
}
185

            
186
38
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
38
  constexpr auto SpanContentLength = "content_length";
190
38
  constexpr auto SpanMethod = "method";
191
38
  constexpr auto SpanUrl = "url";
192

            
193
38
  if (name.empty() || value.empty()) {
194
2
    return;
195
2
  }
196

            
197
36
  if (name == Tracing::Tags::get().HttpUrl) {
198
7
    addToHttpRequestAnnotations(SpanUrl, ValueUtil::stringValue(std::string(value)));
199
29
  } else if (name == Tracing::Tags::get().HttpMethod) {
200
7
    addToHttpRequestAnnotations(SpanMethod, ValueUtil::stringValue(std::string(value)));
201
22
  } else if (name == Tracing::Tags::get().UserAgent) {
202
7
    addToHttpRequestAnnotations(Tracing::Tags::get().UserAgent,
203
7
                                ValueUtil::stringValue(std::string(value)));
204
15
  } else if (name == Tracing::Tags::get().HttpStatusCode) {
205
7
    uint64_t status_code;
206
7
    if (!absl::SimpleAtoi(value, &status_code)) {
207
1
      ENVOY_LOG(debug, "{} must be a number, given: {}", Tracing::Tags::get().HttpStatusCode,
208
1
                value);
209
1
      return;
210
1
    }
211
6
    setResponseStatusCode(status_code);
212
6
    addToHttpResponseAnnotations(Tracing::Tags::get().Status, ValueUtil::numberValue(status_code));
213
8
  } else if (name == Tracing::Tags::get().ResponseSize) {
214
3
    uint64_t response_size;
215
3
    if (!absl::SimpleAtoi(value, &response_size)) {
216
1
      ENVOY_LOG(debug, "{} must be a number, given: {}", Tracing::Tags::get().ResponseSize, value);
217
1
      return;
218
1
    }
219
2
    addToHttpResponseAnnotations(SpanContentLength, ValueUtil::numberValue(response_size));
220
5
  } else if (name == Tracing::Tags::get().PeerAddress) {
221
    // Use PeerAddress if client_ip is not already set from the header.
222
2
    if (!hasKeyInHttpRequestAnnotations(SpanClientIp)) {
223
1
      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
1
      addToHttpRequestAnnotations(SpanXForwardedFor, ValueUtil::boolValue(false));
227
1
    }
228
3
  } else if (name == Tracing::Tags::get().Error && value == Tracing::Tags::get().True) {
229
1
    setServerError();
230
2
  } else {
231
2
    custom_annotations_.emplace(name, value);
232
2
  }
233
36
}
234

            
235
} // namespace XRay
236
} // namespace Tracers
237
} // namespace Extensions
238
} // namespace Envoy