Skip to content

Commit

Permalink
[Tracer] Adds SpanEvents Support for DD and OTEL (#2754)
Browse files Browse the repository at this point in the history
* Adding class details for SpanEvent

* Updated serializer and other C files to add event

* Adding API to add events and use existent ones

* Adding event and checking in test

* Trying to test Otel to DD

* Fixing test and applying nit changes

* Currently passing full AddEvent and RecordException tests

* Updating how we recordException

* Removing manually added code

* Added constructor and updated args were needed

* Doing fixes so that test pass

* Fixing missing CLOCK_REALTIME for Windows build error

* Using inhouse time method and fixing array errors

* Implementing addEvent for system-tests

* Adding ExceptionSpanEvent class and recordException

* Applying C code nits and limiting recordException

* Merging master and Updating ddtrace_arginfo.h

* Applying nits and using exception property in C

* Adding manual usage work for both classes

* Trying to fix CircleCI build nts extension errors

* Trying to change where attributes are handled

* Cleanup span event serialization

Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com>

* Updating broken tests

* Fixing what seems to be a version issue

* Tested with 7.4 passing

* Fixing test based on updated behavior

* Initializing empty array

* Addressing nits, updating libdatadog and recording exceptions

* Cleanup implementation slightly

Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com>

---------

Signed-off-by: Bob Weinand <bob.weinand@datadoghq.com>
Co-authored-by: Bob Weinand <bob.weinand@datadoghq.com>
  • Loading branch information
link04 and bwoebi committed Aug 21, 2024
1 parent 5f56332 commit 4172cb8
Show file tree
Hide file tree
Showing 15 changed files with 703 additions and 18 deletions.
135 changes: 135 additions & 0 deletions ext/ddtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,138 @@ static PHP_GSHUTDOWN_FUNCTION(ddtrace) {
#endif
}

static void dd_span_event_construct(ddtrace_span_event *event, zend_string *name, zend_long timestamp, zval *attributes)
{
zval garbage_name, garbage_timestamp, garbage_attributes;

// Copy current values to temporary zval variables
ZVAL_COPY_VALUE(&garbage_name, &event->property_name);
ZVAL_COPY_VALUE(&garbage_timestamp, &event->property_timestamp);
ZVAL_COPY_VALUE(&garbage_attributes, &event->property_attributes);

ZVAL_STR_COPY(&event->property_name, name);

// Use the provided timestamp or the current time in nanoseconds
if (timestamp == 0) {
struct timespec ts;
timespec_get(&ts, TIME_UTC);
timestamp = ts.tv_sec * ZEND_NANO_IN_SEC + ts.tv_nsec;
}
ZVAL_LONG(&event->property_timestamp, timestamp);

// Initialize attributes
if (attributes) {
ZVAL_COPY(&event->property_attributes, attributes);
} else {
array_init(&event->property_attributes);
}

// Free the copied values after replacement
zval_ptr_dtor(&garbage_name);
zval_ptr_dtor(&garbage_timestamp);
zval_ptr_dtor(&garbage_attributes);
}

/* DDTrace\SpanEvent */
zend_class_entry *ddtrace_ce_span_event;

PHP_METHOD(DDTrace_SpanEvent, jsonSerialize) {
ddtrace_span_event *event = (ddtrace_span_event*)Z_OBJ_P(ZEND_THIS);

zval array;
array_init(&array);

Z_TRY_ADDREF(event->property_name);
add_assoc_zval_ex(&array, ZEND_STRL("name"), &event->property_name);
Z_TRY_ADDREF(event->property_timestamp);
add_assoc_zval_ex(&array, ZEND_STRL("time_unix_nano"), &event->property_timestamp);

// Handle attributes dynamically
zval *attributes = &event->property_attributes;
zval combined_attributes;
array_init(&combined_attributes);

if (instanceof_function(event->std.ce, ddtrace_ce_exception_span_event)) {
// Handle exception attributes dynamically if an exception property exists
ddtrace_exception_span_event *exception_event = (ddtrace_exception_span_event *) event;
zval *exception = &exception_event->property_exception;
if (Z_TYPE_P(exception) == IS_OBJECT && instanceof_function(Z_OBJCE_P(exception), zend_ce_throwable)) {
// Get exception message, type, and stack trace directly
zend_string *message = zai_exception_message(Z_OBJ_P(exception));
if (ZSTR_LEN(message)) {
add_assoc_str_ex(&combined_attributes, ZEND_STRL("exception.message"), zend_string_copy(message));
}
add_assoc_str_ex(&combined_attributes, ZEND_STRL("exception.type"), zend_string_copy(Z_OBJCE_P(exception)->name));

// Get the exception stack trace using zai_get_trace_without_args_from_exception
zend_string *stacktrace = zai_get_trace_without_args_from_exception(Z_OBJ_P(exception));
add_assoc_str_ex(&combined_attributes, ZEND_STRL("exception.stacktrace"), stacktrace);
}
}

if (Z_TYPE_P(attributes) == IS_ARRAY) {
zend_hash_copy(Z_ARRVAL(combined_attributes), Z_ARRVAL_P(attributes), (copy_ctor_func_t)zval_add_ref);
}

if (zend_hash_num_elements(Z_ARRVAL(combined_attributes)) > 0) {
add_assoc_zval_ex(&array, ZEND_STRL("attributes"), &combined_attributes);
} else {
zval_ptr_dtor(&combined_attributes); // Clean up if no elements
}

RETURN_ARR(Z_ARR(array)); // Return the array
}

PHP_METHOD(DDTrace_SpanEvent, __construct)
{
UNUSED(return_value);

zend_string *name;
zval *attributes = NULL;
zend_long timestamp = 0;

ZEND_PARSE_PARAMETERS_START(1, 3)
Z_PARAM_STR(name)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_EX(attributes, 1, 0)
Z_PARAM_LONG(timestamp)
ZEND_PARSE_PARAMETERS_END();

ddtrace_span_event *event = (ddtrace_span_event*)Z_OBJ_P(ZEND_THIS);

// Use the static function to set properties and handle cleanup
dd_span_event_construct(event, name, timestamp, attributes);
}

/* DDTrace\ExceptionSpanEvent */
zend_class_entry *ddtrace_ce_exception_span_event;

PHP_METHOD(DDTrace_ExceptionSpanEvent, __construct)
{
UNUSED(return_value);

zval *exception;
zval *attributes = NULL;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_EX(attributes, 1, 0)
ZEND_PARSE_PARAMETERS_END();

ddtrace_exception_span_event *event = (ddtrace_exception_span_event*)Z_OBJ_P(ZEND_THIS);

// Use the static function to set properties and handle cleanup
zend_string *name = zend_string_init(ZEND_STRL("exception"), 0);
dd_span_event_construct(&event->span_event, name, 0, attributes);
zend_string_release(name);

zval garbage;
ZVAL_COPY_VALUE(&garbage, &event->property_exception);
ZVAL_COPY(&event->property_exception, exception);
zval_ptr_dtor(&garbage);
}

/* DDTrace\SpanLink */
zend_class_entry *ddtrace_ce_span_link;

Expand Down Expand Up @@ -718,6 +850,7 @@ static zend_object *dd_init_span_data_object(zend_class_entry *class_type, ddtra
array_init(&span->property_metrics);
array_init(&span->property_meta_struct);
array_init(&span->property_links);
array_init(&span->property_events);
array_init(&span->property_peer_service_sources);
#endif
// Explicitly assign property-mapped NULLs
Expand Down Expand Up @@ -1169,6 +1302,8 @@ static PHP_MINIT_FUNCTION(ddtrace) {
dd_register_fatal_error_ce();
ddtrace_ce_integration = register_class_DDTrace_Integration();
ddtrace_ce_span_link = register_class_DDTrace_SpanLink(php_json_serializable_ce);
ddtrace_ce_span_event = register_class_DDTrace_SpanEvent(php_json_serializable_ce);
ddtrace_ce_exception_span_event = register_class_DDTrace_ExceptionSpanEvent(ddtrace_ce_span_event);
ddtrace_ce_git_metadata = register_class_DDTrace_GitMetadata();
ddtrace_ce_git_metadata->create_object = ddtrace_git_metadata_create;
memcpy(&ddtrace_git_metadata_handlers, &std_object_handlers, sizeof(zend_object_handlers));
Expand Down
4 changes: 4 additions & 0 deletions ext/ddtrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ extern zend_class_entry *ddtrace_ce_root_span_data;
extern zend_class_entry *ddtrace_ce_span_stack;
extern zend_class_entry *ddtrace_ce_fatal_error;
extern zend_class_entry *ddtrace_ce_span_link;
extern zend_class_entry *ddtrace_ce_span_event;
extern zend_class_entry *ddtrace_ce_exception_span_event;
extern zend_class_entry *ddtrace_ce_integration;
extern zend_class_entry *ddtrace_ce_git_metadata;

Expand All @@ -31,6 +33,8 @@ typedef struct ddtrace_span_data ddtrace_span_data;
typedef struct ddtrace_root_span_data ddtrace_root_span_data;
typedef struct ddtrace_span_stack ddtrace_span_stack;
typedef struct ddtrace_span_link ddtrace_span_link;
typedef struct ddtrace_span_event ddtrace_span_event;
typedef struct ddtrace_exception_span_event ddtrace_exception_span_event;
typedef struct ddtrace_git_metadata ddtrace_git_metadata;

extern datadog_php_sapi ddtrace_active_sapi;
Expand Down
51 changes: 51 additions & 0 deletions ext/ddtrace.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,52 @@
*/
const DBM_PROPAGATION_FULL = UNKNOWN;

class SpanEvent implements \JsonSerializable {
/**
* SpanEvent constructor.
*
* @param string $name The event name.
* @param int|null $timestamp The event start time in nanoseconds, if not provided set the current Unix timestamp.
* @param array $attributes Optional attributes for the event.
*/
public function __construct(string $name, array $attributes = [], ?int $timestamp = null) {}

/**
* @var string The event name
*/
public string $name;

/**
* @var string[] $attributes
*/
public array $attributes;

/**
* @var int The event start time in nanoseconds, if not provided set the current Unix timestamp
*/
public int $timestamp;

/**
* @return mixed
*/
public function jsonSerialize(): mixed {}
}

class ExceptionSpanEvent extends SpanEvent {
/**
* ExceptionSpanEvent constructor.
*
* @param \Throwable $exception exception to record.
* @param array $attributes Optional attributes for the event.
*/
public function __construct(\Throwable $exception, array $attributes = []) {}

/**
* @var \Throwable
*/
public \Throwable $exception;
}

class SpanLink implements \JsonSerializable {
/**
* @var string $traceId A 32-character, lower-case hexadecimal encoded string of the linked trace ID. This field
Expand Down Expand Up @@ -144,6 +190,11 @@ class SpanData {
*/
public array $links = [];

/**
* @var SpanEvent[] $spanEvents An array of span events
*/
public array $events = [];

/**
* @var string[] $peerServiceSources A sorted list of tag names used to set the `peer.service` tag. If a tag
* name is added to this field and the tag exists on the span at serialization time, then the value of the tag
Expand Down
83 changes: 81 additions & 2 deletions ext/ddtrace_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: b7ca444d39b9a8489e4e93042e0f7e7eb9aa8b05 */
* Stub hash: fa4bda312fa3b405b09e09c6bc81a05d2a8e3372 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_trace_method, 0, 3, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, className, IS_STRING, 0)
Expand Down Expand Up @@ -271,9 +271,22 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_dd_trace_synchronous_flush, 0, 0
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "100")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_SpanLink_jsonSerialize, 0, 0, IS_MIXED, 0)
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DDTrace_SpanEvent___construct, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, attributes, IS_ARRAY, 0, "[]")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timestamp, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DDTrace_SpanEvent_jsonSerialize, 0, 0, IS_MIXED, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DDTrace_ExceptionSpanEvent___construct, 0, 0, 1)
ZEND_ARG_OBJ_INFO(0, exception, Throwable, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, attributes, IS_ARRAY, 0, "[]")
ZEND_END_ARG_INFO()

#define arginfo_class_DDTrace_SpanLink_jsonSerialize arginfo_class_DDTrace_SpanEvent_jsonSerialize

ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DDTrace_SpanLink_fromHeaders, 0, 1, DDTrace\\SpanLink, 0)
ZEND_ARG_TYPE_MASK(0, headersOrCallback, MAY_BE_ARRAY|MAY_BE_CALLABLE, NULL)
ZEND_END_ARG_INFO()
Expand Down Expand Up @@ -361,6 +374,9 @@ ZEND_FUNCTION(DDTrace_trace_function);
ZEND_FUNCTION(DDTrace_trace_method);
ZEND_FUNCTION(dd_untrace);
ZEND_FUNCTION(dd_trace_synchronous_flush);
ZEND_METHOD(DDTrace_SpanEvent, __construct);
ZEND_METHOD(DDTrace_SpanEvent, jsonSerialize);
ZEND_METHOD(DDTrace_ExceptionSpanEvent, __construct);
ZEND_METHOD(DDTrace_SpanLink, jsonSerialize);
ZEND_METHOD(DDTrace_SpanLink, fromHeaders);
ZEND_METHOD(DDTrace_SpanData, getDuration);
Expand Down Expand Up @@ -444,6 +460,17 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE_END
};

static const zend_function_entry class_DDTrace_SpanEvent_methods[] = {
ZEND_ME(DDTrace_SpanEvent, __construct, arginfo_class_DDTrace_SpanEvent___construct, ZEND_ACC_PUBLIC)
ZEND_ME(DDTrace_SpanEvent, jsonSerialize, arginfo_class_DDTrace_SpanEvent_jsonSerialize, ZEND_ACC_PUBLIC)
ZEND_FE_END
};

static const zend_function_entry class_DDTrace_ExceptionSpanEvent_methods[] = {
ZEND_ME(DDTrace_ExceptionSpanEvent, __construct, arginfo_class_DDTrace_ExceptionSpanEvent___construct, ZEND_ACC_PUBLIC)
ZEND_FE_END
};

static const zend_function_entry class_DDTrace_SpanLink_methods[] = {
ZEND_ME(DDTrace_SpanLink, jsonSerialize, arginfo_class_DDTrace_SpanLink_jsonSerialize, ZEND_ACC_PUBLIC)
ZEND_ME(DDTrace_SpanLink, fromHeaders, arginfo_class_DDTrace_SpanLink_fromHeaders, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
Expand Down Expand Up @@ -491,6 +518,52 @@ static void register_ddtrace_symbols(int module_number)
REGISTER_LONG_CONSTANT("DD_TRACE_PRIORITY_SAMPLING_UNSET", DDTRACE_PRIORITY_SAMPLING_UNSET, CONST_PERSISTENT);
}

static zend_class_entry *register_class_DDTrace_SpanEvent(zend_class_entry *class_entry_JsonSerializable)
{
zend_class_entry ce, *class_entry;

INIT_NS_CLASS_ENTRY(ce, "DDTrace", "SpanEvent", class_DDTrace_SpanEvent_methods);
class_entry = zend_register_internal_class_ex(&ce, NULL);
zend_class_implements(class_entry, 1, class_entry_JsonSerializable);

zval property_name_default_value;
ZVAL_UNDEF(&property_name_default_value);
zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1);
zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
zend_string_release(property_name_name);

zval property_attributes_default_value;
ZVAL_UNDEF(&property_attributes_default_value);
zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1);
zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY));
zend_string_release(property_attributes_name);

zval property_timestamp_default_value;
ZVAL_UNDEF(&property_timestamp_default_value);
zend_string *property_timestamp_name = zend_string_init("timestamp", sizeof("timestamp") - 1, 1);
zend_declare_typed_property(class_entry, property_timestamp_name, &property_timestamp_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(property_timestamp_name);

return class_entry;
}

static zend_class_entry *register_class_DDTrace_ExceptionSpanEvent(zend_class_entry *class_entry_DDTrace_SpanEvent)
{
zend_class_entry ce, *class_entry;

INIT_NS_CLASS_ENTRY(ce, "DDTrace", "ExceptionSpanEvent", class_DDTrace_ExceptionSpanEvent_methods);
class_entry = zend_register_internal_class_ex(&ce, class_entry_DDTrace_SpanEvent);

zval property_exception_default_value;
ZVAL_UNDEF(&property_exception_default_value);
zend_string *property_exception_name = zend_string_init("exception", sizeof("exception") - 1, 1);
zend_string *property_exception_class_Throwable = zend_string_init("Throwable", sizeof("Throwable")-1, 1);
zend_declare_typed_property(class_entry, property_exception_name, &property_exception_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_exception_class_Throwable, 0, 0));
zend_string_release(property_exception_name);

return class_entry;
}

static zend_class_entry *register_class_DDTrace_SpanLink(zend_class_entry *class_entry_JsonSerializable)
{
zend_class_entry ce, *class_entry;
Expand Down Expand Up @@ -634,6 +707,12 @@ static zend_class_entry *register_class_DDTrace_SpanData(void)
zend_declare_typed_property(class_entry, property_links_name, &property_links_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY));
zend_string_release(property_links_name);

zval property_events_default_value;
ZVAL_EMPTY_ARRAY(&property_events_default_value);
zend_string *property_events_name = zend_string_init("events", sizeof("events") - 1, 1);
zend_declare_typed_property(class_entry, property_events_name, &property_events_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY));
zend_string_release(property_events_name);

zval property_peerServiceSources_default_value;
ZVAL_EMPTY_ARRAY(&property_peerServiceSources_default_value);
zend_string *property_peerServiceSources_name = zend_string_init("peerServiceSources", sizeof("peerServiceSources") - 1, 1);
Expand Down
17 changes: 17 additions & 0 deletions ext/serializer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,23 @@ static void _serialize_meta(zval *el, ddtrace_span_data *span) {
EG(exception) = current_exception;
}

zend_array *span_events = ddtrace_property_array(&span->property_events);
if (zend_hash_num_elements(span_events) > 0) {
// Save the current exception, if any, and clear it for php_json_encode_serializable_object not to fail
// and zend_call_function to actually call the jsonSerialize method
// Restored after span events are serialized
zend_object* current_exception = EG(exception);
EG(exception) = NULL;

smart_str buf = {0};
_dd_serialize_json(span_events, &buf, 0);
add_assoc_str(meta, "events", buf.s);

// Restore the exception
EG(exception) = current_exception;
}


zval *git_metadata = &span->root->property_git_metadata;
if (git_metadata && Z_TYPE_P(git_metadata) == IS_OBJECT) {
ddtrace_git_metadata *metadata = (ddtrace_git_metadata *)Z_OBJ_P(git_metadata);
Expand Down
Loading

0 comments on commit 4172cb8

Please sign in to comment.