Skip to content

Commit d82d281

Browse files
authored
[EXPORTER] Handle OTLP partial success response (#4104)
1 parent 353fa6b commit d82d281

23 files changed

Lines changed: 1198 additions & 279 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ Increment the:
113113
* [SEMANTIC CONVENTIONS] Generate event name constants (e.g. `semconv::exception::kException`)
114114
[#4171](https://github.com/open-telemetry/opentelemetry-cpp/pull/4171)
115115

116+
* [EXPORTER] Handle OTLP partial success response
117+
[#4104](https://github.com/open-telemetry/opentelemetry-cpp/pull/4104)
118+
116119
## [1.27.0] 2026-05-13
117120

118121
* [RELEASE] Bump main branch to 1.27.0-dev

exporters/otlp/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ cc_library(
209209
"//sdk/src/common:base64",
210210
"@com_github_opentelemetry_proto//:common_proto_cc",
211211
"@com_google_absl//absl/strings",
212+
"@com_google_protobuf//:protobuf",
212213
"@github_nlohmann_json//:json",
213214
],
214215
)
@@ -695,6 +696,7 @@ cc_test(
695696
":otlp_grpc_exporter",
696697
"//api",
697698
"//sdk/src/metrics",
699+
"//test_common:headers",
698700
"@com_google_googletest//:gtest_main",
699701
],
700702
)
@@ -876,6 +878,7 @@ cc_test(
876878
"//api",
877879
"//sdk/src/logs",
878880
"//sdk/src/metrics",
881+
"//test_common:headers",
879882
"@com_google_googletest//:gtest_main",
880883
],
881884
)
@@ -909,6 +912,7 @@ cc_test(
909912
":otlp_grpc_metric_exporter",
910913
"//api",
911914
"//sdk/src/metrics",
915+
"//test_common:headers",
912916
"@com_google_googletest//:gtest_main",
913917
],
914918
)

exporters/otlp/CMakeLists.txt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ if(BUILD_TESTING)
907907
add_executable(otlp_grpc_exporter_test test/otlp_grpc_exporter_test.cc)
908908
target_link_libraries(
909909
otlp_grpc_exporter_test ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
910-
${GMOCK_LIB} opentelemetry_exporter_otlp_grpc)
910+
${GMOCK_LIB} opentelemetry_exporter_otlp_grpc opentelemetry_test_common)
911911
gtest_add_tests(
912912
TARGET otlp_grpc_exporter_test
913913
TEST_PREFIX exporter.otlp.
@@ -942,7 +942,8 @@ if(BUILD_TESTING)
942942
opentelemetry_exporter_otlp_grpc
943943
opentelemetry_exporter_otlp_grpc_log
944944
opentelemetry_trace
945-
opentelemetry_logs)
945+
opentelemetry_logs
946+
opentelemetry_test_common)
946947
gtest_add_tests(
947948
TARGET otlp_grpc_log_record_exporter_test
948949
TEST_PREFIX exporter.otlp.
@@ -962,9 +963,13 @@ if(BUILD_TESTING)
962963
add_executable(otlp_grpc_metric_exporter_test
963964
test/otlp_grpc_metric_exporter_test.cc)
964965
target_link_libraries(
965-
otlp_grpc_metric_exporter_test ${GTEST_BOTH_LIBRARIES}
966-
${CMAKE_THREAD_LIBS_INIT} ${GMOCK_LIB} opentelemetry_exporter_otlp_grpc
967-
opentelemetry_exporter_otlp_grpc_metrics)
966+
otlp_grpc_metric_exporter_test
967+
${GTEST_BOTH_LIBRARIES}
968+
${CMAKE_THREAD_LIBS_INIT}
969+
${GMOCK_LIB}
970+
opentelemetry_exporter_otlp_grpc
971+
opentelemetry_exporter_otlp_grpc_metrics
972+
opentelemetry_test_common)
968973
gtest_add_tests(
969974
TARGET otlp_grpc_metric_exporter_test
970975
TEST_PREFIX exporter.otlp.
@@ -992,7 +997,8 @@ if(BUILD_TESTING)
992997
opentelemetry_exporter_otlp_http
993998
opentelemetry_http_client_nosend
994999
nlohmann_json::nlohmann_json
995-
protobuf::libprotobuf)
1000+
protobuf::libprotobuf
1001+
opentelemetry_test_common)
9961002
gtest_add_tests(
9971003
TARGET otlp_http_exporter_test
9981004
TEST_PREFIX exporter.otlp.
@@ -1018,7 +1024,8 @@ if(BUILD_TESTING)
10181024
opentelemetry_exporter_otlp_http_log
10191025
opentelemetry_logs
10201026
opentelemetry_http_client_nosend
1021-
nlohmann_json::nlohmann_json)
1027+
nlohmann_json::nlohmann_json
1028+
opentelemetry_test_common)
10221029
gtest_add_tests(
10231030
TARGET otlp_http_log_record_exporter_test
10241031
TEST_PREFIX exporter.otlp.
@@ -1046,7 +1053,8 @@ if(BUILD_TESTING)
10461053
opentelemetry_metrics
10471054
opentelemetry_http_client_nosend
10481055
nlohmann_json::nlohmann_json
1049-
protobuf::libprotobuf)
1056+
protobuf::libprotobuf
1057+
opentelemetry_test_common)
10501058
gtest_add_tests(
10511059
TARGET otlp_http_metric_exporter_test
10521060
TEST_PREFIX exporter.otlp.

exporters/otlp/include/opentelemetry/exporters/otlp/otlp_grpc_client.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <grpcpp/support/status.h>
88
#include <atomic>
99
#include <chrono>
10+
#include <functional>
1011
#include <memory>
1112
#include <string>
1213

@@ -22,8 +23,6 @@
2223
// clang-format on
2324

2425
#ifdef ENABLE_ASYNC_EXPORT
25-
# include <functional>
26-
2726
# include "opentelemetry/sdk/common/exporter_utils.h"
2827
#endif /* ENABLE_ASYNC_EXPORT */
2928

@@ -151,21 +150,30 @@ class OtlpGrpcClient
151150
std::unique_ptr<grpc::ClientContext> &&context,
152151
std::unique_ptr<google::protobuf::Arena> &&arena,
153152
proto::collector::trace::v1::ExportTraceServiceRequest *request,
154-
proto::collector::trace::v1::ExportTraceServiceResponse *response);
153+
proto::collector::trace::v1::ExportTraceServiceResponse *response,
154+
std::function<void(std::unique_ptr<google::protobuf::Arena> &&,
155+
proto::collector::trace::v1::ExportTraceServiceResponse *)> &&on_complete =
156+
{});
155157

156158
static grpc::Status DelegateExport(
157159
proto::collector::metrics::v1::MetricsService::StubInterface *stub,
158160
std::unique_ptr<grpc::ClientContext> &&context,
159161
std::unique_ptr<google::protobuf::Arena> &&arena,
160162
proto::collector::metrics::v1::ExportMetricsServiceRequest *request,
161-
proto::collector::metrics::v1::ExportMetricsServiceResponse *response);
163+
proto::collector::metrics::v1::ExportMetricsServiceResponse *response,
164+
std::function<void(std::unique_ptr<google::protobuf::Arena> &&,
165+
proto::collector::metrics::v1::ExportMetricsServiceResponse *)>
166+
&&on_complete = {});
162167

163168
static grpc::Status DelegateExport(
164169
proto::collector::logs::v1::LogsService::StubInterface *stub,
165170
std::unique_ptr<grpc::ClientContext> &&context,
166171
std::unique_ptr<google::protobuf::Arena> &&arena,
167172
proto::collector::logs::v1::ExportLogsServiceRequest *request,
168-
proto::collector::logs::v1::ExportLogsServiceResponse *response);
173+
proto::collector::logs::v1::ExportLogsServiceResponse *response,
174+
std::function<void(std::unique_ptr<google::protobuf::Arena> &&,
175+
proto::collector::logs::v1::ExportLogsServiceResponse *)> &&on_complete =
176+
{});
169177

170178
void AddReference(OtlpGrpcClientReferenceGuard &guard,
171179
const OtlpGrpcClientOptions &options) noexcept;

exporters/otlp/include/opentelemetry/exporters/otlp/otlp_http_client.h

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,14 @@
2424
#include "opentelemetry/sdk/common/thread_instrumentation.h"
2525
#include "opentelemetry/version.h"
2626

27-
// forward declare google::protobuf::Message
27+
// forward declare google::protobuf::Message and google::protobuf::Arena
2828
namespace google
2929
{
3030
namespace protobuf
3131
{
32+
class Arena;
3233
class Message;
33-
}
34+
} // namespace protobuf
3435
} // namespace google
3536

3637
OPENTELEMETRY_BEGIN_NAMESPACE
@@ -183,35 +184,23 @@ class OtlpHttpClient
183184
OtlpHttpClient &operator=(OtlpHttpClient &&) = delete;
184185

185186
/**
186-
* Sync export
187-
* @param message message to export, it should be ExportTraceServiceRequest,
188-
* ExportMetricsServiceRequest or ExportLogsServiceRequest
189-
* @return return the status of this operation
190-
*/
191-
sdk::common::ExportResult Export(const google::protobuf::Message &message) noexcept;
192-
193-
/**
194-
* Async export
195-
* @param message message to export, it should be ExportTraceServiceRequest,
196-
* ExportMetricsServiceRequest or ExportLogsServiceRequest
197-
* @param result_callback callback to call when the exporting is done
198-
* @return return the status of this operation
199-
*/
200-
sdk::common::ExportResult Export(
201-
const google::protobuf::Message &message,
202-
std::function<bool(opentelemetry::sdk::common::ExportResult)> &&result_callback) noexcept;
203-
204-
/**
205-
* Async export
187+
* Export message with typed response. Synchronous when max_running_requests is 0,
188+
* asynchronous otherwise.
206189
* @param message message to export, it should be ExportTraceServiceRequest,
207190
* ExportMetricsServiceRequest or ExportLogsServiceRequest
191+
* @param arena protobuf arena that owns response
192+
* @param response the parsed body is written here on 2xx
208193
* @param result_callback callback to call when the exporting is done
209194
* @param max_running_requests wait for at most max_running_requests running requests
210-
* @return return the status of this operation
195+
* @return the export result; kSuccess for asynchronous exports (final result via
196+
* result_callback)
211197
*/
212198
sdk::common::ExportResult Export(
213199
const google::protobuf::Message &message,
214-
std::function<bool(opentelemetry::sdk::common::ExportResult)> &&result_callback,
200+
std::unique_ptr<google::protobuf::Arena> &&arena,
201+
google::protobuf::Message *response,
202+
std::function<bool(opentelemetry::sdk::common::ExportResult, google::protobuf::Message *)>
203+
&&result_callback,
215204
std::size_t max_running_requests) noexcept;
216205

217206
/**
@@ -252,23 +241,36 @@ class OtlpHttpClient
252241
std::shared_ptr<opentelemetry::ext::http::client::Session> session;
253242
std::shared_ptr<opentelemetry::ext::http::client::EventHandler> event_handle;
254243

255-
HttpSessionData() = default;
244+
std::unique_ptr<google::protobuf::Arena> arena;
245+
google::protobuf::Message *response = nullptr;
246+
247+
HttpSessionData() noexcept;
248+
HttpSessionData(std::shared_ptr<opentelemetry::ext::http::client::Session> &&input_session,
249+
std::shared_ptr<opentelemetry::ext::http::client::EventHandler> &&input_handle,
250+
std::unique_ptr<google::protobuf::Arena> &&input_arena,
251+
google::protobuf::Message *input_response) noexcept;
256252

257-
explicit HttpSessionData(
258-
std::shared_ptr<opentelemetry::ext::http::client::Session> &&input_session,
259-
std::shared_ptr<opentelemetry::ext::http::client::EventHandler> &&input_handle)
260-
: session(std::move(input_session)), event_handle(std::move(input_handle))
261-
{}
253+
~HttpSessionData();
254+
HttpSessionData(HttpSessionData &&) noexcept;
255+
HttpSessionData &operator=(HttpSessionData &&) noexcept;
256+
HttpSessionData(const HttpSessionData &) = delete;
257+
HttpSessionData &operator=(const HttpSessionData &) = delete;
262258
};
263259

264260
/**
265-
* @brief Create a Session object or return a error result
261+
* @brief Create a Session object that deserializes the response body or return an error result.
266262
*
267263
* @param message The message to send
264+
* @param arena Protobuf arena that owns response
265+
* @param response the parsed body is written here on 2xx
266+
* @param result_callback Callback for the export result; receives the populated response
268267
*/
269268
nostd::variant<sdk::common::ExportResult, HttpSessionData> createSession(
270269
const google::protobuf::Message &message,
271-
std::function<bool(opentelemetry::sdk::common::ExportResult)> &&result_callback) noexcept;
270+
std::unique_ptr<google::protobuf::Arena> &&arena,
271+
google::protobuf::Message *response,
272+
std::function<bool(opentelemetry::sdk::common::ExportResult, google::protobuf::Message *)>
273+
&&result_callback) noexcept;
272274

273275
/**
274276
* Add http session and hold it's lifetime.

exporters/otlp/src/otlp_grpc_client.cc

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -514,35 +514,60 @@ grpc::Status OtlpGrpcClient::DelegateExport(
514514
std::unique_ptr<grpc::ClientContext> &&context,
515515
std::unique_ptr<google::protobuf::Arena> &&arena,
516516
proto::collector::trace::v1::ExportTraceServiceRequest *request,
517-
proto::collector::trace::v1::ExportTraceServiceResponse *response)
517+
proto::collector::trace::v1::ExportTraceServiceResponse *response,
518+
std::function<void(std::unique_ptr<google::protobuf::Arena> &&,
519+
proto::collector::trace::v1::ExportTraceServiceResponse *)> &&on_complete)
518520
{
519521
auto trace_grpc_context = std::move(context);
520522
auto trace_arena = std::move(arena);
521-
return stub->Export(trace_grpc_context.get(), *request, response);
523+
auto status = stub->Export(trace_grpc_context.get(), *request, response);
524+
if (status.ok() && on_complete)
525+
{
526+
auto callback = std::move(on_complete);
527+
callback(std::move(trace_arena), response);
528+
}
529+
return status;
522530
}
523531

524532
grpc::Status OtlpGrpcClient::DelegateExport(
525533
proto::collector::metrics::v1::MetricsService::StubInterface *stub,
526534
std::unique_ptr<grpc::ClientContext> &&context,
527535
std::unique_ptr<google::protobuf::Arena> &&arena,
528536
proto::collector::metrics::v1::ExportMetricsServiceRequest *request,
529-
proto::collector::metrics::v1::ExportMetricsServiceResponse *response)
537+
proto::collector::metrics::v1::ExportMetricsServiceResponse *response,
538+
std::function<void(std::unique_ptr<google::protobuf::Arena> &&,
539+
proto::collector::metrics::v1::ExportMetricsServiceResponse *)>
540+
&&on_complete)
530541
{
531542
auto metrics_grpc_context = std::move(context);
532543
auto metrics_arena = std::move(arena);
533-
return stub->Export(metrics_grpc_context.get(), *request, response);
544+
auto status = stub->Export(metrics_grpc_context.get(), *request, response);
545+
if (status.ok() && on_complete)
546+
{
547+
auto callback = std::move(on_complete);
548+
callback(std::move(metrics_arena), response);
549+
}
550+
return status;
534551
}
535552

536553
grpc::Status OtlpGrpcClient::DelegateExport(
537554
proto::collector::logs::v1::LogsService::StubInterface *stub,
538555
std::unique_ptr<grpc::ClientContext> &&context,
539556
std::unique_ptr<google::protobuf::Arena> &&arena,
540557
proto::collector::logs::v1::ExportLogsServiceRequest *request,
541-
proto::collector::logs::v1::ExportLogsServiceResponse *response)
558+
proto::collector::logs::v1::ExportLogsServiceResponse *response,
559+
std::function<void(std::unique_ptr<google::protobuf::Arena> &&,
560+
proto::collector::logs::v1::ExportLogsServiceResponse *)> &&on_complete)
542561
{
543562
auto logs_grpc_context = std::move(context);
544563
auto logs_arena = std::move(arena);
545-
return stub->Export(logs_grpc_context.get(), *request, response);
564+
auto status = stub->Export(logs_grpc_context.get(), *request, response);
565+
if (status.ok() && on_complete)
566+
{
567+
auto callback = std::move(on_complete);
568+
callback(std::move(logs_arena), response);
569+
}
570+
return status;
546571
}
547572

548573
void OtlpGrpcClient::AddReference(OtlpGrpcClientReferenceGuard &guard,

0 commit comments

Comments
 (0)