Skip to main content

Axiom Logging & Analytics

CONA uses Axiom for centralized logging, observability, and analytics. This guide explains how our Axiom integration works, how to set up the MCP server for Cursor, and how to generate reports from Axiom data.

Overview

Axiom provides centralized log aggregation and analysis for the CONA application. It collects logs from:
  • Webapp (cona_webapp dataset): Next.js application logs, server actions, API routes, and web vitals
  • Temporal Workers (cona_temporal-workers dataset): Temporal workflow and activity logs
All logs are automatically enriched with metadata including organization IDs, user IDs, request paths, and error stack traces.

Integration Architecture

Server-Side Logging (Next.js)

Server-side logging uses the Axiom JavaScript SDK with Next.js formatters:
// apps/webapp/lib/axiom/server.ts
import { Logger, AxiomJSTransport } from "@axiomhq/logging";
import axiomClient from "@/lib/axiom/axiom";

export const logger = new Logger({
  transports: [
    new AxiomJSTransport({
      axiom: axiomClient,
      dataset: process.env.NEXT_PUBLIC_AXIOM_DATASET,
    }),
  ],
  formatters: axiomNextJs?.nextJsFormatters,
});

Application Logger

The application uses a centralized logger function that automatically sends logs to Axiom:
// apps/webapp/app/lib/logger.ts
import { log } from "@/app/lib/logger";

// Basic usage
await log({
  message: "Operation completed successfully",
  level: "info",
  metadata: {
    organizationId: "org_123",
    userId: "user_456",
    action: "document_created",
  },
});

// Error logging
await log({
  message: "Failed to process document",
  level: "error",
  error: error instanceof Error ? error : new Error("Unknown error"),
  metadata: {
    documentId: "doc_789",
    organizationId: "org_123",
  },
});
Log Levels:
  • info: General informational messages
  • warning: Warning messages for expected issues
  • error: Error messages for failures and exceptions

Client-Side Logging

Client-side logs are sent through a proxy API route to keep tokens server-side:
// apps/webapp/lib/axiom/client.ts
import { Logger, ProxyTransport } from "@axiomhq/logging";

export const logger = new Logger({
  transports: [
    new ProxyTransport({
      url: "/api/axiom/ingest",
    }),
  ],
  formatters: nextJsFormatters,
});

Temporal Workers Logging

Temporal workers use Pino with Axiom transport:
// apps/temporal-workers/src/logger.ts
import pino from "pino";

export function createLogger(name?: string) {
  const logger = pino(
    { level: logLevel, name },
    pino.transport({
      target: "@axiomhq/pino",
      options: {
        dataset: "cona_temporal-workers",
        token: process.env.NEXT_PUBLIC_AXIOM_TOKEN,
      },
    })
  );
  return logger;
}

Axiom MCP Server Setup

The Axiom MCP (Model Context Protocol) server allows you to query Axiom datasets directly from Cursor using natural language queries.

Prerequisites

  1. Cursor with Agent Mode: Ensure you’re using Cursor with Agent mode enabled
  2. Axiom API Token: You’ll need the Cursor Query API Key from Axiom (contact Egor for access)

Configuration

Add the Axiom MCP server to your Cursor configuration. The MCP server configuration should be added to your Cursor settings:
{
  "mcpServers": {
    "axiom": {
      "url": "https://mcp.axiom.co/mcp",
      "headers": {}
    }
  }
}
Note: The API token is handled automatically by the MCP server - you don’t need to include it in the configuration.

Getting the API Token

To get the Cursor Query API Key:
  1. Navigate to Axiom API Tokens
  2. Or contact Egor for access to the token

Setup Guide

For detailed setup instructions, see the Axiom MCP Server documentation.

Using Axiom MCP in Cursor

Once configured, you can query Axiom datasets directly from Cursor using natural language.

Example Queries

  • “Give me the list of Axiom datasets”
  • “Generate me a report for all warnings from cona_webapp dataset for the last 3 days”
  • “Show me all errors from cona_webapp in production for the last 7 days”
  • “What are the most common error messages in cona_temporal-workers?”

Query Capabilities

The MCP server supports:
  • Dataset Listing: List all available datasets
  • Field Discovery: Get schema information for datasets
  • Query Execution: Run APL (Axiom Processing Language) queries
  • Report Generation: Generate formatted reports from query results
  • Dashboard Access: View and retrieve dashboard information
  • Monitor Status: Check monitor statuses and history

Query Language (APL)

Axiom uses APL (Axiom Processing Language) for queries. Most queries start with the dataset name in square brackets:
['cona_webapp'] | where ['level'] == "error" | summarize count() by ['message']

Report Generation

Axiom reports are automatically generated in a standardized format when requested through Cursor.

Report Location

Reports are stored in: .reports/axiom/ If the directory doesn’t exist, Cursor will create it automatically. If creation fails, you can create it manually:
mkdir -p .reports/axiom

Report Format

Each report follows a standardized structure:
  1. YAML Frontmatter: Contains metadata about the report
  2. Summary: High-level overview of findings
  3. Context: Background and assumptions
  4. Findings: Key observations and data points
  5. Analysis: Detailed interpretation
  6. Conclusions: Derived conclusions
  7. Next Actions: Recommended follow-up steps

Report Naming

Reports are named using the format: DD-MM-YYYY-hh:mm:ss.md Example: 22-01-2026-21:46:37.md

Report Index

All reports are automatically indexed in: .reports/axiom/index.md The index lists all reports in reverse chronological order (newest first).

Example Report Request

"Generate me a report of traces from cona_webapp dataset.
Use level == 'error' or level == 'ERROR'.
Use vercel.environment == 'production'.
I want a report for the last 7 days."
This will:
  1. Query the cona_webapp dataset
  2. Filter for errors in production
  3. Analyze the last 7 days of data
  4. Generate a comprehensive report with findings and recommendations

Available Datasets

cona_webapp

Type: Next.js application logs Description: Contains logs from the Next.js web application, including:
  • Server actions
  • API routes
  • Request/response logs
  • Web vitals
  • Error traces
  • Authentication events
Key Fields:
  • level: Log level (info, warning, error)
  • message: Log message
  • org_id: Organization ID
  • user_id: User ID
  • path: Request path
  • vercel.environment: Deployment environment (production, preview, development)
  • request.*: Request metadata (method, path, headers, etc.)
  • fields.*: Custom metadata fields

cona_temporal-workers

Type: Events Description: Contains logs from Temporal workers, including:
  • Workflow execution logs
  • Activity logs
  • Error traces
  • Performance metrics
Key Fields:
  • Log level and message
  • Workflow/activity identifiers
  • Execution metadata
  • Error information

Configuration

Environment Variables

Required environment variables for Axiom integration:
# Axiom API token (for ingestion and queries)
NEXT_PUBLIC_AXIOM_TOKEN=your_token_here

# Dataset name for webapp logs
NEXT_PUBLIC_AXIOM_DATASET=cona_webapp

# Dataset name for Temporal workers (in temporal-workers app)
NEXT_PUBLIC_AXIOM_DATASET=cona_temporal-workers

Git Configuration

The reports directory is excluded from version control:
# .gitignore
.reports/
This ensures generated reports don’t clutter the repository while keeping them available locally.

Best Practices

1. Use the Centralized Logger

Always use the centralized logger function instead of direct Axiom calls:
// ✅ Correct
import { log } from "@/app/lib/logger";

await log({
  message: "Operation completed",
  level: "info",
  metadata: { organizationId, userId },
});

// ❌ Incorrect - direct Axiom calls
import { logger } from "@/lib/axiom/server";
logger.info("Operation completed");

2. Include Context in Metadata

Always include relevant context in log metadata:
await log({
  message: "Document created",
  level: "info",
  metadata: {
    organizationId, // Always include org context
    userId, // Include user context when available
    documentId, // Include relevant entity IDs
    documentType, // Include relevant entity types
    action: "createDocument", // Include action name
  },
});

3. Error Logging

Always log errors with full context:
try {
  // Your code
} catch (error) {
  await log({
    message: "Failed to process document",
    level: "error",
    error: error instanceof Error ? error : new Error("Unknown error"),
    metadata: {
      documentId,
      organizationId,
      action: "processDocument",
    },
  });

  // Handle error...
}

4. Log Levels

Use appropriate log levels:
  • info: Normal operations, successful actions, important events
  • warning: Expected issues (validation failures, auth failures, etc.)
  • error: Unexpected failures, exceptions, bugs

5. Query Best Practices

When querying Axiom datasets:
  • Always restrict time ranges: Use where ['_time'] > ago(7d) to limit data scanned
  • Use aggregations: Prefer summarize over raw data when possible
  • Project specific fields: Only select fields you need
  • Start with dataset name: Most queries should start with ['dataset-name']

Security Monitoring

Axiom serves as CONA’s centralized security monitoring and intrusion detection system. All security events are automatically logged and monitored in real-time.

Key Security Detection Rules

  1. Brute Force Detection - Failed login attempts (>5 in 1 minute)
  2. Unauthorized Access - 401/403 errors on protected routes
  3. API Rate Limiting - Excessive API requests (>100/min)
  4. Admin Access Monitoring - All /admin route access logged
  5. Session Hijacking - Same session from multiple IPs
  6. Suspicious Data Access - Unusual database query patterns
  7. Geographic Anomalies - Logins from multiple countries in 24h
  8. Error Patterns - Security-related errors (SQL injection, XSS attempts)
  9. Data Exfiltration - Excessive downloads (>100MB in 30min)
  10. Database Failures - Connection failures indicating DDoS

Security Queries

Detect brute force attempts:
['cona_webapp']
| where ['_time'] >= ago(5m)
| where ['action'] == "auth_required" and ['blocked'] == true
| summarize failures = count() by ['ip'], bin(['_time'], 1m)
| where failures > 5
Monitor admin access:
['cona_webapp']
| where ['_time'] >= ago(1h)
| where ['request.path'] contains "/admin"
| project ['_time'], ['ip'], ['user_id'], ['request.path'], ['status']
| sort by ['_time'] desc
Detect API abuse:
['cona_webapp']
| where ['_time'] >= ago(1m)
| where ['request.path'] startswith "/api/"
| summarize requests = count() by ['ip'], ['user_id']
| where requests > 100

Alert Configuration

Critical Alerts (Immediate response):
  • Brute force attacks (>10 failed logins/min)
  • Session hijacking (session from multiple IPs)
  • Database connection failures (>5 failures/min)
High Alerts (Response within 1 hour):
  • Unauthorized access attempts (>10 in 15min)
  • Suspicious error patterns (>3 security errors/10min)
  • Unusual download activity (>100MB/30min)
Medium Alerts (Response within 24 hours):
  • API rate limit violations (>100 requests/min)
  • Suspicious data access patterns
  • Anomalous geographic access (>2 countries/24h)

Compliance

Centralized Logging - All security events logged
Intrusion Detection - 10 detection rules covering OWASP Top 10
Access Monitoring - All admin/privileged access logged
Real-time Alerts - Critical alerts within 1 minute
Retention - 12-month log retention for security events

Log Retention Policy

  • Security Events: 12 months (audit compliance)
  • Access Logs: 12 months (German accounting compliance)
  • Error Logs: 6 months
  • Info Logs: 3 months

Troubleshooting

Reports Not Generating

If report generation fails:
  1. Check directory exists: Ensure .reports/axiom/ directory exists
  2. Check MCP connection: Verify Axiom MCP server is configured correctly
  3. Check API token: Ensure you have access to the Cursor Query API Key
  4. Check query syntax: Verify your query uses valid APL syntax

Logs Not Appearing in Axiom

If logs aren’t appearing in Axiom:
  1. Check environment variables: Verify NEXT_PUBLIC_AXIOM_TOKEN and NEXT_PUBLIC_AXIOM_DATASET are set
  2. Check dataset name: Ensure the dataset exists in Axiom
  3. Check network: Verify the application can reach Axiom’s API
  4. Check logger flush: Ensure logger.flush() is called (handled automatically in the centralized logger)

MCP Server Connection Issues

If the MCP server isn’t working:
  1. Verify configuration: Check that the MCP server is properly configured in Cursor
  2. Check API token: Contact Egor to verify API token access
  3. Restart Cursor: Sometimes a restart is needed after configuration changes
  4. Check Axiom status: Verify Axiom service is operational

Contact

For questions or issues related to Axiom:
  • API Token Access: Contact Egor
  • Configuration Issues: Check the Axiom MCP documentation
  • Dataset Access: Verify you have access to the CONA organization in Axiom

Additional Resources