@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
bun add @nuxthub/core @evlog/nuxthub
yarn add @nuxthub/core @evlog/nuxthub
npm install @nuxthub/core @evlog/nuxthub
Or with nuxi:
npx nuxi module add @nuxthub/core @evlog/nuxthub
Setup
Add the module to your 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:
- Installs
evlog/nuxtand@nuxthub/coreif not already registered - Registers the
evlog_eventsdatabase schema with NuxtHub - Hooks into
evlog:drainto store every event in the database - Schedules a cleanup task based on your retention policy
@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:
| Column | Type | Description |
|---|---|---|
id | text | UUID primary key |
timestamp | text | Event timestamp |
level | text | Log level (info, warn, error, debug) |
service | text | Service name |
environment | text | Environment (production, staging, etc.) |
method | text | HTTP method |
path | text | Request path |
status | integer | HTTP status code |
duration_ms | integer | Request duration in milliseconds |
request_id | text | Request correlation ID |
source | text | Event source (server, client) |
error | text | Error details (JSON string) |
data | text | All remaining event fields (JSON) |
created_at | text | Row 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:
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:
export default defineNuxtConfig({
modules: ['@nuxthub/core', '@evlog/nuxthub'],
evlog: {
retention: '7d', // default
},
})
The retention value is a number followed by a unit:
| Unit | Description | Example |
|---|---|---|
d | Days | 7d = 7 days |
h | Hours | 24h = 24 hours |
m | Minutes | 60m = 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:
| Retention | Cron Schedule | Description |
|---|---|---|
60m | */30 * * * * | Every 30 minutes |
24h | 0 */12 * * * | Every 12 hours |
7d | 0 3 * * * | Daily at 3:00 AM |
30d | 0 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:
curl https://your-app.com/api/_cron/evlog-cleanup
If the CRON_SECRET environment variable is set, the endpoint requires a Bearer token:
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:
{
"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.