Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Unreleased
* Support for serializing `ActivityEvent`s.

## 1.14.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,55 @@ internal ArraySegment<byte> SerializeActivity(Activity activity)
cntFields += 1;
}

var eventEnumerator = activity.EnumerateEvents();
if (eventEnumerator.MoveNext())
{
cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "events");
cursor = MessagePackSerializer.WriteArrayHeader(buffer, cursor, ushort.MaxValue);
var idxEventPatch = cursor - 2;

ushort cntEvent = 0;
do
{
ref readonly var evt = ref eventEnumerator.Current;

var eventTagEnumerator = evt.EnumerateTagObjects();
var hasTags = eventTagEnumerator.MoveNext();
var eventFieldCount = hasTags ? 3 : 2;

cursor = MessagePackSerializer.WriteMapHeader(buffer, cursor, (byte)eventFieldCount);
cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "name");
cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, evt.Name);
cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "time");
cursor = MessagePackSerializer.SerializeUtcDateTime(buffer, cursor, evt.Timestamp.UtcDateTime);

if (hasTags)
{
cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "properties");
cursor = MessagePackSerializer.WriteMapHeader(buffer, cursor, ushort.MaxValue);
var idxEventAttributesPatch = cursor - 2;

ushort cntEventAttributes = 0;
do
{
ref readonly var tag = ref eventTagEnumerator.Current;
cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, tag.Key);
cursor = MessagePackSerializer.Serialize(buffer, cursor, tag.Value);
cntEventAttributes += 1;
}
while (eventTagEnumerator.MoveNext());

MessagePackSerializer.WriteUInt16(buffer, idxEventAttributesPatch, cntEventAttributes);
}

cntEvent += 1;
}
while (eventEnumerator.MoveNext());

MessagePackSerializer.WriteUInt16(buffer, idxEventPatch, cntEvent);
cntFields += 1;
}

// TODO: The current approach is to iterate twice over TagObjects so that all
// env_properties can be added the very end. This avoids speculating the size
// and preallocating a separate buffer for it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,28 @@ internal EventBuilder SerializeActivity(Activity activity)
partBFieldsCount++;
}

var eventEnumerator = activity.EnumerateEvents();
if (eventEnumerator.MoveNext())
{
var keyValuePairsForEvents = KeyValuePairs.Value ??= [];
keyValuePairsForEvents.Clear();

do
{
ref readonly var evt = ref eventEnumerator.Current;

keyValuePairsForEvents.Add(new("name", evt.Name));
keyValuePairsForEvents.Add(new("time", evt.Timestamp.UtcDateTime));
keyValuePairsForEvents.Add(new("properties", evt.Tags));
}
while (eventEnumerator.MoveNext());

var serializedEventsStringAsBytes = JsonSerializer.SerializeKeyValuePairsListAsBytes(keyValuePairsForEvents, out var count);
eb.AddCountedAnsiString("events", serializedEventsStringAsBytes, 0, count);

partBFieldsCount++;
}

byte hasEnvProperties = 0;
byte isStatusSuccess = 1;
var statusDescription = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ public void GenevaTraceExporter_Success_Windows()
var link = new ActivityLink(new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded));
using (var activity = source.StartActivity("Foo", ActivityKind.Internal, null, null, [link]))
{
activity?.AddEvent(new ActivityEvent("TestEvent", DateTimeOffset.UtcNow, new ActivityTagsCollection
{
{ "eventKey", "eventValue" },
}));
}

using (var activity = source.StartActivity("Bar"))
Expand Down Expand Up @@ -305,6 +309,14 @@ public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping,
activity?.SetTag("clientRequestId", "58a37988-2c05-427a-891f-5e0e1266fcc5");
activity?.SetTag("foo", 1);
activity?.SetTag("bar", 2);

activity?.AddEvent(new ActivityEvent("TestEvent1", DateTimeOffset.UtcNow, new ActivityTagsCollection
{
{ "eventFoo", 1 },
{ "eventBar", "Hello, World!" },
}));
activity?.AddEvent(new ActivityEvent("TestEvent2"));

#pragma warning disable CS0618 // Type or member is obsolete
activity?.SetStatus(Status.Error.WithDescription("Error description from OTel API"));
#pragma warning restore CS0618 // Type or member is obsolete
Expand Down Expand Up @@ -340,7 +352,7 @@ public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping,
this.AssertMappingEntry(userFieldsLocation, "resourceAndPrepopulated", "comes from prepopulated");
}

// Linked spans are checked in CheckSpanForActivity, so no need to do a custom check here
// Linked spans and events are checked in CheckSpanForActivity, so no need to do a custom check here
});
}

Expand Down Expand Up @@ -1002,6 +1014,41 @@ private void CheckSpanForActivity(GenevaExporterOptions exporterOptions, object
}
#endregion

#region Assert Activity Events
if (activity.Events.Any())
{
Assert.Contains(mapping, m => (m.Key as string) == "events");
var mappingEvents = mapping["events"] as IEnumerable<object>;
using var activityEventsEnumerator = activity.Events.GetEnumerator();
using var mappingEventsEnumerator = mappingEvents.GetEnumerator();
while (activityEventsEnumerator.MoveNext() && mappingEventsEnumerator.MoveNext())
{
var activityEvent = activityEventsEnumerator.Current;
var mappingEvent = mappingEventsEnumerator.Current as Dictionary<object, object>;

Assert.Equal(activityEvent.Name, mappingEvent["name"]);
Assert.Contains("time", mappingEvent.Keys);

if (activityEvent.Tags.Any())
{
Assert.Contains("properties", mappingEvent.Keys);
var mappingEventProperties = mappingEvent["properties"] as Dictionary<object, object>;
Assert.NotNull(mappingEventProperties);
foreach (var tag in activityEvent.Tags)
{
Assert.Contains(mappingEventProperties, kvp => (kvp.Key as string) == tag.Key);
}
}
}

Assert.Equal(activityEventsEnumerator.MoveNext(), mappingEventsEnumerator.MoveNext());
}
else
{
Assert.DoesNotContain(mapping, m => (m.Key as string) == "events");
}
#endregion

#region Assert Activity Tags
if (exporterOptions.CustomFields == null)
{
Expand Down