Skip to content
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

[Tracer] Adds SpanEvents Support for DD and OTEL #2754

Merged
merged 36 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
90802b9
Adding class details for SpanEvent
link04 Jul 8, 2024
5a57d67
Updated serializer and other C files to add event
link04 Jul 8, 2024
738d8f3
Adding API to add events and use existent ones
link04 Jul 9, 2024
62bec04
Adding event and checking in test
link04 Jul 9, 2024
da56ae5
Trying to test Otel to DD
link04 Jul 9, 2024
e5f6cd3
Fixing test and applying nit changes
link04 Jul 10, 2024
634b837
Currently passing full AddEvent and RecordException tests
link04 Jul 11, 2024
5b91448
Updating how we recordException
link04 Jul 11, 2024
63a4cc8
Removing manually added code
link04 Jul 15, 2024
f3e2a3e
Added constructor and updated args were needed
link04 Jul 16, 2024
1fc93f6
Merging master into branch
link04 Jul 24, 2024
daf5d2c
Doing fixes so that test pass
link04 Jul 25, 2024
2fd7eb6
Fixing missing CLOCK_REALTIME for Windows build error
link04 Jul 30, 2024
fc6feef
Using inhouse time method and fixing array errors
link04 Jul 30, 2024
3bddb8e
Implementing addEvent for system-tests
link04 Jul 31, 2024
8f74dff
Adding ExceptionSpanEvent class and recordException
link04 Aug 2, 2024
1a19cca
Applying C code nits and limiting recordException
link04 Aug 6, 2024
16e1039
Merge branch 'master' into maximo/span-events-support
link04 Aug 6, 2024
c682f08
Merging master and Updating ddtrace_arginfo.h
link04 Aug 6, 2024
d41a584
Applying nits and using exception property in C
link04 Aug 6, 2024
3b3cdf9
Adding manual usage work for both classes
link04 Aug 6, 2024
9c8d53a
Trying to fix CircleCI build nts extension errors
link04 Aug 6, 2024
ef1341a
Trying to change where attributes are handled
link04 Aug 7, 2024
853e555
Cleanup span event serialization
bwoebi Aug 8, 2024
8c2e716
Updating broken tests
link04 Aug 8, 2024
8a5ea08
Fixing what seems to be a version issue
link04 Aug 8, 2024
9863c7f
Tested with 7.4 passing
link04 Aug 9, 2024
9d711ad
Fixing test based on updated behavior
link04 Aug 9, 2024
4d316e4
Updating .phpt tests events value
link04 Aug 9, 2024
650ad50
Revert "Updating .phpt tests events value"
link04 Aug 9, 2024
95fd027
Initializing empty array
link04 Aug 9, 2024
c6621ee
Merge branch 'master' into maximo/span-events-support
link04 Aug 9, 2024
7d7734c
Addressing nits, updating libdatadog and recording exceptions
link04 Aug 10, 2024
de384f4
Merge remote-tracking branch 'origin/master' into maximo/span-events-…
bwoebi Aug 19, 2024
aadf273
Cleanup implementation slightly
bwoebi Aug 19, 2024
11478f5
Merge branch 'master' of github.com:DataDog/dd-trace-php into maximo/…
bwoebi Aug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions ext/ddtrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,35 @@ PHP_METHOD(DDTrace_SpanLink, jsonSerialize) {
RETURN_ARR(array);
}

/* 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);

zend_array *array = zend_new_array(3);

zend_string *name = zend_string_init("name", sizeof("name") - 1, 0);
zend_string *timestamp = zend_string_init("time_unix_nano", sizeof("time_unix_nano") - 1, 0);

Z_TRY_ADDREF(event->property_name);
zend_hash_add(array, name, &event->property_name);
link04 marked this conversation as resolved.
Show resolved Hide resolved
Z_TRY_ADDREF(event->property_timestamp);
zend_hash_add(array, timestamp, &event->property_timestamp);

zend_string_release(name);
zend_string_release(timestamp);

if (Z_TYPE(event->property_attributes) != IS_NULL && Z_ARRVAL(event->property_attributes)->nNumOfElements > 0) {
link04 marked this conversation as resolved.
Show resolved Hide resolved
zend_string *attributes = zend_string_init("attributes", sizeof("attributes") - 1, 0);
Z_TRY_ADDREF(event->property_attributes);
zend_hash_add(array, attributes, &event->property_attributes);
zend_string_release(attributes);
}

RETURN_ARR(array);
}

static ddtrace_distributed_tracing_result dd_parse_distributed_tracing_headers_function(INTERNAL_FUNCTION_PARAMETERS, bool *success);
ZEND_METHOD(DDTrace_SpanLink, fromHeaders) {
bool success;
Expand Down Expand Up @@ -1148,6 +1177,7 @@ 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_engine_hooks_minit();

Expand Down
2 changes: 2 additions & 0 deletions ext/ddtrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ 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_integration;
extern zend_class_entry *ddtrace_ce_span_event;

typedef struct ddtrace_span_ids_t ddtrace_span_ids_t;
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;

extern datadog_php_sapi ddtrace_active_sapi;

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

class SpanEvent implements \JsonSerializable {
bwoebi marked this conversation as resolved.
Show resolved Hide resolved
public function __construct(string $name, ?int $timestamp = null, array $attributes = []) {}

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

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

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

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

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 @@ -132,6 +156,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
17 changes: 17 additions & 0 deletions ext/serializer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,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;
}


if (get_DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED()) { // opt-in
zend_array *peer_service_sources = ddtrace_property_array(&span->property_peer_service_sources);
if (zend_hash_str_exists(Z_ARRVAL_P(meta), ZEND_STRL("peer.service"))) { // peer.service is already set by the user, honor it
Expand Down
13 changes: 13 additions & 0 deletions ext/span.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ typedef union ddtrace_span_properties {
zval property_id;
};
zval property_links;
zval property_events;
zval property_peer_service_sources;
union {
union ddtrace_span_properties *parent;
Expand Down Expand Up @@ -170,6 +171,18 @@ struct ddtrace_span_link {
};
};

struct ddtrace_span_event {
union {
zend_object std;
struct {
char object_placeholder[sizeof(zend_object) - sizeof(zval)];
zval property_name;
zval property_timestamp;
zval property_attributes;
};
};
};

void ddtrace_init_span_stacks(void);
void ddtrace_free_span_stacks(bool silent);
void ddtrace_switch_span_stack(ddtrace_span_stack *target_stack);
Expand Down
7 changes: 7 additions & 0 deletions src/DDTrace/OpenTelemetry/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ private static function activateParent(?SpanData $currentSpan): ContextInterface
$links[] = new SDK\Link($linkSpanContext, Attributes::create($spanLink->attributes));
}

// Check for span events
$events = [];
foreach ($currentSpan->events as $spanEvent) {
$events[] = new SDK\Event($spanEvent->name, (int)$spanEvent->timestamp, Attributes::create($spanEvent->attributes ?? []));
}

$OTelCurrentSpan = SDK\Span::startSpan(
$currentSpan,
API\SpanContext::create($currentTraceId, $currentSpanId, $traceFlags, $traceState), // $context
Expand All @@ -201,6 +207,7 @@ private static function activateParent(?SpanData $currentSpan): ContextInterface
[], // $attributesBuilder
$links, // $links
count($links), // $totalRecordedLinks
$events, //$events
false // The span was created using the DD Api
);
ObjectKVStore::put($currentSpan, 'otel_span', $OTelCurrentSpan);
Expand Down
55 changes: 54 additions & 1 deletion src/DDTrace/OpenTelemetry/Span.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DDTrace\SpanData;
use DDTrace\SpanLink;
use DDTrace\SpanEvent;
use DDTrace\Tag;
use DDTrace\OpenTelemetry\Convention;
use DDTrace\Util\ObjectKVStore;
Expand Down Expand Up @@ -43,6 +44,14 @@ final class Span extends API\Span implements ReadWriteSpanInterface
*/
private array $links;

/**
* @readonly
*
* @var list<EventInterface>
*/
private array $events;


/** @readonly */
private int $totalRecordedLinks;

Expand All @@ -69,6 +78,7 @@ private function __construct(
ResourceInfo $resource,
array $links = [],
int $totalRecordedLinks = 0,
array $events = [],
bool $isRemapped = true
) {
$this->span = $span;
Expand All @@ -80,6 +90,7 @@ private function __construct(
$this->resource = $resource;
$this->links = $links;
$this->totalRecordedLinks = $totalRecordedLinks;
$this->events = $events;

$this->status = StatusData::unset();

Expand Down Expand Up @@ -110,6 +121,19 @@ private function __construct(
ObjectKVStore::put($spanLink, "link", $link);
$span->links[] = $spanLink;
}

foreach ($events as $event) {
/** @var EventInterface $event */

$spanEvent = new SpanEvent();
$spanEvent->name = $event->getName();
$spanEvent->timestamp = $event->getEpochNanos();
$spanEvent->attributes = $event->getAttributes()->toArray();

// Save the event
ObjectKVStore::put($spanEvent, "event", $event);
$span->events[] = $spanEvent;
}
}
}

Expand All @@ -136,6 +160,7 @@ public static function startSpan(
array $attributes,
array $links,
int $totalRecordedLinks,
array $events,
bool $isRemapped = true // Answers the question "Was the span created using the OTel API?"
): self {
self::_setAttributes($span, $attributes);
Expand All @@ -156,6 +181,7 @@ public static function startSpan(
$resource,
$links,
$totalRecordedLinks,
$events,
$isRemapped
);

Expand Down Expand Up @@ -203,12 +229,13 @@ public function toSpanData(): SpanDataInterface
$hasEnded = $this->hasEnded();

$this->updateSpanLinks();
$this->updateSpanEvents();

return new ImmutableSpan(
$this,
$this->getName(),
$this->links,
[], // TODO: Handle Span Events
$this->events,
Attributes::create(array_merge($this->span->meta, $this->span->metrics)),
0,
StatusData::create($this->status->getCode(), $this->status->getDescription()),
Expand Down Expand Up @@ -475,4 +502,30 @@ private function updateSpanLinks()
$this->links = $otel;
$this->totalRecordedLinks = count($otel);
}

private function updateSpanEvents()
{
$datadogSpanEvents = $this->span->events;

$otel = [];
foreach ($datadogSpanEvents as $datadogSpanEvent) {
// Check if the event relationship exists
$event = ObjectKVStore::get($datadogSpanEvent, "event");
if ($event === null) {
// Create the event
$event = new Event(
$datadogSpanEvent->name,
(int)$datadogSpanEvent->timestamp,
Attributes::create($datadogSpanEvent->attributes ?? [])
);

// Save the event
ObjectKVStore::put($datadogSpanEvent, "event", $event);
}
$otel[] = $event;
}

// Update the events
$this->events = $otel;
}
}
38 changes: 38 additions & 0 deletions src/DDTrace/OpenTelemetry/SpanBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use OpenTelemetry\SDK\Common\Attribute\AttributesFactory;
use OpenTelemetry\SDK\Common\Instrumentation\InstrumentationScopeInterface;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use Throwable;

final class SpanBuilder implements API\SpanBuilderInterface
{
Expand All @@ -43,6 +44,9 @@ final class SpanBuilder implements API\SpanBuilderInterface
/** @var list<LinkInterface> */
private array $links = [];

/** @var list<EventInterface> */
private array $events = [];

/** @var array */
private array $attributes;

Expand Down Expand Up @@ -92,6 +96,38 @@ public function addLink(SpanContextInterface $context, iterable $attributes = []
return $this;
}

public function addEvent(string $name, int $timestamp = null, iterable $attributes = []): SpanBuilderInterface
{
$attributesArray = Attributes::create($attributes);
$nanoTimestamp = ($timestamp !== null && $timestamp < 1e9) ? $timestamp * 1000 : ($timestamp ?? (int)(microtime(true) * 1e9)); // Convert microseconds to nanoseconds if needed
link04 marked this conversation as resolved.
Show resolved Hide resolved
$this->events[] = new Event($name, $nanoTimestamp, Attributes::create($attributes));
return $this;
}

public function recordException(Throwable $exception, iterable $attributes = []): SpanBuilderInterface
{
// Standardized exception attributes
$exceptionAttributes = [
'exception.message' => $attributes['exception.message'] ?? $exception->getMessage(),
'exception.type' => $attributes['exception.type'] ?? get_class($exception),
'exception.stacktrace' => $attributes['exception.stacktrace'] ?? $exception->getTraceAsString(),
bwoebi marked this conversation as resolved.
Show resolved Hide resolved
'exception.escaped' => false
];

// Update span metadata based on exception attributes
$this->setAttribute(Tag::ERROR_MSG, $exceptionAttributes['exception.message']);
$this->setAttribute(Tag::ERROR_TYPE, $exceptionAttributes['exception.type']);
$this->setAttribute(Tag::ERROR_STACK, $exceptionAttributes['exception.stacktrace']);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about this? Exceptions generally should only be added on spans if they escape their span.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is by design, to match what our Datadog Agent does for OTEL Events when it receives OTLP traces. If there are any exception events on a span, then the last event that is added will be used to populate the error tagging fields on the span.

Note: We do not update the span.Error field when calling this method. To update the span.Error field, the user would have to call the OpenTelemetry span.SetStatus API with the Error status.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error is set automatically when error.message or error.type are present. So, at least that assumption is not correct here.

When manually adding errors, users are supposed to only add error.message and error = 1 is automatically added for them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also IIRC that behaviour of populating error.message is a fallback behaviour from the ages where no span event support existed in the UI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @bwoebi, I've been looking into this ask here since I thought for us to work around this I could check if the span was an error or not, if not then reset the error tag but I am not sure how to accomplish this, for example I saw that the error can be removed from the metrics object using unset($this->span->metrics[Tag::ERROR]); but couldn't find a way to do so for the body of the Span and wanted to check what you'd recommend.

Copy link
Collaborator

@bwoebi bwoebi Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I would recommend is not doing this, honestly.
Like, it's a bad idea which goes against the defined semantics of the tracer. In the PHP tracer, error_msg/error_type is reserved for exceptions escaping the span.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean, will go ahead with only adding the stack tag for now and later connect with Error Tracking to try getting the details from the span event instead, does that sounds better at the moment?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


// Merge additional attributes
$allAttributes = array_merge($exceptionAttributes, iterator_to_array($attributes));
link04 marked this conversation as resolved.
Show resolved Hide resolved

// Record the exception event
$this->addEvent('exception', null, $allAttributes);

return $this;
}

/** @inheritDoc */
public function setAttribute(string $key, $value): API\SpanBuilderInterface
{
Expand Down Expand Up @@ -156,6 +192,7 @@ public function startSpan(): SpanInterface
$this->spanKind,
Attributes::create($this->attributes),
$this->links,
$this->events
);

$span = $span ?? \DDTrace\start_trace_span($this->startEpochNanos);
Expand Down Expand Up @@ -204,6 +241,7 @@ public function startSpan(): SpanInterface
$this->attributes,
$this->links,
$this->totalNumberOfLinksAdded,
$this->events
);
}

Expand Down
Loading
Loading