From a82b08596ed1bb60a483431058fb576bb4d91a87 Mon Sep 17 00:00:00 2001 From: Adesh Kumar Date: Tue, 3 Mar 2026 10:57:08 -0500 Subject: [PATCH 1/4] 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/4] 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/4] 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"); } From 1f83a79131bcbb9930defeb782eb000362953484 Mon Sep 17 00:00:00 2001 From: Adesh Kumar Date: Thu, 12 Mar 2026 16:29:17 -0400 Subject: [PATCH 4/4] refactor: address third round of PR review feedback for C binding --- binding/c/README.md | 2 +- binding/c/include/datadog/c/tracer.h | 34 +++++++++---- binding/c/src/tracer.cpp | 49 ++++++++++--------- binding/c/test/test_c_binding.cpp | 73 ++++++++++++---------------- 4 files changed, 84 insertions(+), 74 deletions(-) diff --git a/binding/c/README.md b/binding/c/README.md index 73afa75d..8f2560d8 100644 --- a/binding/c/README.md +++ b/binding/c/README.md @@ -13,5 +13,5 @@ cmake --build build -j ## Running Tests ```sh -./build/binding/c/test_c_binding +./build/test/tests "[c_binding]" ``` diff --git a/binding/c/include/datadog/c/tracer.h b/binding/c/include/datadog/c/tracer.h index 554f6c4d..1b045bc7 100644 --- a/binding/c/include/datadog/c/tracer.h +++ b/binding/c/include/datadog/c/tracer.h @@ -48,12 +48,24 @@ typedef struct { const char* name; const char* resource; const char* service; + const char* service_type; + const char* environment; + const char* version; } dd_span_options_t; -// Error details populated on failure. Caller provides the struct, -// callee fills it in. Pass NULL to ignore errors. +// Error codes returned by the C binding. +typedef enum { + DD_ERROR_OK = 0, + DD_ERROR_NULL_ARGUMENT = 1, + DD_ERROR_INVALID_CONFIG = 2, + DD_ERROR_ALLOCATION_FAILURE = 3 +} dd_error_code; + +// Error details populated on failure. The caller must allocate this struct +// and pass a pointer to it. The callee fills in the code and message fields. +// Pass NULL to ignore errors. typedef struct { - int code; + dd_error_code code; char message[256]; } dd_error_t; @@ -77,13 +89,15 @@ DD_TRACE_C_API void dd_tracer_conf_free(dd_conf_t* handle); // @param option Configuration option // @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); + dd_tracer_option option, + const void* value); // Creates a tracer instance. The configuration handle may be freed with // dd_tracer_conf_free after this call returns. // // @param conf_handle Configuration handle (not modified) -// @param error Optional error output (may be NULL) +// @param error If non-NULL, filled with error details on failure. +// The caller must allocate the dd_error_t struct. // // @return Tracer handle, or NULL on error DD_TRACE_C_API dd_tracer_t* dd_tracer_new(const dd_conf_t* conf_handle, @@ -100,8 +114,8 @@ DD_TRACE_C_API void dd_tracer_free(dd_tracer_t* tracer_handle); // @param options Span options (name must not be NULL) // // @return Span handle, or NULL on error -DD_TRACE_C_API dd_span_t* dd_tracer_create_span( - dd_tracer_t* tracer_handle, const dd_span_options_t* options); +DD_TRACE_C_API dd_span_t* dd_tracer_create_span(dd_tracer_t* tracer_handle, + 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 @@ -114,7 +128,7 @@ DD_TRACE_C_API dd_span_t* dd_tracer_create_span( // @return Span handle, or NULL if arguments are invalid 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); + 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 @@ -158,8 +172,8 @@ DD_TRACE_C_API void dd_span_inject(dd_span_t* span_handle, // @param options Span options (name must not be NULL) // // @return Child span handle, or NULL -DD_TRACE_C_API dd_span_t* dd_span_create_child( - dd_span_t* span_handle, const dd_span_options_t* options); +DD_TRACE_C_API dd_span_t* dd_span_create_child(dd_span_t* span_handle, + 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 dd_span_free. diff --git a/binding/c/src/tracer.cpp b/binding/c/src/tracer.cpp index 99abacac..2a81a40f 100644 --- a/binding/c/src/tracer.cpp +++ b/binding/c/src/tracer.cpp @@ -44,24 +44,30 @@ class ContextWriter : public dd::DictWriter { } }; -dd::SpanConfig make_span_config(const dd_span_options_t *options) { +dd::SpanConfig make_span_config(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->name != nullptr) { - span_config.name = options->name; + if (options.resource != nullptr) { + span_config.resource = options.resource; } - if (options->resource != nullptr) { - span_config.resource = options->resource; + if (options.service != nullptr) { + span_config.service = options.service; } - if (options->service != nullptr) { - span_config.service = options->service; + if (options.service_type != nullptr) { + span_config.service_type = options.service_type; + } + if (options.environment != nullptr) { + span_config.environment = options.environment; + } + if (options.version != nullptr) { + span_config.version = options.version; } return span_config; } -void set_error(dd_error_t *error, int code, const char *message) { +void set_error(dd_error_t *error, dd_error_code code, const char *message) { if (error == nullptr) { return; } @@ -90,7 +96,7 @@ void dd_tracer_conf_free(dd_conf_t *handle) { } void dd_tracer_conf_set(dd_conf_t *handle, dd_tracer_option option, - void *value) { + const void *value) { if (handle == nullptr || value == nullptr) { return; } @@ -121,21 +127,22 @@ void dd_tracer_conf_set(dd_conf_t *handle, dd_tracer_option option, 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"); + set_error(error, DD_ERROR_NULL_ARGUMENT, "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()); + set_error(error, DD_ERROR_INVALID_CONFIG, + 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"); + set_error(error, DD_ERROR_ALLOCATION_FAILURE, "failed to allocate tracer"); return nullptr; } } @@ -148,9 +155,8 @@ void dd_tracer_free(dd_tracer_t *tracer_handle) { } 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) { + dd_span_options_t options) { + if (tracer_handle == nullptr || options.name == nullptr) { return nullptr; } @@ -167,9 +173,9 @@ dd_span_t *dd_tracer_create_span(dd_tracer_t *tracer_handle, 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) { + dd_span_options_t options) { if (tracer_handle == nullptr || on_context_read == nullptr || - options == nullptr || options->name == nullptr) { + options.name == nullptr) { return nullptr; } @@ -227,9 +233,8 @@ void dd_span_inject(dd_span_t *span_handle, } 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) { + dd_span_options_t options) { + if (span_handle == nullptr || options.name == nullptr) { return nullptr; } diff --git a/binding/c/test/test_c_binding.cpp b/binding/c/test/test_c_binding.cpp index 3bcf76c3..7b93516b 100644 --- a/binding/c/test/test_c_binding.cpp +++ b/binding/c/test/test_c_binding.cpp @@ -35,7 +35,7 @@ struct TestTracer { TestTracer make_tracer() { auto *conf = dd_tracer_conf_new(); - dd_tracer_conf_set(conf, DD_OPT_SERVICE_NAME, (void *)"test-service"); + dd_tracer_conf_set(conf, DD_OPT_SERVICE_NAME, "test-service"); // Inject mocks before const handoff to dd_tracer_new. auto *cfg = reinterpret_cast(conf); @@ -56,12 +56,12 @@ 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"); + dd_tracer_conf_set(conf, DD_OPT_SERVICE_NAME, "my-service"); + dd_tracer_conf_set(conf, DD_OPT_ENV, "staging"); + dd_tracer_conf_set(conf, DD_OPT_VERSION, "1.0.0"); + dd_tracer_conf_set(conf, DD_OPT_AGENT_URL, "http://foo:8080"); + dd_tracer_conf_set(conf, DD_OPT_INTEGRATION_NAME, "my-integration"); + dd_tracer_conf_set(conf, DD_OPT_INTEGRATION_VERSION, "2.0.0"); // Inject mocks so dd_tracer_new succeeds without a real agent. auto *cfg = reinterpret_cast(conf); @@ -76,15 +76,13 @@ TEST_CASE("tracer lifecycle", "[c_binding]") { } 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"); + dd_tracer_conf_set(conf, DD_OPT_AGENT_URL, "not://valid"); dd_error_t err = {}; auto *tracer = dd_tracer_new(conf, &err); CHECK(tracer == nullptr); - CHECK(err.code != 0); + CHECK(err.code == DD_ERROR_INVALID_CONFIG); CHECK(err.message[0] != '\0'); dd_tracer_conf_free(conf); @@ -93,8 +91,7 @@ TEST_CASE("tracer new propagates error", "[c_binding]") { 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); + auto *span = dd_tracer_create_span(ctx.tracer, {.name = "test.op"}); REQUIRE(span != nullptr); dd_span_set_tag(span, "http.method", "GET"); @@ -117,9 +114,8 @@ TEST_CASE("span create, tag, finish, free", "[c_binding]") { 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); + auto *span = dd_tracer_create_span( + ctx.tracer, {.name = "web.request", .resource = "GET /api/users"}); REQUIRE(span != nullptr); dd_span_finish(span); @@ -132,8 +128,7 @@ TEST_CASE("create span with resource", "[c_binding]") { TEST_CASE("span free without finish auto-finishes", "[c_binding]") { auto ctx = make_tracer(); - dd_span_options_t opts = {.name = "auto.finish"}; - auto *span = dd_tracer_create_span(ctx.tracer, &opts); + auto *span = dd_tracer_create_span(ctx.tracer, {.name = "auto.finish"}); REQUIRE(span != nullptr); dd_span_set_tag(span, "key", "value"); @@ -149,8 +144,7 @@ TEST_CASE("span free without finish auto-finishes", "[c_binding]") { TEST_CASE("inject then extract preserves trace ID", "[c_binding]") { auto ctx = make_tracer(); - dd_span_options_t opts_1 = {.name = "producer"}; - auto *span_1 = dd_tracer_create_span(ctx.tracer, &opts_1); + auto *span_1 = dd_tracer_create_span(ctx.tracer, {.name = "producer"}); g_headers.clear(); dd_span_inject(span_1, test_header_writer); CHECK(!g_headers.empty()); @@ -158,10 +152,9 @@ TEST_CASE("inject then extract preserves trace ID", "[c_binding]") { 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); + auto *span_2 = dd_tracer_extract_or_create_span( + ctx.tracer, test_header_reader, + {.name = "consumer", .resource = "GET /downstream"}); REQUIRE(span_2 != nullptr); char trace_id_2[trace_id_buf_size] = {}; @@ -178,12 +171,10 @@ TEST_CASE("inject then extract preserves trace ID", "[c_binding]") { 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); + auto *parent = dd_tracer_create_span(ctx.tracer, {.name = "parent.op"}); REQUIRE(parent != nullptr); - dd_span_options_t child_opts = {.name = "child.op"}; - auto *child = dd_span_create_child(parent, &child_opts); + auto *child = dd_span_create_child(parent, {.name = "child.op"}); REQUIRE(child != nullptr); char parent_trace[trace_id_buf_size] = {}; @@ -204,16 +195,18 @@ TEST_CASE("child span shares trace ID", "[c_binding]") { dd_span_free(parent); } -TEST_CASE("child span with service and resource", "[c_binding]") { +TEST_CASE("child span with all options", "[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); + auto *parent = dd_tracer_create_span(ctx.tracer, {.name = "parent.op"}); 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); + auto *child = dd_span_create_child(parent, {.name = "db.query", + .resource = "SELECT *", + .service = "postgres", + .service_type = "sql", + .environment = "staging", + .version = "2.0"}); REQUIRE(child != nullptr); dd_span_finish(child); @@ -229,12 +222,12 @@ TEST_CASE("child span with service and resource", "[c_binding]") { CHECK(child_sd.name == "db.query"); CHECK(child_sd.resource == "SELECT *"); CHECK(child_sd.service == "postgres"); + CHECK(child_sd.service_type == "sql"); } 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"); + dd_tracer_conf_set(conf, DD_OPT_AGENT_URL, "not://valid"); auto *tracer = dd_tracer_new(conf, nullptr); CHECK(tracer == nullptr); @@ -243,18 +236,16 @@ TEST_CASE("tracer new with invalid config and null error", "[c_binding]") { } TEST_CASE("null arguments do not crash", "[c_binding]") { - // 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); + CHECK(dd_tracer_create_span(nullptr, {.name = "x"}) == nullptr); + CHECK(dd_span_create_child(nullptr, {.name = "x"}) == 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_conf_set(nullptr, DD_OPT_SERVICE_NAME, "x"); dd_tracer_free(nullptr); dd_span_free(nullptr); dd_span_set_tag(nullptr, "k", "v");