-
Notifications
You must be signed in to change notification settings - Fork 19
feat(c-binding): expose custom collector callback #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,17 @@ | ||
| #include "datadog/c/tracer.h" | ||
|
|
||
| #include <datadog/collector.h> | ||
| #include <datadog/hex.h> | ||
| #include <datadog/msgpack.h> | ||
| #include <datadog/span_data.h> | ||
| #include <datadog/trace_segment.h> | ||
| #include <datadog/tracer.h> | ||
|
|
||
| #include <cstddef> | ||
| #include <cstring> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <utility> | ||
|
|
||
| namespace dd = datadog::tracing; | ||
|
|
||
|
|
@@ -44,6 +49,37 @@ class ContextWriter : public dd::DictWriter { | |
| } | ||
| }; | ||
|
|
||
| // Collector that forwards finished trace chunks to a C callback. The payload | ||
| // matches the /v0.4/traces HTTP body: a 1-element outer array containing | ||
| // the chunk, produced by the same msgpack encoder the DatadogAgent uses. | ||
| class CallbackCollector : public dd::Collector { | ||
| dd_trace_msgpack_callback callback_; | ||
| void *user_data_; | ||
|
|
||
| public: | ||
| CallbackCollector(dd_trace_msgpack_callback callback, void *user_data) | ||
| : callback_(callback), user_data_(user_data) {} | ||
|
|
||
| dd::Expected<void> send( | ||
| std::vector<std::unique_ptr<dd::SpanData>> &&spans, | ||
| const std::shared_ptr<dd::TraceSampler> & /*response_handler*/) override { | ||
| std::string buffer; | ||
| if (auto rc = dd::msgpack::pack_array(buffer, std::size_t{1}); !rc) { | ||
| return rc; | ||
| } | ||
| if (auto rc = dd::msgpack_encode(buffer, spans); !rc) { | ||
| return rc; | ||
| } | ||
| callback_(reinterpret_cast<const uint8_t *>(buffer.data()), buffer.size(), | ||
| user_data_); | ||
| return {}; | ||
| } | ||
|
|
||
| std::string config() const override { | ||
| return "{\"type\":\"datadog::binding::c::CallbackCollector\"}"; | ||
| } | ||
| }; | ||
|
|
||
| dd::SpanConfig make_span_config(dd_span_options_t options) { | ||
| dd::SpanConfig span_config; | ||
| if (options.name != nullptr) { | ||
|
|
@@ -125,20 +161,53 @@ void dd_tracer_conf_set(dd_conf_t *handle, dd_tracer_option option, | |
| } | ||
| } | ||
|
|
||
| void dd_tracer_conf_set_collector_callback(dd_conf_t *handle, | ||
| dd_trace_msgpack_callback callback, | ||
| void *user_data) { | ||
| if (handle == nullptr) { | ||
| return; | ||
| } | ||
| auto *cfg = reinterpret_cast<dd::TracerConfig *>(handle); | ||
| if (callback == nullptr) { | ||
| cfg->collector.reset(); | ||
| return; | ||
| } | ||
| cfg->collector = std::make_shared<CallbackCollector>(callback, user_data); | ||
| // Telemetry is silenced unconditionally in dd_tracer_new after | ||
| // finalize_config, because env vars like DD_INSTRUMENTATION_TELEMETRY_ENABLED | ||
| // outrank user-config fields set here. Remote-config polling is implicitly | ||
| // disabled: when a custom collector is present, the Tracer skips DatadogAgent | ||
| // construction entirely, so no RC loop starts. The agent URL is left alone: | ||
| // callback mode never contacts it, but clobbering it here would silently | ||
| // lose a user-set URL across a set/clear callback cycle. | ||
| } | ||
|
|
||
| dd_tracer_t *dd_tracer_new(const dd_conf_t *conf_handle, dd_error_t *error) { | ||
| if (conf_handle == nullptr) { | ||
| set_error(error, DD_ERROR_NULL_ARGUMENT, "conf_handle is NULL"); | ||
| return nullptr; | ||
| } | ||
|
|
||
| const auto *config = reinterpret_cast<const dd::TracerConfig *>(conf_handle); | ||
| const auto validated_config = dd::finalize_config(*config); | ||
| auto validated_config = dd::finalize_config(*config); | ||
| if (!validated_config) { | ||
| set_error(error, DD_ERROR_INVALID_CONFIG, | ||
| validated_config.error().message.c_str()); | ||
| return nullptr; | ||
| } | ||
|
|
||
| // When a custom collector is installed (only possible via | ||
| // dd_tracer_conf_set_collector_callback), force telemetry off. We must do | ||
| // this after finalize_config because env vars like | ||
| // DD_INSTRUMENTATION_TELEMETRY_ENABLED outrank user-config fields and would | ||
| // otherwise keep telemetry network traffic alive despite the callback. | ||
| if (std::holds_alternative<std::shared_ptr<dd::Collector>>( | ||
| validated_config->collector)) { | ||
| validated_config->telemetry.enabled = false; | ||
| validated_config->telemetry.report_metrics = false; | ||
| validated_config->telemetry.report_logs = false; | ||
|
Comment on lines
+206
to
+208
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This only mutates the local tracer config, but telemetry is process-global and initialized once ( Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documented as a caveat in the header docstring. Telemetry is process-global; the config setter can only affect this tracer's finalized config. In multi-tracer processes where an earlier tracer already initialized telemetry as enabled, that traffic continues. Fully silencing it from the C binding would require touching the telemetry module's singleton, which is out of scope. The typical C-binding use case (Kong) is single-tracer-per-process, where this is a non-issue. |
||
| } | ||
|
|
||
| try { | ||
| return reinterpret_cast<dd_tracer_t *>(new dd::Tracer{*validated_config}); | ||
| } catch (...) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dd_tracer_newalways runsfinalize_config(*config)before handling the callback-collector path, so tracer creation still fails whenDD_TRACE_AGENT_URL/DD_OPT_AGENT_URLis malformed even though traces are being diverted to the in-process callback.finalize_configvalidates agent config unconditionally (src/datadog/tracer_config.cpp), which makes this new callback mode unusable in callback-only deployments that intentionally do not rely on agent transport.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documented as a caveat in the header docstring — malformed DD_TRACE_AGENT_URL / DD_OPT_AGENT_URL still fails finalize_config before the callback path is taken. Fixing this properly requires either unsetenv() (process-wide side effect) or refactoring finalize_config to defer agent validation, both of which are out of scope for this PR. Callers in callback mode should simply not set a malformed URL.