1
#include "source/extensions/tracers/xray/xray_tracer_impl.h"
2

            
3
#include "source/common/common/macros.h"
4
#include "source/common/common/utility.h"
5
#include "source/common/http/headers.h"
6
#include "source/extensions/tracers/xray/localized_sampling.h"
7
#include "source/extensions/tracers/xray/tracer.h"
8
#include "source/extensions/tracers/xray/xray_configuration.h"
9

            
10
namespace Envoy {
11
namespace Extensions {
12
namespace Tracers {
13
namespace XRay {
14

            
15
namespace {
16
constexpr auto DefaultDaemonEndpoint = "127.0.0.1:2000";
17
4
XRayHeader parseXRayHeader(const Http::LowerCaseString& header) {
18
4
  const auto& lowered_header = header.get();
19
4
  XRayHeader result;
20
11
  for (const auto& token : StringUtil::splitToken(lowered_header, ";")) {
21
11
    if (absl::StartsWith(token, "root=")) {
22
4
      result.trace_id_ = std::string(StringUtil::cropLeft(token, "="));
23
7
    } else if (absl::StartsWith(token, "parent=")) {
24
4
      result.parent_id_ = std::string(StringUtil::cropLeft(token, "="));
25
4
    } else if (absl::StartsWith(token, "sampled=")) {
26
3
      const auto s = StringUtil::cropLeft(token, "=");
27
3
      if (s == "1") {
28
1
        result.sample_decision_ = SamplingDecision::Sampled;
29
2
      } else if (s == "0") {
30
1
        result.sample_decision_ = SamplingDecision::NotSampled;
31
1
      } else {
32
1
        result.sample_decision_ = SamplingDecision::Unknown;
33
1
      }
34
3
    }
35
11
  }
36
4
  return result;
37
4
}
38
} // namespace
39

            
40
Driver::Driver(const XRayConfiguration& config,
41
               Server::Configuration::TracerFactoryContext& context)
42
10
    : xray_config_(config),
43
10
      tls_slot_ptr_(context.serverFactoryContext().threadLocal().allocateSlot()) {
44

            
45
10
  const std::string daemon_endpoint =
46
10
      config.daemon_endpoint_.empty() ? DefaultDaemonEndpoint : config.daemon_endpoint_;
47

            
48
10
  ENVOY_LOG(debug, "send X-Ray generated segments to daemon address on {}", daemon_endpoint);
49
10
  sampling_strategy_ = std::make_unique<XRay::LocalizedSamplingStrategy>(
50
10
      xray_config_.sampling_rules_, context.serverFactoryContext().api().randomGenerator(),
51
10
      context.serverFactoryContext().timeSource());
52

            
53
10
  tls_slot_ptr_->set([this, daemon_endpoint,
54
10
                      &context](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr {
55
10
    DaemonBrokerPtr broker = std::make_unique<DaemonBrokerImpl>(daemon_endpoint);
56
10
    TracerPtr tracer = std::make_unique<Tracer>(
57
10
        xray_config_.segment_name_, xray_config_.origin_, xray_config_.aws_metadata_,
58
10
        std::move(broker), context.serverFactoryContext().timeSource(),
59
10
        context.serverFactoryContext().api().randomGenerator());
60
10
    return std::make_shared<XRay::Driver::TlsTracer>(std::move(tracer), *this);
61
10
  });
62
10
}
63

            
64
Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config,
65
                                   Tracing::TraceContext& trace_context,
66
                                   const StreamInfo::StreamInfo& stream_info,
67
                                   const std::string& operation_name,
68
7
                                   Tracing::Decision tracing_decision) {
69
  // First thing is to determine whether this request will be sampled or not.
70
  // if there's a X-Ray header and it has a sampling decision already determined (i.e. Sample=1)
71
  // then we can move on; otherwise, we ask the sampling strategy whether this request should be
72
  // sampled or not.
73
  //
74
  // The second step is create a Span.
75
  // If we have a XRay TraceID in the headers, then we create a SpanContext to pass that trace-id
76
  // around if no TraceID (which means no x-ray header) then this is a brand new span.
77

            
78
  // TODO(suniltheta) - how do we factor this into the logic above
79
7
  UNREFERENCED_PARAMETER(tracing_decision);
80
7
  const auto header = xRayTraceHeader().get(trace_context);
81
7
  absl::optional<bool> should_trace;
82
7
  XRayHeader xray_header;
83
7
  if (header.has_value()) {
84
    // This is an implicitly untrusted header, so only the first value is used.
85
4
    Http::LowerCaseString lowered_header_value{header.value()};
86
4
    xray_header = parseXRayHeader(lowered_header_value);
87
    // if the sample_decision in the x-ray header is unknown then we try to make a decision based
88
    // on the sampling strategy
89
4
    if (xray_header.sample_decision_ == SamplingDecision::Sampled) {
90
1
      should_trace = true;
91
3
    } else if (xray_header.sample_decision_ == SamplingDecision::NotSampled) {
92
1
      should_trace = false;
93
2
    } else {
94
2
      ENVOY_LOG(
95
2
          trace,
96
2
          "Unable to determine from the X-Ray trace header whether request is sampled or not");
97
2
    }
98
4
  }
99

            
100
7
  if (!should_trace.has_value()) {
101
5
    const SamplingRequest request{trace_context.host(), trace_context.method(),
102
5
                                  trace_context.path()};
103

            
104
5
    should_trace = sampling_strategy_->shouldTrace(request);
105
5
  }
106

            
107
7
  auto* tracer = tls_slot_ptr_->getTyped<Driver::TlsTracer>().tracer_.get();
108
7
  if (should_trace.value()) {
109
5
    return tracer->startSpan(config, operation_name, stream_info.startTime(),
110
5
                             header.has_value() ? absl::optional<XRayHeader>(xray_header)
111
5
                                                : absl::nullopt,
112
5
                             xForwardedForHeader().get(trace_context));
113
5
  }
114

            
115
  // Instead of returning nullptr, we return a Span that is marked as not-sampled.
116
  // This is important to communicate that information to upstream services (see injectContext()).
117
  // Otherwise, the upstream service can decide to sample the request regardless and we end up with
118
  // more samples than we asked for.
119
2
  return tracer->createNonSampledSpan(header.has_value() ? absl::optional<XRayHeader>(xray_header)
120
2
                                                         : absl::nullopt);
121
7
}
122

            
123
} // namespace XRay
124
} // namespace Tracers
125
} // namespace Extensions
126
} // namespace Envoy