Claude Skill

api-observability-setup-axiom-pino-sentry

Pino, Axiom, Sentry installation - one-time project setup for logging and error tracking with source maps upload

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download agents-inc-skills-dist_plugins_api-observability-setup-axiom-pino-sentry_skills_api-observability-setup-axiom-pino-sentry-3a51ef5.zip · 14 KB
Part of agents-inc/skills — 130 skills

Install

skills CLI 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 Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
Git 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. 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:


<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



<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.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>

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.

No comments yet.

Reviews (0)

No reviews yet.

Related