Skip to content

Commit 7ede319

Browse files
committed
fix: Align observability event coverage
Include broadcast outbox age and activation message correlation. Keep the\ncombined diagnostics category cap explicit and remove unnecessary broad\ntype annotations from telemetry delivery.
1 parent e128a95 commit 7ede319

9 files changed

Lines changed: 59 additions & 17 deletions

File tree

‎docs/observability.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ runs before reading summaries or registering observers; possessing an actor ID
9494
confers no permission.
9595

9696
Diagnostics read at most `limit + 1` rows per queue source, with a hard limit of 100. Each category returns `sampled`, `truncated`, and `oldestAgeMilliseconds`.
97+
The limit applies to each combined category: one effect plus one broadcast with
98+
`limit: 1` returns `sampled: 1, truncated: true`, even when both source queries
99+
returned all their rows. The extra row proves that the category exceeds its cap.
97100
The last value measures nonnegative time since availability (or terminal failure
98101
for recovery callbacks); future reminders have zero age. No payloads or row
99102
identifiers are returned. Samples are observations across several queries,

‎src/actor-runtime.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ export interface ActorRuntime {
3737
): Promise<ActorDiagnostics>
3838
observe(
3939
reference: ActorReferenceCore<Actor>,
40-
options: { onEvent: EventObserver; authorizationContext?: unknown },
40+
options: {
41+
onEvent: EventObserver
42+
authorizationContext?: SnapshotOptions["authorizationContext"]
43+
},
4144
): Promise<() => void>
4245
ref<ActorType extends Actor>(
4346
actorClass: ActorClass<ActorType>,

‎src/cloudflare/engine.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ export class ActorEngine {
857857
try {
858858
this.emit("outbox.age", {
859859
messageId: outbox.messageId,
860+
outboxKind: outbox.kind,
860861
attempt: outbox.attempt + 1,
861862
ageMilliseconds: Math.max(0, Date.now() - outbox.availableAt),
862863
})

‎src/cloudflare/runtime.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ export class CloudflareRuntime implements ActorRuntime {
8282

8383
async observe(
8484
_reference: ActorReferenceCore<Actor>,
85-
_options: { onEvent: EventObserver; authorizationContext?: unknown },
85+
_options: {
86+
onEvent: EventObserver
87+
authorizationContext?: SnapshotOptions["authorizationContext"]
88+
},
8689
): Promise<() => void> {
8790
return unsupported(
8891
"process-local observers; configure instrumentation on the actor Durable Object host",
@@ -100,7 +103,7 @@ export class CloudflareRuntime implements ActorRuntime {
100103
authorizationContext: normalizeJson(options.authorizationContext ?? null),
101104
payload: { action: "diagnostics", limit: options.limit ?? 100 },
102105
})
103-
return readonlyCopy(value) as unknown as ActorDiagnostics
106+
return readonlyCopy(value) as ActorDiagnostics & JsonObject
104107
}
105108

106109
async invoke<Result = JsonValue>(options: {

‎src/reference.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,17 @@ export class ActorReferenceCore<ActorType extends Actor> {
244244

245245
observe(options: {
246246
onEvent: EventObserver
247-
authorizationContext?: unknown
247+
authorizationContext?: SnapshotOptions["authorizationContext"]
248248
}): Promise<() => void> {
249249
return this.runtime.observe(this, options)
250250
}
251251

252252
on(
253253
name: string,
254-
options: { onEvent: EventObserver; authorizationContext?: unknown },
254+
options: {
255+
onEvent: EventObserver
256+
authorizationContext?: SnapshotOptions["authorizationContext"]
257+
},
255258
): Promise<() => void> {
256259
return this.observe({
257260
...options,

‎src/runtime.ts‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,9 +1310,7 @@ export class SolidObjectsRuntime {
13101310
await withActorContext({ actor, runtime: this }, () => actor.activate())
13111311
activated = true
13121312
this.emitInstrumentation("activation.completed", {
1313-
actorType: turn.message.actor_type,
1314-
actorId: turn.message.actor_id,
1315-
instanceId: turn.instance.id,
1313+
...messageInstrumentation(turn.message),
13161314
generation: String(turn.activationGeneration),
13171315
})
13181316
} catch (error) {
@@ -1454,11 +1452,10 @@ export class SolidObjectsRuntime {
14541452
})
14551453
if (outcome === "retrying")
14561454
this.emitInstrumentation("message.retry", messageInstrumentation(turn.message))
1457-
if (outcome === "dead") {
1458-
if (recoveryMessage(turn.message))
1459-
this.emitInstrumentation("recovery.failed", messageInstrumentation(turn.message))
1455+
if (outcome === "dead" && recoveryMessage(turn.message))
1456+
this.emitInstrumentation("recovery.failed", messageInstrumentation(turn.message))
1457+
if (outcome === "dead")
14601458
this.emitInstrumentation("dead_letter.created", messageInstrumentation(turn.message))
1461-
}
14621459
return { actor, retainActivation: activated, activated }
14631460
}
14641461
}
@@ -1574,7 +1571,10 @@ export class SolidObjectsRuntime {
15741571

15751572
async observe(
15761573
reference: ActorReferenceCore<Actor>,
1577-
options: { onEvent: EventObserver; authorizationContext?: unknown },
1574+
options: {
1575+
onEvent: EventObserver
1576+
authorizationContext?: SnapshotOptions["authorizationContext"]
1577+
},
15781578
): Promise<() => void> {
15791579
await this.authorizeAdministration({
15801580
action: "observe",
@@ -1613,6 +1613,7 @@ export class SolidObjectsRuntime {
16131613
try {
16141614
this.emitInstrumentation("outbox.age", {
16151615
...effectInstrumentation(effect),
1616+
outboxKind: "effect",
16161617
ageMilliseconds: Math.max(0, Date.now() - Number(effect.available_at_ms)),
16171618
})
16181619
const handler = this.effects.get(effect.name)
@@ -1676,6 +1677,11 @@ export class SolidObjectsRuntime {
16761677
async executeBroadcast(broadcast: BroadcastRow): Promise<void> {
16771678
const deliver = this.settings.broadcast
16781679
try {
1680+
this.emitInstrumentation("outbox.age", {
1681+
...broadcastInstrumentation(broadcast),
1682+
outboxKind: "broadcast",
1683+
ageMilliseconds: Math.max(0, Date.now() - Number(broadcast.available_at_ms)),
1684+
})
16791685
const event = readonlyCopy({
16801686
actorType: broadcast.actor_type,
16811687
actorId: broadcast.actor_id,
@@ -2527,6 +2533,7 @@ function effectInstrumentation(effect: EffectRow): JsonObject {
25272533

25282534
function broadcastInstrumentation(broadcast: BroadcastRow): JsonObject {
25292535
return {
2536+
instanceId: broadcast.instance_id,
25302537
broadcastId: broadcast.id,
25312538
messageId: broadcast.message_id,
25322539
actorType: broadcast.actor_type,

‎src/telemetry.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const fields = new Set([
6464
"nextRunAt",
6565
"name",
6666
"commitAction",
67+
"outboxKind",
6768
"truncated",
6869
"payload",
6970
"waitingOn",
@@ -126,9 +127,9 @@ export function deliverTelemetry(options: {
126127
event: InstrumentationEvent
127128
logger: Logger
128129
}): void {
129-
const failed = (error: unknown) => {
130+
const failed = <ErrorValue>(error: ErrorValue) => {
130131
try {
131-
const result: unknown = options.logger.error({
132+
const result = options.logger.error({
132133
event: "solid_objects.instrumentation.failed",
133134
instrumentationEvent: options.event.name,
134135
errorName: error instanceof Error ? error.name : "Error",

‎src/wake-up-notification.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function notifyWakeUp(options: {
88
}): void {
99
const logFailure = (errorName: string): void => {
1010
try {
11-
const result: unknown = options.logger.error({
11+
const result = options.logger.error({
1212
event: "solid_objects.wake_up.failed",
1313
role: options.role,
1414
errorName,

‎test/outboxes.test.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { afterEach, describe, expect, it } from "vitest"
22
import { Actor, broadcastInvalidation, broadcastValue } from "../src/actor.js"
3-
import type { BroadcastEvent, SolidObjectsConfiguration } from "../src/configuration.js"
3+
import type {
4+
BroadcastEvent,
5+
InstrumentationEvent,
6+
SolidObjectsConfiguration,
7+
} from "../src/configuration.js"
48
import { NonRetryableError } from "../src/errors.js"
59
import { configure, type SolidObjectsRuntime } from "../src/runtime.js"
610
import { sqlite } from "../src/database/sqlite.js"
@@ -414,7 +418,11 @@ describe("reminders", () => {
414418
describe("observable broadcasts", () => {
415419
it("delivers values and invalidation-only observable names", async () => {
416420
const events: BroadcastEvent[] = []
421+
const telemetry: InstrumentationEvent[] = []
417422
runtime = configuredRuntime({
423+
instrumentation: (event) => {
424+
telemetry.push(event)
425+
},
418426
broadcast: async (event) => {
419427
events.push(event)
420428
},
@@ -424,6 +432,19 @@ describe("observable broadcasts", () => {
424432
await ObservableCounter.ref("counter").increment()
425433
expect(await runtime.broadcastWorker().runUntilIdle()).toBe(1)
426434

435+
expect(telemetry).toEqual(
436+
expect.arrayContaining([
437+
expect.objectContaining({
438+
name: "solid_objects.outbox.age",
439+
actorId: "counter",
440+
incarnation: expect.any(String),
441+
attributes: expect.objectContaining({
442+
outboxKind: "broadcast",
443+
ageMilliseconds: expect.any(Number),
444+
}),
445+
}),
446+
]),
447+
)
427448
expect(events).toHaveLength(1)
428449
expect(events[0]).toMatchObject({
429450
actorType: "ObservableCounter",

0 commit comments

Comments
 (0)