Self-hosted

NuxtHub Storage

Self-hosted log retention for evlog using NuxtHub database storage. Store, query, and automatically clean up your structured logs with zero external dependencies.

@evlog/nuxthub stores your evlog wide events directly in your NuxtHub database. No external logging service needed. Your logs live next to your data, with automatic cleanup based on a retention policy.

Store evlog wide events in NuxtHub

Why Self-Hosted Logs?

External logging services (Axiom, Datadog, etc.) are great for production at scale. But sometimes you want:

  • Zero external dependencies - logs stored in the same database as your app
  • Full data ownership - no third-party access to your log data
  • Free tier friendly - no per-event pricing, just your existing database
  • Development & staging - full log visibility without paying for a service

@evlog/nuxthub works as a drop-in drain. Your existing evlog setup stays the same, you just get a database-backed storage layer on top.

Install

pnpm add @nuxthub/core @evlog/nuxthub

Or with nuxi:

Terminal
npx nuxi module add @nuxthub/core @evlog/nuxthub

Setup

Add the module to your nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxthub/core', '@evlog/nuxthub'],

  evlog: {
    retention: '7d',
  },
})

Even if @evlog/nuxthub can auto-register missing modules, we recommend explicitly installing @nuxthub/core and registering it in modules for a clearer and more predictable setup.

That's it. The module automatically:

  1. Installs evlog/nuxt and @nuxthub/core if not already registered
  2. Registers the evlog_events database schema with NuxtHub
  3. Hooks into evlog:drain to store every event in the database
  4. Schedules a cleanup task based on your retention policy
Prerequisites: Your project must use NuxtHub with a database configured. @evlog/nuxthub uses Drizzle ORM to interact with the database.

How It Works

Request → evlog wide event → evlog:drain hook → INSERT into evlog_events table
                                                          ↓
                          Cron task (automatic) → DELETE events older than retention

Every wide event emitted by evlog is stored as a row in the evlog_events table. The drain plugin handles both single events and batches (when used with the pipeline).

Database Schema

The evlog_events table stores indexed columns for fast querying and a data JSON column for all remaining fields:

ColumnTypeDescription
idtextUUID primary key
timestamptextEvent timestamp
leveltextLog level (info, warn, error, debug)
servicetextService name
environmenttextEnvironment (production, staging, etc.)
methodtextHTTP method
pathtextRequest path
statusintegerHTTP status code
duration_msintegerRequest duration in milliseconds
request_idtextRequest correlation ID
sourcetextEvent source (server, client)
errortextError details (JSON string)
datatextAll remaining event fields (JSON)
created_attextRow insertion timestamp

Indexed columns: timestamp, level, service, status, request_id, created_at.

Dialect Support

The schema is automatically registered for your NuxtHub database dialect:

  • SQLite (default for Cloudflare D1)
  • MySQL
  • PostgreSQL

The correct schema is selected via the hub:db:schema:extend hook based on your NuxtHub configuration.

Combining with External Adapters

@evlog/nuxthub doesn't replace external adapters, you can use both. The module registers its own evlog:drain hook, so any other drain plugins you have will still work:

server/plugins/evlog-drain.ts
import { createAxiomDrain } from 'evlog/axiom'

export default defineNitroPlugin((nitroApp) => {
  // This runs alongside @evlog/nuxthub's built-in drain
  nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})

Retention

@evlog/nuxthub automatically deletes old events based on your retention policy. No manual cleanup needed.

Configuration

Set the retention period in your nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxthub/core', '@evlog/nuxthub'],

  evlog: {
    retention: '7d', // default
  },
})

The retention value is a number followed by a unit:

UnitDescriptionExample
dDays7d = 7 days
hHours24h = 24 hours
mMinutes60m = 60 minutes

How Cleanup Works

The module registers a Nitro scheduled task (evlog:cleanup) that runs on a cron schedule derived from your retention value. The cron frequency is set to roughly half the retention period:

RetentionCron ScheduleDescription
60m*/30 * * * *Every 30 minutes
24h0 */12 * * *Every 12 hours
7d0 3 * * *Daily at 3:00 AM
30d0 3 * * *Daily at 3:00 AM

The cleanup task deletes all rows in evlog_events where created_at is older than the retention period.

Manual Cleanup

You can trigger cleanup manually via the API endpoint:

Terminal
curl https://your-app.com/api/_cron/evlog-cleanup

If the CRON_SECRET environment variable is set, the endpoint requires a Bearer token:

Terminal
curl -H "Authorization: Bearer your-secret" \
  https://your-app.com/api/_cron/evlog-cleanup

This is recommended for production deployments to prevent unauthorized cleanup triggers.

Vercel Cron

When installing the module with nuxi module add, you'll be prompted to create a vercel.json with the appropriate cron schedule:

vercel.json
{
  "crons": [
    {
      "path": "/api/_cron/evlog-cleanup",
      "schedule": "0 3 * * *"
    }
  ]
}

On Vercel, the CRON_SECRET environment variable is automatically set and validated.

Cloudflare & Other Platforms

On Cloudflare Workers and other platforms, the Nitro scheduled task handles cleanup automatically without any additional cron configuration. The task is registered with experimental.tasks enabled in the Nitro config.

Next Steps

  • Adapters - Send logs to external services alongside NuxtHub storage
  • Pipeline - Batch events for better database performance