1
#include "source/extensions/stat_sinks/open_telemetry/open_telemetry_http_impl.h"
2

            
3
#include "source/common/common/enum_to_int.h"
4
#include "source/common/http/headers.h"
5
#include "source/common/http/message_impl.h"
6
#include "source/common/http/utility.h"
7
#include "source/common/protobuf/protobuf.h"
8
#include "source/extensions/access_loggers/open_telemetry/otlp_log_utils.h"
9

            
10
namespace Envoy {
11
namespace Extensions {
12
namespace StatSinks {
13
namespace OpenTelemetry {
14

            
15
OpenTelemetryHttpMetricsExporter::OpenTelemetryHttpMetricsExporter(
16
    Upstream::ClusterManager& cluster_manager,
17
    const envoy::config::core::v3::HttpService& http_service)
18
12
    : cluster_manager_(cluster_manager), http_service_(http_service) {
19
  // Parse headers at construction time to avoid copies per request.
20
12
  for (const auto& header_value_option : http_service_.request_headers_to_add()) {
21
4
    parsed_headers_to_add_.push_back({Http::LowerCaseString(header_value_option.header().key()),
22
4
                                      header_value_option.header().value()});
23
4
  }
24
12
}
25

            
26
13
void OpenTelemetryHttpMetricsExporter::send(MetricsExportRequestPtr&& metrics) {
27
13
  std::string request_body;
28
13
  const auto ok = metrics->SerializeToString(&request_body);
29
13
  if (!ok) {
30
    ENVOY_LOG(warn, "Error while serializing the binary proto ExportMetricsServiceRequest.");
31
    return;
32
  }
33

            
34
13
  const auto thread_local_cluster =
35
13
      cluster_manager_.getThreadLocalCluster(http_service_.http_uri().cluster());
36
13
  if (thread_local_cluster == nullptr) {
37
1
    ENVOY_LOG(error, "OTLP HTTP metrics exporter failed: [cluster = {}] is not configured",
38
1
              http_service_.http_uri().cluster());
39
1
    return;
40
1
  }
41

            
42
12
  Http::RequestMessagePtr message = Http::Utility::prepareHeaders(http_service_.http_uri());
43

            
44
  // The request follows the OTLP HTTP specification:
45
  // https://github.com/open-telemetry/opentelemetry-proto/blob/v1.9.0/docs/specification.md#otlphttp
46
12
  message->headers().setReferenceMethod(Http::Headers::get().MethodValues.Post);
47
12
  message->headers().setReferenceContentType(Http::Headers::get().ContentTypeValues.Protobuf);
48

            
49
  // User-Agent header follows the OTLP specification.
50
12
  message->headers().setReferenceUserAgent(AccessLoggers::OpenTelemetry::getOtlpUserAgentHeader());
51

            
52
  // Add custom headers from config.
53
12
  for (const auto& header_pair : parsed_headers_to_add_) {
54
2
    message->headers().setReference(header_pair.first, header_pair.second);
55
2
  }
56
12
  message->body().add(request_body);
57

            
58
12
  const auto options =
59
12
      Http::AsyncClient::RequestOptions()
60
12
          .setTimeout(std::chrono::milliseconds(
61
12
              DurationUtil::durationToMilliseconds(http_service_.http_uri().timeout())))
62
12
          .setDiscardResponseBody(true);
63

            
64
12
  Http::AsyncClient::Request* in_flight_request =
65
12
      thread_local_cluster->httpAsyncClient().send(std::move(message), *this, options);
66

            
67
12
  if (in_flight_request != nullptr) {
68
11
    active_requests_.add(*in_flight_request);
69
11
  }
70
12
}
71

            
72
void OpenTelemetryHttpMetricsExporter::onSuccess(const Http::AsyncClient::Request& request,
73
10
                                                 Http::ResponseMessagePtr&& http_response) {
74
10
  active_requests_.remove(request);
75
10
  const auto response_code = Http::Utility::getResponseStatus(http_response->headers());
76
10
  if (response_code != enumToInt(Http::Code::OK)) {
77
5
    ENVOY_LOG(error,
78
5
              "OTLP HTTP metrics exporter received a non-success status code: {} while "
79
5
              "exporting the OTLP message",
80
5
              response_code);
81
5
  }
82
10
}
83

            
84
void OpenTelemetryHttpMetricsExporter::onFailure(const Http::AsyncClient::Request& request,
85
1
                                                 Http::AsyncClient::FailureReason reason) {
86
1
  active_requests_.remove(request);
87
1
  ENVOY_LOG(warn, "OTLP HTTP metrics export request failed. Failure reason: {}", enumToInt(reason));
88
1
}
89

            
90
} // namespace OpenTelemetry
91
} // namespace StatSinks
92
} // namespace Extensions
93
} // namespace Envoy