✦ For Durable Objects · WebSockets · Alarms · State Isolation ✦
Durable Objects are stateful isolates. When they crash, the entire isolate dies — and any logs buffered inside it are gone forever. FlareLog persists DO errors, WebSocket events, and alarm failures outside the isolate.
Whether your DO handles HTTP requests, WebSocket sessions, or scheduled alarms, FlareLog captures structured logs with the DO ID attached.
import { DurableObject } from "cloudflare:workers";
import { FlareLog } from "@flarelog/sdk";
export class Counter extends DurableObject {
private logger: FlareLog;
private count: number;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.logger = new FlareLog({
apiKey: env.FLARELOG_API_KEY,
environment: "production",
serverName: `do-${ctx.id.toString()}`,
});
this.count = 0;
}
async fetch(request: Request) {
const traceId = request.headers.get("x-trace-id") || crypto.randomUUID();
return this.logger.withRequest(
{ request, traceId },
{ waitUntil: (p) => this.ctx.waitUntil(p) },
async () => {
this.logger.info("DO request", {
doId: this.ctx.id.toString(),
path: new URL(request.url).pathname,
});
this.count++;
return new Response(`Count: ${this.count}`);
}
);
}
}Every request logged with DO ID as metadata.
import { DurableObject } from "cloudflare:workers";
import { FlareLog } from "@flarelog/sdk";
export class ChatRoom extends DurableObject {
private logger: FlareLog;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.logger = new FlareLog({
apiKey: env.FLARELOG_API_KEY,
serverName: `chat-${ctx.id.toString()}`,
});
}
async fetch(request: Request) {
const [client, server] = Object.values(new WebSocketPair());
this.ctx.acceptWebSocket(server);
this.logger.info("WebSocket connected", {
doId: this.ctx.id.toString(),
connections: this.ctx.getWebSockets().length,
});
return new Response(null, { status: 101, webSocket: client });
}
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
this.logger.info("Chat message", {
doId: this.ctx.id.toString(),
size: message.length,
});
this.ctx.getWebSockets().forEach((socket) => socket.send(message));
}
async webSocketError(ws: WebSocket, error: Error) {
this.logger.logError(error, { doId: this.ctx.id.toString() });
}
async webSocketClose(ws: WebSocket, code: number, reason: string) {
this.logger.info("WebSocket closed", { code, reason });
}
}Capture connect, message, error, and close events.
import { DurableObject } from "cloudflare:workers";
import { FlareLog } from "@flarelog/sdk";
export class Scheduler extends DurableObject {
private logger: FlareLog;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.logger = new FlareLog({
apiKey: env.FLARELOG_API_KEY,
serverName: `scheduler-${ctx.id.toString()}`,
});
}
async fetch(request: Request) {
// Schedule next alarm
await this.ctx.storage.setAlarm(Date.now() + 60_000);
this.logger.info("Alarm scheduled");
return new Response("OK");
}
async alarm() {
try {
this.logger.info("Alarm fired", { doId: this.ctx.id.toString() });
await runScheduledTask();
this.logger.info("Alarm completed");
} catch (err) {
this.logger.logError(err, {
message: "Alarm execution failed",
doId: this.ctx.id.toString(),
});
} finally {
await this.ctx.waitUntil(this.logger.flush());
}
}
}Alarm failures are invisible in Cloudflare's dashboard — FlareLog records them.
# wrangler.toml
[vars]
FLARELOG_ENVIRONMENT = "production"
# Secret
wrangler secret put FLARELOG_API_KEYUse serverName or metadata to attach the DO ID to every log line. Search, filter, and group by DO across HTTP, WebSocket, and alarm handlers.
The Tail Worker sees failures outside the isolate — init crashes, OOM, CPU limits, evictions — even when your DO never logged in.
Log connect, message, error, and close events. Know why a session ended, not just that it did.
Record alarm schedule, fire, success, and failure. Get alerted when a scheduled task throws or retries too many times.
Inject W3C trace context into DO-to-DO RPC calls and service bindings. Follow a request across isolates as if they were one service.
Debug long-lived bugs, recurring alarms, and intermittent WebSocket issues with 90 days of searchable logs on Pro.
90d
retention on Pro
DO ID
on every log line
10k
free logs/mo
$19
flat Pro bucket
Yes. The Tail Worker attaches to any Worker or Durable Object producer. Because the Tail Worker runs outside the isolate, it can record DO exits caused by init failures, OOM, CPU limits, and evictions.
Yes. Logs written inside the DO are flushed before hibernation via ctx.waitUntil(). Wake-time errors are captured by the Tail Worker.
Use logger.injectTraceContext() when making DO-to-DO or service-binding calls. The trace ID ties requests together across isolates.
FlareLog records each alarm invocation, including failure and retry count. You can alert when an alarm fails repeatedly or hasn't run on schedule.
No. Logging is asynchronous and uses waitUntil. For short-lived or alarm handlers, workerMode: true flushes aggressively so nothing is lost.
Yes. The SDK and Tail Worker work on every plan. FlareLog's free tier covers 10,000 logs per month.
Free tier includes 10,000 logs/month. Add one SDK import or the Tail Worker template and start seeing DO crashes, alarm failures, and WebSocket events in minutes.