Skip to content

Commit 3539919

Browse files
torosentCopilot
andcommitted
Add timer and event spans matching .NET SDK
- Timer span: emitted on TIMERFIRED in orchestrator (Internal kind, durabletask.fire_at attribute, name: orchestration:<name>:timer) - Event span from worker: emitted on sendEvent in orchestrator (Producer kind, name: orchestration_event:<eventName>) - Event span from client: emitted on raiseEvent in client (Producer kind, name: orchestration_event:<eventName>) - Added 3 new tests (17 TracingHelper tests, 24 total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ccdefe9 commit 3539919

File tree

4 files changed

+152
-1
lines changed

4 files changed

+152
-1
lines changed

client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public void raiseEvent(String instanceId, String eventName, Object eventPayload)
150150
Helpers.throwIfArgumentNull(instanceId, "instanceId");
151151
Helpers.throwIfArgumentNull(eventName, "eventName");
152152

153+
// Emit an event span matching .NET SDK's StartActivityForNewEventRaisedFromClient
154+
TracingHelper.emitEventRaisedFromClientSpan(eventName, instanceId);
155+
153156
RaiseEventRequest.Builder builder = RaiseEventRequest.newBuilder()
154157
.setInstanceId(instanceId)
155158
.setName(eventName);

client/src/main/java/com/microsoft/durabletask/TaskOrchestrationExecutor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,9 @@ public void sendEvent(String instanceId, String eventName, Object eventData) {
396396
eventName,
397397
id,
398398
serializedEventData != null ? serializedEventData : "(null)"));
399+
400+
// Emit an event span matching .NET SDK's StartTraceActivityForEventRaisedFromWorker
401+
TracingHelper.emitEventRaisedFromWorkerSpan(eventName, this.instanceId, instanceId);
399402
}
400403
}
401404

@@ -733,7 +736,15 @@ public void handleTimerFired(HistoryEvent e) {
733736
}
734737

735738
if (!this.isReplaying) {
736-
// TODO: Log timer fired, including the scheduled fire-time
739+
// Emit a timer span matching .NET SDK's EmitTraceActivityForTimer
740+
String fireAt = timerFiredEvent.hasFireAt()
741+
? DataConverter.getInstantFromTimestamp(timerFiredEvent.getFireAt()).toString()
742+
: null;
743+
TracingHelper.emitTimerSpan(
744+
this.getName(),
745+
this.instanceId,
746+
timerEventId,
747+
fireAt);
737748
}
738749

739750
CompletableTask<?> task = record.getTask();

client/src/main/java/com/microsoft/durabletask/TracingHelper.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,17 @@ final class TracingHelper {
3838
static final String TYPE_ORCHESTRATION = "orchestration";
3939
static final String TYPE_ACTIVITY = "activity";
4040
static final String TYPE_CREATE_ORCHESTRATION = "create_orchestration";
41+
static final String TYPE_TIMER = "timer";
42+
static final String TYPE_EVENT = "event";
43+
static final String TYPE_ORCHESTRATION_EVENT = "orchestration_event";
4144

4245
// Attribute keys matching .NET SDK schema
4346
static final String ATTR_TYPE = "durabletask.type";
4447
static final String ATTR_TASK_NAME = "durabletask.task.name";
4548
static final String ATTR_INSTANCE_ID = "durabletask.task.instance_id";
4649
static final String ATTR_TASK_ID = "durabletask.task.task_id";
50+
static final String ATTR_FIRE_AT = "durabletask.fire_at";
51+
static final String ATTR_EVENT_TARGET_INSTANCE_ID = "durabletask.event.target_instance_id";
4752

4853
private TracingHelper() {
4954
// Static utility class
@@ -264,4 +269,92 @@ static void endSpan(@Nullable Span span, @Nullable Throwable error) {
264269
}
265270
span.end();
266271
}
272+
273+
/**
274+
* Emits a short-lived span for a durable timer that has fired.
275+
* Matches .NET SDK's {@code EmitTraceActivityForTimer}.
276+
*
277+
* @param orchestrationName The name of the orchestration that created the timer.
278+
* @param instanceId The orchestration instance ID.
279+
* @param timerId The timer event ID.
280+
* @param fireAt The ISO-8601 formatted fire time.
281+
*/
282+
static void emitTimerSpan(
283+
String orchestrationName,
284+
@Nullable String instanceId,
285+
int timerId,
286+
@Nullable String fireAt) {
287+
Tracer tracer = GlobalOpenTelemetry.getTracer(TRACER_NAME);
288+
SpanBuilder spanBuilder = tracer.spanBuilder(
289+
TYPE_ORCHESTRATION + ":" + orchestrationName + ":" + TYPE_TIMER)
290+
.setSpanKind(SpanKind.INTERNAL)
291+
.setAttribute(ATTR_TYPE, TYPE_TIMER)
292+
.setAttribute(ATTR_TASK_NAME, orchestrationName)
293+
.setAttribute(ATTR_TASK_ID, String.valueOf(timerId));
294+
295+
if (instanceId != null) {
296+
spanBuilder.setAttribute(ATTR_INSTANCE_ID, instanceId);
297+
}
298+
if (fireAt != null) {
299+
spanBuilder.setAttribute(ATTR_FIRE_AT, fireAt);
300+
}
301+
302+
Span span = spanBuilder.startSpan();
303+
span.end();
304+
}
305+
306+
/**
307+
* Emits a short-lived Producer span for an event raised from the orchestrator (worker side).
308+
* Matches .NET SDK's {@code StartTraceActivityForEventRaisedFromWorker}.
309+
*
310+
* @param eventName The name of the event being raised.
311+
* @param instanceId The orchestration instance ID sending the event.
312+
* @param targetInstanceId The target orchestration instance ID, may be {@code null}.
313+
*/
314+
static void emitEventRaisedFromWorkerSpan(
315+
String eventName,
316+
@Nullable String instanceId,
317+
@Nullable String targetInstanceId) {
318+
Tracer tracer = GlobalOpenTelemetry.getTracer(TRACER_NAME);
319+
SpanBuilder spanBuilder = tracer.spanBuilder(
320+
TYPE_ORCHESTRATION_EVENT + ":" + eventName)
321+
.setSpanKind(SpanKind.PRODUCER)
322+
.setAttribute(ATTR_TYPE, TYPE_EVENT)
323+
.setAttribute(ATTR_TASK_NAME, eventName);
324+
325+
if (instanceId != null) {
326+
spanBuilder.setAttribute(ATTR_INSTANCE_ID, instanceId);
327+
}
328+
if (targetInstanceId != null) {
329+
spanBuilder.setAttribute(ATTR_EVENT_TARGET_INSTANCE_ID, targetInstanceId);
330+
}
331+
332+
Span span = spanBuilder.startSpan();
333+
span.end();
334+
}
335+
336+
/**
337+
* Emits a short-lived Producer span for an event raised from the client.
338+
* Matches .NET SDK's {@code StartActivityForNewEventRaisedFromClient}.
339+
*
340+
* @param eventName The name of the event being raised.
341+
* @param targetInstanceId The target orchestration instance ID.
342+
*/
343+
static void emitEventRaisedFromClientSpan(
344+
String eventName,
345+
@Nullable String targetInstanceId) {
346+
Tracer tracer = GlobalOpenTelemetry.getTracer(TRACER_NAME);
347+
SpanBuilder spanBuilder = tracer.spanBuilder(
348+
TYPE_ORCHESTRATION_EVENT + ":" + eventName)
349+
.setSpanKind(SpanKind.PRODUCER)
350+
.setAttribute(ATTR_TYPE, TYPE_EVENT)
351+
.setAttribute(ATTR_TASK_NAME, eventName);
352+
353+
if (targetInstanceId != null) {
354+
spanBuilder.setAttribute(ATTR_EVENT_TARGET_INSTANCE_ID, targetInstanceId);
355+
}
356+
357+
Span span = spanBuilder.startSpan();
358+
span.end();
359+
}
267360
}

client/src/test/java/com/microsoft/durabletask/TracingHelperTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,48 @@ void createClientSpan_withNullParent_returnsNull() {
290290
// With a registered SDK, it should create a root span
291291
assertNotNull(result);
292292
}
293+
294+
@Test
295+
void emitTimerSpan_createsInternalSpanWithFireAt() {
296+
TracingHelper.emitTimerSpan("MyOrchestration", "instance-1", 5, "2026-01-01T00:00:00Z");
297+
298+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
299+
assertEquals(1, spans.size());
300+
SpanData sd = spans.get(0);
301+
assertEquals("orchestration:MyOrchestration:timer", sd.getName());
302+
assertEquals(SpanKind.INTERNAL, sd.getKind());
303+
assertEquals("timer", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.type")));
304+
assertEquals("MyOrchestration", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.task.name")));
305+
assertEquals("instance-1", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.task.instance_id")));
306+
assertEquals("5", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.task.task_id")));
307+
assertEquals("2026-01-01T00:00:00Z", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.fire_at")));
308+
}
309+
310+
@Test
311+
void emitEventRaisedFromWorkerSpan_createsProducerSpan() {
312+
TracingHelper.emitEventRaisedFromWorkerSpan("ApprovalEvent", "orch-1", "target-orch-2");
313+
314+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
315+
assertEquals(1, spans.size());
316+
SpanData sd = spans.get(0);
317+
assertEquals("orchestration_event:ApprovalEvent", sd.getName());
318+
assertEquals(SpanKind.PRODUCER, sd.getKind());
319+
assertEquals("event", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.type")));
320+
assertEquals("ApprovalEvent", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.task.name")));
321+
assertEquals("orch-1", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.task.instance_id")));
322+
assertEquals("target-orch-2", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.event.target_instance_id")));
323+
}
324+
325+
@Test
326+
void emitEventRaisedFromClientSpan_createsProducerSpan() {
327+
TracingHelper.emitEventRaisedFromClientSpan("ApprovalEvent", "target-orch-1");
328+
329+
List<SpanData> spans = spanExporter.getFinishedSpanItems();
330+
assertEquals(1, spans.size());
331+
SpanData sd = spans.get(0);
332+
assertEquals("orchestration_event:ApprovalEvent", sd.getName());
333+
assertEquals(SpanKind.PRODUCER, sd.getKind());
334+
assertEquals("event", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.type")));
335+
assertEquals("target-orch-1", sd.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("durabletask.event.target_instance_id")));
336+
}
293337
}

0 commit comments

Comments
 (0)