
Sentry Cloudflare Sdk
- 1.9k installs
- 243 repo stars
- Updated July 27, 2026
- getsentry/sentry-for-ai
sentry-cloudflare-sdk is an agent skill that Full Sentry SDK setup for Cloudflare Workers and Pages. Use when asked to "add Sentry to Cloudflare Workers", "install @sentry/cloudflare", or configure error monitoring,.
About
The sentry-cloudflare-sdk skill. Full Sentry SDK setup for Cloudflare Workers and Pages. Use when asked to "add Sentry to Cloudflare Workers", "install @sentry/cloudflare", or configure error monitoring, tracing, logging, crons, or AI monitoring for Cloudflare Workers, Pages, Durable Objects, Queues, Workflows, or Hono on Cloudflare. > Always verify against [docs.sentry.io/platforms/javascript/guides/cloudflare/](https://docs.sentry.io/platforms/javascript/guides/cloudflare/) before implementing. --- ## Phase 1: Detect Run these commands to understand the project before making any recommendations: **What to determine:** | Question | Impact | |----------|--------| | Workers or Pages?. | Determines wrapper: vs | | Hono framework?. | Recommend standalone package (v10.55.0+) for cleaner integration | | already installed?. | Skip install, go to feature config | | Durable Objects configured?. | auto-instruments queue handlers | | Workflows configured?. The workflow follows the source SKILL.md contract with progressive reference loading, clear trigger phrases, and practical steps developers can apply directly in agent sessions.
- User asks to "add Sentry to Cloudflare Workers" or "set up Sentry" in a Cloudflare project
- User wants to install or configure `@sentry/cloudflare`
- User wants error monitoring, tracing, logging, crons, or AI monitoring for Cloudflare Workers or Pages
- User asks about `withSentry`, `sentryPagesPlugin`, `instrumentDurableObjectWithSentry`, or `instrumentD1WithSentry`
- User wants to monitor Durable Objects, Queues, Workflows, Scheduled handlers, or Email handlers on Cloudflare
Sentry Cloudflare Sdk by the numbers
- 1,943 all-time installs (skills.sh)
- +66 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #104 of 1,453 DevOps & CI/CD skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
sentry-cloudflare-sdk capabilities & compatibility
- Capabilities
- user asks to "add sentry to cloudflare workers" · user wants to install or configure `@sentry/clou · user wants error monitoring, tracing, logging, c · user asks about `withsentry`, `sentrypagesplugin · user wants to monitor durable objects, queues, w
- Use cases
- testing · debugging · ci cd
What sentry-cloudflare-sdk says it does
> Always verify against [docs.sentry.io/platforms/javascript/guides/cloudflare/](https://docs.sentry.io/platforms/javascript/guides/cloudflare/) before implementing.
npx skills add https://github.com/getsentry/sentry-for-ai --skill sentry-cloudflare-sdkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.9k |
|---|---|
| repo stars | ★ 243 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | getsentry/sentry-for-ai ↗ |
How do I apply sentry-cloudflare-sdk correctly using the SKILL.md workflows and reference files?
Full Sentry SDK setup for Cloudflare Workers and Pages. Use when asked to "add Sentry to Cloudflare Workers", "install @sentry/cloudflare", or configure error monitoring, tracing, logging, crons, or A
Who is it for?
Developers and software engineers working with sentry-cloudflare-sdk patterns from the skill documentation.
Skip if: Skip when cached docs are empty, boilerplate-only, or outside the skill documented scope.
When should I use this skill?
Full Sentry SDK setup for Cloudflare Workers and Pages. Use when asked to "add Sentry to Cloudflare Workers", "install @sentry/cloudflare", or configure error monitoring, tracing, logging, crons, or AI monitoring for Clo
What you get
Grounded sentry-cloudflare-sdk guidance with highlights, triggers, and evidence quotes from SKILL.md.
- Instrumented scheduled handler
- Sentry Crons monitor configuration
By the numbers
- Requires @sentry/cloudflare v8.0.0+ for captureCheckIn and withMonitor
- Auto-instruments scheduled handlers on @sentry/cloudflare v10.x+
Files
All Skills > SDK Setup > Cloudflare SDK
Sentry Cloudflare SDK
Opinionated wizard that scans your Cloudflare project and guides you through complete Sentry setup for Workers, Pages, Durable Objects, Queues, Workflows, and Hono.
Invoke This Skill When
- User asks to "add Sentry to Cloudflare Workers" or "set up Sentry" in a Cloudflare project
- User wants to install or configure
@sentry/cloudflare - User wants error monitoring, tracing, logging, crons, or AI monitoring for Cloudflare Workers or Pages
- User asks about
withSentry,sentryPagesPlugin,instrumentDurableObjectWithSentry, orinstrumentD1WithSentry - User wants to monitor Durable Objects, Queues, Workflows, Scheduled handlers, or Email handlers on Cloudflare
Note: SDK versions and APIs below reflect current Sentry docs at time of writing (@sentry/cloudflare v10.55.0).Always verify against docs.sentry.io/platforms/javascript/guides/cloudflare/ before implementing.
---
Phase 1: Detect
Run these commands to understand the project before making any recommendations:
# Detect Cloudflare project
ls wrangler.toml wrangler.jsonc wrangler.json 2>/dev/null
# Detect existing Sentry
cat package.json 2>/dev/null | grep -E '"@sentry/'
# Detect project type (Workers vs Pages)
ls functions/ functions/_middleware.js functions/_middleware.ts 2>/dev/null && echo "Pages detected"
cat wrangler.toml 2>/dev/null | grep -E 'main|pages_build_output_dir'
# Detect framework
cat package.json 2>/dev/null | grep -E '"hono"|"remix"|"astro"|"svelte"'
# Detect Durable Objects
cat wrangler.toml 2>/dev/null | grep -i 'durable_objects'
# Detect D1 databases
cat wrangler.toml 2>/dev/null | grep -i 'd1_databases'
# Detect Queues
cat wrangler.toml 2>/dev/null | grep -i 'queues'
# Detect Workflows
cat wrangler.toml 2>/dev/null | grep -i 'workflows'
# Detect Scheduled handlers (cron triggers)
cat wrangler.toml 2>/dev/null | grep -i 'crons\|triggers'
# Detect compatibility flags
cat wrangler.toml 2>/dev/null | grep -i 'compatibility_flags'
cat wrangler.jsonc 2>/dev/null | grep -i 'compatibility_flags'
# Detect AI/LLM libraries
cat package.json 2>/dev/null | grep -E '"openai"|"@anthropic-ai"|"ai"|"@google/generative-ai"|"@langchain"'
# Detect logging libraries
cat package.json 2>/dev/null | grep -E '"pino"|"winston"'
# Check for companion frontend
ls frontend/ web/ client/ 2>/dev/null
cat package.json 2>/dev/null | grep -E '"react"|"vue"|"svelte"|"next"'What to determine:
| Question | Impact |
|---|---|
| Workers or Pages? | Determines wrapper: withSentry vs sentryPagesPlugin |
| Hono framework? | Recommend standalone @sentry/hono package (v10.55.0+) for cleaner integration |
@sentry/cloudflare already installed? | Skip install, go to feature config |
| Durable Objects configured? | Recommend instrumentDurableObjectWithSentry |
| D1 databases bound? | Recommend instrumentD1WithSentry |
| Queues configured? | withSentry auto-instruments queue handlers |
| Workflows configured? | Recommend instrumentWorkflowWithSentry |
| Cron triggers configured? | withSentry auto-instruments scheduled handlers; recommend Crons monitoring |
nodejs_als or nodejs_compat flag set? | Required — SDK needs AsyncLocalStorage |
| AI/LLM libraries? | Recommend AI Monitoring integrations |
| Companion frontend? | Trigger Phase 4 cross-link |
---
Phase 2: Recommend
Present a concrete recommendation based on what you found. Don't ask open-ended questions — lead with a proposal:
Recommended (core coverage):
- ✅ Error Monitoring — always; captures unhandled exceptions in fetch, scheduled, queue, email, and Durable Object handlers
- ✅ Tracing — automatic HTTP request spans, outbound fetch tracing, D1 query spans
Optional (enhanced observability):
- ⚡ Logging — structured logs via
Sentry.logger.*; recommend when log search is needed - ⚡ Crons — detect missed/failed scheduled jobs; recommend when cron triggers are configured
- ⚡ D1 Instrumentation — automatic query spans and breadcrumbs; recommend when D1 is bound
- ⚡ Durable Objects — automatic error capture and spans for DO methods; recommend when DOs are configured
- ⚡ Workflows — automatic span creation for workflow steps; recommend when Workflows are configured
- ⚡ AI Monitoring — Vercel AI SDK, OpenAI, Anthropic, LangChain; recommend when AI libraries detected
Recommendation logic:
| Feature | Recommend when... |
|---|---|
| Error Monitoring | Always — non-negotiable baseline |
| Tracing | Always — HTTP request tracing and outbound fetch are high-value |
| Logging | App needs structured log search or log-to-trace correlation |
| Crons | Cron triggers configured in wrangler.toml |
| D1 Instrumentation | D1 database bindings present |
| Durable Objects | Durable Object bindings configured |
| Workflows | Workflow bindings configured |
| AI Monitoring | App uses Vercel AI SDK, OpenAI, Anthropic, or LangChain |
| Metrics | App needs custom counters, gauges, or distributions |
Propose: "I recommend setting up Error Monitoring + Tracing. Want me to also add D1 instrumentation and Crons monitoring?"
---
Phase 3: Guide
Option 1: Source Maps Wizard
You need to run this yourself — the wizard opens a browser for login and requires interactive input that the agent can't handle. Copy-paste into your terminal:
>
```
npx @sentry/wizard@latest -i sourcemaps
```
>
This sets up source map uploading so your production stack traces show readable code. It does not set up the SDK initialization — you still need to follow Option 2 below for the actual SDK setup.
>
Once it finishes, continue with Option 2 for SDK setup.
Note: Unlike framework SDKs (Next.js, SvelteKit), there is no Cloudflare-specific wizard integration. The sourcemaps wizard only handles source map upload configuration.---
Option 2: Manual Setup
Prerequisites: Compatibility Flags
The SDK requires AsyncLocalStorage. Add one of these flags to your Wrangler config:
wrangler.toml:
compatibility_flags = ["nodejs_als"]
# or: compatibility_flags = ["nodejs_compat"]wrangler.jsonc:
{
"compatibility_flags": ["nodejs_als"]
}nodejs_alsis lighter — it only enablesAsyncLocalStorage. Usenodejs_compatif your code also needs other Node.js APIs.
Install
npm install @sentry/cloudflareWorkers Setup
Wrap your handler with withSentry. This automatically instruments fetch, scheduled, queue, email, and tail handlers:
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
enableLogs: true,
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
}),
{
async fetch(request, env, ctx) {
return new Response("Hello World!");
},
} satisfies ExportedHandler<Env>,
);Key points:
- The first argument is a callback that receives
env— use this to read secrets likeSENTRY_DSN - The SDK reads DSN, environment, release, debug, tunnel, and traces sample rate from
envautomatically (see Environment Variables) withSentrywraps all exported handlers — you do not need separate wrappers forscheduled,queue, etc.
Pages Setup
Use sentryPagesPlugin as middleware:
// functions/_middleware.ts
import * as Sentry from "@sentry/cloudflare";
export const onRequest = Sentry.sentryPagesPlugin((context) => ({
dsn: context.env.SENTRY_DSN,
tracesSampleRate: 1.0,
enableLogs: true,
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
}));Chaining multiple middlewares:
import * as Sentry from "@sentry/cloudflare";
export const onRequest = [
// Sentry must be first
Sentry.sentryPagesPlugin((context) => ({
dsn: context.env.SENTRY_DSN,
tracesSampleRate: 1.0,
})),
// Add more middlewares here
];Using `wrapRequestHandler` directly (for frameworks like SvelteKit on Cloudflare Pages):
import * as Sentry from "@sentry/cloudflare";
export const handle = ({ event, resolve }) => {
return Sentry.wrapRequestHandler(
{
options: {
dsn: event.platform.env.SENTRY_DSN,
tracesSampleRate: 1.0,
},
request: event.request,
context: event.platform.ctx,
},
() => resolve(event),
);
};Hono on Cloudflare Workers
Recommended (v10.55.0+): Use the standalone @sentry/hono package for Hono apps:
npm install @sentry/hono @sentry/cloudflareThe @sentry/cloudflare package is a peer dependency and must stay in sync with @sentry/hono.
import { Hono } from "hono";
import { sentry } from "@sentry/hono/cloudflare";
type Bindings = { SENTRY_DSN: string };
const app = new Hono<{ Bindings: Bindings }>();
// Initialize Sentry middleware as early as possible
app.use(
sentry(app, (env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
})),
);
app.get("/", (ctx) => ctx.json({ message: "Hello" }));
app.get("/error", () => {
throw new Error("Test error");
});
export default app;The sentry() middleware automatically captures errors and creates transaction spans with route patterns.
Legacy approach (deprecated): Using @sentry/cloudflare with withSentry still works, but honoIntegration is deprecated:
import { Hono } from "hono";
import * as Sentry from "@sentry/cloudflare";
const app = new Hono();
app.get("/", (ctx) => ctx.json({ message: "Hello" }));
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
app,
);Set Up the SENTRY_DSN Secret
Store your DSN as a Cloudflare secret — do not hardcode it:
# Local development: add to .dev.vars
echo 'SENTRY_DSN="https://examplePublicKey@o0.ingest.sentry.io/0"' >> .dev.vars
# Production: set as a secret
npx wrangler secret put SENTRY_DSNAdd the binding to your Env type:
interface Env {
SENTRY_DSN: string;
// ... other bindings
}Source Maps Setup
Source maps make production stack traces readable. Without them, you see minified/bundled code.
Step 1: Generate a Sentry auth token
Go to sentry.io/settings/auth-tokens/ and create a token with project:releases and org:read scopes.
Step 2: Install the Sentry Vite plugin (most Cloudflare projects use Vite via Wrangler):
npm install @sentry/vite-plugin --save-devStep 3: Configure `vite.config.ts` (if your project has one):
import { defineConfig } from "vite";
import { sentryVitePlugin } from "@sentry/vite-plugin";
export default defineConfig({
build: {
sourcemap: true,
},
plugins: [
sentryVitePlugin({
org: "___ORG_SLUG___",
project: "___PROJECT_SLUG___",
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
],
});Step 4: Set environment variables in CI
SENTRY_AUTH_TOKEN=sntrys_eyJ...
SENTRY_ORG=my-org
SENTRY_PROJECT=my-projectStep 5: Add to `.gitignore`
.dev.vars
.env.sentry-build-plugin---
Automatic Release Detection
The SDK can automatically detect the release version via Cloudflare's version metadata binding:
wrangler.toml:
[version_metadata]
binding = "CF_VERSION_METADATA"Release priority (highest to lowest): 1. release option passed to Sentry.init() 2. SENTRY_RELEASE environment variable 3. CF_VERSION_METADATA.id binding
---
For Each Agreed Feature
Load the corresponding reference file and follow its steps:
| Feature | Reference file | Load when... |
|---|---|---|
| Error Monitoring | references/error-monitoring.md | Always (baseline) — unhandled exceptions, manual capture, scopes, enrichment |
| Tracing | references/tracing.md | HTTP request tracing, outbound fetch spans, D1 query spans, distributed tracing |
| Logging | references/logging.md | Structured logs via Sentry.logger.*, log-to-trace correlation |
| Crons | references/crons.md | Scheduled handler monitoring, withMonitor, check-in API |
| Durable Objects | references/durable-objects.md | Instrument Durable Object classes for error capture and spans |
For each feature: read the reference file, follow its steps exactly, and verify before moving on.
---
Configuration Reference
Sentry.init() Options
| Option | Type | Default | Notes |
|---|---|---|---|
dsn | string | — | Required. Read from env.SENTRY_DSN automatically if not set |
tracesSampleRate | number | — | 0–1; 1.0 in dev, lower in prod recommended |
tracesSampler | function | — | Dynamic sampling function; mutually exclusive with tracesSampleRate |
dataCollection | object | {} | Controls what data the SDK captures (userInfo, httpBodies, etc.). See Data Collection Reference |
sendDefaultPii | boolean | false | Legacy. Prefer dataCollection for control over captured data |
enableLogs | boolean | false | Enable Sentry Logs product |
environment | string | auto | Read from env.SENTRY_ENVIRONMENT if not set |
release | string | auto | Detected from CF_VERSION_METADATA.id or SENTRY_RELEASE |
debug | boolean | false | Read from env.SENTRY_DEBUG if not set. Log SDK activity to console |
tunnel | string | — | Read from env.SENTRY_TUNNEL if not set |
beforeSend | function | — | Filter/modify error events before sending |
beforeSendTransaction | function | — | Filter/modify transaction events before sending |
beforeSendLog | function | — | Filter/modify log entries before sending |
tracePropagationTargets | `(string\ | RegExp)[]` | all URLs |
skipOpenTelemetrySetup | boolean | false | Opt-out of OpenTelemetry compatibility tracer |
instrumentPrototypeMethods | `boolean \ | string[]` | false |
Data Collection Reference
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},Environment Variables (Read from env)
The SDK reads these from the Cloudflare env object automatically:
| Variable | Purpose |
|---|---|
SENTRY_DSN | DSN for Sentry init |
SENTRY_RELEASE | Release version string |
SENTRY_ENVIRONMENT | Environment name (production, staging) |
SENTRY_TRACES_SAMPLE_RATE | Traces sample rate (parsed as float) |
SENTRY_DEBUG | Enable debug mode ("true" / "1") |
SENTRY_TUNNEL | Tunnel URL for event proxying |
CF_VERSION_METADATA | Cloudflare version metadata binding (auto-detected release) |
Default Integrations
These are registered automatically by getDefaultIntegrations():
| Integration | Purpose |
|---|---|
dedupeIntegration | Prevent duplicate events (disabled for Workflows) |
inboundFiltersIntegration | Filter events by type, message, URL |
functionToStringIntegration | Preserve original function names |
linkedErrorsIntegration | Follow cause chains in errors |
fetchIntegration | Trace outbound fetch() calls, create breadcrumbs |
honoIntegration | Deprecated in v10.55.0 — use @sentry/hono package instead. Auto-capture Hono onError exceptions |
requestDataIntegration | Attach request data to events |
consoleIntegration | Capture console.* calls as breadcrumbs |
---
Verification
After setup, verify Sentry is working:
// Add temporarily to your fetch handler, then remove
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
{
async fetch(request, env, ctx) {
throw new Error("Sentry test error — delete me");
},
} satisfies ExportedHandler<Env>,
);Deploy and trigger the route, then check your Sentry Issues dashboard — the error should appear within ~30 seconds.
Verification checklist:
| Check | How |
|---|---|
| Errors captured | Throw in a fetch handler, verify in Sentry |
| Tracing working | Check Performance tab for HTTP spans |
| Source maps working | Check stack trace shows readable file/line names |
| D1 spans (if configured) | Run a D1 query, check for db.query spans |
| Scheduled monitoring (if configured) | Trigger a cron, check Crons dashboard |
---
Phase 4: Cross-Link
After completing Cloudflare setup, check for companion services:
# Check for companion frontend
ls frontend/ web/ client/ ui/ 2>/dev/null
cat package.json 2>/dev/null | grep -E '"react"|"vue"|"svelte"|"next"|"astro"'
# Check for companion backend in adjacent directories
ls ../backend ../server ../api 2>/dev/null
cat ../go.mod ../requirements.txt ../Gemfile 2>/dev/null | head -3If a frontend is found, suggest the matching SDK skill:
| Frontend detected | Suggest skill |
|---|---|
| React | sentry-react-sdk |
| Next.js | sentry-nextjs-sdk |
| Svelte/SvelteKit | sentry-svelte-sdk |
| Vue/Nuxt | See docs.sentry.io/platforms/javascript/guides/vue/ |
If a backend is found in a different directory:
| Backend detected | Suggest skill |
|---|---|
Go (go.mod) | sentry-go-sdk |
Python (requirements.txt, pyproject.toml) | sentry-python-sdk |
Ruby (Gemfile) | sentry-ruby-sdk |
| Node.js (Express, Fastify) | sentry-node-sdk |
Connecting frontend and backend with linked Sentry projects enables distributed tracing — stack traces that span your browser, Cloudflare Worker, and backend API in a single trace view.
---
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Events not appearing | DSN not set or debug: false hiding errors | Set debug: true temporarily in init options; verify SENTRY_DSN secret is set with wrangler secret list |
AsyncLocalStorage is not defined | Missing compatibility flag | Add nodejs_als or nodejs_compat to compatibility_flags in wrangler.toml |
| Stack traces show minified code | Source maps not uploaded | Configure @sentry/vite-plugin or run npx @sentry/wizard -i sourcemaps; verify SENTRY_AUTH_TOKEN in CI |
| Events lost on short-lived requests | SDK not flushing before worker terminates | Ensure withSentry or sentryPagesPlugin wraps your handler — they use ctx.waitUntil() to flush |
| Hono errors not captured | Hono app not instrumented | Use @sentry/hono/cloudflare — import sentry middleware and call app.use(sentry(app, options)) |
| Durable Object errors missing | DO class not instrumented | Wrap class with Sentry.instrumentDurableObjectWithSentry() — see references/durable-objects.md |
| D1 queries not creating spans | D1 binding not instrumented | Wrap binding with Sentry.instrumentD1WithSentry(env.DB) before use |
| Scheduled handler not monitored | withSentry not wrapping the handler | Ensure export default Sentry.withSentry(...) wraps your entire exported handler object |
| Release not auto-detected | CF_VERSION_METADATA binding not configured | Add [version_metadata] with binding = "CF_VERSION_METADATA" to wrangler.toml |
| Duplicate events in Workflows | Dedupe integration filtering step failures | SDK automatically disables dedupe for Workflows; verify you use instrumentWorkflowWithSentry |
Crons / Job Monitoring — Sentry Cloudflare SDK
Minimum SDK:@sentry/cloudflarev8.0.0+ (captureCheckIn,withMonitor)
Auto-instrumented scheduled handler: v10.x+Status: ✅ Generally Available
---
Overview
Sentry Crons tracks whether scheduled tasks run on time, succeed, and complete within expected durations. Sentry alerts when a job:
- Misses its scheduled start time (checkin margin exceeded)
- Takes too long to complete (maxRuntime exceeded)
- Fails (status
"error")
Cloudflare Workers support cron triggers via the scheduled handler. When wrapped with withSentry, the scheduled handler is automatically instrumented with a faas.cron span that includes:
faas.cron— the cron expressionfaas.time— the scheduled time (ISO 8601)faas.trigger—"timer"
---
Automatic Scheduled Handler Instrumentation
When you use withSentry, the scheduled handler is automatically wrapped. Errors are captured and the span records duration:
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
{
async fetch(request, env, ctx) {
return new Response("OK");
},
async scheduled(controller, env, ctx) {
// Automatically instrumented — errors captured, spans created
await cleanupOldRecords(env.DB);
},
} satisfies ExportedHandler<Env>,
);Configure the cron trigger in wrangler.toml:
[triggers]
crons = ["*/5 * * * *"] # Every 5 minutes---
Sentry.withMonitor — Named Monitor Tracking
For fine-grained monitoring with named monitors (visible in the Sentry Crons dashboard):
async scheduled(controller, env, ctx) {
ctx.waitUntil(
Sentry.withMonitor("cleanup-old-records", async () => {
await cleanupOldRecords(env.DB);
}),
);
},With Monitor Config (Upsert)
Supply a config to auto-create or update the monitor in Sentry:
const monitorConfig = {
schedule: {
type: "crontab",
value: "*/5 * * * *",
},
checkinMargin: 2, // In minutes — how late is "missed"
maxRuntime: 10, // In minutes — when to alert for "timed out"
timezone: "America/Los_Angeles",
};
async scheduled(controller, env, ctx) {
ctx.waitUntil(
Sentry.withMonitor(
"cleanup-old-records",
async () => {
await cleanupOldRecords(env.DB);
},
monitorConfig,
),
);
},---
Sentry.captureCheckIn — Manual Check-Ins
For more control over the check-in lifecycle:
Heartbeat (Single-Shot)
// Report success
Sentry.captureCheckIn({ monitorSlug: "health-check", status: "ok" });
// Report failure
Sentry.captureCheckIn({ monitorSlug: "health-check", status: "error" });In-Progress + Completion
// Signal start
const checkInId = Sentry.captureCheckIn({
monitorSlug: "data-sync",
status: "in_progress",
});
try {
await syncData(env.DB);
// Signal success
Sentry.captureCheckIn({
checkInId,
monitorSlug: "data-sync",
status: "ok",
});
} catch (error) {
// Signal failure
Sentry.captureCheckIn({
checkInId,
monitorSlug: "data-sync",
status: "error",
});
throw error;
}With Upsert Config
const checkInId = Sentry.captureCheckIn(
{ monitorSlug: "data-sync", status: "in_progress" },
{
schedule: { type: "crontab", value: "0 * * * *" },
checkinMargin: 5,
maxRuntime: 30,
timezone: "UTC",
},
);---
Schedule Types
| Type | Format | Example |
|---|---|---|
crontab | Standard cron expression | "*/5 * * * *" (every 5 min) |
interval | Repeated interval | { value: 10, unit: "minute" } |
Interval Schedule
const monitorConfig = {
schedule: {
type: "interval",
value: 10,
unit: "minute", // "minute", "hour", "day", "week", "month", "year"
},
checkinMargin: 2,
maxRuntime: 10,
};---
Monitor Config Options
| Option | Type | Default | Notes |
|---|---|---|---|
schedule.type | `"crontab" \ | "interval"` | — |
schedule.value | `string \ | number` | — |
schedule.unit | string | — | Required for interval type |
checkinMargin | number | — | Minutes before a check-in is considered missed |
maxRuntime | number | — | Minutes before a running job is considered timed out |
timezone | string | "UTC" | IANA timezone for crontab schedules |
failureIssueThreshold | number | — | Number of consecutive failures before creating an issue |
recoveryThreshold | number | — | Number of consecutive successes before resolving an issue |
---
Best Practices
1. Use `withMonitor` for most cases — it handles the check-in lifecycle automatically and records duration.
2. Use `ctx.waitUntil` — wrap withMonitor in ctx.waitUntil() to ensure the check-in is flushed before the worker terminates.
3. Use upsert configs — supply monitorConfig to auto-create monitors. This avoids manual configuration in the Sentry UI.
4. Name monitors clearly — use descriptive slugs like "daily-cleanup" or "hourly-sync", not "cron-1".
5. Set reasonable thresholds — checkinMargin should be slightly larger than typical scheduling jitter. maxRuntime should be longer than the 99th percentile duration.
---
Troubleshooting
| Issue | Solution |
|---|---|
| Monitor not appearing in Crons dashboard | Ensure captureCheckIn or withMonitor is called at least once with a valid monitorSlug |
| Check-in always shows "missed" | Verify checkinMargin is large enough for scheduling jitter |
| Check-in shows "timed out" | Verify maxRuntime exceeds expected job duration |
| In-progress check-in never completes | Ensure both in_progress and ok/error check-ins use the same checkInId |
| Schedule mismatch | Ensure schedule.value in config matches the actual cron expression in wrangler.toml |
Durable Objects, Workflows, and D1 — Sentry Cloudflare SDK
Minimum SDK: @sentry/cloudflare v8.0.0+Durable Object instrumentation: v8.x+
instrumentPrototypeMethods: v10.x+Workflow instrumentation: v10.x+
D1 instrumentation: v8.x+
Durable Object Storage instrumentation: v10.x+
---
Durable Objects
Overview
instrumentDurableObjectWithSentry wraps a Durable Object class to automatically:
- Initialize the Sentry SDK per-request
- Capture unhandled errors in all DO methods
- Create spans for fetch, alarm, WebSocket, and RPC methods
- Track Durable Object Storage operations (get, put, delete, list)
Setup
import * as Sentry from "@sentry/cloudflare";
import { DurableObject } from "cloudflare:workers";
class MyDurableObjectBase extends DurableObject<Env> {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/process") {
await this.processData();
return new Response("Processed");
}
return new Response("OK");
}
async alarm(): Promise<void> {
await this.runMaintenance();
}
async processData(): Promise<void> {
// Business logic — automatically instrumented as RPC span
await this.ctx.storage.put("last-processed", Date.now());
}
}
// Wrap the class with Sentry instrumentation
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
}),
MyDurableObjectBase,
);Important: Export the wrapped class, not the base class. The wrapped class must be the one referenced in wrangler.toml.Instrumented Methods
| Method | Span Op | Auto-captured |
|---|---|---|
fetch | http.server | ✅ Errors and spans |
alarm | — (named alarm) | ✅ Errors and spans |
webSocketMessage | — (named webSocketMessage) | ✅ Errors and spans |
webSocketClose | — (named webSocketClose) | ✅ Errors and spans |
webSocketError | — (named webSocketError) | ✅ Errors captured with handled: false |
| Instance methods (RPC) | rpc | ✅ Errors and spans |
Prototype Method Instrumentation
By default, only instance methods (defined directly on the object) are instrumented. To also instrument methods defined on the prototype chain (useful for RPC methods defined in a base class), enable instrumentPrototypeMethods:
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
instrumentPrototypeMethods: true, // Instrument ALL prototype methods
}),
MyDurableObjectBase,
);Or instrument only specific methods:
export const MyDurableObject = Sentry.instrumentDurableObjectWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
instrumentPrototypeMethods: ["myRpcMethod", "anotherMethod"],
}),
MyDurableObjectBase,
);Durable Object Storage Instrumentation
Durable Object Storage operations (get, put, delete, list) are automatically instrumented when using instrumentDurableObjectWithSentry. Each storage operation creates a span.
class MyDurableObjectBase extends DurableObject<Env> {
async fetch(request: Request): Promise<Response> {
// These storage operations are automatically traced
await this.ctx.storage.put("key", "value");
const value = await this.ctx.storage.get("key");
await this.ctx.storage.delete("key");
const entries = await this.ctx.storage.list();
return new Response("OK");
}
}---
Workflows
Overview
instrumentWorkflowWithSentry wraps a Workflow class to automatically:
- Initialize the Sentry SDK for each workflow run
- Create a consistent trace ID derived from the workflow instance ID
- Create spans for each
step.do()call - Capture errors in workflow steps with
handled: true(since steps may retry) - Disable the dedupe integration (to capture all step failures, even duplicates)
Setup
import * as Sentry from "@sentry/cloudflare";
import { WorkflowEntrypoint } from "cloudflare:workers";
class MyWorkflowBase extends WorkflowEntrypoint<Env, { orderId: string }> {
async run(event, step) {
const order = await step.do("fetch-order", async () => {
return await fetchOrder(event.payload.orderId);
});
await step.do("process-payment", { retries: { limit: 3, delay: "1s" } }, async () => {
return await processPayment(order);
});
await step.do("send-confirmation", async () => {
return await sendEmail(order.email);
});
}
}
export const MyWorkflow = Sentry.instrumentWorkflowWithSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
MyWorkflowBase,
);Step Span Attributes
Each step.do() creates a span with:
| Attribute | Value |
|---|---|
op | function.step.do |
name | The step name (first argument to step.do()) |
cloudflare.workflow.timeout | Step timeout config (if set) |
cloudflare.workflow.retries.limit | Max retries (if set) |
cloudflare.workflow.retries.delay | Retry delay (if set) |
cloudflare.workflow.retries.backoff | Backoff strategy (if set) |
Trace Consistency
The SDK generates a deterministic trace ID from the workflow instance ID. This means:
- All steps in the same workflow instance share the same trace
- Retried steps appear as separate spans within the same trace
- The sampling decision is consistent across steps
Other Step Types
step.sleep(), step.sleepUntil(), and step.waitForEvent() are passed through without instrumentation (they don't execute user code).
---
D1 Database Instrumentation
Overview
instrumentD1WithSentry wraps a Cloudflare D1 database binding to automatically create spans and breadcrumbs for all queries.
Setup
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
{
async fetch(request, env, ctx) {
// Wrap the D1 binding
const db = Sentry.instrumentD1WithSentry(env.DB);
// Use as normal — all queries are traced
const users = await db.prepare("SELECT * FROM users WHERE active = ?").bind(1).all();
return new Response(JSON.stringify(users.results));
},
} satisfies ExportedHandler<Env>,
);Instrumented Methods
| Method | Span Name | Notes |
|---|---|---|
statement.first() | SQL query text | Returns first row |
statement.run() | SQL query text | Execute with metadata return |
statement.all() | SQL query text | Returns all rows with metadata |
statement.raw() | SQL query text | Returns raw row arrays |
All methods create:
- A
db.queryspan with the SQL statement as the span name - A breadcrumb in the
querycategory - Span attributes:
cloudflare.d1.query_type,cloudflare.d1.duration,cloudflare.d1.rows_read,cloudflare.d1.rows_written
Bind Support
The instrumentation follows through statement.bind():
const db = Sentry.instrumentD1WithSentry(env.DB);
// bind() returns a new statement — it's also instrumented
const result = await db
.prepare("INSERT INTO users (name, email) VALUES (?, ?)")
.bind("Alice", "alice@example.com")
.run();Limitations
db.exec()anddb.batch()are not instrumented — only prepared statements- Query parameters are not captured in span data (to avoid PII leakage)
---
Best Practices
1. Instrument D1 once per request — call instrumentD1WithSentry(env.DB) at the top of your handler and use the wrapped binding throughout.
2. Export wrapped classes — always export the instrumented class (Sentry.instrumentDurableObjectWithSentry(...)) as the binding target, not the base class.
3. Use `instrumentPrototypeMethods` selectively — it wraps all prototype methods which adds overhead. Use an array of method names if you only need specific RPC methods.
4. Don't wrap already-wrapped objects — calling instrumentD1WithSentry twice on the same binding is harmless (it checks for existing instrumentation) but unnecessary.
5. Workflow error handling — step errors are captured with handled: true since Workflows may retry steps. The dedupe integration is automatically disabled.
---
Troubleshooting
| Issue | Solution |
|---|---|
| DO errors not captured | Ensure you exported the instrumented class, not the base class |
| RPC methods not creating spans | Enable instrumentPrototypeMethods: true or list specific methods |
| D1 queries not traced | Call instrumentD1WithSentry(env.DB) before executing queries |
| Workflow spans disconnected | Verify all steps in the same workflow instance share the same trace (automatic) |
| Storage operations not traced | Ensure you're using instrumentDurableObjectWithSentry — storage instrumentation is included |
db.batch() not creating spans | Expected — batch and exec are not instrumented; use prepared statements |
Error Monitoring — Sentry Cloudflare SDK
Minimum SDK: @sentry/cloudflare v8.0.0+Hono integration: v10.0.0+
Durable Object instrumentation: v8.x+
Queue/Email/Tail handler instrumentation: v10.x+
---
Overview
The @sentry/cloudflare SDK captures errors across all Cloudflare runtime contexts:
- Fetch handlers (Workers and Pages)
- Scheduled handlers (cron triggers)
- Queue handlers (Cloudflare Queues consumers)
- Email handlers (Email Workers)
- Durable Object methods (fetch, alarm, WebSocket, RPC)
- Workflow steps
- Hono `onError` handler
When you wrap your handler with withSentry or sentryPagesPlugin, unhandled exceptions are automatically captured with proper mechanism metadata.
---
Automatic Error Capture
Workers (via withSentry)
All exported handler methods are automatically instrumented:
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
}),
{
async fetch(request, env, ctx) {
// Unhandled errors here are captured automatically
throw new Error("This is captured by Sentry");
},
async scheduled(controller, env, ctx) {
// Unhandled errors in scheduled handlers are captured too
throw new Error("Cron job failed");
},
async queue(batch, env, ctx) {
// Queue handler errors are captured with queue metadata
for (const message of batch.messages) {
await processMessage(message);
}
},
async email(message, env, ctx) {
// Email handler errors are captured automatically
await forwardEmail(message);
},
} satisfies ExportedHandler<Env>,
);Pages (via sentryPagesPlugin)
// functions/_middleware.ts
import * as Sentry from "@sentry/cloudflare";
export const onRequest = Sentry.sentryPagesPlugin((context) => ({
dsn: context.env.SENTRY_DSN,
dataCollection: {
// To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit:
// https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection
// userInfo: false,
// httpBodies: [],
},
}));Errors in any Pages function are captured, re-thrown (so your error responses still work), and flushed via ctx.waitUntil().
---
Manual Error Capture
Sentry.captureException(error, hint?)
try {
await riskyOperation();
} catch (error) {
Sentry.captureException(error, {
tags: { operation: "risky" },
extra: { inputData: someData },
});
// Handle the error gracefully
return new Response("Something went wrong", { status: 500 });
}Sentry.captureMessage(message, level?)
Sentry.captureMessage("User performed unusual action", "warning");Supported levels: "fatal", "error", "warning", "info", "debug".
Sentry.captureEvent(event)
Sentry.captureEvent({
message: "Custom event",
level: "info",
tags: { component: "auth" },
});---
Enriching Events
Tags
Tags are indexed key-value pairs for filtering and searching:
Sentry.setTag("region", "us-east-1");
Sentry.setTag("worker_name", "api-gateway");
Sentry.setTags({
version: "2.1.0",
tier: "premium",
});User Context
Sentry.setUser({
id: "user-123",
email: "user@example.com",
ip_address: "{{auto}}",
});
// Clear user on logout
Sentry.setUser(null);PII note: UsedataCollection.cookies: truein init options to include cookies (off by default for Cloudflare). Headers are captured by default with sensitive values redacted. For more control, configuredataCollection.httpHeadersanddataCollection.httpBodies.
Extra Data
For arbitrary unindexed data attached to events:
Sentry.setExtra("requestBody", JSON.stringify(body));
Sentry.setExtras({
queryParams: Object.fromEntries(url.searchParams),
workerVersion: "1.2.3",
});Context
Structured context data for specific categories:
Sentry.setContext("cloudflare", {
worker: "api-gateway",
route: "/api/users",
datacenter: request.cf?.colo,
});The SDK automatically sets cloud_resource context with cloud.provider: "cloudflare" and culture context with timezone from request.cf.
Breadcrumbs
Breadcrumbs record a trail of events leading to an error:
Sentry.addBreadcrumb({
category: "auth",
message: "User authenticated via API key",
level: "info",
data: { method: "api_key" },
});The fetchIntegration (default) automatically creates breadcrumbs for outbound fetch() calls. The consoleIntegration (default) captures console.* calls as breadcrumbs.
---
Scopes
withScope — Temporary Scope
Sentry.withScope((scope) => {
scope.setTag("handler", "api");
scope.setExtra("requestId", requestId);
Sentry.captureException(error);
// Tags and extras only apply to this captureException call
});withIsolationScope — Request-Level Isolation
Each request processed by withSentry or sentryPagesPlugin automatically runs in its own isolation scope. You typically don't need to call this manually.
getCurrentScope / getIsolationScope / getGlobalScope
const currentScope = Sentry.getCurrentScope();
const isolationScope = Sentry.getIsolationScope();
const globalScope = Sentry.getGlobalScope();---
Event Filtering
beforeSend
Filter or modify events before they are sent:
Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
beforeSend(event, hint) {
// Drop events with specific messages
if (event.message?.includes("expected error")) {
return null;
}
// Scrub sensitive data
if (event.request?.headers) {
delete event.request.headers["authorization"];
}
return event;
},
}),
handler,
);ignoreErrors
Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
ignoreErrors: [
"AbortError",
/^NetworkError/,
"Non-Error promise rejection captured",
],
}),
handler,
);---
Cloudflare-Specific Request Data
The SDK automatically captures Cloudflare-specific request data when request.cf is available:
- Timezone — set as
culturecontext fromrequest.cf.timezone - HTTP protocol — set as
network.protocol.namespan attribute fromrequest.cf.httpProtocol - Cloud provider — always set as
cloud.provider: "cloudflare"incloud_resourcecontext - Request data — URL, method, headers (respects
dataCollection.httpHeadersanddataCollection.cookies) - Content-Length — captured as
http.request.body.sizespan attribute - User-Agent — captured as
user_agent.originalspan attribute
---
Best Practices
1. Always use `withSentry` or `sentryPagesPlugin` — don't call Sentry.init() directly. The wrappers handle per-request isolation, flushing via ctx.waitUntil(), and client disposal.
2. Store DSN as a secret — use wrangler secret put SENTRY_DSN, not environment variables in wrangler.toml (which are visible in source control).
3. Configure `dataCollection` thoughtfully — use dataCollection.cookies: true to include cookies for user context. Headers are captured with sensitive values redacted by default. Consider privacy implications when enabling additional data collection.
4. Set `tracesSampleRate` lower in production — 1.0 is fine for development; use 0.1–0.5 for production to manage costs.
5. Don't catch and swallow errors silently — if you catch an error for graceful handling, still call Sentry.captureException(error) to report it.
---
Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing | Verify SENTRY_DSN is set; add debug: true to init options; check worker logs for SDK output |
| Duplicate events | Ensure handler is wrapped only once; don't nest withSentry calls |
| Missing request data | Set dataCollection.cookies: true to include cookies. Headers are captured by default with sensitive values redacted |
| Events cut off mid-request | Ensure withSentry/sentryPagesPlugin is used — they handle ctx.waitUntil() for flushing |
captureException returns undefined | Verify SDK is initialized — Sentry.isInitialized() should return true inside a handler |
Logging — Sentry Cloudflare SDK
Minimum SDK: @sentry/cloudflare v9.41.0+ (stable GA)First experimental: v9.10.0+ (via _experiments.enableLogs)Status: ✅ Generally Available
---
Overview
Sentry Logs are high-cardinality structured log entries that link directly to traces and errors. They let you answer why something broke, not just what broke.
Key characteristics:
- Sent as structured data — each attribute is individually searchable in Sentry UI
- Automatically linked to the active trace (if tracing is enabled)
- Buffered and batched — no per-log network overhead
- NOT a replacement for a logging library; designed to complement one
---
Initialization
enableLogs: true is required. Logging is disabled by default.
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
enableLogs: true,
tracesSampleRate: 1.0,
beforeSendLog: (log) => {
// Optional: filter or transform logs
if (log.level === "debug") return null; // Drop debug logs
return log;
},
}),
handler,
);---
Logger API
All six methods live at Sentry.logger.*:
Sentry.logger.trace("Processing request for path %s", [request.url]);
Sentry.logger.debug("Cache lookup result: %s", [cacheHit ? "hit" : "miss"]);
Sentry.logger.info("User %s authenticated successfully", [userId]);
Sentry.logger.warn("Rate limit approaching for key %s: %d/%d", [apiKey, current, limit]);
Sentry.logger.error("Payment processing failed for order %s", [orderId]);
Sentry.logger.fatal("Worker initialization failed: %s", [error.message]);Signature
Sentry.logger.<level>(message: string, params?: unknown[], attributes?: Record<string, unknown>)- `message` — format string with
%s,%d,%f,%o,%Oplaceholders (printf-style) - `params` — parameter values substituted into the format string
- `attributes` — structured key-value data attached to the log entry
With Attributes
Sentry.logger.info("Request processed", [], {
"http.method": request.method,
"http.url": request.url,
"http.status_code": response.status,
"response.time_ms": elapsed,
});---
Console Integration
The consoleIntegration (enabled by default) captures console.log, console.warn, console.error, etc. as breadcrumbs — not as Sentry Logs.
For actual Sentry Logs that appear in the Logs product, use Sentry.logger.*.
To also forward console.* calls to Sentry Logs, add the consoleLoggingIntegration:
import * as Sentry from "@sentry/cloudflare";
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
enableLogs: true,
integrations: [
Sentry.consoleLoggingIntegration({ levels: ["warn", "error"] }),
],
}),
handler,
);This captures console.warn() and console.error() calls as Sentry Logs in addition to their normal breadcrumb behavior.
---
Log-to-Trace Correlation
When tracing is enabled, logs are automatically linked to the active trace. In the Sentry UI, you can navigate from a log entry to the trace timeline and vice versa.
await Sentry.startSpan(
{ op: "function", name: "processOrder" },
async () => {
Sentry.logger.info("Starting order processing for %s", [orderId]);
await validateOrder(orderId);
Sentry.logger.debug("Order validated", [], { orderId });
await chargePayment(orderId);
Sentry.logger.info("Payment charged for order %s", [orderId]);
},
);
// All three log entries are linked to the "processOrder" span---
Configuration
| Option | Type | Default | Notes |
|---|---|---|---|
enableLogs | boolean | false | Must be true to enable Sentry Logs |
beforeSendLog | `(log) => log \ | null` | — |
---
Best Practices
1. Use structured attributes — put searchable data in the attributes parameter, not in the message string. This makes logs filterable in the Sentry UI.
2. Use format strings with parameters — Sentry.logger.info("User %s did %s", [userId, action]) is better than template literals because Sentry can group similar logs.
3. Don't log everything — Sentry Logs are for observability, not a firehose. Focus on key events: authentication, payment, external API calls, errors.
4. Combine with `console.log` for local dev — Sentry.logger.* sends to Sentry only. Keep console.log for local development output and Sentry.logger.* for production observability.
5. Filter noisy logs — use beforeSendLog to drop debug/trace level logs in production if they generate too much volume.
---
Troubleshooting
| Issue | Solution |
|---|---|
| Logs not appearing in Sentry | Verify enableLogs: true is set in init options |
| Logs not linked to traces | Ensure tracing is enabled (tracesSampleRate or tracesSampler set) |
console.log not in Sentry Logs | console.* creates breadcrumbs, not Logs. Use consoleLoggingIntegration to also forward to Sentry Logs |
| Log volume too high | Use beforeSendLog to filter by level or content; avoid logging in tight loops |
Tracing — Sentry Cloudflare SDK
Minimum SDK: @sentry/cloudflare v8.0.0+Streaming response span tracking: v10.x+
propagateTraceparent: v10.x+OpenTelemetry compatibility tracer: v10.x+
---
How Tracing Works
The Cloudflare SDK is not natively OpenTelemetry-based (unlike @sentry/node), but it sets up an OpenTelemetry compatibility tracer. This means:
- Spans emitted via
@opentelemetry/apiare captured by Sentry - The SDK creates its own HTTP server spans for incoming requests
- Outbound
fetch()calls are automatically traced viafetchIntegration - D1 queries are traced when you use
instrumentD1WithSentry
---
Activating Tracing
Set tracesSampleRate or tracesSampler in your init options. Without one of these, no spans are created.
export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0, // 100% in dev, lower in production
}),
handler,
);The SDK also reads SENTRY_TRACES_SAMPLE_RATE from env automatically:
# wrangler.toml
[vars]
SENTRY_TRACES_SAMPLE_RATE = "0.1"---
tracesSampleRate — Uniform Sampling
A number between 0.0 and 1.0:
Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: env.ENVIRONMENT === "production" ? 0.1 : 1.0,
}),
handler,
);---
tracesSampler — Dynamic Sampling
For fine-grained control:
Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampler: (samplingContext) => {
const url = samplingContext.attributes?.["url.full"] as string | undefined;
// Always trace health checks
if (url?.includes("/health")) return 0;
// Sample API routes at 20%
if (url?.includes("/api/")) return 0.2;
// Default: 10%
return 0.1;
},
}),
handler,
);---
Automatic Spans
HTTP Server Spans
Every incoming request wrapped by withSentry or sentryPagesPlugin creates an http.server span with:
| Attribute | Source |
|---|---|
http.request.method | request.method |
url.full | request.url |
http.response.status_code | Response status |
http.request.body.size | Content-Length header |
user_agent.original | User-Agent header |
network.protocol.name | request.cf.httpProtocol |
Note:OPTIONSandHEADrequests do not create spans (to reduce noise) but errors are still captured.
Streaming Response Tracking
The SDK detects streaming responses and keeps the root span alive until the stream is fully consumed. This ensures accurate duration measurement for SSE, streaming AI responses, etc.
Outbound Fetch Spans
The fetchIntegration (enabled by default) automatically traces all outbound fetch() calls:
// This fetch call is automatically traced
const response = await fetch("https://api.example.com/data");Each outbound fetch creates a child span with method, URL, and response status.
D1 Query Spans
When you instrument D1 with instrumentD1WithSentry, all queries create db.query spans:
const db = Sentry.instrumentD1WithSentry(env.DB);
// This creates a db.query span with the SQL statement
const result = await db.prepare("SELECT * FROM users WHERE id = ?").bind(1).run();Span attributes include:
cloudflare.d1.query_type—first,run,all, orrawcloudflare.d1.duration— query durationcloudflare.d1.rows_read— number of rows readcloudflare.d1.rows_written— number of rows written
---
Custom Spans
Sentry.startSpan
Wrap a block of code in a span:
const result = await Sentry.startSpan(
{
op: "function",
name: "processPayment",
attributes: { "payment.provider": "stripe" },
},
async (span) => {
const payment = await chargeCustomer(amount);
span.setAttributes({ "payment.id": payment.id });
return payment;
},
);Sentry.startInactiveSpan
Create a span without making it the active span:
const span = Sentry.startInactiveSpan({
op: "cache.lookup",
name: "Check KV cache",
});
const cached = await env.KV.get(key);
span.end();Sentry.startSpanManual
Full control over span lifecycle:
await Sentry.startSpanManual(
{ op: "task", name: "Background processing" },
async (span) => {
try {
await doWork();
span.setStatus({ code: 1 }); // OK
} catch (error) {
span.setStatus({ code: 2, message: "internal_error" }); // ERROR
throw error;
} finally {
span.end();
}
},
);---
Distributed Tracing
Incoming Trace Propagation
The SDK automatically reads sentry-trace and baggage headers from incoming requests and continues the trace. This works out of the box with withSentry and sentryPagesPlugin.
Outbound Trace Propagation
The fetchIntegration automatically injects sentry-trace and baggage headers into outbound fetch() calls. Control which URLs get trace headers with tracePropagationTargets:
Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
tracePropagationTargets: [
"api.myservice.com",
/^https:\/\/.*\.myapp\.com/,
],
}),
handler,
);By default (when tracePropagationTargets is not set), trace headers are attached to all outbound requests.
propagateTraceparent
Controls whether the sentry-trace header is attached to outgoing requests (default: SDK behavior). Set explicitly to control:
Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
propagateTraceparent: true, // explicit opt-in
}),
handler,
);Manual Trace Continuation
const traceData = Sentry.getTraceData();
// Returns { "sentry-trace": "...", "baggage": "..." }
// Inject into outbound request manually
const response = await fetch("https://api.example.com", {
headers: {
...traceData,
},
});HTML Meta Tags (for frontend)
const metaTags = Sentry.getTraceMetaTags();
// Returns: <meta name="sentry-trace" content="..."/><meta name="baggage" content="..."/>
// Include in HTML response for frontend SDK to continue the trace
return new Response(`<html><head>${metaTags}</head>...`, {
headers: { "Content-Type": "text/html" },
});---
Durable Object Tracing
Durable Objects instrumented with instrumentDurableObjectWithSentry automatically create spans for:
fetch— createshttp.serverspans (same as regular fetch handlers)alarm— creates spans namedalarmwebSocketMessage— creates spans namedwebSocketMessagewebSocketClose— creates spans namedwebSocketClosewebSocketError— creates spans namedwebSocketError- RPC methods — any public instance method creates spans with
op: "rpc"
See references/durable-objects.md for full setup.
---
Workflow Step Tracing
Workflows instrumented with instrumentWorkflowWithSentry create spans for each step.do() call:
// Each step.do() creates a span with op "function.step.do"
await step.do("process-payment", async () => {
return await processPayment();
});See the Workflows section in references/durable-objects.md for full setup.
---
Best Practices
1. Set `tracesSampleRate` low in production — Cloudflare Workers handle high request volumes. Start with 0.05–0.1 and adjust based on volume and cost.
2. Use `tracePropagationTargets` — avoid leaking trace headers to third-party APIs. Only propagate to your own services.
3. Instrument D1 — instrumentD1WithSentry adds almost no overhead and gives you query-level visibility.
4. Use `startSpan` for custom operations — wrap business logic in spans for detailed visibility beyond HTTP/DB.
5. Don't forget `span.end()` — when using startInactiveSpan or startSpanManual, always end the span.
---
Troubleshooting
| Issue | Solution |
|---|---|
| No traces appearing | Verify tracesSampleRate or tracesSampler is set in init options |
| Missing outbound fetch spans | Ensure fetchIntegration is not removed from defaultIntegrations |
| Trace headers not propagated | Check tracePropagationTargets includes the target URL |
| D1 spans not appearing | Ensure instrumentD1WithSentry(env.DB) is called before queries |
| Very short span durations (0ms) | Expected for CPU-bound work — Cloudflare Workers timers only advance during I/O |
| Streaming response spans too short | Update to latest SDK — streaming response tracking was added in v10.x |
Related skills
How it compares
Pick sentry-cloudflare-sdk over generic Sentry skills when cron-triggered Cloudflare Workers need Crons check-in instrumentation specifically.
FAQ
Who is sentry-cloudflare-sdk for?
Developers and software engineers working with sentry-cloudflare-sdk patterns from the skill documentation.
When should I use sentry-cloudflare-sdk?
Full Sentry SDK setup for Cloudflare Workers and Pages. Use when asked to "add Sentry to Cloudflare Workers", "install @sentry/cloudflare", or configure error monitoring, tracing, logging, crons, or AI monitoring for Cloudflare Workers, Pages, Durable Objects, Queues, Workflows,
Is sentry-cloudflare-sdk safe to install?
Review the Security Audits panel on this page before installing in production.