From a82b08596ed1bb60a483431058fb576bb4d91a87 Mon Sep 17 00:00:00 2001 From: Adesh Kumar Date: Tue, 3 Mar 2026 10:57:08 -0500 Subject: [PATCH 1/3] feat: add C language binding --- CMakeLists.txt | 5 + bin/format | 2 +- binding/c/CMakeLists.txt | 92 +++++++++ binding/c/include/datadog/c/tracer.h | 237 ++++++++++++++++++++++++ binding/c/src/tracer.cpp | 266 +++++++++++++++++++++++++++ binding/c/test/test_c_binding.cpp | 181 ++++++++++++++++++ 6 files changed, 782 insertions(+), 1 deletion(-) create mode 100644 binding/c/CMakeLists.txt create mode 100644 binding/c/include/datadog/c/tracer.h create mode 100644 binding/c/src/tracer.cpp create mode 100644 binding/c/test/test_c_binding.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c8f49148..be16ed34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ project( option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_STATIC_LIBS "Build static libraries" ON) +option(BUILD_C_BINDING "Build C binding" OFF) if (WIN32) option(DD_TRACE_STATIC_CRT "Build dd-trace-cpp with static CRT with MSVC" OFF) @@ -382,3 +383,7 @@ install( "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" ) + +if (BUILD_C_BINDING) + add_subdirectory(binding/c) +endif () diff --git a/bin/format b/bin/format index 6a5d66ac..4dc98b48 100755 --- a/bin/format +++ b/bin/format @@ -18,7 +18,7 @@ formatter=clang-format-$version formatter_options="--style=file -i $*" find_sources() { - find include/ src/ examples/ test/ fuzz/ -type f \( -name '*.h' -o -name '*.cpp' \) "$@" + find include/ src/ examples/ test/ fuzz/ binding/ -type f \( -name '*.h' -o -name '*.cpp' \) "$@" } # If the correct version of clang-format is installed, then use it and quit. diff --git a/binding/c/CMakeLists.txt b/binding/c/CMakeLists.txt new file mode 100644 index 00000000..6bcac07f --- /dev/null +++ b/binding/c/CMakeLists.txt @@ -0,0 +1,92 @@ +add_library(dd_trace_c SHARED) +add_library(dd-trace-cpp::c_binding ALIAS dd_trace_c) + +add_dependencies(dd_trace_c dd-trace-cpp::obj) + +target_compile_definitions(dd_trace_c PRIVATE DDOG_TRACE_C_BUILDING) + +target_sources(dd_trace_c + PRIVATE + $ + src/tracer.cpp +) + +if(DD_TRACE_TRANSPORT STREQUAL "curl") + add_dependencies(dd_trace_c CURL::libcurl_shared) + + target_sources(dd_trace_c + PRIVATE + ${CMAKE_SOURCE_DIR}/src/datadog/curl.cpp + ${CMAKE_SOURCE_DIR}/src/datadog/default_http_client_curl.cpp + ) + + target_link_libraries(dd_trace_c + PRIVATE + CURL::libcurl_shared + ) +else() + target_sources(dd_trace_c + PRIVATE + ${CMAKE_SOURCE_DIR}/src/datadog/default_http_client_null.cpp + ) +endif() + +target_include_directories(dd_trace_c + PUBLIC + $ + $ + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(dd_trace_c + PUBLIC + dd-trace-cpp::obj + PRIVATE + dd-trace-cpp::specs +) + +install(TARGETS dd_trace_c + EXPORT dd-trace-cpp-targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/datadog + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +if (DD_TRACE_BUILD_TESTING) + add_executable(test_c_binding + test/test_c_binding.cpp + ${CMAKE_SOURCE_DIR}/test/test.cpp + ${CMAKE_SOURCE_DIR}/test/mocks/collectors.cpp + ${CMAKE_SOURCE_DIR}/test/mocks/loggers.cpp + ) + + target_include_directories(test_c_binding + PRIVATE + ${CMAKE_SOURCE_DIR}/test + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/datadog + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include/datadog + ${CMAKE_CURRENT_SOURCE_DIR}/include + ) + + target_compile_definitions(test_c_binding + PUBLIC + CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS + ) + + target_link_libraries(test_c_binding + PRIVATE + dd_trace_c + dd-trace-cpp::static + dd-trace-cpp::specs + ) + + catch_discover_tests(test_c_binding) +endif() diff --git a/binding/c/include/datadog/c/tracer.h b/binding/c/include/datadog/c/tracer.h new file mode 100644 index 00000000..231cdb05 --- /dev/null +++ b/binding/c/include/datadog/c/tracer.h @@ -0,0 +1,237 @@ +#ifndef DDOG_TRACE_C_TRACER_H +#define DDOG_TRACE_C_TRACER_H + +#if defined(_WIN32) +#if defined(DDOG_TRACE_C_BUILDING) +#define DDOG_TRACE_C_API __declspec(dllexport) +#else +#define DDOG_TRACE_C_API __declspec(dllimport) +#endif +#elif defined(__GNUC__) || defined(__clang__) +#define DDOG_TRACE_C_API __attribute__((visibility("default"))) +#else +#define DDOG_TRACE_C_API +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +// Callback used during trace context extraction. The tracer calls this +// function for each propagation header it needs to read (e.g. "x-datadog-*"). +// +// @param key Header name to look up +// @return Header value, or NULL if the header is not present. +// The returned pointer must remain valid until +// ddog_trace_tracer_extract_or_create_span returns. +typedef const char* (*ddog_trace_context_read_callback)(const char* key); + +// Callback used during trace context injection. The tracer calls this +// function for each propagation header it needs to write. +// +// @param key Header name to set +// @param value Header value to set +typedef void (*ddog_trace_context_write_callback)(const char* key, + const char* value); + +enum ddog_trace_tracer_option { + DDOG_TRACE_OPT_SERVICE_NAME = 0, + DDOG_TRACE_OPT_ENV = 1, + DDOG_TRACE_OPT_VERSION = 2, + DDOG_TRACE_OPT_AGENT_URL = 3, + DDOG_TRACE_OPT_INTEGRATION_NAME = 4, + DDOG_TRACE_OPT_INTEGRATION_VERSION = 5 +}; + +typedef void ddog_trace_conf_t; +typedef void ddog_trace_tracer_t; +typedef void ddog_trace_span_t; + +// Creates a tracer configuration instance. +// +// @return Configuration handle, or NULL on allocation failure +DDOG_TRACE_C_API ddog_trace_conf_t* ddog_trace_tracer_conf_new(); + +// Release a tracer configuration. Safe to call with NULL. +// +// @param handle Configuration handle to release +DDOG_TRACE_C_API void ddog_trace_tracer_conf_free(ddog_trace_conf_t* handle); + +// Set or update a configuration field. No-op if handle or value is NULL. +// +// @param handle Configuration handle +// @param option Configuration option +// @param value Configuration value +DDOG_TRACE_C_API void ddog_trace_tracer_conf_set( + ddog_trace_conf_t* handle, enum ddog_trace_tracer_option option, + const char* value); + +// Creates a tracer instance. +// +// @param conf_handle Configuration handle +// +// @return Tracer handle, or NULL on error (e.g. invalid config) +DDOG_TRACE_C_API ddog_trace_tracer_t* ddog_trace_tracer_new( + ddog_trace_conf_t* conf_handle); + +// Release a tracer instance. Safe to call with NULL. +// +// @param tracer_handle Tracer handle to release +DDOG_TRACE_C_API void ddog_trace_tracer_free( + ddog_trace_tracer_t* tracer_handle); + +// Create a span using a Tracer. +// +// @param tracer_handle Tracer handle +// @param name Name of the span +// +// @return Span handle, or NULL on error +DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_tracer_create_span( + ddog_trace_tracer_t* tracer_handle, const char* name); + +// Extract trace context from incoming headers, or create a new root span +// if extraction fails. Never returns an error span; on extraction failure +// a fresh root span is created. +// +// @param tracer_handle Tracer handle +// @param on_context_read Callback invoked to read propagation headers +// @param name Span name +// @param resource Resource name (may be NULL) +// +// @return Span handle, or NULL if arguments are invalid +DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_tracer_extract_or_create_span( + ddog_trace_tracer_t* tracer_handle, + ddog_trace_context_read_callback on_context_read, const char* name, + const char* resource); + +// Release a span instance. Safe to call with NULL. +// +// @param span_handle Span handle +DDOG_TRACE_C_API void ddog_trace_span_free(ddog_trace_span_t* span_handle); + +// Set a tag (key-value pair) on a span. No-op if any argument is NULL. +// +// @param span_handle Span handle +// @param key Tag key +// @param value Tag value +DDOG_TRACE_C_API void ddog_trace_span_set_tag(ddog_trace_span_t* span_handle, + const char* key, + const char* value); + +// Mark a span as erroneous. No-op if span_handle is NULL. +// +// @param span_handle Span handle +// @param error_value Non-zero to mark as error, zero to clear +DDOG_TRACE_C_API void ddog_trace_span_set_error(ddog_trace_span_t* span_handle, + int error_value); + +// Set an error message on a span. No-op if any argument is NULL. +// +// @param span_handle Span handle +// @param error_message Error message string +DDOG_TRACE_C_API void ddog_trace_span_set_error_message( + ddog_trace_span_t* span_handle, const char* error_message); + +// Inject trace context into outgoing headers via callback. +// No-op if any argument is NULL. +// +// @param span_handle Span handle +// @param on_context_write Callback invoked per propagation header +DDOG_TRACE_C_API void ddog_trace_span_inject( + ddog_trace_span_t* span_handle, + ddog_trace_context_write_callback on_context_write); + +// Create a child span. Returns NULL if any required argument is NULL. +// +// @param span_handle Parent span handle +// @param name Name of the child span +// +// @return Child span handle, or NULL +DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_span_create_child( + ddog_trace_span_t* span_handle, const char* name); + +// Finish a span by recording its end time. No-op if span_handle is NULL. +// After finishing, the span should be freed with ddog_trace_span_free. +// +// @param span_handle Span handle +DDOG_TRACE_C_API void ddog_trace_span_finish(ddog_trace_span_t* span_handle); + +// Get the trace ID as a zero-padded hex string. +// +// @param span_handle Span handle +// @param buffer Output buffer (at least 33 bytes for 128-bit IDs) +// @param buffer_size Size of the buffer +// @return Number of characters written, or -1 on error +DDOG_TRACE_C_API int ddog_trace_span_get_trace_id( + ddog_trace_span_t* span_handle, char* buffer, int buffer_size); + +// Get the span ID as a zero-padded hex string. +// +// @param span_handle Span handle +// @param buffer Output buffer (at least 17 bytes) +// @param buffer_size Size of the buffer +// @return Number of characters written (16), or -1 on error +DDOG_TRACE_C_API int ddog_trace_span_get_span_id(ddog_trace_span_t* span_handle, + char* buffer, int buffer_size); + +// Set the resource name on a span. No-op if any argument is NULL. +// +// @param span_handle Span handle +// @param resource Resource name +DDOG_TRACE_C_API void ddog_trace_span_set_resource( + ddog_trace_span_t* span_handle, const char* resource); + +// Set the service name on a span. No-op if any argument is NULL. +// +// @param span_handle Span handle +// @param service Service name +DDOG_TRACE_C_API void ddog_trace_span_set_service( + ddog_trace_span_t* span_handle, const char* service); + +// Set multiple tags at once. No-op if any required argument is NULL or +// count <= 0. Individual entries where key or value is NULL are skipped. +// +// @param span_handle Span handle +// @param keys Array of tag keys +// @param values Array of tag values +// @param count Number of tags +DDOG_TRACE_C_API void ddog_trace_span_set_tags(ddog_trace_span_t* span_handle, + const char** keys, + const char** values, int count); + +// Get the sampling priority for the trace this span belongs to. +// +// @param span_handle Span handle +// @param priority Output: sampling priority value +// @return 1 if a priority was written, 0 if no decision yet, +// -1 on error (NULL arguments) +DDOG_TRACE_C_API int ddog_trace_span_get_sampling_priority( + ddog_trace_span_t* span_handle, int* priority); + +// Override the sampling priority for the trace this span belongs to. +// No-op if span_handle is NULL. +// +// @param span_handle Span handle +// @param priority Sampling priority value +DDOG_TRACE_C_API void ddog_trace_span_set_sampling_priority( + ddog_trace_span_t* span_handle, int priority); + +// Create a child span with explicit service and resource names. +// Returns NULL if span_handle or name is NULL. service and resource may +// be NULL (inherits from parent). +// +// @param span_handle Parent span handle +// @param name Span name (required) +// @param service Service name (may be NULL) +// @param resource Resource name (may be NULL) +// +// @return Child span handle, or NULL +DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_span_create_child_with_options( + ddog_trace_span_t* span_handle, const char* name, const char* service, + const char* resource); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/binding/c/src/tracer.cpp b/binding/c/src/tracer.cpp new file mode 100644 index 00000000..90ef6f91 --- /dev/null +++ b/binding/c/src/tracer.cpp @@ -0,0 +1,266 @@ +#include "datadog/c/tracer.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace dd = datadog::tracing; + +class ContextReader : public dd::DictReader { + ddog_trace_context_read_callback read_; + + public: + explicit ContextReader(ddog_trace_context_read_callback read_callback) + : read_(read_callback) {} + + dd::Optional lookup(dd::StringView key) const override { + std::string key_str(key); + if (auto value = read_(key_str.c_str())) { + return value; + } + return dd::nullopt; + } + + void visit(const std::function& /* visitor */) + const override {} +}; + +class ContextWriter : public dd::DictWriter { + ddog_trace_context_write_callback write_; + + public: + explicit ContextWriter(ddog_trace_context_write_callback func) + : write_(func) {} + + void set(dd::StringView key, dd::StringView value) override { + std::string key_str(key); + std::string value_str(value); + write_(key_str.c_str(), value_str.c_str()); + } +}; + +extern "C" { + +ddog_trace_conf_t* ddog_trace_tracer_conf_new() { return new dd::TracerConfig; } + +void ddog_trace_tracer_conf_free(ddog_trace_conf_t* handle) { + if (!handle) return; + delete static_cast(handle); +} + +void ddog_trace_tracer_conf_set(ddog_trace_conf_t* handle, + enum ddog_trace_tracer_option option, + const char* value) { + if (!handle || !value) return; + + auto* cfg = static_cast(handle); + + if (option == DDOG_TRACE_OPT_SERVICE_NAME) { + cfg->service = value; + } else if (option == DDOG_TRACE_OPT_ENV) { + cfg->environment = value; + } else if (option == DDOG_TRACE_OPT_VERSION) { + cfg->version = value; + } else if (option == DDOG_TRACE_OPT_AGENT_URL) { + cfg->agent.url = value; + } else if (option == DDOG_TRACE_OPT_INTEGRATION_NAME) { + cfg->integration_name = value; + } else if (option == DDOG_TRACE_OPT_INTEGRATION_VERSION) { + cfg->integration_version = value; + } +} + +ddog_trace_tracer_t* ddog_trace_tracer_new(ddog_trace_conf_t* conf_handle) { + if (!conf_handle) return nullptr; + + auto* config = static_cast(conf_handle); + const auto validated_config = dd::finalize_config(*config); + if (!validated_config) { + return nullptr; + } + + return new dd::Tracer{*validated_config}; +} + +void ddog_trace_tracer_free(ddog_trace_tracer_t* tracer_handle) { + if (!tracer_handle) return; + delete static_cast(tracer_handle); +} + +ddog_trace_span_t* ddog_trace_tracer_create_span( + ddog_trace_tracer_t* tracer_handle, const char* name) { + if (!tracer_handle || !name) return nullptr; + + auto* tracer = static_cast(tracer_handle); + dd::SpanConfig opt; + opt.name = name; + + return new dd::Span(tracer->create_span(opt)); +} + +ddog_trace_span_t* ddog_trace_tracer_extract_or_create_span( + ddog_trace_tracer_t* tracer_handle, + ddog_trace_context_read_callback on_context_read, const char* name, + const char* resource) { + if (!tracer_handle || !on_context_read || !name) return nullptr; + + auto* tracer = static_cast(tracer_handle); + dd::SpanConfig span_config; + span_config.name = name; + if (resource) { + span_config.resource = resource; + } + + ContextReader reader(on_context_read); + return new dd::Span(tracer->extract_or_create_span(reader, span_config)); +} + +void ddog_trace_span_free(ddog_trace_span_t* span_handle) { + if (!span_handle) return; + delete static_cast(span_handle); +} + +void ddog_trace_span_set_tag(ddog_trace_span_t* span_handle, const char* key, + const char* value) { + if (!span_handle || !key || !value) return; + static_cast(span_handle)->set_tag(key, value); +} + +void ddog_trace_span_set_error(ddog_trace_span_t* span_handle, + int error_value) { + if (!span_handle) return; + static_cast(span_handle)->set_error(error_value != 0); +} + +void ddog_trace_span_set_error_message(ddog_trace_span_t* span_handle, + const char* error_message) { + if (!span_handle || !error_message) return; + static_cast(span_handle)->set_error_message(error_message); +} + +void ddog_trace_span_inject( + ddog_trace_span_t* span_handle, + ddog_trace_context_write_callback on_context_write) { + if (!span_handle || !on_context_write) return; + + auto* span = static_cast(span_handle); + ContextWriter writer(on_context_write); + span->inject(writer); +} + +ddog_trace_span_t* ddog_trace_span_create_child(ddog_trace_span_t* span_handle, + const char* name) { + if (!span_handle || !name) return nullptr; + + auto* span = static_cast(span_handle); + dd::SpanConfig config; + config.name = name; + + return new dd::Span(span->create_child(config)); +} + +void ddog_trace_span_finish(ddog_trace_span_t* span_handle) { + if (!span_handle) return; + static_cast(span_handle) + ->set_end_time(std::chrono::steady_clock::now()); +} + +int ddog_trace_span_get_trace_id(ddog_trace_span_t* span_handle, char* buffer, + int buffer_size) { + if (!span_handle || !buffer || buffer_size <= 0) return -1; + + auto* span = static_cast(span_handle); + std::string hex = span->trace_id().hex_padded(); + + if (static_cast(hex.size()) >= buffer_size) return -1; + + std::strncpy(buffer, hex.c_str(), buffer_size); + buffer[buffer_size - 1] = '\0'; + return static_cast(hex.size()); +} + +int ddog_trace_span_get_span_id(ddog_trace_span_t* span_handle, char* buffer, + int buffer_size) { + if (!span_handle || !buffer || buffer_size <= 0) return -1; + + auto* span = static_cast(span_handle); + std::string hex = dd::hex_padded(span->id()); + + if (static_cast(hex.size()) >= buffer_size) return -1; + + std::strncpy(buffer, hex.c_str(), buffer_size); + buffer[buffer_size - 1] = '\0'; + return static_cast(hex.size()); +} + +void ddog_trace_span_set_resource(ddog_trace_span_t* span_handle, + const char* resource) { + if (!span_handle || !resource) return; + static_cast(span_handle)->set_resource_name(resource); +} + +void ddog_trace_span_set_service(ddog_trace_span_t* span_handle, + const char* service) { + if (!span_handle || !service) return; + static_cast(span_handle)->set_service_name(service); +} + +void ddog_trace_span_set_tags(ddog_trace_span_t* span_handle, const char** keys, + const char** values, int count) { + if (!span_handle || !keys || !values || count <= 0) return; + + auto* span = static_cast(span_handle); + for (int i = 0; i < count; ++i) { + if (keys[i] && values[i]) { + span->set_tag(keys[i], values[i]); + } + } +} + +int ddog_trace_span_get_sampling_priority(ddog_trace_span_t* span_handle, + int* priority) { + if (!span_handle || !priority) return -1; + + auto* span = static_cast(span_handle); + auto decision = span->trace_segment().sampling_decision(); + if (decision) { + *priority = decision->priority; + return 1; + } + return 0; +} + +void ddog_trace_span_set_sampling_priority(ddog_trace_span_t* span_handle, + int priority) { + if (!span_handle) return; + static_cast(span_handle) + ->trace_segment() + .override_sampling_priority(priority); +} + +ddog_trace_span_t* ddog_trace_span_create_child_with_options( + ddog_trace_span_t* span_handle, const char* name, const char* service, + const char* resource) { + if (!span_handle || !name) return nullptr; + + auto* span = static_cast(span_handle); + dd::SpanConfig config; + config.name = name; + if (service) { + config.service = service; + } + if (resource) { + config.resource = resource; + } + + return new dd::Span(span->create_child(config)); +} + +} // extern "C" diff --git a/binding/c/test/test_c_binding.cpp b/binding/c/test/test_c_binding.cpp new file mode 100644 index 00000000..c7a3ea66 --- /dev/null +++ b/binding/c/test/test_c_binding.cpp @@ -0,0 +1,181 @@ +#define CATCH_CONFIG_MAIN + +#include +#include +#include +#include + +#include +#include +#include + +#include "mocks/collectors.h" +#include "null_logger.h" +#include "test.h" + +namespace dd = datadog::tracing; + +namespace { + +std::shared_ptr g_collector; + +ddog_trace_tracer_t* make_test_tracer() { + auto collector = std::make_shared(); + g_collector = collector; + + dd::TracerConfig cfg; + cfg.service = "test-service"; + cfg.collector = collector; + cfg.logger = std::make_shared(); + + auto finalized = dd::finalize_config(cfg); + REQUIRE(finalized); + + auto* tracer = new dd::Tracer{*finalized}; + return static_cast(tracer); +} + +std::unordered_map g_headers; + +const char* test_header_reader(const char* key) { + auto it = g_headers.find(key); + if (it != g_headers.end()) { + return it->second.c_str(); + } + return nullptr; +} + +void test_header_writer(const char* key, const char* value) { + g_headers[key] = value; +} + +} // namespace + +TEST_CASE("tracer lifecycle", "[c_binding]") { + auto* conf = ddog_trace_tracer_conf_new(); + REQUIRE(conf != nullptr); + + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_SERVICE_NAME, "my-service"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_ENV, "staging"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_VERSION, "1.0.0"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_AGENT_URL, + "http://localhost:8126"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_NAME, + "my-integration"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_VERSION, "2.0.0"); + + ddog_trace_tracer_conf_free(conf); + + CHECK(ddog_trace_tracer_new(nullptr) == nullptr); + + auto* tracer = make_test_tracer(); + REQUIRE(tracer != nullptr); + ddog_trace_tracer_free(tracer); +} + +TEST_CASE("span create, tag, finish, free", "[c_binding]") { + auto* tracer = make_test_tracer(); + auto* span = ddog_trace_tracer_create_span(tracer, "test.op"); + REQUIRE(span != nullptr); + + ddog_trace_span_set_tag(span, "http.method", "GET"); + ddog_trace_span_set_resource(span, "GET /api/users"); + ddog_trace_span_set_service(span, "user-service"); + ddog_trace_span_set_error(span, 1); + ddog_trace_span_set_error_message(span, "something broke"); + + ddog_trace_span_finish(span); + ddog_trace_span_free(span); + ddog_trace_tracer_free(tracer); + + const auto& sd = g_collector->first_span(); + CHECK(sd.tags.at("http.method") == "GET"); + CHECK(sd.resource == "GET /api/users"); + CHECK(sd.service == "user-service"); + CHECK(sd.error == true); + CHECK(sd.tags.at("error.message") == "something broke"); +} + +TEST_CASE("inject then extract preserves trace ID", "[c_binding]") { + auto* tracer = make_test_tracer(); + + auto* span1 = ddog_trace_tracer_create_span(tracer, "producer"); + g_headers.clear(); + ddog_trace_span_inject(span1, test_header_writer); + CHECK(!g_headers.empty()); + + char trace_id_1[33] = {}; + ddog_trace_span_get_trace_id(span1, trace_id_1, sizeof(trace_id_1)); + + auto* span2 = ddog_trace_tracer_extract_or_create_span( + tracer, test_header_reader, "consumer", "GET /downstream"); + REQUIRE(span2 != nullptr); + + char trace_id_2[33] = {}; + ddog_trace_span_get_trace_id(span2, trace_id_2, sizeof(trace_id_2)); + + CHECK(std::string(trace_id_1) == std::string(trace_id_2)); + + ddog_trace_span_finish(span1); + ddog_trace_span_free(span1); + ddog_trace_span_finish(span2); + ddog_trace_span_free(span2); + ddog_trace_tracer_free(tracer); +} + +TEST_CASE("child span shares trace ID", "[c_binding]") { + auto* tracer = make_test_tracer(); + auto* parent = ddog_trace_tracer_create_span(tracer, "parent.op"); + REQUIRE(parent != nullptr); + + auto* child = ddog_trace_span_create_child(parent, "child.op"); + REQUIRE(child != nullptr); + + char parent_trace[33] = {}; + char child_trace[33] = {}; + ddog_trace_span_get_trace_id(parent, parent_trace, sizeof(parent_trace)); + ddog_trace_span_get_trace_id(child, child_trace, sizeof(child_trace)); + CHECK(std::string(parent_trace) == std::string(child_trace)); + + char parent_span_id[17] = {}; + char child_span_id[17] = {}; + ddog_trace_span_get_span_id(parent, parent_span_id, sizeof(parent_span_id)); + ddog_trace_span_get_span_id(child, child_span_id, sizeof(child_span_id)); + CHECK(std::string(parent_span_id) != std::string(child_span_id)); + + ddog_trace_span_finish(child); + ddog_trace_span_free(child); + ddog_trace_span_finish(parent); + ddog_trace_span_free(parent); + ddog_trace_tracer_free(tracer); +} + +TEST_CASE("null arguments do not crash", "[c_binding]") { + // Functions that return handles should return nullptr. + CHECK(ddog_trace_tracer_new(nullptr) == nullptr); + CHECK(ddog_trace_tracer_create_span(nullptr, "x") == nullptr); + CHECK(ddog_trace_span_create_child(nullptr, "x") == nullptr); + CHECK(ddog_trace_span_create_child_with_options(nullptr, "n", "s", "r") == + nullptr); + + char buf[33]; + CHECK(ddog_trace_span_get_trace_id(nullptr, buf, sizeof(buf)) == -1); + CHECK(ddog_trace_span_get_span_id(nullptr, buf, sizeof(buf)) == -1); + + int priority = 0; + CHECK(ddog_trace_span_get_sampling_priority(nullptr, &priority) == -1); + + // Void functions with null handles should simply not crash. + ddog_trace_tracer_conf_free(nullptr); + ddog_trace_tracer_conf_set(nullptr, DDOG_TRACE_OPT_SERVICE_NAME, "x"); + ddog_trace_tracer_free(nullptr); + ddog_trace_span_free(nullptr); + ddog_trace_span_set_tag(nullptr, "k", "v"); + ddog_trace_span_set_error(nullptr, 1); + ddog_trace_span_set_error_message(nullptr, "msg"); + ddog_trace_span_inject(nullptr, test_header_writer); + ddog_trace_span_finish(nullptr); + ddog_trace_span_set_resource(nullptr, "res"); + ddog_trace_span_set_service(nullptr, "svc"); + ddog_trace_span_set_sampling_priority(nullptr, 1); +} From 527d3e23cc1a0501467eac352bcec9ff275994f6 Mon Sep 17 00:00:00 2001 From: Adesh Kumar Date: Wed, 4 Mar 2026 17:23:52 -0500 Subject: [PATCH 2/3] refactor: address PR review feedback for C binding --- CMakeLists.txt | 14 +- bin/format | 2 +- binding/c/CMakeLists.txt | 22 +- binding/c/README.md | 17 ++ binding/c/include/datadog/c/tracer.h | 27 +- binding/c/src/tracer.cpp | 428 +++++++++++++++------------ binding/c/test/test_c_binding.cpp | 304 ++++++++++--------- 7 files changed, 454 insertions(+), 360 deletions(-) create mode 100644 binding/c/README.md diff --git a/CMakeLists.txt b/CMakeLists.txt index be16ed34..9d8d00b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,10 @@ if (DD_TRACE_BUILD_TOOLS) add_subdirectory(tools/config-inversion) endif () +if (BUILD_C_BINDING) + add_subdirectory(binding/c) +endif () + add_library(dd-trace-cpp-objects OBJECT) add_library(dd-trace-cpp::obj ALIAS dd-trace-cpp-objects) @@ -295,11 +299,11 @@ if (BUILD_SHARED_LIBS) ) install( - TARGETS dd-trace-cpp-shared + TARGETS dd-trace-cpp-shared EXPORT dd-trace-cpp-targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) endif () @@ -351,7 +355,7 @@ if (BUILD_STATIC_LIBS) EXPORT dd-trace-cpp-targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) endif () @@ -383,7 +387,3 @@ install( "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" ) - -if (BUILD_C_BINDING) - add_subdirectory(binding/c) -endif () diff --git a/bin/format b/bin/format index 4dc98b48..9bcc3993 100755 --- a/bin/format +++ b/bin/format @@ -18,7 +18,7 @@ formatter=clang-format-$version formatter_options="--style=file -i $*" find_sources() { - find include/ src/ examples/ test/ fuzz/ binding/ -type f \( -name '*.h' -o -name '*.cpp' \) "$@" + find binding/ examples/ fuzz/ include/ src/ test/ -type f \( -name '*.h' -o -name '*.cpp' \) "$@" } # If the correct version of clang-format is installed, then use it and quit. diff --git a/binding/c/CMakeLists.txt b/binding/c/CMakeLists.txt index 6bcac07f..eef24c97 100644 --- a/binding/c/CMakeLists.txt +++ b/binding/c/CMakeLists.txt @@ -1,8 +1,6 @@ add_library(dd_trace_c SHARED) add_library(dd-trace-cpp::c_binding ALIAS dd_trace_c) -add_dependencies(dd_trace_c dd-trace-cpp::obj) - target_compile_definitions(dd_trace_c PRIVATE DDOG_TRACE_C_BUILDING) target_sources(dd_trace_c @@ -11,9 +9,7 @@ target_sources(dd_trace_c src/tracer.cpp ) -if(DD_TRACE_TRANSPORT STREQUAL "curl") - add_dependencies(dd_trace_c CURL::libcurl_shared) - +if (DD_TRACE_TRANSPORT STREQUAL "curl") target_sources(dd_trace_c PRIVATE ${CMAKE_SOURCE_DIR}/src/datadog/curl.cpp @@ -24,12 +20,12 @@ if(DD_TRACE_TRANSPORT STREQUAL "curl") PRIVATE CURL::libcurl_shared ) -else() +else () target_sources(dd_trace_c PRIVATE ${CMAKE_SOURCE_DIR}/src/datadog/default_http_client_null.cpp ) -endif() +endif () target_include_directories(dd_trace_c PUBLIC @@ -40,9 +36,8 @@ target_include_directories(dd_trace_c ) target_link_libraries(dd_trace_c - PUBLIC - dd-trace-cpp::obj PRIVATE + dd-trace-cpp::obj dd-trace-cpp::specs ) @@ -50,7 +45,7 @@ install(TARGETS dd_trace_c EXPORT dd-trace-cpp-targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) install( @@ -72,10 +67,11 @@ if (DD_TRACE_BUILD_TESTING) ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/datadog ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/include/datadog ${CMAKE_CURRENT_SOURCE_DIR}/include ) + target_compile_features(test_c_binding PRIVATE cxx_std_17) + target_compile_definitions(test_c_binding PUBLIC CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS @@ -84,9 +80,7 @@ if (DD_TRACE_BUILD_TESTING) target_link_libraries(test_c_binding PRIVATE dd_trace_c - dd-trace-cpp::static - dd-trace-cpp::specs ) catch_discover_tests(test_c_binding) -endif() +endif () diff --git a/binding/c/README.md b/binding/c/README.md new file mode 100644 index 00000000..dff21294 --- /dev/null +++ b/binding/c/README.md @@ -0,0 +1,17 @@ +# C Binding for dd-trace-cpp + +A C language binding (`ddog_trace_*`) wrapping the C++ tracing library for +integration from C-based projects. + +## Building + +```sh +cmake -B build -DBUILD_C_BINDING=ON -DDD_TRACE_BUILD_TESTING=ON . +cmake --build build -j +``` + +## Running Tests + +```sh +./build/binding/c/test_c_binding +``` diff --git a/binding/c/include/datadog/c/tracer.h b/binding/c/include/datadog/c/tracer.h index 231cdb05..2a074698 100644 --- a/binding/c/include/datadog/c/tracer.h +++ b/binding/c/include/datadog/c/tracer.h @@ -1,5 +1,6 @@ -#ifndef DDOG_TRACE_C_TRACER_H -#define DDOG_TRACE_C_TRACER_H +#pragma once + +#include #if defined(_WIN32) #if defined(DDOG_TRACE_C_BUILDING) @@ -34,23 +35,23 @@ typedef const char* (*ddog_trace_context_read_callback)(const char* key); typedef void (*ddog_trace_context_write_callback)(const char* key, const char* value); -enum ddog_trace_tracer_option { +typedef enum { DDOG_TRACE_OPT_SERVICE_NAME = 0, DDOG_TRACE_OPT_ENV = 1, DDOG_TRACE_OPT_VERSION = 2, DDOG_TRACE_OPT_AGENT_URL = 3, DDOG_TRACE_OPT_INTEGRATION_NAME = 4, DDOG_TRACE_OPT_INTEGRATION_VERSION = 5 -}; +} ddog_trace_tracer_option; -typedef void ddog_trace_conf_t; -typedef void ddog_trace_tracer_t; -typedef void ddog_trace_span_t; +typedef struct ddog_trace_conf_s ddog_trace_conf_t; +typedef struct ddog_trace_tracer_s ddog_trace_tracer_t; +typedef struct ddog_trace_span_s ddog_trace_span_t; // Creates a tracer configuration instance. // // @return Configuration handle, or NULL on allocation failure -DDOG_TRACE_C_API ddog_trace_conf_t* ddog_trace_tracer_conf_new(); +DDOG_TRACE_C_API ddog_trace_conf_t* ddog_trace_tracer_conf_new(void); // Release a tracer configuration. Safe to call with NULL. // @@ -63,7 +64,7 @@ DDOG_TRACE_C_API void ddog_trace_tracer_conf_free(ddog_trace_conf_t* handle); // @param option Configuration option // @param value Configuration value DDOG_TRACE_C_API void ddog_trace_tracer_conf_set( - ddog_trace_conf_t* handle, enum ddog_trace_tracer_option option, + ddog_trace_conf_t* handle, ddog_trace_tracer_option option, const char* value); // Creates a tracer instance. @@ -163,7 +164,7 @@ DDOG_TRACE_C_API void ddog_trace_span_finish(ddog_trace_span_t* span_handle); // @param buffer_size Size of the buffer // @return Number of characters written, or -1 on error DDOG_TRACE_C_API int ddog_trace_span_get_trace_id( - ddog_trace_span_t* span_handle, char* buffer, int buffer_size); + ddog_trace_span_t* span_handle, char* buffer, size_t buffer_size); // Get the span ID as a zero-padded hex string. // @@ -171,8 +172,8 @@ DDOG_TRACE_C_API int ddog_trace_span_get_trace_id( // @param buffer Output buffer (at least 17 bytes) // @param buffer_size Size of the buffer // @return Number of characters written (16), or -1 on error -DDOG_TRACE_C_API int ddog_trace_span_get_span_id(ddog_trace_span_t* span_handle, - char* buffer, int buffer_size); +DDOG_TRACE_C_API int ddog_trace_span_get_span_id( + ddog_trace_span_t* span_handle, char* buffer, size_t buffer_size); // Set the resource name on a span. No-op if any argument is NULL. // @@ -233,5 +234,3 @@ DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_span_create_child_with_options( #if defined(__cplusplus) } #endif - -#endif diff --git a/binding/c/src/tracer.cpp b/binding/c/src/tracer.cpp index 90ef6f91..1eabcad4 100644 --- a/binding/c/src/tracer.cpp +++ b/binding/c/src/tracer.cpp @@ -1,266 +1,328 @@ #include "datadog/c/tracer.h" -#include -#include #include -#include #include #include +#include #include #include namespace dd = datadog::tracing; -class ContextReader : public dd::DictReader { - ddog_trace_context_read_callback read_; - - public: - explicit ContextReader(ddog_trace_context_read_callback read_callback) - : read_(read_callback) {} +namespace { - dd::Optional lookup(dd::StringView key) const override { - std::string key_str(key); - if (auto value = read_(key_str.c_str())) { - return value; +class ContextReader : public dd::DictReader { + ddog_trace_context_read_callback read_; + + public: + explicit ContextReader(ddog_trace_context_read_callback read_callback) + : read_(read_callback) {} + + dd::Optional lookup(dd::StringView key) const override { + std::string key_str(key); + if (auto value = read_(key_str.c_str())) { + return value; + } + return dd::nullopt; } - return dd::nullopt; - } - void visit(const std::function& /* visitor */) - const override {} + void + visit(const std::function + & /* visitor */) const override {} }; class ContextWriter : public dd::DictWriter { - ddog_trace_context_write_callback write_; + ddog_trace_context_write_callback write_; - public: - explicit ContextWriter(ddog_trace_context_write_callback func) - : write_(func) {} + public: + explicit ContextWriter(ddog_trace_context_write_callback func) + : write_(func) {} - void set(dd::StringView key, dd::StringView value) override { - std::string key_str(key); - std::string value_str(value); - write_(key_str.c_str(), value_str.c_str()); - } + void set(dd::StringView key, dd::StringView value) override { + std::string key_str(key); + std::string value_str(value); + write_(key_str.c_str(), value_str.c_str()); + } }; +} // namespace + extern "C" { -ddog_trace_conf_t* ddog_trace_tracer_conf_new() { return new dd::TracerConfig; } +ddog_trace_conf_t *ddog_trace_tracer_conf_new(void) { + try { + return reinterpret_cast(new dd::TracerConfig); + } catch (...) { + return nullptr; + } +} -void ddog_trace_tracer_conf_free(ddog_trace_conf_t* handle) { - if (!handle) return; - delete static_cast(handle); +void ddog_trace_tracer_conf_free(ddog_trace_conf_t *handle) { + if (!handle) + return; + delete reinterpret_cast(handle); } -void ddog_trace_tracer_conf_set(ddog_trace_conf_t* handle, - enum ddog_trace_tracer_option option, - const char* value) { - if (!handle || !value) return; - - auto* cfg = static_cast(handle); - - if (option == DDOG_TRACE_OPT_SERVICE_NAME) { - cfg->service = value; - } else if (option == DDOG_TRACE_OPT_ENV) { - cfg->environment = value; - } else if (option == DDOG_TRACE_OPT_VERSION) { - cfg->version = value; - } else if (option == DDOG_TRACE_OPT_AGENT_URL) { - cfg->agent.url = value; - } else if (option == DDOG_TRACE_OPT_INTEGRATION_NAME) { - cfg->integration_name = value; - } else if (option == DDOG_TRACE_OPT_INTEGRATION_VERSION) { - cfg->integration_version = value; - } +void ddog_trace_tracer_conf_set(ddog_trace_conf_t *handle, + ddog_trace_tracer_option option, + const char *value) { + if (!handle || !value) + return; + + auto *cfg = reinterpret_cast(handle); + + switch (option) { + case DDOG_TRACE_OPT_SERVICE_NAME: + cfg->service = value; + break; + case DDOG_TRACE_OPT_ENV: + cfg->environment = value; + break; + case DDOG_TRACE_OPT_VERSION: + cfg->version = value; + break; + case DDOG_TRACE_OPT_AGENT_URL: + cfg->agent.url = value; + break; + case DDOG_TRACE_OPT_INTEGRATION_NAME: + cfg->integration_name = value; + break; + case DDOG_TRACE_OPT_INTEGRATION_VERSION: + cfg->integration_version = value; + break; + } } -ddog_trace_tracer_t* ddog_trace_tracer_new(ddog_trace_conf_t* conf_handle) { - if (!conf_handle) return nullptr; +ddog_trace_tracer_t *ddog_trace_tracer_new(ddog_trace_conf_t *conf_handle) { + if (!conf_handle) + return nullptr; - auto* config = static_cast(conf_handle); - const auto validated_config = dd::finalize_config(*config); - if (!validated_config) { - return nullptr; - } + auto *config = reinterpret_cast(conf_handle); + const auto validated_config = dd::finalize_config(*config); + if (!validated_config) { + return nullptr; + } - return new dd::Tracer{*validated_config}; + try { + return reinterpret_cast( + new dd::Tracer{*validated_config}); + } catch (...) { + return nullptr; + } } -void ddog_trace_tracer_free(ddog_trace_tracer_t* tracer_handle) { - if (!tracer_handle) return; - delete static_cast(tracer_handle); +void ddog_trace_tracer_free(ddog_trace_tracer_t *tracer_handle) { + if (!tracer_handle) + return; + delete reinterpret_cast(tracer_handle); } -ddog_trace_span_t* ddog_trace_tracer_create_span( - ddog_trace_tracer_t* tracer_handle, const char* name) { - if (!tracer_handle || !name) return nullptr; - - auto* tracer = static_cast(tracer_handle); - dd::SpanConfig opt; - opt.name = name; - - return new dd::Span(tracer->create_span(opt)); +ddog_trace_span_t * +ddog_trace_tracer_create_span(ddog_trace_tracer_t *tracer_handle, + const char *name) { + if (!tracer_handle || !name) + return nullptr; + + auto *tracer = reinterpret_cast(tracer_handle); + dd::SpanConfig opt; + opt.name = name; + + try { + return reinterpret_cast( + new dd::Span(tracer->create_span(opt))); + } catch (...) { + return nullptr; + } } -ddog_trace_span_t* ddog_trace_tracer_extract_or_create_span( - ddog_trace_tracer_t* tracer_handle, - ddog_trace_context_read_callback on_context_read, const char* name, - const char* resource) { - if (!tracer_handle || !on_context_read || !name) return nullptr; - - auto* tracer = static_cast(tracer_handle); - dd::SpanConfig span_config; - span_config.name = name; - if (resource) { - span_config.resource = resource; - } - - ContextReader reader(on_context_read); - return new dd::Span(tracer->extract_or_create_span(reader, span_config)); +ddog_trace_span_t *ddog_trace_tracer_extract_or_create_span( + ddog_trace_tracer_t *tracer_handle, + ddog_trace_context_read_callback on_context_read, const char *name, + const char *resource) { + if (!tracer_handle || !on_context_read || !name) + return nullptr; + + auto *tracer = reinterpret_cast(tracer_handle); + dd::SpanConfig span_config; + span_config.name = name; + if (resource) { + span_config.resource = resource; + } + + ContextReader reader(on_context_read); + try { + return reinterpret_cast( + new dd::Span(tracer->extract_or_create_span(reader, span_config))); + } catch (...) { + return nullptr; + } } -void ddog_trace_span_free(ddog_trace_span_t* span_handle) { - if (!span_handle) return; - delete static_cast(span_handle); +void ddog_trace_span_free(ddog_trace_span_t *span_handle) { + if (!span_handle) + return; + delete reinterpret_cast(span_handle); } -void ddog_trace_span_set_tag(ddog_trace_span_t* span_handle, const char* key, - const char* value) { - if (!span_handle || !key || !value) return; - static_cast(span_handle)->set_tag(key, value); +void ddog_trace_span_set_tag(ddog_trace_span_t *span_handle, const char *key, + const char *value) { + if (!span_handle || !key || !value) + return; + reinterpret_cast(span_handle)->set_tag(key, value); } -void ddog_trace_span_set_error(ddog_trace_span_t* span_handle, +void ddog_trace_span_set_error(ddog_trace_span_t *span_handle, int error_value) { - if (!span_handle) return; - static_cast(span_handle)->set_error(error_value != 0); + if (!span_handle) + return; + reinterpret_cast(span_handle)->set_error(error_value != 0); } -void ddog_trace_span_set_error_message(ddog_trace_span_t* span_handle, - const char* error_message) { - if (!span_handle || !error_message) return; - static_cast(span_handle)->set_error_message(error_message); +void ddog_trace_span_set_error_message(ddog_trace_span_t *span_handle, + const char *error_message) { + if (!span_handle || !error_message) + return; + reinterpret_cast(span_handle)->set_error_message(error_message); } void ddog_trace_span_inject( - ddog_trace_span_t* span_handle, + ddog_trace_span_t *span_handle, ddog_trace_context_write_callback on_context_write) { - if (!span_handle || !on_context_write) return; + if (!span_handle || !on_context_write) + return; - auto* span = static_cast(span_handle); - ContextWriter writer(on_context_write); - span->inject(writer); + auto *span = reinterpret_cast(span_handle); + ContextWriter writer(on_context_write); + span->inject(writer); } -ddog_trace_span_t* ddog_trace_span_create_child(ddog_trace_span_t* span_handle, - const char* name) { - if (!span_handle || !name) return nullptr; +ddog_trace_span_t *ddog_trace_span_create_child(ddog_trace_span_t *span_handle, + const char *name) { + if (!span_handle || !name) + return nullptr; - auto* span = static_cast(span_handle); - dd::SpanConfig config; - config.name = name; + auto *span = reinterpret_cast(span_handle); + dd::SpanConfig config; + config.name = name; - return new dd::Span(span->create_child(config)); + try { + return reinterpret_cast( + new dd::Span(span->create_child(config))); + } catch (...) { + return nullptr; + } } -void ddog_trace_span_finish(ddog_trace_span_t* span_handle) { - if (!span_handle) return; - static_cast(span_handle) - ->set_end_time(std::chrono::steady_clock::now()); +void ddog_trace_span_finish(ddog_trace_span_t *span_handle) { + if (!span_handle) + return; + reinterpret_cast(span_handle) + ->set_end_time(std::chrono::steady_clock::now()); } -int ddog_trace_span_get_trace_id(ddog_trace_span_t* span_handle, char* buffer, - int buffer_size) { - if (!span_handle || !buffer || buffer_size <= 0) return -1; +int ddog_trace_span_get_trace_id(ddog_trace_span_t *span_handle, char *buffer, + size_t buffer_size) { + if (!span_handle || !buffer || buffer_size == 0) + return -1; - auto* span = static_cast(span_handle); - std::string hex = span->trace_id().hex_padded(); + auto *span = reinterpret_cast(span_handle); + std::string hex = span->trace_id().hex_padded(); - if (static_cast(hex.size()) >= buffer_size) return -1; + if (hex.size() >= buffer_size) + return -1; - std::strncpy(buffer, hex.c_str(), buffer_size); - buffer[buffer_size - 1] = '\0'; - return static_cast(hex.size()); + std::strncpy(buffer, hex.c_str(), buffer_size); + return static_cast(hex.size()); } -int ddog_trace_span_get_span_id(ddog_trace_span_t* span_handle, char* buffer, - int buffer_size) { - if (!span_handle || !buffer || buffer_size <= 0) return -1; +int ddog_trace_span_get_span_id(ddog_trace_span_t *span_handle, char *buffer, + size_t buffer_size) { + if (!span_handle || !buffer || buffer_size == 0) + return -1; - auto* span = static_cast(span_handle); - std::string hex = dd::hex_padded(span->id()); + auto *span = reinterpret_cast(span_handle); + std::string hex = dd::hex_padded(span->id()); - if (static_cast(hex.size()) >= buffer_size) return -1; + if (hex.size() >= buffer_size) + return -1; - std::strncpy(buffer, hex.c_str(), buffer_size); - buffer[buffer_size - 1] = '\0'; - return static_cast(hex.size()); + std::strncpy(buffer, hex.c_str(), buffer_size); + return static_cast(hex.size()); } -void ddog_trace_span_set_resource(ddog_trace_span_t* span_handle, - const char* resource) { - if (!span_handle || !resource) return; - static_cast(span_handle)->set_resource_name(resource); +void ddog_trace_span_set_resource(ddog_trace_span_t *span_handle, + const char *resource) { + if (!span_handle || !resource) + return; + reinterpret_cast(span_handle)->set_resource_name(resource); } -void ddog_trace_span_set_service(ddog_trace_span_t* span_handle, - const char* service) { - if (!span_handle || !service) return; - static_cast(span_handle)->set_service_name(service); +void ddog_trace_span_set_service(ddog_trace_span_t *span_handle, + const char *service) { + if (!span_handle || !service) + return; + reinterpret_cast(span_handle)->set_service_name(service); } -void ddog_trace_span_set_tags(ddog_trace_span_t* span_handle, const char** keys, - const char** values, int count) { - if (!span_handle || !keys || !values || count <= 0) return; +void ddog_trace_span_set_tags(ddog_trace_span_t *span_handle, const char **keys, + const char **values, int count) { + if (!span_handle || !keys || !values || count <= 0) + return; - auto* span = static_cast(span_handle); - for (int i = 0; i < count; ++i) { - if (keys[i] && values[i]) { - span->set_tag(keys[i], values[i]); + auto *span = reinterpret_cast(span_handle); + for (int i = 0; i < count; ++i) { + if (keys[i] && values[i]) { + span->set_tag(keys[i], values[i]); + } } - } } -int ddog_trace_span_get_sampling_priority(ddog_trace_span_t* span_handle, - int* priority) { - if (!span_handle || !priority) return -1; - - auto* span = static_cast(span_handle); - auto decision = span->trace_segment().sampling_decision(); - if (decision) { - *priority = decision->priority; - return 1; - } - return 0; +int ddog_trace_span_get_sampling_priority(ddog_trace_span_t *span_handle, + int *priority) { + if (!span_handle || !priority) + return -1; + + auto *span = reinterpret_cast(span_handle); + auto decision = span->trace_segment().sampling_decision(); + if (decision) { + *priority = decision->priority; + return 1; + } + return 0; } -void ddog_trace_span_set_sampling_priority(ddog_trace_span_t* span_handle, +void ddog_trace_span_set_sampling_priority(ddog_trace_span_t *span_handle, int priority) { - if (!span_handle) return; - static_cast(span_handle) - ->trace_segment() - .override_sampling_priority(priority); + if (!span_handle) + return; + reinterpret_cast(span_handle) + ->trace_segment() + .override_sampling_priority(priority); } -ddog_trace_span_t* ddog_trace_span_create_child_with_options( - ddog_trace_span_t* span_handle, const char* name, const char* service, - const char* resource) { - if (!span_handle || !name) return nullptr; - - auto* span = static_cast(span_handle); - dd::SpanConfig config; - config.name = name; - if (service) { - config.service = service; - } - if (resource) { - config.resource = resource; - } - - return new dd::Span(span->create_child(config)); +ddog_trace_span_t * +ddog_trace_span_create_child_with_options(ddog_trace_span_t *span_handle, + const char *name, const char *service, + const char *resource) { + if (!span_handle || !name) + return nullptr; + + auto *span = reinterpret_cast(span_handle); + dd::SpanConfig config; + config.name = name; + if (service) { + config.service = service; + } + if (resource) { + config.resource = resource; + } + try { + return reinterpret_cast( + new dd::Span(span->create_child(config))); + } catch (...) { + return nullptr; + } } -} // extern "C" +} // extern "C" diff --git a/binding/c/test/test_c_binding.cpp b/binding/c/test/test_c_binding.cpp index c7a3ea66..c6347674 100644 --- a/binding/c/test/test_c_binding.cpp +++ b/binding/c/test/test_c_binding.cpp @@ -1,181 +1,203 @@ #define CATCH_CONFIG_MAIN #include -#include #include -#include - -#include -#include -#include #include "mocks/collectors.h" #include "null_logger.h" -#include "test.h" namespace dd = datadog::tracing; namespace { -std::shared_ptr g_collector; - -ddog_trace_tracer_t* make_test_tracer() { - auto collector = std::make_shared(); - g_collector = collector; - - dd::TracerConfig cfg; - cfg.service = "test-service"; - cfg.collector = collector; - cfg.logger = std::make_shared(); - - auto finalized = dd::finalize_config(cfg); - REQUIRE(finalized); - - auto* tracer = new dd::Tracer{*finalized}; - return static_cast(tracer); -} +constexpr size_t kTraceIdBufSize = 33; +constexpr size_t kSpanIdBufSize = 17; std::unordered_map g_headers; -const char* test_header_reader(const char* key) { - auto it = g_headers.find(key); - if (it != g_headers.end()) { - return it->second.c_str(); - } - return nullptr; +const char *test_header_reader(const char *key) { + auto it = g_headers.find(key); + if (it != g_headers.end()) { + return it->second.c_str(); + } + return nullptr; } -void test_header_writer(const char* key, const char* value) { - g_headers[key] = value; +void test_header_writer(const char *key, const char *value) { + g_headers[key] = value; } -} // namespace +} // namespace -TEST_CASE("tracer lifecycle", "[c_binding]") { - auto* conf = ddog_trace_tracer_conf_new(); - REQUIRE(conf != nullptr); +struct CBindingFixture { + std::shared_ptr collector; + ddog_trace_tracer_t *tracer; + + CBindingFixture() { + auto *conf = ddog_trace_tracer_conf_new(); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_SERVICE_NAME, + "test-service"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_SERVICE_NAME, "my-service"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_ENV, "staging"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_VERSION, "1.0.0"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_AGENT_URL, - "http://localhost:8126"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_NAME, - "my-integration"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_VERSION, "2.0.0"); + auto *cfg = reinterpret_cast(conf); + collector = std::make_shared(); + cfg->collector = collector; + cfg->logger = std::make_shared(); - ddog_trace_tracer_conf_free(conf); + tracer = ddog_trace_tracer_new(conf); + ddog_trace_tracer_conf_free(conf); + REQUIRE(tracer != nullptr); + } - CHECK(ddog_trace_tracer_new(nullptr) == nullptr); + ~CBindingFixture() { ddog_trace_tracer_free(tracer); } +}; - auto* tracer = make_test_tracer(); - REQUIRE(tracer != nullptr); - ddog_trace_tracer_free(tracer); +TEST_CASE("tracer lifecycle", "[c_binding]") { + auto *conf = ddog_trace_tracer_conf_new(); + REQUIRE(conf != nullptr); + + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_SERVICE_NAME, "my-service"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_ENV, "staging"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_VERSION, "1.0.0"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_AGENT_URL, + "http://localhost:8126"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_NAME, + "my-integration"); + ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_VERSION, + "2.0.0"); + + // Inject mocks so ddog_trace_tracer_new succeeds without a real agent. + auto *cfg = reinterpret_cast(conf); + auto collector = std::make_shared(); + cfg->collector = collector; + cfg->logger = std::make_shared(); + + auto *tracer = ddog_trace_tracer_new(conf); + REQUIRE(tracer != nullptr); + + ddog_trace_tracer_conf_free(conf); + ddog_trace_tracer_free(tracer); } -TEST_CASE("span create, tag, finish, free", "[c_binding]") { - auto* tracer = make_test_tracer(); - auto* span = ddog_trace_tracer_create_span(tracer, "test.op"); - REQUIRE(span != nullptr); - - ddog_trace_span_set_tag(span, "http.method", "GET"); - ddog_trace_span_set_resource(span, "GET /api/users"); - ddog_trace_span_set_service(span, "user-service"); - ddog_trace_span_set_error(span, 1); - ddog_trace_span_set_error_message(span, "something broke"); - - ddog_trace_span_finish(span); - ddog_trace_span_free(span); - ddog_trace_tracer_free(tracer); - - const auto& sd = g_collector->first_span(); - CHECK(sd.tags.at("http.method") == "GET"); - CHECK(sd.resource == "GET /api/users"); - CHECK(sd.service == "user-service"); - CHECK(sd.error == true); - CHECK(sd.tags.at("error.message") == "something broke"); +TEST_CASE_METHOD(CBindingFixture, "span create, tag, finish, free", + "[c_binding]") { + auto *span = ddog_trace_tracer_create_span(tracer, "test.op"); + REQUIRE(span != nullptr); + + ddog_trace_span_set_tag(span, "http.method", "GET"); + ddog_trace_span_set_resource(span, "GET /api/users"); + ddog_trace_span_set_service(span, "user-service"); + ddog_trace_span_set_error(span, 1); + ddog_trace_span_set_error_message(span, "something broke"); + + const char *keys[] = {"batch.key1", "batch.key2"}; + const char *vals[] = {"val1", "val2"}; + ddog_trace_span_set_tags(span, keys, vals, 2); + + ddog_trace_span_finish(span); + ddog_trace_span_free(span); + ddog_trace_tracer_free(tracer); + tracer = nullptr; + + const auto &sd = collector->first_span(); + CHECK(sd.tags.at("http.method") == "GET"); + CHECK(sd.resource == "GET /api/users"); + CHECK(sd.service == "user-service"); + CHECK(sd.error == true); + CHECK(sd.tags.at("error.message") == "something broke"); + CHECK(sd.tags.at("batch.key1") == "val1"); + CHECK(sd.tags.at("batch.key2") == "val2"); } -TEST_CASE("inject then extract preserves trace ID", "[c_binding]") { - auto* tracer = make_test_tracer(); +TEST_CASE_METHOD(CBindingFixture, "inject then extract preserves trace ID", + "[c_binding]") { + auto *span1 = ddog_trace_tracer_create_span(tracer, "producer"); + g_headers.clear(); + ddog_trace_span_inject(span1, test_header_writer); + CHECK(!g_headers.empty()); - auto* span1 = ddog_trace_tracer_create_span(tracer, "producer"); - g_headers.clear(); - ddog_trace_span_inject(span1, test_header_writer); - CHECK(!g_headers.empty()); + char trace_id_1[kTraceIdBufSize] = {}; + ddog_trace_span_get_trace_id(span1, trace_id_1, sizeof(trace_id_1)); - char trace_id_1[33] = {}; - ddog_trace_span_get_trace_id(span1, trace_id_1, sizeof(trace_id_1)); + auto *span2 = ddog_trace_tracer_extract_or_create_span( + tracer, test_header_reader, "consumer", "GET /downstream"); + REQUIRE(span2 != nullptr); - auto* span2 = ddog_trace_tracer_extract_or_create_span( - tracer, test_header_reader, "consumer", "GET /downstream"); - REQUIRE(span2 != nullptr); + char trace_id_2[kTraceIdBufSize] = {}; + ddog_trace_span_get_trace_id(span2, trace_id_2, sizeof(trace_id_2)); - char trace_id_2[33] = {}; - ddog_trace_span_get_trace_id(span2, trace_id_2, sizeof(trace_id_2)); + CHECK(std::string(trace_id_1) == std::string(trace_id_2)); - CHECK(std::string(trace_id_1) == std::string(trace_id_2)); + ddog_trace_span_finish(span1); + ddog_trace_span_free(span1); + ddog_trace_span_finish(span2); + ddog_trace_span_free(span2); +} - ddog_trace_span_finish(span1); - ddog_trace_span_free(span1); - ddog_trace_span_finish(span2); - ddog_trace_span_free(span2); - ddog_trace_tracer_free(tracer); +TEST_CASE_METHOD(CBindingFixture, "child span shares trace ID", "[c_binding]") { + auto *parent = ddog_trace_tracer_create_span(tracer, "parent.op"); + REQUIRE(parent != nullptr); + + auto *child = ddog_trace_span_create_child(parent, "child.op"); + REQUIRE(child != nullptr); + + char parent_trace[kTraceIdBufSize] = {}; + char child_trace[kTraceIdBufSize] = {}; + ddog_trace_span_get_trace_id(parent, parent_trace, sizeof(parent_trace)); + ddog_trace_span_get_trace_id(child, child_trace, sizeof(child_trace)); + CHECK(std::string(parent_trace) == std::string(child_trace)); + + char parent_span_id[kSpanIdBufSize] = {}; + char child_span_id[kSpanIdBufSize] = {}; + ddog_trace_span_get_span_id(parent, parent_span_id, sizeof(parent_span_id)); + ddog_trace_span_get_span_id(child, child_span_id, sizeof(child_span_id)); + CHECK(std::string(parent_span_id) != std::string(child_span_id)); + + ddog_trace_span_finish(child); + ddog_trace_span_free(child); + ddog_trace_span_finish(parent); + ddog_trace_span_free(parent); } -TEST_CASE("child span shares trace ID", "[c_binding]") { - auto* tracer = make_test_tracer(); - auto* parent = ddog_trace_tracer_create_span(tracer, "parent.op"); - REQUIRE(parent != nullptr); - - auto* child = ddog_trace_span_create_child(parent, "child.op"); - REQUIRE(child != nullptr); - - char parent_trace[33] = {}; - char child_trace[33] = {}; - ddog_trace_span_get_trace_id(parent, parent_trace, sizeof(parent_trace)); - ddog_trace_span_get_trace_id(child, child_trace, sizeof(child_trace)); - CHECK(std::string(parent_trace) == std::string(child_trace)); - - char parent_span_id[17] = {}; - char child_span_id[17] = {}; - ddog_trace_span_get_span_id(parent, parent_span_id, sizeof(parent_span_id)); - ddog_trace_span_get_span_id(child, child_span_id, sizeof(child_span_id)); - CHECK(std::string(parent_span_id) != std::string(child_span_id)); - - ddog_trace_span_finish(child); - ddog_trace_span_free(child); - ddog_trace_span_finish(parent); - ddog_trace_span_free(parent); - ddog_trace_tracer_free(tracer); +TEST_CASE_METHOD(CBindingFixture, "sampling priority", "[c_binding]") { + auto *span = ddog_trace_tracer_create_span(tracer, "test.op"); + REQUIRE(span != nullptr); + + ddog_trace_span_set_sampling_priority(span, 2); + int priority = -99; + int result = ddog_trace_span_get_sampling_priority(span, &priority); + CHECK(result == 1); + CHECK(priority == 2); + + ddog_trace_span_finish(span); + ddog_trace_span_free(span); } TEST_CASE("null arguments do not crash", "[c_binding]") { - // Functions that return handles should return nullptr. - CHECK(ddog_trace_tracer_new(nullptr) == nullptr); - CHECK(ddog_trace_tracer_create_span(nullptr, "x") == nullptr); - CHECK(ddog_trace_span_create_child(nullptr, "x") == nullptr); - CHECK(ddog_trace_span_create_child_with_options(nullptr, "n", "s", "r") == - nullptr); - - char buf[33]; - CHECK(ddog_trace_span_get_trace_id(nullptr, buf, sizeof(buf)) == -1); - CHECK(ddog_trace_span_get_span_id(nullptr, buf, sizeof(buf)) == -1); - - int priority = 0; - CHECK(ddog_trace_span_get_sampling_priority(nullptr, &priority) == -1); - - // Void functions with null handles should simply not crash. - ddog_trace_tracer_conf_free(nullptr); - ddog_trace_tracer_conf_set(nullptr, DDOG_TRACE_OPT_SERVICE_NAME, "x"); - ddog_trace_tracer_free(nullptr); - ddog_trace_span_free(nullptr); - ddog_trace_span_set_tag(nullptr, "k", "v"); - ddog_trace_span_set_error(nullptr, 1); - ddog_trace_span_set_error_message(nullptr, "msg"); - ddog_trace_span_inject(nullptr, test_header_writer); - ddog_trace_span_finish(nullptr); - ddog_trace_span_set_resource(nullptr, "res"); - ddog_trace_span_set_service(nullptr, "svc"); - ddog_trace_span_set_sampling_priority(nullptr, 1); + // Functions that return handles should return nullptr. + CHECK(ddog_trace_tracer_new(nullptr) == nullptr); + CHECK(ddog_trace_tracer_create_span(nullptr, "x") == nullptr); + CHECK(ddog_trace_span_create_child(nullptr, "x") == nullptr); + CHECK(ddog_trace_span_create_child_with_options(nullptr, "n", "s", "r") == + nullptr); + + char buf[kTraceIdBufSize] = {}; + CHECK(ddog_trace_span_get_trace_id(nullptr, buf, sizeof(buf)) == -1); + CHECK(ddog_trace_span_get_span_id(nullptr, buf, sizeof(buf)) == -1); + + int priority = 0; + CHECK(ddog_trace_span_get_sampling_priority(nullptr, &priority) == -1); + + // Void functions with null handles should simply not crash. + ddog_trace_tracer_conf_free(nullptr); + ddog_trace_tracer_conf_set(nullptr, DDOG_TRACE_OPT_SERVICE_NAME, "x"); + ddog_trace_tracer_free(nullptr); + ddog_trace_span_free(nullptr); + ddog_trace_span_set_tag(nullptr, "k", "v"); + ddog_trace_span_set_error(nullptr, 1); + ddog_trace_span_set_error_message(nullptr, "msg"); + ddog_trace_span_inject(nullptr, test_header_writer); + ddog_trace_span_finish(nullptr); + ddog_trace_span_set_resource(nullptr, "res"); + ddog_trace_span_set_service(nullptr, "svc"); + ddog_trace_span_set_sampling_priority(nullptr, 1); } From f6ecdb06bd5d5de22efeb3112a86941462c70aa0 Mon Sep 17 00:00:00 2001 From: Adesh Kumar Date: Wed, 11 Mar 2026 16:28:13 -0400 Subject: [PATCH 3/3] refactor: address second round of PR review feedback for C binding --- CMakeLists.txt | 4 +- bin/check-format | 2 +- binding/c/CMakeLists.txt | 34 +- binding/c/README.md | 4 +- binding/c/include/datadog/c/tracer.h | 186 +++++----- binding/c/src/tracer.cpp | 491 +++++++++++++-------------- binding/c/test/test_c_binding.cpp | 382 ++++++++++++--------- 7 files changed, 546 insertions(+), 557 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d8d00b2..8cc154f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ project( option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_STATIC_LIBS "Build static libraries" ON) -option(BUILD_C_BINDING "Build C binding" OFF) +option(DD_TRACE_BUILD_C_BINDING "Build C binding" OFF) if (WIN32) option(DD_TRACE_STATIC_CRT "Build dd-trace-cpp with static CRT with MSVC" OFF) @@ -105,7 +105,7 @@ if (DD_TRACE_BUILD_TOOLS) add_subdirectory(tools/config-inversion) endif () -if (BUILD_C_BINDING) +if (DD_TRACE_BUILD_C_BINDING) add_subdirectory(binding/c) endif () diff --git a/bin/check-format b/bin/check-format index d8131b23..0a840785 100755 --- a/bin/check-format +++ b/bin/check-format @@ -1,4 +1,4 @@ #!/bin/sh -find include/ src/ examples/ test/ -type f \( -name '*.h' -o -name '*.cpp' \) -print0 | \ +find binding/ examples/ fuzz/ include/ src/ test/ -type f \( -name '*.h' -o -name '*.cpp' \) -print0 | \ xargs -0 clang-format-14 --style=file --dry-run -Werror diff --git a/binding/c/CMakeLists.txt b/binding/c/CMakeLists.txt index eef24c97..34a87c3f 100644 --- a/binding/c/CMakeLists.txt +++ b/binding/c/CMakeLists.txt @@ -1,7 +1,7 @@ -add_library(dd_trace_c SHARED) +add_library(dd_trace_c) add_library(dd-trace-cpp::c_binding ALIAS dd_trace_c) -target_compile_definitions(dd_trace_c PRIVATE DDOG_TRACE_C_BUILDING) +target_compile_definitions(dd_trace_c PRIVATE DD_TRACE_C_BUILDING) target_sources(dd_trace_c PRIVATE @@ -54,33 +54,13 @@ install( ) if (DD_TRACE_BUILD_TESTING) - add_executable(test_c_binding - test/test_c_binding.cpp - ${CMAKE_SOURCE_DIR}/test/test.cpp - ${CMAKE_SOURCE_DIR}/test/mocks/collectors.cpp - ${CMAKE_SOURCE_DIR}/test/mocks/loggers.cpp + target_sources(tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_c_binding.cpp ) - target_include_directories(test_c_binding - PRIVATE - ${CMAKE_SOURCE_DIR}/test - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/datadog - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/include - ) - - target_compile_features(test_c_binding PRIVATE cxx_std_17) - - target_compile_definitions(test_c_binding - PUBLIC - CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS - ) - - target_link_libraries(test_c_binding - PRIVATE - dd_trace_c + target_include_directories(tests PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include ) - catch_discover_tests(test_c_binding) + target_link_libraries(tests PRIVATE dd_trace_c) endif () diff --git a/binding/c/README.md b/binding/c/README.md index dff21294..73afa75d 100644 --- a/binding/c/README.md +++ b/binding/c/README.md @@ -1,12 +1,12 @@ # C Binding for dd-trace-cpp -A C language binding (`ddog_trace_*`) wrapping the C++ tracing library for +A C binding interface on the C++ tracing library for integration from C-based projects. ## Building ```sh -cmake -B build -DBUILD_C_BINDING=ON -DDD_TRACE_BUILD_TESTING=ON . +cmake -B build -DDD_TRACE_BUILD_C_BINDING=ON -DDD_TRACE_BUILD_TESTING=ON cmake --build build -j ``` diff --git a/binding/c/include/datadog/c/tracer.h b/binding/c/include/datadog/c/tracer.h index 2a074698..554f6c4d 100644 --- a/binding/c/include/datadog/c/tracer.h +++ b/binding/c/include/datadog/c/tracer.h @@ -3,15 +3,15 @@ #include #if defined(_WIN32) -#if defined(DDOG_TRACE_C_BUILDING) -#define DDOG_TRACE_C_API __declspec(dllexport) +#if defined(DD_TRACE_C_BUILDING) +#define DD_TRACE_C_API __declspec(dllexport) #else -#define DDOG_TRACE_C_API __declspec(dllimport) +#define DD_TRACE_C_API __declspec(dllimport) #endif #elif defined(__GNUC__) || defined(__clang__) -#define DDOG_TRACE_C_API __attribute__((visibility("default"))) +#define DD_TRACE_C_API __attribute__((visibility("default"))) #else -#define DDOG_TRACE_C_API +#define DD_TRACE_C_API #endif #if defined(__cplusplus) @@ -24,71 +24,84 @@ extern "C" { // @param key Header name to look up // @return Header value, or NULL if the header is not present. // The returned pointer must remain valid until -// ddog_trace_tracer_extract_or_create_span returns. -typedef const char* (*ddog_trace_context_read_callback)(const char* key); +// dd_tracer_extract_or_create_span returns. +typedef const char* (*dd_context_read_callback)(const char* key); // Callback used during trace context injection. The tracer calls this // function for each propagation header it needs to write. // // @param key Header name to set // @param value Header value to set -typedef void (*ddog_trace_context_write_callback)(const char* key, - const char* value); +typedef void (*dd_context_write_callback)(const char* key, const char* value); typedef enum { - DDOG_TRACE_OPT_SERVICE_NAME = 0, - DDOG_TRACE_OPT_ENV = 1, - DDOG_TRACE_OPT_VERSION = 2, - DDOG_TRACE_OPT_AGENT_URL = 3, - DDOG_TRACE_OPT_INTEGRATION_NAME = 4, - DDOG_TRACE_OPT_INTEGRATION_VERSION = 5 -} ddog_trace_tracer_option; - -typedef struct ddog_trace_conf_s ddog_trace_conf_t; -typedef struct ddog_trace_tracer_s ddog_trace_tracer_t; -typedef struct ddog_trace_span_s ddog_trace_span_t; + DD_OPT_SERVICE_NAME = 0, + DD_OPT_ENV = 1, + DD_OPT_VERSION = 2, + DD_OPT_AGENT_URL = 3, + DD_OPT_INTEGRATION_NAME = 4, + DD_OPT_INTEGRATION_VERSION = 5 +} dd_tracer_option; + +// Options for creating a span. Unset fields default to NULL. +typedef struct { + const char* name; + const char* resource; + const char* service; +} dd_span_options_t; + +// Error details populated on failure. Caller provides the struct, +// callee fills it in. Pass NULL to ignore errors. +typedef struct { + int code; + char message[256]; +} dd_error_t; + +typedef struct dd_conf_s dd_conf_t; +typedef struct dd_tracer_s dd_tracer_t; +typedef struct dd_span_s dd_span_t; // Creates a tracer configuration instance. // // @return Configuration handle, or NULL on allocation failure -DDOG_TRACE_C_API ddog_trace_conf_t* ddog_trace_tracer_conf_new(void); +DD_TRACE_C_API dd_conf_t* dd_tracer_conf_new(void); // Release a tracer configuration. Safe to call with NULL. // // @param handle Configuration handle to release -DDOG_TRACE_C_API void ddog_trace_tracer_conf_free(ddog_trace_conf_t* handle); +DD_TRACE_C_API void dd_tracer_conf_free(dd_conf_t* handle); -// Set or update a configuration field. No-op if handle or value is NULL. +// Set or update a configuration field. No-op if handle is NULL. // // @param handle Configuration handle // @param option Configuration option -// @param value Configuration value -DDOG_TRACE_C_API void ddog_trace_tracer_conf_set( - ddog_trace_conf_t* handle, ddog_trace_tracer_option option, - const char* value); +// @param value Configuration value (interpretation depends on option) +DD_TRACE_C_API void dd_tracer_conf_set(dd_conf_t* handle, + dd_tracer_option option, void* value); -// Creates a tracer instance. +// Creates a tracer instance. The configuration handle may be freed with +// dd_tracer_conf_free after this call returns. // -// @param conf_handle Configuration handle +// @param conf_handle Configuration handle (not modified) +// @param error Optional error output (may be NULL) // -// @return Tracer handle, or NULL on error (e.g. invalid config) -DDOG_TRACE_C_API ddog_trace_tracer_t* ddog_trace_tracer_new( - ddog_trace_conf_t* conf_handle); +// @return Tracer handle, or NULL on error +DD_TRACE_C_API dd_tracer_t* dd_tracer_new(const dd_conf_t* conf_handle, + dd_error_t* error); // Release a tracer instance. Safe to call with NULL. // // @param tracer_handle Tracer handle to release -DDOG_TRACE_C_API void ddog_trace_tracer_free( - ddog_trace_tracer_t* tracer_handle); +DD_TRACE_C_API void dd_tracer_free(dd_tracer_t* tracer_handle); // Create a span using a Tracer. // // @param tracer_handle Tracer handle -// @param name Name of the span +// @param options Span options (name must not be NULL) // // @return Span handle, or NULL on error -DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_tracer_create_span( - ddog_trace_tracer_t* tracer_handle, const char* name); +DD_TRACE_C_API dd_span_t* dd_tracer_create_span( + dd_tracer_t* tracer_handle, const dd_span_options_t* options); // Extract trace context from incoming headers, or create a new root span // if extraction fails. Never returns an error span; on extraction failure @@ -96,66 +109,63 @@ DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_tracer_create_span( // // @param tracer_handle Tracer handle // @param on_context_read Callback invoked to read propagation headers -// @param name Span name -// @param resource Resource name (may be NULL) +// @param options Span options (name must not be NULL) // // @return Span handle, or NULL if arguments are invalid -DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_tracer_extract_or_create_span( - ddog_trace_tracer_t* tracer_handle, - ddog_trace_context_read_callback on_context_read, const char* name, - const char* resource); +DD_TRACE_C_API dd_span_t* dd_tracer_extract_or_create_span( + dd_tracer_t* tracer_handle, dd_context_read_callback on_context_read, + const dd_span_options_t* options); // Release a span instance. Safe to call with NULL. +// If the span has not been finished with dd_span_finish, it is +// automatically finished (its end time is recorded) before being freed. // // @param span_handle Span handle -DDOG_TRACE_C_API void ddog_trace_span_free(ddog_trace_span_t* span_handle); +DD_TRACE_C_API void dd_span_free(dd_span_t* span_handle); // Set a tag (key-value pair) on a span. No-op if any argument is NULL. // // @param span_handle Span handle // @param key Tag key // @param value Tag value -DDOG_TRACE_C_API void ddog_trace_span_set_tag(ddog_trace_span_t* span_handle, - const char* key, - const char* value); +DD_TRACE_C_API void dd_span_set_tag(dd_span_t* span_handle, const char* key, + const char* value); // Mark a span as erroneous. No-op if span_handle is NULL. // // @param span_handle Span handle // @param error_value Non-zero to mark as error, zero to clear -DDOG_TRACE_C_API void ddog_trace_span_set_error(ddog_trace_span_t* span_handle, - int error_value); +DD_TRACE_C_API void dd_span_set_error(dd_span_t* span_handle, int error_value); // Set an error message on a span. No-op if any argument is NULL. // // @param span_handle Span handle // @param error_message Error message string -DDOG_TRACE_C_API void ddog_trace_span_set_error_message( - ddog_trace_span_t* span_handle, const char* error_message); +DD_TRACE_C_API void dd_span_set_error_message(dd_span_t* span_handle, + const char* error_message); // Inject trace context into outgoing headers via callback. // No-op if any argument is NULL. // // @param span_handle Span handle // @param on_context_write Callback invoked per propagation header -DDOG_TRACE_C_API void ddog_trace_span_inject( - ddog_trace_span_t* span_handle, - ddog_trace_context_write_callback on_context_write); +DD_TRACE_C_API void dd_span_inject(dd_span_t* span_handle, + dd_context_write_callback on_context_write); // Create a child span. Returns NULL if any required argument is NULL. // // @param span_handle Parent span handle -// @param name Name of the child span +// @param options Span options (name must not be NULL) // // @return Child span handle, or NULL -DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_span_create_child( - ddog_trace_span_t* span_handle, const char* name); +DD_TRACE_C_API dd_span_t* dd_span_create_child( + dd_span_t* span_handle, const dd_span_options_t* options); // Finish a span by recording its end time. No-op if span_handle is NULL. -// After finishing, the span should be freed with ddog_trace_span_free. +// After finishing, the span should be freed with dd_span_free. // // @param span_handle Span handle -DDOG_TRACE_C_API void ddog_trace_span_finish(ddog_trace_span_t* span_handle); +DD_TRACE_C_API void dd_span_finish(dd_span_t* span_handle); // Get the trace ID as a zero-padded hex string. // @@ -163,8 +173,8 @@ DDOG_TRACE_C_API void ddog_trace_span_finish(ddog_trace_span_t* span_handle); // @param buffer Output buffer (at least 33 bytes for 128-bit IDs) // @param buffer_size Size of the buffer // @return Number of characters written, or -1 on error -DDOG_TRACE_C_API int ddog_trace_span_get_trace_id( - ddog_trace_span_t* span_handle, char* buffer, size_t buffer_size); +DD_TRACE_C_API int dd_span_get_trace_id(dd_span_t* span_handle, char* buffer, + size_t buffer_size); // Get the span ID as a zero-padded hex string. // @@ -172,64 +182,22 @@ DDOG_TRACE_C_API int ddog_trace_span_get_trace_id( // @param buffer Output buffer (at least 17 bytes) // @param buffer_size Size of the buffer // @return Number of characters written (16), or -1 on error -DDOG_TRACE_C_API int ddog_trace_span_get_span_id( - ddog_trace_span_t* span_handle, char* buffer, size_t buffer_size); +DD_TRACE_C_API int dd_span_get_span_id(dd_span_t* span_handle, char* buffer, + size_t buffer_size); // Set the resource name on a span. No-op if any argument is NULL. // // @param span_handle Span handle // @param resource Resource name -DDOG_TRACE_C_API void ddog_trace_span_set_resource( - ddog_trace_span_t* span_handle, const char* resource); +DD_TRACE_C_API void dd_span_set_resource(dd_span_t* span_handle, + const char* resource); // Set the service name on a span. No-op if any argument is NULL. // // @param span_handle Span handle // @param service Service name -DDOG_TRACE_C_API void ddog_trace_span_set_service( - ddog_trace_span_t* span_handle, const char* service); - -// Set multiple tags at once. No-op if any required argument is NULL or -// count <= 0. Individual entries where key or value is NULL are skipped. -// -// @param span_handle Span handle -// @param keys Array of tag keys -// @param values Array of tag values -// @param count Number of tags -DDOG_TRACE_C_API void ddog_trace_span_set_tags(ddog_trace_span_t* span_handle, - const char** keys, - const char** values, int count); - -// Get the sampling priority for the trace this span belongs to. -// -// @param span_handle Span handle -// @param priority Output: sampling priority value -// @return 1 if a priority was written, 0 if no decision yet, -// -1 on error (NULL arguments) -DDOG_TRACE_C_API int ddog_trace_span_get_sampling_priority( - ddog_trace_span_t* span_handle, int* priority); - -// Override the sampling priority for the trace this span belongs to. -// No-op if span_handle is NULL. -// -// @param span_handle Span handle -// @param priority Sampling priority value -DDOG_TRACE_C_API void ddog_trace_span_set_sampling_priority( - ddog_trace_span_t* span_handle, int priority); - -// Create a child span with explicit service and resource names. -// Returns NULL if span_handle or name is NULL. service and resource may -// be NULL (inherits from parent). -// -// @param span_handle Parent span handle -// @param name Span name (required) -// @param service Service name (may be NULL) -// @param resource Resource name (may be NULL) -// -// @return Child span handle, or NULL -DDOG_TRACE_C_API ddog_trace_span_t* ddog_trace_span_create_child_with_options( - ddog_trace_span_t* span_handle, const char* name, const char* service, - const char* resource); +DD_TRACE_C_API void dd_span_set_service(dd_span_t* span_handle, + const char* service); #if defined(__cplusplus) } diff --git a/binding/c/src/tracer.cpp b/binding/c/src/tracer.cpp index 1eabcad4..99abacac 100644 --- a/binding/c/src/tracer.cpp +++ b/binding/c/src/tracer.cpp @@ -13,316 +13,293 @@ namespace dd = datadog::tracing; namespace { class ContextReader : public dd::DictReader { - ddog_trace_context_read_callback read_; - - public: - explicit ContextReader(ddog_trace_context_read_callback read_callback) - : read_(read_callback) {} - - dd::Optional lookup(dd::StringView key) const override { - std::string key_str(key); - if (auto value = read_(key_str.c_str())) { - return value; - } - return dd::nullopt; + dd_context_read_callback read_; + + public: + explicit ContextReader(dd_context_read_callback read_callback) + : read_(read_callback) {} + + dd::Optional lookup(dd::StringView key) const override { + std::string key_str(key); + if (auto value = read_(key_str.c_str())) { + return value; } + return dd::nullopt; + } - void - visit(const std::function - & /* visitor */) const override {} + void visit(const std::function + & /* visitor */) const override {} }; class ContextWriter : public dd::DictWriter { - ddog_trace_context_write_callback write_; + dd_context_write_callback write_; - public: - explicit ContextWriter(ddog_trace_context_write_callback func) - : write_(func) {} + public: + explicit ContextWriter(dd_context_write_callback func) : write_(func) {} - void set(dd::StringView key, dd::StringView value) override { - std::string key_str(key); - std::string value_str(value); - write_(key_str.c_str(), value_str.c_str()); - } + void set(dd::StringView key, dd::StringView value) override { + std::string key_str(key); + std::string value_str(value); + write_(key_str.c_str(), value_str.c_str()); + } }; -} // namespace - -extern "C" { - -ddog_trace_conf_t *ddog_trace_tracer_conf_new(void) { - try { - return reinterpret_cast(new dd::TracerConfig); - } catch (...) { - return nullptr; - } +dd::SpanConfig make_span_config(const dd_span_options_t *options) { + dd::SpanConfig span_config; + if (options == nullptr) { + return span_config; + } + if (options->name != nullptr) { + span_config.name = options->name; + } + if (options->resource != nullptr) { + span_config.resource = options->resource; + } + if (options->service != nullptr) { + span_config.service = options->service; + } + return span_config; } -void ddog_trace_tracer_conf_free(ddog_trace_conf_t *handle) { - if (!handle) - return; - delete reinterpret_cast(handle); +void set_error(dd_error_t *error, int code, const char *message) { + if (error == nullptr) { + return; + } + error->code = code; + std::strncpy(error->message, message, sizeof(error->message) - 1); + error->message[sizeof(error->message) - 1] = '\0'; } -void ddog_trace_tracer_conf_set(ddog_trace_conf_t *handle, - ddog_trace_tracer_option option, - const char *value) { - if (!handle || !value) - return; - - auto *cfg = reinterpret_cast(handle); - - switch (option) { - case DDOG_TRACE_OPT_SERVICE_NAME: - cfg->service = value; - break; - case DDOG_TRACE_OPT_ENV: - cfg->environment = value; - break; - case DDOG_TRACE_OPT_VERSION: - cfg->version = value; - break; - case DDOG_TRACE_OPT_AGENT_URL: - cfg->agent.url = value; - break; - case DDOG_TRACE_OPT_INTEGRATION_NAME: - cfg->integration_name = value; - break; - case DDOG_TRACE_OPT_INTEGRATION_VERSION: - cfg->integration_version = value; - break; - } -} +} // namespace -ddog_trace_tracer_t *ddog_trace_tracer_new(ddog_trace_conf_t *conf_handle) { - if (!conf_handle) - return nullptr; - - auto *config = reinterpret_cast(conf_handle); - const auto validated_config = dd::finalize_config(*config); - if (!validated_config) { - return nullptr; - } +extern "C" { - try { - return reinterpret_cast( - new dd::Tracer{*validated_config}); - } catch (...) { - return nullptr; - } +dd_conf_t *dd_tracer_conf_new(void) { + try { + return reinterpret_cast(new dd::TracerConfig); + } catch (...) { + return nullptr; + } } -void ddog_trace_tracer_free(ddog_trace_tracer_t *tracer_handle) { - if (!tracer_handle) - return; - delete reinterpret_cast(tracer_handle); +void dd_tracer_conf_free(dd_conf_t *handle) { + if (handle == nullptr) { + return; + } + delete reinterpret_cast(handle); } -ddog_trace_span_t * -ddog_trace_tracer_create_span(ddog_trace_tracer_t *tracer_handle, - const char *name) { - if (!tracer_handle || !name) - return nullptr; - - auto *tracer = reinterpret_cast(tracer_handle); - dd::SpanConfig opt; - opt.name = name; - - try { - return reinterpret_cast( - new dd::Span(tracer->create_span(opt))); - } catch (...) { - return nullptr; - } +void dd_tracer_conf_set(dd_conf_t *handle, dd_tracer_option option, + void *value) { + if (handle == nullptr || value == nullptr) { + return; + } + + auto *cfg = reinterpret_cast(handle); + + switch (option) { + case DD_OPT_SERVICE_NAME: + cfg->service = static_cast(value); + break; + case DD_OPT_ENV: + cfg->environment = static_cast(value); + break; + case DD_OPT_VERSION: + cfg->version = static_cast(value); + break; + case DD_OPT_AGENT_URL: + cfg->agent.url = static_cast(value); + break; + case DD_OPT_INTEGRATION_NAME: + cfg->integration_name = static_cast(value); + break; + case DD_OPT_INTEGRATION_VERSION: + cfg->integration_version = static_cast(value); + break; + } } -ddog_trace_span_t *ddog_trace_tracer_extract_or_create_span( - ddog_trace_tracer_t *tracer_handle, - ddog_trace_context_read_callback on_context_read, const char *name, - const char *resource) { - if (!tracer_handle || !on_context_read || !name) - return nullptr; - - auto *tracer = reinterpret_cast(tracer_handle); - dd::SpanConfig span_config; - span_config.name = name; - if (resource) { - span_config.resource = resource; - } - - ContextReader reader(on_context_read); - try { - return reinterpret_cast( - new dd::Span(tracer->extract_or_create_span(reader, span_config))); - } catch (...) { - return nullptr; - } +dd_tracer_t *dd_tracer_new(const dd_conf_t *conf_handle, dd_error_t *error) { + if (conf_handle == nullptr) { + set_error(error, 1, "conf_handle is NULL"); + return nullptr; + } + + const auto *config = reinterpret_cast(conf_handle); + const auto validated_config = dd::finalize_config(*config); + if (!validated_config) { + set_error(error, 2, validated_config.error().message.c_str()); + return nullptr; + } + + try { + return reinterpret_cast(new dd::Tracer{*validated_config}); + } catch (...) { + set_error(error, 3, "failed to allocate tracer"); + return nullptr; + } } -void ddog_trace_span_free(ddog_trace_span_t *span_handle) { - if (!span_handle) - return; - delete reinterpret_cast(span_handle); +void dd_tracer_free(dd_tracer_t *tracer_handle) { + if (tracer_handle == nullptr) { + return; + } + delete reinterpret_cast(tracer_handle); } -void ddog_trace_span_set_tag(ddog_trace_span_t *span_handle, const char *key, - const char *value) { - if (!span_handle || !key || !value) - return; - reinterpret_cast(span_handle)->set_tag(key, value); +dd_span_t *dd_tracer_create_span(dd_tracer_t *tracer_handle, + const dd_span_options_t *options) { + if (tracer_handle == nullptr || options == nullptr || + options->name == nullptr) { + return nullptr; + } + + auto *tracer = reinterpret_cast(tracer_handle); + auto span_config = make_span_config(options); + + try { + return reinterpret_cast( + new dd::Span(tracer->create_span(span_config))); + } catch (...) { + return nullptr; + } } -void ddog_trace_span_set_error(ddog_trace_span_t *span_handle, - int error_value) { - if (!span_handle) - return; - reinterpret_cast(span_handle)->set_error(error_value != 0); +dd_span_t *dd_tracer_extract_or_create_span( + dd_tracer_t *tracer_handle, dd_context_read_callback on_context_read, + const dd_span_options_t *options) { + if (tracer_handle == nullptr || on_context_read == nullptr || + options == nullptr || options->name == nullptr) { + return nullptr; + } + + auto *tracer = reinterpret_cast(tracer_handle); + auto span_config = make_span_config(options); + + ContextReader reader(on_context_read); + try { + return reinterpret_cast( + new dd::Span(tracer->extract_or_create_span(reader, span_config))); + } catch (...) { + return nullptr; + } } -void ddog_trace_span_set_error_message(ddog_trace_span_t *span_handle, - const char *error_message) { - if (!span_handle || !error_message) - return; - reinterpret_cast(span_handle)->set_error_message(error_message); +void dd_span_free(dd_span_t *span_handle) { + if (span_handle == nullptr) { + return; + } + delete reinterpret_cast(span_handle); } -void ddog_trace_span_inject( - ddog_trace_span_t *span_handle, - ddog_trace_context_write_callback on_context_write) { - if (!span_handle || !on_context_write) - return; - - auto *span = reinterpret_cast(span_handle); - ContextWriter writer(on_context_write); - span->inject(writer); +void dd_span_set_tag(dd_span_t *span_handle, const char *key, + const char *value) { + if (span_handle == nullptr || key == nullptr || value == nullptr) { + return; + } + reinterpret_cast(span_handle)->set_tag(key, value); } -ddog_trace_span_t *ddog_trace_span_create_child(ddog_trace_span_t *span_handle, - const char *name) { - if (!span_handle || !name) - return nullptr; - - auto *span = reinterpret_cast(span_handle); - dd::SpanConfig config; - config.name = name; - - try { - return reinterpret_cast( - new dd::Span(span->create_child(config))); - } catch (...) { - return nullptr; - } +void dd_span_set_error(dd_span_t *span_handle, int error_value) { + if (span_handle == nullptr) { + return; + } + reinterpret_cast(span_handle)->set_error(error_value != 0); } -void ddog_trace_span_finish(ddog_trace_span_t *span_handle) { - if (!span_handle) - return; - reinterpret_cast(span_handle) - ->set_end_time(std::chrono::steady_clock::now()); +void dd_span_set_error_message(dd_span_t *span_handle, + const char *error_message) { + if (span_handle == nullptr || error_message == nullptr) { + return; + } + reinterpret_cast(span_handle)->set_error_message(error_message); } -int ddog_trace_span_get_trace_id(ddog_trace_span_t *span_handle, char *buffer, - size_t buffer_size) { - if (!span_handle || !buffer || buffer_size == 0) - return -1; +void dd_span_inject(dd_span_t *span_handle, + dd_context_write_callback on_context_write) { + if (span_handle == nullptr || on_context_write == nullptr) { + return; + } - auto *span = reinterpret_cast(span_handle); - std::string hex = span->trace_id().hex_padded(); - - if (hex.size() >= buffer_size) - return -1; - - std::strncpy(buffer, hex.c_str(), buffer_size); - return static_cast(hex.size()); + auto *span = reinterpret_cast(span_handle); + ContextWriter writer(on_context_write); + span->inject(writer); } -int ddog_trace_span_get_span_id(ddog_trace_span_t *span_handle, char *buffer, - size_t buffer_size) { - if (!span_handle || !buffer || buffer_size == 0) - return -1; +dd_span_t *dd_span_create_child(dd_span_t *span_handle, + const dd_span_options_t *options) { + if (span_handle == nullptr || options == nullptr || + options->name == nullptr) { + return nullptr; + } + + auto *span = reinterpret_cast(span_handle); + auto span_config = make_span_config(options); + + try { + return reinterpret_cast( + new dd::Span(span->create_child(span_config))); + } catch (...) { + return nullptr; + } +} - auto *span = reinterpret_cast(span_handle); - std::string hex = dd::hex_padded(span->id()); +void dd_span_finish(dd_span_t *span_handle) { + if (span_handle == nullptr) { + return; + } + reinterpret_cast(span_handle) + ->set_end_time(std::chrono::steady_clock::now()); +} - if (hex.size() >= buffer_size) - return -1; +int dd_span_get_trace_id(dd_span_t *span_handle, char *buffer, + size_t buffer_size) { + if (span_handle == nullptr || buffer == nullptr || buffer_size == 0) { + return -1; + } - std::strncpy(buffer, hex.c_str(), buffer_size); - return static_cast(hex.size()); -} + auto *span = reinterpret_cast(span_handle); + std::string hex = span->trace_id().hex_padded(); -void ddog_trace_span_set_resource(ddog_trace_span_t *span_handle, - const char *resource) { - if (!span_handle || !resource) - return; - reinterpret_cast(span_handle)->set_resource_name(resource); -} + if (hex.size() >= buffer_size) { + return -1; + } -void ddog_trace_span_set_service(ddog_trace_span_t *span_handle, - const char *service) { - if (!span_handle || !service) - return; - reinterpret_cast(span_handle)->set_service_name(service); + std::strncpy(buffer, hex.c_str(), buffer_size); + // Safe narrowing: hex trace IDs are at most 32 characters. + return static_cast(hex.size()); } -void ddog_trace_span_set_tags(ddog_trace_span_t *span_handle, const char **keys, - const char **values, int count) { - if (!span_handle || !keys || !values || count <= 0) - return; +int dd_span_get_span_id(dd_span_t *span_handle, char *buffer, + size_t buffer_size) { + if (span_handle == nullptr || buffer == nullptr || buffer_size == 0) { + return -1; + } - auto *span = reinterpret_cast(span_handle); - for (int i = 0; i < count; ++i) { - if (keys[i] && values[i]) { - span->set_tag(keys[i], values[i]); - } - } -} + auto *span = reinterpret_cast(span_handle); + std::string hex = dd::hex_padded(span->id()); -int ddog_trace_span_get_sampling_priority(ddog_trace_span_t *span_handle, - int *priority) { - if (!span_handle || !priority) - return -1; + if (hex.size() >= buffer_size) { + return -1; + } - auto *span = reinterpret_cast(span_handle); - auto decision = span->trace_segment().sampling_decision(); - if (decision) { - *priority = decision->priority; - return 1; - } - return 0; + std::strncpy(buffer, hex.c_str(), buffer_size); + // Safe narrowing: hex span IDs are 16 characters. + return static_cast(hex.size()); } -void ddog_trace_span_set_sampling_priority(ddog_trace_span_t *span_handle, - int priority) { - if (!span_handle) - return; - reinterpret_cast(span_handle) - ->trace_segment() - .override_sampling_priority(priority); +void dd_span_set_resource(dd_span_t *span_handle, const char *resource) { + if (span_handle == nullptr || resource == nullptr) { + return; + } + reinterpret_cast(span_handle)->set_resource_name(resource); } -ddog_trace_span_t * -ddog_trace_span_create_child_with_options(ddog_trace_span_t *span_handle, - const char *name, const char *service, - const char *resource) { - if (!span_handle || !name) - return nullptr; - - auto *span = reinterpret_cast(span_handle); - dd::SpanConfig config; - config.name = name; - if (service) { - config.service = service; - } - if (resource) { - config.resource = resource; - } - try { - return reinterpret_cast( - new dd::Span(span->create_child(config))); - } catch (...) { - return nullptr; - } +void dd_span_set_service(dd_span_t *span_handle, const char *service) { + if (span_handle == nullptr || service == nullptr) { + return; + } + reinterpret_cast(span_handle)->set_service_name(service); } -} // extern "C" +} // extern "C" diff --git a/binding/c/test/test_c_binding.cpp b/binding/c/test/test_c_binding.cpp index c6347674..3bcf76c3 100644 --- a/binding/c/test/test_c_binding.cpp +++ b/binding/c/test/test_c_binding.cpp @@ -1,203 +1,267 @@ -#define CATCH_CONFIG_MAIN - #include #include #include "mocks/collectors.h" #include "null_logger.h" +#include "test.h" namespace dd = datadog::tracing; namespace { -constexpr size_t kTraceIdBufSize = 33; -constexpr size_t kSpanIdBufSize = 17; +constexpr size_t trace_id_buf_size = 33; +constexpr size_t span_id_buf_size = 17; std::unordered_map g_headers; const char *test_header_reader(const char *key) { - auto it = g_headers.find(key); - if (it != g_headers.end()) { - return it->second.c_str(); - } - return nullptr; + auto it = g_headers.find(key); + if (it != g_headers.end()) { + return it->second.c_str(); + } + return nullptr; } void test_header_writer(const char *key, const char *value) { - g_headers[key] = value; + g_headers[key] = value; } -} // namespace +struct TestTracer { + std::shared_ptr collector; + dd_tracer_t *tracer; -struct CBindingFixture { - std::shared_ptr collector; - ddog_trace_tracer_t *tracer; + ~TestTracer() { dd_tracer_free(tracer); } +}; - CBindingFixture() { - auto *conf = ddog_trace_tracer_conf_new(); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_SERVICE_NAME, - "test-service"); +TestTracer make_tracer() { + auto *conf = dd_tracer_conf_new(); + dd_tracer_conf_set(conf, DD_OPT_SERVICE_NAME, (void *)"test-service"); + + // Inject mocks before const handoff to dd_tracer_new. + auto *cfg = reinterpret_cast(conf); + TestTracer result; + result.collector = std::make_shared(); + cfg->collector = result.collector; + cfg->logger = std::make_shared(); + + result.tracer = dd_tracer_new(conf, nullptr); + dd_tracer_conf_free(conf); + REQUIRE(result.tracer != nullptr); + return result; +} - auto *cfg = reinterpret_cast(conf); - collector = std::make_shared(); - cfg->collector = collector; - cfg->logger = std::make_shared(); +} // namespace - tracer = ddog_trace_tracer_new(conf); - ddog_trace_tracer_conf_free(conf); - REQUIRE(tracer != nullptr); - } +TEST_CASE("tracer lifecycle", "[c_binding]") { + auto *conf = dd_tracer_conf_new(); + REQUIRE(conf != nullptr); + + dd_tracer_conf_set(conf, DD_OPT_SERVICE_NAME, (void *)"my-service"); + dd_tracer_conf_set(conf, DD_OPT_ENV, (void *)"staging"); + dd_tracer_conf_set(conf, DD_OPT_VERSION, (void *)"1.0.0"); + dd_tracer_conf_set(conf, DD_OPT_AGENT_URL, (void *)"http://foo:8080"); + dd_tracer_conf_set(conf, DD_OPT_INTEGRATION_NAME, (void *)"my-integration"); + dd_tracer_conf_set(conf, DD_OPT_INTEGRATION_VERSION, (void *)"2.0.0"); + + // Inject mocks so dd_tracer_new succeeds without a real agent. + auto *cfg = reinterpret_cast(conf); + cfg->collector = std::make_shared(); + cfg->logger = std::make_shared(); + + auto *tracer = dd_tracer_new(conf, nullptr); + REQUIRE(tracer != nullptr); + + dd_tracer_conf_free(conf); + dd_tracer_free(tracer); +} - ~CBindingFixture() { ddog_trace_tracer_free(tracer); } -}; +TEST_CASE("tracer new propagates error", "[c_binding]") { + // Create config without injecting mocks — dd_tracer_new should fail + // and populate the error struct. + auto *conf = dd_tracer_conf_new(); + dd_tracer_conf_set(conf, DD_OPT_AGENT_URL, (void *)"not://valid"); -TEST_CASE("tracer lifecycle", "[c_binding]") { - auto *conf = ddog_trace_tracer_conf_new(); - REQUIRE(conf != nullptr); - - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_SERVICE_NAME, "my-service"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_ENV, "staging"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_VERSION, "1.0.0"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_AGENT_URL, - "http://localhost:8126"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_NAME, - "my-integration"); - ddog_trace_tracer_conf_set(conf, DDOG_TRACE_OPT_INTEGRATION_VERSION, - "2.0.0"); - - // Inject mocks so ddog_trace_tracer_new succeeds without a real agent. - auto *cfg = reinterpret_cast(conf); - auto collector = std::make_shared(); - cfg->collector = collector; - cfg->logger = std::make_shared(); - - auto *tracer = ddog_trace_tracer_new(conf); - REQUIRE(tracer != nullptr); - - ddog_trace_tracer_conf_free(conf); - ddog_trace_tracer_free(tracer); + dd_error_t err = {}; + auto *tracer = dd_tracer_new(conf, &err); + CHECK(tracer == nullptr); + CHECK(err.code != 0); + CHECK(err.message[0] != '\0'); + + dd_tracer_conf_free(conf); +} + +TEST_CASE("span create, tag, finish, free", "[c_binding]") { + auto ctx = make_tracer(); + + dd_span_options_t opts = {.name = "test.op"}; + auto *span = dd_tracer_create_span(ctx.tracer, &opts); + REQUIRE(span != nullptr); + + dd_span_set_tag(span, "http.method", "GET"); + dd_span_set_resource(span, "GET /api/users"); + dd_span_set_service(span, "user-service"); + dd_span_set_error(span, 1); + dd_span_set_error_message(span, "something broke"); + + dd_span_finish(span); + dd_span_free(span); + + const auto &sd = ctx.collector->first_span(); + CHECK(sd.tags.at("http.method") == "GET"); + CHECK(sd.resource == "GET /api/users"); + CHECK(sd.service == "user-service"); + CHECK(sd.error == true); + CHECK(sd.tags.at("error.message") == "something broke"); } -TEST_CASE_METHOD(CBindingFixture, "span create, tag, finish, free", - "[c_binding]") { - auto *span = ddog_trace_tracer_create_span(tracer, "test.op"); - REQUIRE(span != nullptr); - - ddog_trace_span_set_tag(span, "http.method", "GET"); - ddog_trace_span_set_resource(span, "GET /api/users"); - ddog_trace_span_set_service(span, "user-service"); - ddog_trace_span_set_error(span, 1); - ddog_trace_span_set_error_message(span, "something broke"); - - const char *keys[] = {"batch.key1", "batch.key2"}; - const char *vals[] = {"val1", "val2"}; - ddog_trace_span_set_tags(span, keys, vals, 2); - - ddog_trace_span_finish(span); - ddog_trace_span_free(span); - ddog_trace_tracer_free(tracer); - tracer = nullptr; - - const auto &sd = collector->first_span(); - CHECK(sd.tags.at("http.method") == "GET"); - CHECK(sd.resource == "GET /api/users"); - CHECK(sd.service == "user-service"); - CHECK(sd.error == true); - CHECK(sd.tags.at("error.message") == "something broke"); - CHECK(sd.tags.at("batch.key1") == "val1"); - CHECK(sd.tags.at("batch.key2") == "val2"); +TEST_CASE("create span with resource", "[c_binding]") { + auto ctx = make_tracer(); + + dd_span_options_t opts = {.name = "web.request", + .resource = "GET /api/users"}; + auto *span = dd_tracer_create_span(ctx.tracer, &opts); + REQUIRE(span != nullptr); + + dd_span_finish(span); + dd_span_free(span); + + const auto &sd = ctx.collector->first_span(); + CHECK(sd.resource == "GET /api/users"); } -TEST_CASE_METHOD(CBindingFixture, "inject then extract preserves trace ID", - "[c_binding]") { - auto *span1 = ddog_trace_tracer_create_span(tracer, "producer"); - g_headers.clear(); - ddog_trace_span_inject(span1, test_header_writer); - CHECK(!g_headers.empty()); +TEST_CASE("span free without finish auto-finishes", "[c_binding]") { + auto ctx = make_tracer(); - char trace_id_1[kTraceIdBufSize] = {}; - ddog_trace_span_get_trace_id(span1, trace_id_1, sizeof(trace_id_1)); + dd_span_options_t opts = {.name = "auto.finish"}; + auto *span = dd_tracer_create_span(ctx.tracer, &opts); + REQUIRE(span != nullptr); - auto *span2 = ddog_trace_tracer_extract_or_create_span( - tracer, test_header_reader, "consumer", "GET /downstream"); - REQUIRE(span2 != nullptr); + dd_span_set_tag(span, "key", "value"); + + // Free without calling dd_span_finish — should auto-finish. + dd_span_free(span); + + const auto &sd = ctx.collector->first_span(); + CHECK(sd.name == "auto.finish"); + CHECK(sd.tags.at("key") == "value"); +} - char trace_id_2[kTraceIdBufSize] = {}; - ddog_trace_span_get_trace_id(span2, trace_id_2, sizeof(trace_id_2)); +TEST_CASE("inject then extract preserves trace ID", "[c_binding]") { + auto ctx = make_tracer(); - CHECK(std::string(trace_id_1) == std::string(trace_id_2)); + dd_span_options_t opts_1 = {.name = "producer"}; + auto *span_1 = dd_tracer_create_span(ctx.tracer, &opts_1); + g_headers.clear(); + dd_span_inject(span_1, test_header_writer); + CHECK(!g_headers.empty()); + + char trace_id_1[trace_id_buf_size] = {}; + dd_span_get_trace_id(span_1, trace_id_1, sizeof(trace_id_1)); + + dd_span_options_t opts_2 = {.name = "consumer", + .resource = "GET /downstream"}; + auto *span_2 = + dd_tracer_extract_or_create_span(ctx.tracer, test_header_reader, &opts_2); + REQUIRE(span_2 != nullptr); + + char trace_id_2[trace_id_buf_size] = {}; + dd_span_get_trace_id(span_2, trace_id_2, sizeof(trace_id_2)); + + CHECK(std::string(trace_id_1) == std::string(trace_id_2)); + + dd_span_finish(span_1); + dd_span_free(span_1); + dd_span_finish(span_2); + dd_span_free(span_2); +} - ddog_trace_span_finish(span1); - ddog_trace_span_free(span1); - ddog_trace_span_finish(span2); - ddog_trace_span_free(span2); +TEST_CASE("child span shares trace ID", "[c_binding]") { + auto ctx = make_tracer(); + + dd_span_options_t parent_opts = {.name = "parent.op"}; + auto *parent = dd_tracer_create_span(ctx.tracer, &parent_opts); + REQUIRE(parent != nullptr); + + dd_span_options_t child_opts = {.name = "child.op"}; + auto *child = dd_span_create_child(parent, &child_opts); + REQUIRE(child != nullptr); + + char parent_trace[trace_id_buf_size] = {}; + char child_trace[trace_id_buf_size] = {}; + dd_span_get_trace_id(parent, parent_trace, sizeof(parent_trace)); + dd_span_get_trace_id(child, child_trace, sizeof(child_trace)); + CHECK(std::string(parent_trace) == std::string(child_trace)); + + char parent_span_id[span_id_buf_size] = {}; + char child_span_id[span_id_buf_size] = {}; + dd_span_get_span_id(parent, parent_span_id, sizeof(parent_span_id)); + dd_span_get_span_id(child, child_span_id, sizeof(child_span_id)); + CHECK(std::string(parent_span_id) != std::string(child_span_id)); + + dd_span_finish(child); + dd_span_free(child); + dd_span_finish(parent); + dd_span_free(parent); } -TEST_CASE_METHOD(CBindingFixture, "child span shares trace ID", "[c_binding]") { - auto *parent = ddog_trace_tracer_create_span(tracer, "parent.op"); - REQUIRE(parent != nullptr); - - auto *child = ddog_trace_span_create_child(parent, "child.op"); - REQUIRE(child != nullptr); - - char parent_trace[kTraceIdBufSize] = {}; - char child_trace[kTraceIdBufSize] = {}; - ddog_trace_span_get_trace_id(parent, parent_trace, sizeof(parent_trace)); - ddog_trace_span_get_trace_id(child, child_trace, sizeof(child_trace)); - CHECK(std::string(parent_trace) == std::string(child_trace)); - - char parent_span_id[kSpanIdBufSize] = {}; - char child_span_id[kSpanIdBufSize] = {}; - ddog_trace_span_get_span_id(parent, parent_span_id, sizeof(parent_span_id)); - ddog_trace_span_get_span_id(child, child_span_id, sizeof(child_span_id)); - CHECK(std::string(parent_span_id) != std::string(child_span_id)); - - ddog_trace_span_finish(child); - ddog_trace_span_free(child); - ddog_trace_span_finish(parent); - ddog_trace_span_free(parent); +TEST_CASE("child span with service and resource", "[c_binding]") { + auto ctx = make_tracer(); + + dd_span_options_t parent_opts = {.name = "parent.op"}; + auto *parent = dd_tracer_create_span(ctx.tracer, &parent_opts); + REQUIRE(parent != nullptr); + + dd_span_options_t child_opts = { + .name = "db.query", .resource = "SELECT *", .service = "postgres"}; + auto *child = dd_span_create_child(parent, &child_opts); + REQUIRE(child != nullptr); + + dd_span_finish(child); + dd_span_free(child); + dd_span_finish(parent); + dd_span_free(parent); + + // Spans are sent as a chunk in registration order: + // parent first, child second. + REQUIRE(ctx.collector->chunks.size() >= 1); + REQUIRE(ctx.collector->chunks[0].size() >= 2); + const auto &child_sd = *ctx.collector->chunks[0][1]; + CHECK(child_sd.name == "db.query"); + CHECK(child_sd.resource == "SELECT *"); + CHECK(child_sd.service == "postgres"); } -TEST_CASE_METHOD(CBindingFixture, "sampling priority", "[c_binding]") { - auto *span = ddog_trace_tracer_create_span(tracer, "test.op"); - REQUIRE(span != nullptr); +TEST_CASE("tracer new with invalid config and null error", "[c_binding]") { + // Invalid config + NULL error pointer should not crash. + auto *conf = dd_tracer_conf_new(); + dd_tracer_conf_set(conf, DD_OPT_AGENT_URL, (void *)"not://valid"); - ddog_trace_span_set_sampling_priority(span, 2); - int priority = -99; - int result = ddog_trace_span_get_sampling_priority(span, &priority); - CHECK(result == 1); - CHECK(priority == 2); + auto *tracer = dd_tracer_new(conf, nullptr); + CHECK(tracer == nullptr); - ddog_trace_span_finish(span); - ddog_trace_span_free(span); + dd_tracer_conf_free(conf); } TEST_CASE("null arguments do not crash", "[c_binding]") { - // Functions that return handles should return nullptr. - CHECK(ddog_trace_tracer_new(nullptr) == nullptr); - CHECK(ddog_trace_tracer_create_span(nullptr, "x") == nullptr); - CHECK(ddog_trace_span_create_child(nullptr, "x") == nullptr); - CHECK(ddog_trace_span_create_child_with_options(nullptr, "n", "s", "r") == - nullptr); - - char buf[kTraceIdBufSize] = {}; - CHECK(ddog_trace_span_get_trace_id(nullptr, buf, sizeof(buf)) == -1); - CHECK(ddog_trace_span_get_span_id(nullptr, buf, sizeof(buf)) == -1); - - int priority = 0; - CHECK(ddog_trace_span_get_sampling_priority(nullptr, &priority) == -1); - - // Void functions with null handles should simply not crash. - ddog_trace_tracer_conf_free(nullptr); - ddog_trace_tracer_conf_set(nullptr, DDOG_TRACE_OPT_SERVICE_NAME, "x"); - ddog_trace_tracer_free(nullptr); - ddog_trace_span_free(nullptr); - ddog_trace_span_set_tag(nullptr, "k", "v"); - ddog_trace_span_set_error(nullptr, 1); - ddog_trace_span_set_error_message(nullptr, "msg"); - ddog_trace_span_inject(nullptr, test_header_writer); - ddog_trace_span_finish(nullptr); - ddog_trace_span_set_resource(nullptr, "res"); - ddog_trace_span_set_service(nullptr, "svc"); - ddog_trace_span_set_sampling_priority(nullptr, 1); + // Functions that return handles should return nullptr. + CHECK(dd_tracer_new(nullptr, nullptr) == nullptr); + CHECK(dd_tracer_create_span(nullptr, nullptr) == nullptr); + CHECK(dd_span_create_child(nullptr, nullptr) == nullptr); + + char buf[trace_id_buf_size] = {}; + CHECK(dd_span_get_trace_id(nullptr, buf, sizeof(buf)) == -1); + CHECK(dd_span_get_span_id(nullptr, buf, sizeof(buf)) == -1); + + // Void functions with null handles should simply not crash. + dd_tracer_conf_free(nullptr); + dd_tracer_conf_set(nullptr, DD_OPT_SERVICE_NAME, (void *)"x"); + dd_tracer_free(nullptr); + dd_span_free(nullptr); + dd_span_set_tag(nullptr, "k", "v"); + dd_span_set_error(nullptr, 1); + dd_span_set_error_message(nullptr, "msg"); + dd_span_inject(nullptr, test_header_writer); + dd_span_finish(nullptr); + dd_span_set_resource(nullptr, "res"); + dd_span_set_service(nullptr, "svc"); }