Skip to content

feat: instrument Symfony Scheduler#41

Merged
jstojiljkovic merged 6 commits into
masterfrom
feat/scheduler-instrumentation
May 13, 2026
Merged

feat: instrument Symfony Scheduler#41
jstojiljkovic merged 6 commits into
masterfrom
feat/scheduler-instrumentation

Conversation

@jstojiljkovic

@jstojiljkovic jstojiljkovic commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #24.

What

Automatic OpenTelemetry instrumentation for Symfony Scheduler (https://symfony.com/doc/current/scheduler.html). One CONSUMER span per
scheduled-task run, emitted by SchedulerSubscriber listening to PreRunEvent / PostRunEvent / FailureEvent.

Attributes

OTel messaging semconv:

  • messaging.system = symfony_scheduler
  • messaging.operation.name = process, messaging.operation.type = process
  • messaging.message.id (from MessageContext::$id)
  • messaging.message.class (FQCN of the dispatched message)
  • messaging.destination.name (schedule name)

Traceway-specific scheduler.* namespace (anticipating any future OTel scheduler semconv):

  • scheduler.schedule.name
  • scheduler.trigger.type (short class name of the trigger, e.g. CronExpressionTrigger, PeriodicalTrigger)
  • scheduler.trigger.expression ((string) $trigger — cron expression, interval, etc.)
  • scheduler.triggered_at (ATOM-formatted)
  • scheduler.trigger.next_run_at (ATOM-formatted, only set when known)
  • scheduler.cancelled = true when a listener calls PreRunEvent::shouldCancel(true)

Errors: recordException + error.type via ErrorTypeResolver. FailureEvent::shouldIgnore() is honored — ignored failures end with STATUS_OK.

Coordination with Messenger middleware

Scheduler dispatches go through the Messenger bus, which would otherwise produce duplicate PRODUCER/CONSUMER spans alongside the scheduler span.
OpenTelemetryMiddleware now skips any envelope carrying Symfony's ScheduledStamp when scheduler instrumentation is active (toggled via
$excludeScheduledMessages, wired automatically in the extension). The stamp class is referenced by string + class_exists guard so Messenger
users without symfony/scheduler aren't affected.

Configuration

open_telemetry:
scheduler_enabled: true # default

Service definition is only registered when symfony/scheduler is installed (checked via interface_exists(ScheduleProviderInterface::class),
matching the bundle's pattern for optional-dep gating).

Lifecycle

  • Spans are keyed by message object identity (SplObjectStorage) to handle nested/concurrent runs.
  • Implements ResetInterface: reset() drains any in-flight spans (long-running workers, container resets).
  • __destruct() drains as a last-resort safety net with scope-detach notices suppressed.
  • Cancellation is observed via a low-priority (-256) PreRun listener so any user listener has already had a chance to set shouldCancel(true)
    before we end the span.

Tests

  • SchedulerSubscriberTest (235 LOC) covers PreRun/PostRun success, FailureEvent (recorded vs ignored), cancellation, nested runs, reset
    behavior.
  • OpenTelemetryMiddlewareTest adds 77 LOC verifying ScheduledStamp envelopes bypass the middleware on both dispatch and consume paths.

@jstojiljkovic jstojiljkovic self-assigned this May 13, 2026
@jstojiljkovic
jstojiljkovic merged commit a7c326a into master May 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symfony Scheduler Support

1 participant