api-observability-setup-axiom-pino-sentry
Pino, Axiom, Sentry installation - one-time project setup for logging and error tracking with source maps upload
Install
npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/api-observability-setup-axiom-pino-sentry/skills/api-observability-setup-axiom-pino-sentry
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
git clone https://github.com/agents-inc/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Observability Setup (Pino + Axiom + Sentry)
Quick Guide: One-time project setup for observability. Install
pino,next-axiom,@sentry/nextjs. Configure Axiom dataset + Vercel integration. Set up Sentry DSN and config files. Wrapnext.config.tswithwithAxiomthenwithSentryConfig. Addinstrumentation.tsfor runtime-specific Sentry init. Source maps are uploaded automatically whenSENTRY_AUTH_TOKENis set in CI.
Detailed Resources:
- For code examples, see examples/ folder:
- examples/core.md - Dependencies, env vars, next.config.ts, instrumentation
- examples/sentry-config.md - Sentry configuration files (client, server, edge)
- examples/pino-logger.md - Pino logger setup with redaction
- examples/axiom-integration.md - Web Vitals and dashboard queries
- examples/ci-cd.md - GitHub Actions source maps upload
- examples/health-check.md - Health check endpoints
- For decision frameworks and anti-patterns, see reference.md
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST create separate Axiom datasets for each environment - development, staging, production)
(You MUST configure all three Sentry config files - sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts)
(You MUST add source maps upload to CI/CD - Sentry needs source maps for readable stack traces)
(You MUST install pino-pretty as a devDependency only - never use in production)
</critical_requirements>
Auto-detection: pino, next-axiom, @sentry/nextjs, Axiom, Sentry, observability setup, logging setup, error tracking setup, source maps, sentry.client.config, sentry.server.config, sentry.edge.config, withAxiom, withSentryConfig
When to use:
- Setting up a new project that needs logging and error tracking
- Adding observability to an existing project without it
- Migrating from another logging/error tracking solution to Axiom + Sentry
When NOT to use:
- Adding new log statements to existing code (ongoing usage, not initial setup)
- Configuring alerts, monitors, or dashboards after initial setup
- Debugging production issues with existing observability
Key patterns covered:
- Dependency installation (Pino, next-axiom, @sentry/nextjs, pino-pretty)
- Environment variables template (
.env.example) next.config.tswithwithAxiom()andwithSentryConfig()wrappers- Sentry configuration files (client, server, edge)
instrumentation.tsfor Sentry initialization- GitHub Actions for source maps upload
- Pino logger with development/production modes
- Health check endpoints
- Initial Axiom dashboard setup
<decision_framework>
Decision Framework
See reference.md for complete decision trees:
- Log Destinations: Where logs should go in each environment
- Sentry vs Axiom for Errors: Which system handles which error types
</decision_framework>
<red_flags>
RED FLAGS
See reference.md for complete list.
High Priority:
- Committing Axiom tokens or Sentry DSN to version control
- Using pino-pretty in production
- Missing source maps upload in CI
- Same Axiom dataset for all environments
Common Mistakes:
- Forgetting to wrap
next.config.tswithwithAxiom - Missing
instrumentation.ts - Using removed Sentry options (
hideSourceMaps,enableTracing,disableServerWebpackPlugin) - Hardcoding sample rates instead of named constants
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST create separate Axiom datasets for each environment - development, staging, production)
(You MUST configure all three Sentry config files - sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts)
(You MUST add source maps upload to CI/CD - Sentry needs source maps for readable stack traces)
(You MUST install pino-pretty as a devDependency only - never use in production)
Failure to follow these rules will result in missing logs, unreadable errors, and security vulnerabilities.
</critical_reminders>
Files (skills)
-
examples
-
axiom-integration.md 1.8 KB
# Observability Setup - Axiom Integration Examples > Axiom Web Vitals component and dashboard configuration. **Navigation:** [Back to SKILL.md](../SKILL.md) | [core.md](core.md) | [sentry-config.md](sentry-config.md) | [pino-logger.md](pino-logger.md) | [ci-cd.md](ci-cd.md) | [health-check.md](health-check.md) --- ## Pattern 6: Web Vitals Component **File: `app/layout.tsx`** ```typescript // Good Example - Web Vitals in root layout import { AxiomWebVitals } from "next-axiom"; import type { Metadata } from "next"; import type { ReactNode } from "react"; export const metadata: Metadata = { title: "My App", }; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <body> <AxiomWebVitals /> {children} </body> </html> ); } ``` **Why good:** `AxiomWebVitals` component automatically reports Core Web Vitals (LCP, INP, CLS) to Axiom, no additional configuration needed **Note:** Web Vitals are only sent from production deployments, not local development. --- ## Pattern 10: Axiom Dashboard Setup **Dashboard Components to Create:** 1. **Request Volume** - Count of requests over time 2. **Error Rate** - Percentage of 4xx/5xx responses 3. **Response Time P95** - 95th percentile latency 4. **Top Errors** - Most frequent error messages 5. **Web Vitals** - LCP, INP, CLS metrics **Axiom APL Queries:** ```apl // Request volume per minute ['myapp-prod'] | summarize count() by bin_auto(_time) // Error rate ['myapp-prod'] | where level == "error" | summarize errors = count() by bin(_time, 1m) // Response time P95 ['myapp-prod'] | where isnotnull(duration) | summarize p95 = percentile(duration, 95) by bin(_time, 5m) // Top errors ['myapp-prod'] | where level == "error" | summarize count() by message | top 10 by count_ ``` -
ci-cd.md 2.4 KB
# Observability Setup - CI/CD Examples > GitHub Actions workflow for source maps upload and Sentry release tracking. **Navigation:** [Back to SKILL.md](../SKILL.md) | [core.md](core.md) | [sentry-config.md](sentry-config.md) | [pino-logger.md](pino-logger.md) | [axiom-integration.md](axiom-integration.md) | [health-check.md](health-check.md) --- ## Pattern 7: GitHub Actions Source Maps Upload **File: `.github/workflows/deploy.yml`** ```yaml # Good Example - Source maps upload in GitHub Actions name: Deploy on: push: branches: [main] env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "20" cache: "npm" - name: Install dependencies run: npm ci - name: Build with source maps run: npm run build env: CI: true NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }} NEXT_PUBLIC_AXIOM_TOKEN: ${{ secrets.NEXT_PUBLIC_AXIOM_TOKEN }} NEXT_PUBLIC_AXIOM_DATASET: ${{ secrets.NEXT_PUBLIC_AXIOM_DATASET }} NEXT_PUBLIC_ENVIRONMENT: production NEXT_PUBLIC_APP_VERSION: ${{ github.sha }} - name: Create Sentry release uses: getsentry/action-release@v3 env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} with: environment: production version: ${{ github.sha }} # Deploy to Vercel/other platform... ``` **Why good:** Environment variables from secrets (not hardcoded), `CI=true` enables source map upload in build, version tied to git SHA for release tracking, Sentry release action notifies Sentry of deployment --- ### Bad Example ```yaml # Bad Example - Missing source maps configuration name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run build # No Sentry release, no source maps ``` **Why bad:** No source maps upload means Sentry shows minified stack traces (unreadable), no release tracking means can't correlate errors with deployments -
core.md 4.6 KB
# Observability Setup - Core Examples > Essential patterns for initial Pino + Axiom + Sentry setup. Always review these first. **Navigation:** [Back to SKILL.md](../SKILL.md) | [sentry-config.md](sentry-config.md) | [pino-logger.md](pino-logger.md) | [axiom-integration.md](axiom-integration.md) | [ci-cd.md](ci-cd.md) | [health-check.md](health-check.md) --- ## Pattern 1: Dependency Installation ```bash # Good Example - Production dependencies npm install pino next-axiom @sentry/nextjs # Development dependencies (pretty printing for local dev) npm install -D pino-pretty ``` **Why good:** `pino-pretty` as devDependency prevents production bundle bloat, all core packages are production dependencies for runtime use ```bash # Bad Example - pino-pretty as production dependency npm install pino pino-pretty next-axiom @sentry/nextjs ``` **Why bad:** `pino-pretty` adds ~500KB to production bundle unnecessarily, degrades performance in production where JSON logs should be sent directly to your log aggregator --- ## Pattern 2: Environment Variables Template **File: `.env.example`** ```bash # Good Example - Complete observability env template # ================================================================ # OBSERVABILITY CONFIGURATION # ================================================================ # ==================================== # Axiom (Logging & Traces) # ==================================== # Axiom dataset name (create at https://app.axiom.co/datasets) # Use separate datasets per environment: myapp-dev, myapp-staging, myapp-prod NEXT_PUBLIC_AXIOM_DATASET=myapp-dev # Axiom API token (create at https://app.axiom.co/settings/api-tokens) # Requires ingest permission for the dataset NEXT_PUBLIC_AXIOM_TOKEN= # ==================================== # Sentry (Error Tracking) # ==================================== # Sentry DSN (from Project Settings > Client Keys) # https://docs.sentry.io/platforms/javascript/guides/nextjs/ NEXT_PUBLIC_SENTRY_DSN= # Sentry auth token (for source maps upload in CI) # Create at https://sentry.io/settings/auth-tokens/ # Required scopes: project:releases, org:read SENTRY_AUTH_TOKEN= # Sentry organization slug SENTRY_ORG=your-org # Sentry project slug SENTRY_PROJECT=your-project # ==================================== # Environment Identification # ==================================== # Current environment (development, staging, production) NEXT_PUBLIC_ENVIRONMENT=development # App version (set by CI, used for Sentry releases) NEXT_PUBLIC_APP_VERSION=0.0.0-local ``` **Why good:** Grouped by service for easy navigation, comments explain where to get each value, separate datasets per environment prevents data mixing, `NEXT_PUBLIC_` prefix makes client-side accessible variables explicit ```bash # Bad Example - Incomplete template AXIOM_TOKEN= SENTRY_DSN= ``` **Why bad:** Missing `NEXT_PUBLIC_` prefix means variables undefined in client, no documentation for where to get values, no dataset separation per environment --- ## Pattern 3: next.config.ts with withAxiom **File: `next.config.ts`** ```typescript // Good Example - withAxiom wrapper with Sentry (v9+ compatible) import { withSentryConfig } from "@sentry/nextjs"; import { withAxiom } from "next-axiom"; import type { NextConfig } from "next"; const nextConfig: NextConfig = { // Your existing config... reactStrictMode: true, }; // Wrap with Axiom first (inner), then Sentry (outer) export default withSentryConfig(withAxiom(nextConfig), { // Organization and project from env vars org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, // Auth token for source map upload authToken: process.env.SENTRY_AUTH_TOKEN, // Only print source map upload logs in CI silent: !process.env.CI, }); ``` **Why good:** ESM imports with TypeScript (`next.config.ts`), `withAxiom` wraps first for logging integration, Sentry wraps outer for source map handling, `silent: !process.env.CI` suppresses upload noise locally, source maps are hidden by default in v9+ (no `hideSourceMaps` needed) ```typescript // Bad Example - Using removed v7/v8 options import { withSentryConfig } from "@sentry/nextjs"; export default withSentryConfig(nextConfig, { disableServerWebpackPlugin: !process.env.CI, // REMOVED in v8+ disableClientWebpackPlugin: !process.env.CI, // REMOVED in v8+ hideSourceMaps: true, // REMOVED in v9 (now default behavior) disableLogger: true, // REMOVED in v9 }); ``` **Why bad:** `disableServerWebpackPlugin`, `disableClientWebpackPlugin` removed in v8, `hideSourceMaps` removed in v9 (SDK emits hidden source maps by default), `disableLogger` removed in v9 -
health-check.md 2.3 KB
# Observability Setup - Health Check Examples > Health check endpoints that integrate with your observability stack. **Navigation:** [Back to SKILL.md](../SKILL.md) | [core.md](core.md) | [sentry-config.md](sentry-config.md) | [pino-logger.md](pino-logger.md) | [axiom-integration.md](axiom-integration.md) | [ci-cd.md](ci-cd.md) --- ## Pattern 8: Health Check Endpoint Health checks should report version info (tied to Sentry releases) and log results via your logger for Axiom dashboards. ### Shallow Check (for Load Balancers) ```typescript // Good Example - Shallow health check const HTTP_STATUS_OK = 200; export async function handleHealthCheck(): Promise<Response> { return Response.json( { status: "healthy", timestamp: new Date().toISOString(), version: process.env.APP_VERSION || "unknown", }, { status: HTTP_STATUS_OK }, ); } ``` ### Deep Check (with Dependency Verification) ```typescript // Good Example - Deep health check logging failures via Pino import { logger } from "./logger"; // Your Pino logger instance const HTTP_STATUS_OK = 200; const HTTP_STATUS_SERVICE_UNAVAILABLE = 503; const HEALTH_CHECK_TIMEOUT_MS = 5000; export async function handleDeepHealthCheck( checkDatabase: () => Promise<void>, ): Promise<Response> { let dbStatus: "connected" | "disconnected" = "disconnected"; try { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), HEALTH_CHECK_TIMEOUT_MS), ); await Promise.race([checkDatabase(), timeoutPromise]); dbStatus = "connected"; } catch (error) { logger.warn({ error }, "Health check: database unreachable"); dbStatus = "disconnected"; } const isHealthy = dbStatus === "connected"; return Response.json( { status: isHealthy ? "healthy" : "unhealthy", timestamp: new Date().toISOString(), version: process.env.APP_VERSION || "unknown", dependencies: { database: dbStatus }, }, { status: isHealthy ? HTTP_STATUS_OK : HTTP_STATUS_SERVICE_UNAVAILABLE }, ); } ``` **Why good:** Version field ties to Sentry releases for correlation, failures logged via Pino (visible in Axiom dashboards), framework-agnostic using standard `Response` API, database check injected as dependency for testability -
pino-logger.md 2 KB
# Observability Setup - Pino Logger Examples > Pino logger configuration with development/production modes and security redaction. **Navigation:** [Back to SKILL.md](../SKILL.md) | [core.md](core.md) | [sentry-config.md](sentry-config.md) | [axiom-integration.md](axiom-integration.md) | [ci-cd.md](ci-cd.md) | [health-check.md](health-check.md) --- ## Pattern 9: Pino Logger Setup **File: `src/lib/logger.ts`** ```typescript // Good Example - Pino logger configuration import pino from "pino"; const LOG_LEVEL_DEVELOPMENT = "debug"; const LOG_LEVEL_PRODUCTION = "info"; const isDevelopment = process.env.NODE_ENV === "development"; export const logger = pino({ level: isDevelopment ? LOG_LEVEL_DEVELOPMENT : LOG_LEVEL_PRODUCTION, // Use pino-pretty in development only transport: isDevelopment ? { target: "pino-pretty", options: { colorize: true, translateTime: "SYS:standard", ignore: "pid,hostname", }, } : undefined, // Base fields included in every log base: { service: "api", version: process.env.APP_VERSION || "unknown", }, // Redact sensitive fields redact: [ "req.headers.authorization", "req.headers.cookie", "password", "token", "apiKey", ], }); // Create child logger for specific context export const createLogger = (context: Record<string, unknown>) => { return logger.child(context); }; ``` **Why good:** Development gets pretty-printed logs for readability, production gets JSON for log aggregator ingestion, base fields provide context in every log, sensitive fields redacted automatically --- ### Bad Example ```typescript // Bad Example - pino-pretty in production import pino from "pino"; export const logger = pino({ transport: { target: "pino-pretty", // Always pretty - BAD in production }, }); ``` **Why bad:** pino-pretty adds significant overhead in production, defeats Pino's performance advantages, should be development-only -
sentry-config.md 5.5 KB
# Observability Setup - Sentry Configuration Examples > Sentry configuration files for client, server, and edge runtimes, plus instrumentation setup. **Navigation:** [Back to SKILL.md](../SKILL.md) | [core.md](core.md) | [pino-logger.md](pino-logger.md) | [axiom-integration.md](axiom-integration.md) | [ci-cd.md](ci-cd.md) | [health-check.md](health-check.md) --- ## Sentry v9 Migration Notes **Breaking changes from v8 to v9:** - `enableTracing` option removed - use `tracesSampleRate: 1` or `tracesSampleRate: 0` instead - `hideSourceMaps` option removed - SDK emits hidden source maps by default - `beforeSendSpan` can no longer return `null` to drop spans - use integrations instead - `captureUserFeedback()` renamed to `captureFeedback()`, field `comments` renamed to `message` - Metrics API completely removed (was deprecated) - Minimum Node.js version is 18.0.0 **Session Replay v8+ changes:** - `unblock` and `unmask` options no longer add default DOM selectors - To maintain v7 behavior, explicitly add: `unblock: [".sentry-unblock, [data-sentry-unblock]"]` --- ## Pattern 4: Sentry Configuration Files ### Client Config **File: `sentry.client.config.ts`** ```typescript // Good Example - Client-side Sentry config (v9 compatible) import * as Sentry from "@sentry/nextjs"; const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN; const ENVIRONMENT = process.env.NEXT_PUBLIC_ENVIRONMENT || "development"; const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0-local"; const SAMPLE_RATE_DEVELOPMENT = 1.0; const SAMPLE_RATE_PRODUCTION = 0.1; const TRACES_SAMPLE_RATE = 0.2; const REPLAY_SESSION_SAMPLE_RATE = 0.1; const REPLAY_ON_ERROR_SAMPLE_RATE = 1.0; Sentry.init({ dsn: SENTRY_DSN, environment: ENVIRONMENT, release: APP_VERSION, // Sample rate for error events (1.0 = 100%) sampleRate: ENVIRONMENT === "production" ? SAMPLE_RATE_PRODUCTION : SAMPLE_RATE_DEVELOPMENT, // Sample rate for performance (v9: enableTracing removed, use this directly) tracesSampleRate: TRACES_SAMPLE_RATE, // Enable debug in development debug: ENVIRONMENT === "development", // Integrations integrations: [ Sentry.replayIntegration({ // Mask all text for privacy maskAllText: true, blockAllMedia: true, // v8+: Default selectors removed, add explicitly if needed unmask: [".sentry-unmask", "[data-sentry-unmask]"], unblock: [".sentry-unblock", "[data-sentry-unblock]"], }), ], // Replay settings replaysSessionSampleRate: REPLAY_SESSION_SAMPLE_RATE, replaysOnErrorSampleRate: REPLAY_ON_ERROR_SAMPLE_RATE, // Filter out expected errors beforeSend(event, hint) { // Ignore cancelled requests if (event.exception?.values?.[0]?.value?.includes("AbortError")) { return null; } return event; }, // v9: beforeSendSpan can no longer return null to drop spans // Use integrations to control span recording instead }); ``` **Why good:** Named constants for all sample rates, v9-compatible configuration (no deprecated options), replay integration with explicit unmask/unblock selectors, `beforeSend` filters expected errors to reduce noise --- ### Server Config **File: `sentry.server.config.ts`** ```typescript // Good Example - Server-side Sentry config import * as Sentry from "@sentry/nextjs"; const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN; const ENVIRONMENT = process.env.NEXT_PUBLIC_ENVIRONMENT || "development"; const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0-local"; const TRACES_SAMPLE_RATE = 0.2; Sentry.init({ dsn: SENTRY_DSN, environment: ENVIRONMENT, release: APP_VERSION, tracesSampleRate: TRACES_SAMPLE_RATE, // Enable debug in development debug: ENVIRONMENT === "development", // Server-specific: capture more context includeLocalVariables: true, }); ``` --- ### Edge Config **File: `sentry.edge.config.ts`** ```typescript // Good Example - Edge runtime Sentry config import * as Sentry from "@sentry/nextjs"; const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN; const ENVIRONMENT = process.env.NEXT_PUBLIC_ENVIRONMENT || "development"; const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0-local"; const TRACES_SAMPLE_RATE = 0.2; Sentry.init({ dsn: SENTRY_DSN, environment: ENVIRONMENT, release: APP_VERSION, tracesSampleRate: TRACES_SAMPLE_RATE, // Edge runtime has limited features debug: false, }); ``` --- ### Bad Example ```typescript // Bad Example - Single config for all runtimes // sentry.config.ts import * as Sentry from "@sentry/nextjs"; Sentry.init({ dsn: "https://xxx@sentry.io/123", // Hardcoded DSN tracesSampleRate: 1.0, // Magic number, too high for production }); ``` **Why bad:** Single config doesn't handle different runtime requirements, hardcoded DSN is a security risk and prevents environment separation, 100% trace rate overwhelms Sentry quota in production --- ## Pattern 5: Instrumentation File **File: `instrumentation.ts`** ```typescript // Good Example - Instrumentation for Sentry import * as Sentry from "@sentry/nextjs"; export async function register() { if (process.env.NEXT_RUNTIME === "nodejs") { await import("./sentry.server.config"); } if (process.env.NEXT_RUNTIME === "edge") { await import("./sentry.edge.config"); } } // Next.js 15+ error handling hook export const onRequestError = Sentry.captureRequestError; ``` **Why good:** Dynamic imports prevent loading wrong config for runtime, `onRequestError` hook captures Server Component errors automatically (Next.js 15+)
-
-
reference.md 7.8 KB
# Observability Setup - Reference > Decision frameworks, red flags, and anti-patterns for Pino + Axiom + Sentry setup. --- ## Decision Framework ### Choosing Log Destinations ``` Where should logs go? ├─ Local development? │ ├─ Console with pino-pretty (human readable) │ └─ Optionally also to local Axiom dataset ├─ CI/Test environment? │ ├─ Console only (JSON format) │ └─ No external services (fast, isolated) └─ Production? ├─ Axiom (primary - searchable, dashboards) └─ Console (fallback - captured by hosting platform) ``` ### Sentry vs Axiom for Errors ``` Where should errors go? ├─ Application errors (exceptions, crashes)? │ └─ Sentry (source maps, stack traces, releases) ├─ Expected errors (404s, validation)? │ └─ Axiom logs (don't pollute Sentry quota) └─ Performance issues? └─ Axiom traces (longer retention, cheaper) ``` --- ## RED FLAGS **High Priority Issues:** - Committing Axiom tokens or Sentry DSN to version control - Using pino-pretty in production (performance degradation) - Missing source maps upload (unreadable stack traces in Sentry) - Same Axiom dataset for all environments (data mixing) - Missing `sentry.edge.config.ts` (middleware errors not tracked) **Medium Priority Issues:** - No health check endpoints (can't monitor service status) - 100% trace sample rate in production (expensive, unnecessary) - Missing `beforeSend` filter (noise from expected errors) - No Web Vitals tracking (missing performance insights) **Common Mistakes:** - Forgetting to wrap `next.config.ts` with `withAxiom` - Using `SENTRY_DSN` without `NEXT_PUBLIC_` prefix (undefined in client) - Using removed Sentry options (`hideSourceMaps`, `enableTracing`, `disableServerWebpackPlugin`) - Missing `instrumentation.ts` (Sentry not initialized properly) - Hardcoding sample rates instead of using named constants **Gotchas & Edge Cases:** - Web Vitals only sent in production, not development - Source maps upload requires `SENTRY_AUTH_TOKEN` in the build environment - Edge runtime has limited Sentry features (no replay) - Axiom token needs ingest permission for the specific dataset - Sentry auth token needs `project:releases` and `org:read` scopes - FID metric replaced by INP (Interaction to Next Paint) in Sentry v10 --- ## Anti-Patterns to Avoid ### Hardcoded Credentials ```typescript // ANTI-PATTERN: Hardcoded DSN Sentry.init({ dsn: "https://abc123@sentry.io/456789", }); ``` **Why it's wrong:** Credentials in code get committed to git, can't rotate without code change. **What to do instead:** Use environment variables: `process.env.NEXT_PUBLIC_SENTRY_DSN` --- ### pino-pretty in Production ```typescript // ANTI-PATTERN: Always using pino-pretty import pino from "pino"; export const logger = pino({ transport: { target: "pino-pretty", }, }); ``` **Why it's wrong:** pino-pretty is slow, adds ~500KB, defeats Pino's performance benefits. **What to do instead:** Conditionally use pino-pretty only when `NODE_ENV === 'development'` --- ### Missing Environment Separation ```bash # ANTI-PATTERN: Same dataset for all environments NEXT_PUBLIC_AXIOM_DATASET=myapp ``` **Why it's wrong:** Development logs mixed with production, hard to filter, pollutes dashboards. **What to do instead:** Use `myapp-dev`, `myapp-staging`, `myapp-prod` --- ### No Source Maps in CI ```yaml # ANTI-PATTERN: Build without source maps - run: npm run build # No SENTRY_AUTH_TOKEN set ``` **Why it's wrong:** Sentry shows minified code in stack traces, impossible to debug. **What to do instead:** Set `SENTRY_AUTH_TOKEN` as a GitHub secret and pass it to the build environment. --- ### Magic Numbers for Sample Rates ```typescript // ANTI-PATTERN: Magic numbers Sentry.init({ sampleRate: 0.1, tracesSampleRate: 0.2, }); ``` **Why it's wrong:** Unclear what these values mean, hard to change consistently. **What to do instead:** Use named constants: ```typescript const SAMPLE_RATE_PRODUCTION = 0.1; const TRACES_SAMPLE_RATE = 0.2; Sentry.init({ sampleRate: SAMPLE_RATE_PRODUCTION, tracesSampleRate: TRACES_SAMPLE_RATE, }); ``` --- ### Missing beforeSend Filter ```typescript // ANTI-PATTERN: No error filtering Sentry.init({ dsn: SENTRY_DSN, // No beforeSend - all errors sent }); ``` **Why it's wrong:** Expected errors (cancelled requests, validation) pollute Sentry, waste quota. **What to do instead:** Add `beforeSend` to filter expected errors: ```typescript beforeSend(event, hint) { if (event.exception?.values?.[0]?.value?.includes("AbortError")) { return null; } return event; } ``` --- ### Single Sentry Config for All Runtimes ```typescript // ANTI-PATTERN: One config file for everything // sentry.config.ts Sentry.init({ /* ... */ }); ``` **Why it's wrong:** Client, server, and edge runtimes have different capabilities and requirements. **What to do instead:** Create three separate config files: - `sentry.client.config.ts` - With replay integration - `sentry.server.config.ts` - With local variables capture - `sentry.edge.config.ts` - With limited features --- ### Using Removed Sentry Options ```typescript // ANTI-PATTERN: Options removed in v8/v9 withSentryConfig(config, { disableServerWebpackPlugin: true, // Removed in v8 disableClientWebpackPlugin: true, // Removed in v8 hideSourceMaps: true, // Removed in v9 (now default) disableLogger: true, // Removed in v9 enableTracing: true, // Removed in v9 }); ``` **Why it's wrong:** These options no longer exist and will be silently ignored or cause errors. **What to do instead:** Use current v9+ API: `silent: !process.env.CI`, `sourcemaps.disable`, `sourcemaps.deleteSourcemapsAfterUpload`. Source maps are hidden by default. --- ## Checklist: First-Time Setup - [ ] Install dependencies: `pino`, `next-axiom`, `@sentry/nextjs`, `pino-pretty` (dev) - [ ] Create Axiom account and datasets (dev, staging, prod) - [ ] Create Axiom API token with ingest permission - [ ] Create Sentry project and get DSN - [ ] Create Sentry auth token with `project:releases` scope - [ ] Add all environment variables to `.env.example` - [ ] Configure environment variables in hosting platform - [ ] Wrap `next.config.ts` with `withAxiom` and `withSentryConfig` - [ ] Create `sentry.client.config.ts` - [ ] Create `sentry.server.config.ts` - [ ] Create `sentry.edge.config.ts` - [ ] Create `instrumentation.ts` - [ ] Add `<AxiomWebVitals />` to root layout - [ ] Add health check endpoints - [ ] Configure GitHub Actions for source maps upload - [ ] Create initial Axiom dashboard - [ ] Test error tracking (throw test error) - [ ] Test log ingestion (log test message) - [ ] Verify Web Vitals appearing in Axiom --- ## Sentry v9 Migration Checklist If upgrading from v8 to v9: - [ ] Remove `enableTracing` option (use `tracesSampleRate` directly) - [ ] Remove `hideSourceMaps` option (now default behavior) - [ ] Remove `disableServerWebpackPlugin` / `disableClientWebpackPlugin` (removed in v8) - [ ] Update `beforeSendSpan` if returning null (no longer supported) - [ ] Rename `captureUserFeedback()` to `captureFeedback()` - [ ] Rename `comments` field to `message` in feedback - [ ] Remove any Metrics API usage (completely removed) - [ ] Ensure Node.js 18.0.0+ (minimum version) - [ ] Add explicit `unmask`/`unblock` selectors if relying on defaults - [ ] Update `withSentryConfig` to use `silent: !process.env.CI` instead of removed options --- ## Resources **Official Documentation:** - [Axiom Documentation](https://axiom.co/docs) - [Sentry Next.js SDK](https://docs.sentry.io/platforms/javascript/guides/nextjs/) - [Sentry v8 to v9 Migration](https://docs.sentry.io/platforms/javascript/guides/nextjs/migration/v8-to-v9/) - [Sentry v9 to v10 Migration](https://docs.sentry.io/platforms/javascript/guides/nextjs/migration/v9-to-v10/) - [Pino Documentation](https://getpino.io/) - [next-axiom GitHub](https://github.com/axiomhq/next-axiom) -
SKILL.md 10.7 KB
--- name: api-observability-setup-axiom-pino-sentry description: Pino, Axiom, Sentry installation - one-time project setup for logging and error tracking with source maps upload --- # Observability Setup (Pino + Axiom + Sentry) > **Quick Guide:** One-time project setup for observability. Install `pino`, `next-axiom`, `@sentry/nextjs`. Configure Axiom dataset + Vercel integration. Set up Sentry DSN and config files. Wrap `next.config.ts` with `withAxiom` then `withSentryConfig`. Add `instrumentation.ts` for runtime-specific Sentry init. Source maps are uploaded automatically when `SENTRY_AUTH_TOKEN` is set in CI. --- **Detailed Resources:** - For code examples, see [examples/](examples/) folder: - [examples/core.md](examples/core.md) - Dependencies, env vars, next.config.ts, instrumentation - [examples/sentry-config.md](examples/sentry-config.md) - Sentry configuration files (client, server, edge) - [examples/pino-logger.md](examples/pino-logger.md) - Pino logger setup with redaction - [examples/axiom-integration.md](examples/axiom-integration.md) - Web Vitals and dashboard queries - [examples/ci-cd.md](examples/ci-cd.md) - GitHub Actions source maps upload - [examples/health-check.md](examples/health-check.md) - Health check endpoints - For decision frameworks and anti-patterns, see [reference.md](reference.md) --- <critical_requirements> ## CRITICAL: Before Using This Skill > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST create separate Axiom datasets for each environment - development, staging, production)** **(You MUST configure all three Sentry config files - `sentry.client.config.ts`, `sentry.server.config.ts`, `sentry.edge.config.ts`)** **(You MUST add source maps upload to CI/CD - Sentry needs source maps for readable stack traces)** **(You MUST install `pino-pretty` as a devDependency only - never use in production)** </critical_requirements> --- **Auto-detection:** pino, next-axiom, @sentry/nextjs, Axiom, Sentry, observability setup, logging setup, error tracking setup, source maps, sentry.client.config, sentry.server.config, sentry.edge.config, withAxiom, withSentryConfig **When to use:** - Setting up a new project that needs logging and error tracking - Adding observability to an existing project without it - Migrating from another logging/error tracking solution to Axiom + Sentry **When NOT to use:** - Adding new log statements to existing code (ongoing usage, not initial setup) - Configuring alerts, monitors, or dashboards after initial setup - Debugging production issues with existing observability **Key patterns covered:** - Dependency installation (Pino, next-axiom, @sentry/nextjs, pino-pretty) - Environment variables template (`.env.example`) - `next.config.ts` with `withAxiom()` and `withSentryConfig()` wrappers - Sentry configuration files (client, server, edge) - `instrumentation.ts` for Sentry initialization - GitHub Actions for source maps upload - Pino logger with development/production modes - Health check endpoints - Initial Axiom dashboard setup --- <philosophy> ## Philosophy **Observability is not optional for production apps.** Without logging and error tracking, debugging production issues becomes guesswork. The Pino + Axiom + Sentry stack provides: - **Pino**: Fast structured JSON logging (5x faster than Winston) - **Axiom**: Unified logs, traces, and metrics with Vercel integration - **Sentry**: Error tracking with source maps and release tracking **This skill covers one-time setup only.** For ongoing usage patterns (log levels, structured fields, correlation IDs, alert configuration), use your observability usage skill. </philosophy> --- <patterns> ## Core Patterns ### Pattern 1: Dependency Installation Install all observability packages with correct dependency types. ```bash # Production dependencies npm install pino next-axiom @sentry/nextjs # Development dependencies (pretty printing for local dev) npm install -D pino-pretty ``` **Why:** `pino-pretty` as devDependency prevents production bundle bloat (~500KB), all core packages are production dependencies for runtime use. For detailed code examples with good/bad comparisons, see [examples/core.md](examples/core.md#pattern-1-dependency-installation). --- ### Pattern 2: Environment Variables Template Create `.env.example` with all required observability variables documented. Group by service, use comments to explain where to get each value, and maintain separate datasets per environment. Key variables needed: - `NEXT_PUBLIC_AXIOM_DATASET` - Dataset name (e.g., `myapp-dev`, `myapp-prod`) - `NEXT_PUBLIC_AXIOM_TOKEN` - API token with ingest permission - `NEXT_PUBLIC_SENTRY_DSN` - Sentry DSN from project settings - `SENTRY_AUTH_TOKEN` - For source maps upload in CI - `SENTRY_ORG` / `SENTRY_PROJECT` - Organization and project slugs For complete template with all variables, see [examples/core.md](examples/core.md#pattern-2-environment-variables-template). --- ### Pattern 3: next.config.ts with withAxiom and withSentryConfig Wrap Next.js config with `withAxiom` for logging integration, then `withSentryConfig` for source map handling. Key configuration points: - `withAxiom` wraps first (inner), Sentry wraps outer - `silent: !process.env.CI` suppresses source map upload logs locally - Source maps are hidden by default in v9+ (no `hideSourceMaps` needed) - Use `sourcemaps.deleteSourcemapsAfterUpload` to clean up after upload ```typescript import { withSentryConfig } from "@sentry/nextjs"; import { withAxiom } from "next-axiom"; const nextConfig = { /* your config */ }; export default withSentryConfig(withAxiom(nextConfig), { org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, silent: !process.env.CI, }); ``` For complete configuration example, see [examples/core.md](examples/core.md#pattern-3-nextconfigts-with-withaxiom). --- ### Pattern 4: Sentry Configuration Files Create all three Sentry config files for client, server, and edge runtimes. **Required files:** - `sentry.client.config.ts` - Client-side with replay integration - `sentry.server.config.ts` - Server-side with local variables capture - `sentry.edge.config.ts` - Edge runtime with limited features Key considerations: - Use named constants for sample rates - Environment-specific configuration (debug mode, sample rates) - Filter expected errors with `beforeSend` - v9+: `hideSourceMaps` and `enableTracing` removed, source maps hidden by default For complete file templates, see [examples/sentry-config.md](examples/sentry-config.md#pattern-4-sentry-configuration-files). --- ### Pattern 5: Instrumentation File Create `instrumentation.ts` for proper Sentry initialization in Next.js. Uses dynamic imports to load the correct config for each runtime. ```typescript import * as Sentry from "@sentry/nextjs"; export async function register() { if (process.env.NEXT_RUNTIME === "nodejs") { await import("./sentry.server.config"); } if (process.env.NEXT_RUNTIME === "edge") { await import("./sentry.edge.config"); } } // Next.js 15+ error handling hook export const onRequestError = Sentry.captureRequestError; ``` **Why:** Dynamic imports prevent loading wrong config for runtime, `onRequestError` hook captures Server Component errors automatically (Next.js 15+). --- ### Pattern 6: Web Vitals Component Add `<AxiomWebVitals />` component to root layout for automatic Core Web Vitals (LCP, INP, CLS) reporting to Axiom. **Note:** Web Vitals are only sent from production deployments, not local development. For implementation example, see [examples/axiom-integration.md](examples/axiom-integration.md#pattern-6-web-vitals-component). --- ### Pattern 7: GitHub Actions Source Maps Upload Configure CI/CD to upload source maps to Sentry on deployment. Key requirements: - `SENTRY_AUTH_TOKEN` in build environment enables automatic upload - Use `getsentry/action-release@v3` for release creation - Tie version to git SHA for release tracking For complete workflow template, see [examples/ci-cd.md](examples/ci-cd.md#pattern-7-github-actions-source-maps-upload). --- ### Pattern 8: Health Check Endpoint Add health check endpoints that integrate with your observability stack: - **Shallow check** - Fast response for load balancer probes, includes version for Sentry release correlation - **Deep check** - Verifies dependencies, logs failures via Pino for Axiom dashboard visibility For implementation examples, see [examples/health-check.md](examples/health-check.md#pattern-8-health-check-endpoint). --- ### Pattern 9: Pino Logger Setup Configure Pino with development/production modes: - Development: `pino-pretty` for human-readable output - Production: JSON for log aggregation ingestion - Base fields for context in every log - Redaction of sensitive fields For complete configuration, see [examples/pino-logger.md](examples/pino-logger.md#pattern-9-pino-logger-setup). --- ### Pattern 10: Axiom Dashboard Setup After setting up, create initial dashboards in Axiom: - Request volume per minute - Error rate percentage - Response time P95 - Top errors - Web Vitals metrics For APL query examples, see [examples/axiom-integration.md](examples/axiom-integration.md#pattern-10-axiom-dashboard-setup). </patterns> --- <decision_framework> ## Decision Framework See [reference.md](reference.md#decision-framework) for complete decision trees: - **Log Destinations**: Where logs should go in each environment - **Sentry vs Axiom for Errors**: Which system handles which error types </decision_framework> --- <red_flags> ## RED FLAGS See [reference.md](reference.md#red-flags) for complete list. **High Priority:** - Committing Axiom tokens or Sentry DSN to version control - Using pino-pretty in production - Missing source maps upload in CI - Same Axiom dataset for all environments **Common Mistakes:** - Forgetting to wrap `next.config.ts` with `withAxiom` - Missing `instrumentation.ts` - Using removed Sentry options (`hideSourceMaps`, `enableTracing`, `disableServerWebpackPlugin`) - Hardcoding sample rates instead of named constants </red_flags> --- <critical_reminders> ## CRITICAL REMINDERS > **All code must follow project conventions in CLAUDE.md** **(You MUST create separate Axiom datasets for each environment - development, staging, production)** **(You MUST configure all three Sentry config files - `sentry.client.config.ts`, `sentry.server.config.ts`, `sentry.edge.config.ts`)** **(You MUST add source maps upload to CI/CD - Sentry needs source maps for readable stack traces)** **(You MUST install `pino-pretty` as a devDependency only - never use in production)** **Failure to follow these rules will result in missing logs, unreadable errors, and security vulnerabilities.** </critical_reminders>
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.