| 364 | } |
| 365 | |
| 366 | void OTel::ExportImpl(boost::asio::yield_context& yc) const |
| 367 | { |
| 368 | AsioProtobufOutStream outputS{*m_Stream, m_ConnInfo, yc}; |
| 369 | [[maybe_unused]] auto serialized = m_Request->SerializeToZeroCopyStream(&outputS); |
| 370 | ASSERT(serialized); |
| 371 | // Must have completed chunk writing successfully, otherwise reading the response will hang forever. |
| 372 | if (!outputS.WriterDone()) { |
| 373 | BOOST_THROW_EXCEPTION(std::runtime_error("BUG: Protobuf output stream writer did not complete successfully.")); |
| 374 | } |
| 375 | |
| 376 | IncomingHttpResponse responseMsg{*m_Stream}; |
| 377 | responseMsg.Parse(yc); |
| 378 | |
| 379 | if (auto ct = responseMsg[http::field::content_type]; ct != "application/x-protobuf") { |
| 380 | if (responseMsg.result() == http::status::ok) { |
| 381 | // Some OpenTelemetry Collector compatible backends (e.g., Prometheus OTLP Receiver) respond with 200 OK |
| 382 | // but without the expected Protobuf content type. So, don't do anything here since the request succeeded. |
| 383 | return; |
| 384 | } |
| 385 | Log(LogWarning, "OTelExporter") |
| 386 | << "Unexpected Content-Type from OpenTelemetry backend '" << ct << "' (" << responseMsg.reason() << "):\n" |
| 387 | << responseMsg.body(); |
| 388 | } else if (responseMsg.result_int() >= 200 && responseMsg.result_int() <= 299) { |
| 389 | // We've got a valid Protobuf response, so we've to deserialize the body to check for partial success. |
| 390 | // See https://opentelemetry.io/docs/specs/otlp/#partial-success-1. |
| 391 | google::protobuf::Arena arena; |
| 392 | auto* response = MetricsResponse::default_instance().New(&arena); |
| 393 | [[maybe_unused]] auto deserialized = response->ParseFromString(responseMsg.body()); |
| 394 | ASSERT(deserialized); |
| 395 | |
| 396 | if (response->has_partial_success()) { |
| 397 | const auto& ps = response->partial_success(); |
| 398 | const auto& msg = ps.error_message(); |
| 399 | if (ps.rejected_data_points() > 0 || !msg.empty()) { |
| 400 | Log(LogWarning, "OTelExporter") |
| 401 | << "OpenTelemetry backend reported partial success: " << (msg.empty() ? "<none>" : msg) |
| 402 | << " (" << ps.rejected_data_points() << " metric data points rejected)."; |
| 403 | } |
| 404 | } |
| 405 | } else if (IsRetryableExportError(responseMsg.result())) { |
| 406 | uint64_t throttleSeconds = 0; |
| 407 | if (auto throttle = responseMsg[http::field::retry_after]; !throttle.empty()) { |
| 408 | try { |
| 409 | throttleSeconds = boost::lexical_cast<uint64_t>(throttle); |
| 410 | } catch (const std::exception& ex) { |
| 411 | Log(LogWarning, "OTelExporter") |
| 412 | << "Failed to parse 'Retry-After' header from OpenTelemetry backend response: " << ex.what(); |
| 413 | } |
| 414 | } |
| 415 | BOOST_THROW_EXCEPTION(RetryableExportError{throttleSeconds}); |
| 416 | } else { |
| 417 | Log(LogWarning, "OTelExporter") |
| 418 | << "OpenTelemetry backend responded with non-success and non-retryable status code " |
| 419 | << responseMsg.result_int() << " (" << responseMsg.reason() << ").\n" << responseMsg.body(); |
| 420 | } |
| 421 | } |
| 422 | |
| 423 | /** |
nothing calls this directly
no test coverage detected