Skip to main content

worklane_core/
observer.rs

1//! The observer SPI: a telemetry extension point for job resolution.
2//!
3//! A [`JobObserver`] is the symmetric counterpart to a [`Broker`](crate::Broker):
4//! where a broker is the storage extension point, an observer is the telemetry
5//! extension point. It lives in `worklane-core` so a telemetry integration (for
6//! example `worklane-metrics`) can depend on the contract alone, without pulling
7//! in the `worklane` facade and its runtime.
8//!
9//! The `worklane` facade re-exports these types, and its `Worker` calls the
10//! observer inline as it resolves jobs.
11
12use std::time::Duration;
13
14/// What ultimately happened to a job, reported to a [`JobObserver`].
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum JobOutcome {
18    /// The handler succeeded and the job was acked.
19    Acked,
20    /// The handler failed and the job was scheduled for a retry.
21    Retried,
22    /// The job was moved to the dead-letter store (attempts exhausted, an
23    /// unrecoverable payload, an unknown kind, or a timeout with no retries left).
24    DeadLettered,
25}
26
27/// A finished-job event handed to a [`JobObserver`].
28///
29/// Reported only when the resolution actually took effect — a resolution rejected
30/// as stale (the lease was lost and the job will be redelivered) is *not* reported,
31/// so a redelivered job is counted once, when it finally resolves.
32#[derive(Debug, Clone, Copy)]
33#[non_exhaustive]
34pub struct JobEvent<'a> {
35    /// The lane the job ran on.
36    pub lane: &'a str,
37    /// The job kind.
38    pub kind: &'a str,
39    /// The terminal outcome.
40    pub outcome: JobOutcome,
41    /// Time from dispatch start to resolution (handler plus result-store and
42    /// broker calls). Near zero for an unknown-kind job (no handler runs).
43    pub duration: Duration,
44}
45
46impl<'a> JobEvent<'a> {
47    /// Construct a job event. The worker builds these; this constructor lets
48    /// observer implementors build one in their own tests despite the type being
49    /// `#[non_exhaustive]`.
50    pub fn new(lane: &'a str, kind: &'a str, outcome: JobOutcome, duration: Duration) -> Self {
51        JobEvent {
52            lane,
53            kind,
54            outcome,
55            duration,
56        }
57    }
58}
59
60/// A per-attempt in-flight event handed to a [`JobObserver`].
61#[derive(Debug, Clone, Copy)]
62#[non_exhaustive]
63pub struct JobAttemptEvent<'a> {
64    /// The lane the job attempt is running on.
65    pub lane: &'a str,
66    /// The job kind.
67    pub kind: &'a str,
68}
69
70impl<'a> JobAttemptEvent<'a> {
71    /// Construct an attempt event. The worker builds these; this constructor lets
72    /// observer implementors build one in their own tests despite the type being
73    /// `#[non_exhaustive]`.
74    pub fn new(lane: &'a str, kind: &'a str) -> Self {
75        JobAttemptEvent { lane, kind }
76    }
77}
78
79/// Observes the outcome of every job a `Worker` resolves.
80///
81/// A hook for telemetry — most usefully metrics (job counts by outcome, a
82/// processing-duration histogram). The `worklane-metrics` crate provides an
83/// implementation over the `metrics` facade. The callback runs inline on the
84/// worker, so it must be cheap and non-blocking (record and return).
85pub trait JobObserver: Send + Sync {
86    /// Called when a reserved job attempt enters the worker's in-flight set.
87    fn on_job_started(&self, _event: JobAttemptEvent<'_>) {}
88
89    /// Called when an in-flight job attempt leaves the worker, including stale
90    /// resolution, defer, timeout, or future cancellation. This pairs with
91    /// [`on_job_started`](JobObserver::on_job_started) for in-flight gauges.
92    fn on_job_stopped(&self, _event: JobAttemptEvent<'_>) {}
93
94    /// Called once per job, after its resolution takes effect.
95    fn on_job_finished(&self, event: JobEvent<'_>);
96}