- name
- observability
- description
- Vercel Observability expert guidance — Drains (logs, traces, speed insights, web analytics), Web Analytics, Speed Insights, runtime logs, custom events, OpenTelemetry integration, and monitoring dashboards. Use when instrumenting, debugging, or optimizing application performance and user experience on Vercel.
- metadata
- {"priority":6,"docs":["https://vercel.com/docs/observability","https://vercel.com/docs/observability/otel-overview"],"sitemap":"https://vercel.com/sitemap/docs.xml","pathPatterns":["instrumentation.ts","instrumentation.js","src/instrumentation.ts","src/instrumentation.js","app/layout.*","src/app/layout.*","pages/_app.*","src/pages/_app.*","apps/*/instrumentation.ts","apps/*/instrumentation.js","apps/*/app/layout.*","apps/*/src/app/layout.*","apps/*/pages/_app.*","apps/*/src/pages/_app.*","sentry.client.config.*","sentry.server.config.*","sentry.edge.config.*"],"bashPatterns":["\\bvercel\\s+logs?\\b","\\bvercel\\s+logs?\\s+.*--follow\\b","\\bvercel\\s+logs?\\s+.*--level\\b","\\bvercel\\s+logs?\\s+.*--since\\b","\\bcurl\\s+.*deployments.*events\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/analytics\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/analytics\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@vercel/analytics\\b","\\byarn\\s+add\\s+[^\\n]*@vercel/analytics\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/speed-insights\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@vercel/speed-insights\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@vercel/speed-insights\\b","\\byarn\\s+add\\s+[^\\n]*@vercel/speed-insights\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@sentry/nextjs\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@sentry/nextjs\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@sentry/nextjs\\b","\\byarn\\s+add\\s+[^\\n]*@sentry/nextjs\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@sentry/node\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@sentry/node\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@sentry/node\\b","\\byarn\\s+add\\s+[^\\n]*@sentry/node\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*@datadog/browser-rum\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*@datadog/browser-rum\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*@datadog/browser-rum\\b","\\byarn\\s+add\\s+[^\\n]*@datadog/browser-rum\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bcheckly\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bcheckly\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bcheckly\\b","\\byarn\\s+add\\s+[^\\n]*\\bcheckly\\b","\\bnpm\\s+(install|i|add)\\s+[^\\n]*\\bnewrelic\\b","\\bpnpm\\s+(install|i|add)\\s+[^\\n]*\\bnewrelic\\b","\\bbun\\s+(install|i|add)\\s+[^\\n]*\\bnewrelic\\b","[Truncated]"],"promptSignals":{"phrases":["add logging","add logs","set up logging","setup logging","configure logging","structured logging","log drain","log drains","vercel analytics","speed insights","web analytics","opentelemetry","otel","instrumentation","monitoring","set up monitoring","add observability","track errors","error tracking","sentry","datadog","check the logs","show me the error","what went wrong","where did it fail","show me the logs","find the error","why did it fail","debug the error"],"allOf":["[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]"],"anyOf":["logging","monitoring","analytics","observability","telemetry","traces","metrics","debug","debugging","stuck","hanging","hung","waiting","stalled","spinning","timeout","slow","pending","unresponsive"],"minScore":6}}
# Vercel Observability
You are an expert in Vercel's observability stack — runtime logs, structured logging, Drains, Web Analytics, Speed Insights, and monitoring integrations. **Always start with logging.** When something is stuck, slow, or broken, the first step is always to check or add logs.
## Structured Logging Baseline
Add this to every API route and server action as a minimum. If the user reports something stuck, hanging, or slow, verify this baseline exists first:
```ts
const start = Date.now();
console.log(JSON.stringify({ level: "info", msg: "start", route: "/api/example", requestId: req.headers.get("x-vercel-id") }));
// ... your logic ...
console.log(JSON.stringify({ level: "info", msg: "done", route: "/api/example", ms: Date.now() - start }));
// On error:
console.error(JSON.stringify({ level: "error", msg: "failed", route: "/api/example", error: err.message, ms: Date.now() - start }));
```
## Runtime Logs
Vercel provides real-time logs for all function invocations.
### Structured Logging
```ts
// app/api/process/route.ts
export async function POST(req: Request) {
const start = Date.now()
const data = await req.json()
// Structured logs appear in Vercel's log viewer
console.log(JSON.stringify({
level: 'info',
message: 'Processing request',
requestId: req.headers.get('x-vercel-id'),
payload_size: JSON.stringify(data).length,
}))
try {
const result = await processData(data)
console.log(JSON.stringify({
level: 'info',
message: 'Request completed',
duration_ms: Date.now() - start,
}))
return Response.json(result)
} catch (error) {
console.error(JSON.stringify({
level: 'error',
message: 'Processing failed',
error: error instanceof Error ? error.message : String(error),
duration_ms: Date.now() - start,
}))
return Response.json({ error: 'Internal error' }, { status: 500 })
}
}
```
### Next.js Instrumentation
```ts
// instrumentation.ts (Next.js 16)
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
// Initialize monitoring on server startup
const { initMonitoring } = await import('./lib/monitoring')
initMonitoring()
}
}
```
### Runtime Logs via REST API
Query deployment runtime logs programmatically. The endpoint returns `application/stream+json` — a streaming response where each line is a separate JSON object.
```bash
# Stream runtime logs for a deployment (returns application/stream+json)
curl -N -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v3/deployments/<deployment-id>/events" \
--max-time 120
```
> **Streaming guidance:** The response is unbounded — always set a timeout (`--max-time` in curl, `AbortController` with `setTimeout` in fetch). Parse line-by-line as NDJSON. Each line contains `{ timestamp, text, level, source }`.
```ts
// Programmatic streaming with timeout
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 60_000) // 60s max
const res = await fetch(
`https://api.vercel.com/v3/deployments/${deploymentId}/events`,
{
headers: { Authorization: `Bearer ${process.env.VERCEL_TOKEN}` },
signal: controller.signal,
}
)
const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop()! // keep incomplete line in buffer
for (const line of lines) {
if (!line.trim()) continue
const event = JSON.parse(line)
console.log(`[${event.level}] ${event.text}`)
}
}
} finally {
clearTimeout(timeout)
}
```
> **MCP alternative:** Use `get_runtime_logs` via the Vercel MCP server for agent-friendly log queries without managing streams directly. See `⤳ skill: vercel-api`.
## Web Analytics
Privacy-friendly, first-party analytics with no cookie banners required.
### Installation
```bash
npm install @vercel/analytics
```
### Setup (Next.js App Router)
```tsx
// app/layout.tsx
import { Analytics } from '@vercel/analytics/next'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
)
}
```
### Custom Events (Pro/Enterprise)
Track business-specific events beyond pageviews.
```ts
import { track } from '@vercel/analytics'
// Track a conversion
track('purchase', {
product: 'pro-plan',
value: 20,
currency: 'USD',
})
// Track a feature usage
track('feature_used', {
name: 'ai-chat',
duration_ms: 3200,
})
```
### Server-Side Tracking
```ts
import { track } from '@vercel/analytics/server'
export async function POST(req: Request) {
const data = await req.json()
await processOrder(data)
track('order_completed', {
order_id: data.id,
total: data.total,
})
return Response.json({ success: true })
}
```
## Speed Insights
Real-user performance monitoring built on Core Web Vitals.
### Installation
```bash
npm install @vercel/speed-insights
```
### Setup (Next.js App Router)
```tsx
// app/layout.tsx
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<SpeedInsights />
</body>
</html>
)
}
```
### Metrics Tracked
| Metric | What It Measures | Good Threshold |
|--------|-----------------|----------------|
| LCP | Largest Contentful Paint | < 2.5s |
| INP | Interaction to Next Paint | < 200ms |
| CLS | Cumulative Layout Shift | < 0.1 |
| FCP | First Contentful Paint | < 1.8s |
| TTFB | Time to First Byte | < 800ms |
### Performance Attribution
Speed Insights attributes metrics to specific routes and pages, letting you identify which pages are slow and why.
## Drains
Drains forward observability data from Vercel to external endpoints. They are the primary mechanism for exporting logs, traces, Speed Insights, and Web Analytics data to third-party platforms.
> **Plan requirement:** Drains require a **Pro or Enterprise** plan. For Hobby plans, see the [Fallback Guidance](#fallback-guidance-no-drains) section below.
### Data Types
Drains can forward multiple categories of telemetry:
| Data Type | What It Contains | Use Case |
|-----------|-----------------|----------|
| **Logs** | Runtime function logs, build logs, static access logs | Centralized log aggregation |
| **Traces** | OpenTelemetry-compatible distributed traces | End-to-end request tracing |
| **Speed Insights** | Core Web Vitals and performance metrics | Performance monitoring pipelines |
| **Web Analytics** | Pageviews, custom events, visitor data | Analytics data warehousing |
### Supported Formats
| Format | Protocol | Best For |
|--------|----------|----------|
| JSON | HTTPS POST | Custom backends, generic log collectors |
| NDJSON | HTTPS POST | Streaming-friendly consumers, high-volume pipelines |
| Syslog | TLS syslog | Traditional log management (rsyslog, syslog-ng) |
### Setting Up Drains
Drains are configured via the **Vercel Dashboard** at `https://vercel.com/dashboard/{team}/~/settings/log-drains` or the **REST API**.
#### Via Dashboard
1. Open `https://vercel.com/dashboard/{team}/~/settings/log-drains` (replace `{team}` with your team slug)
2. Click **Add Log Drain**
3. Select the drain type (JSON, NDJSON, or syslog) and enter the endpoint URL
4. Choose which environments and sources to include
5. Click **Create** to activate the drain
#### Via REST API (`/v1/drains`)
```bash
# List all drains
curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v1/drains?teamId=$TEAM_ID" | jq
# Create a JSON drain
curl -X POST -H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
"https://api.vercel.com/v1/drains?teamId=$TEAM_ID" \
-d '{
"url": "https://your-endpoint.example.com/logs",
"type": "json",
"sources": ["lambda", "edge", "static"],
"environments": ["production"]
}'
# Test a drain (sends a test payload to your endpoint)
curl -X POST -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v1/drains/<drain-id>/test?teamId=$TEAM_ID"
# Update a drain (change URL, sources, or environments)
curl -X PATCH -H "Authorization: Bearer $VERCEL_TOKEN" \
-H "Content-Type: application/json" \
"https://api.vercel.com/v1/drains/<drain-id>?teamId=$TEAM_ID" \
-d '{
"url": "https://new-endpoint.example.com/logs",
"environments": ["production", "preview"]
}'
# Delete a drain
curl -X DELETE -H "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v1/drains/<drain-id>?teamId=$TEAM_ID"
```
### Web Analytics Drains Reference
When a drain is configured to receive Web Analytics data, payloads arrive as batched events. The format depends on your drain type.
#### JSON Payload Schema
```json
[
{
"type": "pageview",
"url": "https://example.com/blog/post-1",
"referrer": "https://google.com",
"timestamp": 1709568000000,
"geo": { "country": "US", "region": "CA", "city": "San Francisco" },
"device": { "os": "macOS", "browser": "Chrome", "isBot": false },
"projectId": "prj_xxxxx",
"environment": "production"
},
{
"type": "custom_event",
"name": "purchase",
"url": "https://example.com/checkout",
"properties": { "product": "pro-plan", "value": 20 },
"timestamp": 1709568100000,
"geo": { "country": "US" },
"device": { "os": "macOS", "browser": "Chrome", "isBot": false },
"projectId": "prj_xxxxx",
"environment": "production"
}
]
```
#### NDJSON Payload Format
Each line is a separate JSON object (one event per line):
```
{"type":"pageview","url":"https://example.com/","timestamp":1709568000000,"geo":{"country":"US"},"device":{"browser":"Chrome"},...}
View on GitHub