Axiom
Structured log ingestion, querying, and dashboards.
Axiom is our log management platform. It ingests structured logs and events, lets you query them with APL (Axiom Processing Language), and build dashboards for monitoring and incident investigation.
What Axiom provides
| Feature | Purpose |
|---|---|
| Log ingestion | Collect logs from apps, servers, and OTel collectors |
| APL queries | Search and aggregate logs with a SQL-like language |
| Dashboards | Visualize error rates, latency, and custom metrics |
| Monitors | Alert when query results cross a threshold |
| Long retention | Store logs cost-effectively without managing infrastructure |
How Axiom fits our stack
Structured logging setup
Use structured JSON logs — never plain console.log in production code.
// lib/logger.ts
type LogLevel = "debug" | "info" | "warn" | "error";
interface LogEvent {
level: LogLevel;
message: string;
requestId?: string;
userId?: string;
service: string;
[key: string]: unknown;
}
export function log(event: LogEvent) {
console.log(
JSON.stringify({
...event,
timestamp: new Date().toISOString(),
service: event.service ?? process.env.OTEL_SERVICE_NAME,
}),
);
}log({
level: "info",
message: "Order created",
requestId: req.headers["x-request-id"],
orderId: order.id,
userId: user.id,
});Shipping logs to Axiom
Option 1 — Direct HTTP ingest
pnpm add @axiomhq/jsimport { Axiom } from "@axiomhq/js";
const axiom = new Axiom({ token: process.env.AXIOM_TOKEN! });
async function shipLog(event: LogEvent) {
await axiom.ingest(process.env.AXIOM_DATASET!, [event]);
}Option 2 — OpenTelemetry log exporter
Configure OTel to export logs to Axiom's OTLP endpoint:
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://api.axiom.co/v1/logs
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${AXIOM_TOKEN},X-Axiom-Dataset=${AXIOM_DATASET}Option 3 — Vercel log drain
For Next.js on Vercel, configure a log drain pointing to Axiom's ingest URL.
Environment variables
AXIOM_TOKEN=xaat-... # Ingest token — vault only
AXIOM_DATASET=logs-prod # Dataset name per environment
AXIOM_ORG_ID=company # Organization identifier| Environment | Dataset |
|---|---|
| Alpha | logs-alpha |
| Beta | logs-beta |
| Production | logs-prod |
Querying with APL
Find errors for a specific request:
['logs-prod']
| where level == "error"
| where requestId == "req_01HZ..."
| project timestamp, message, service, _raw
| sort by timestamp descError rate over the last hour:
['logs-prod']
| where timestamp > ago(1h)
| summarize errorCount = countif(level == "error") by bin(timestamp, 5m)
| sort by timestamp ascMonitors and alerts
Create monitors in the Axiom UI for:
- Error rate > 5% over 5 minutes →
#engineeringSlack level == "error"count > 50 in 1 minute → PagerDuty- Missing health check logs → SRE alert
Incident investigation workflow
- Start from Sentry issue — copy the
requestId. - Query Axiom:
where requestId == "..."to see full request log trail. - Check OpenTelemetry trace for the same ID in Sentry Performance.
- Correlate timestamps across all three tools.