Memory Adapter
The Memory adapter stores wide events in a module-level ring buffer. Unlike the File System adapter, it has zero runtime dependencies and runs anywhere — including Cloudflare Workers (workerd), Deno Deploy, and other edge runtimes that don't expose Node's fs module.
The primary use case is local dev agent access: wire the drain during development, expose a lightweight HTTP endpoint, and let your AI agent fetch structured logs over HTTP without any external tooling.
Add the memory drain adapter
Installation
The Memory adapter comes bundled with evlog:
import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'
Quick Start
// src/index.ts
import { Hono } from 'hono'
import { evlog } from 'evlog/hono'
import { createMemoryDrain, readMemoryLogs } from 'evlog/memory'
const app = new Hono()
app.use(evlog({ drain: createMemoryDrain() }))
// Dev-only endpoint — restrict or remove in production
app.get('/_evlog/logs', (c) => {
return c.json(readMemoryLogs())
})
// server/plugins/evlog-drain.ts
import { createMemoryDrain } from 'evlog/memory'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createMemoryDrain())
})
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createMemoryDrain } from 'evlog/memory'
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
drain: createMemoryDrain(),
})
import { evlog } from 'evlog/express'
import { createMemoryDrain } from 'evlog/memory'
app.use(evlog({ drain: createMemoryDrain() }))
import { evlog } from 'evlog/fastify'
import { createMemoryDrain } from 'evlog/memory'
await app.register(evlog, { drain: createMemoryDrain() })
import { evlog } from 'evlog/elysia'
import { createMemoryDrain } from 'evlog/memory'
app.use(evlog({ drain: createMemoryDrain() }))
import { createMemoryDrain } from 'evlog/memory'
EvlogModule.forRoot({ drain: createMemoryDrain() })
import { createMemoryDrain } from 'evlog/memory'
initLogger({ drain: createMemoryDrain() })
Agent Access via HTTP
Expose a route so agents can retrieve structured logs during a local dev session. Use parseReadMemoryLogsQuery to let agents pass filter params directly as query strings:
import { readMemoryLogs, parseReadMemoryLogsQuery } from 'evlog/memory'
// Restrict to dev — agents hit this endpoint to retrieve logs
if (process.env.NODE_ENV !== 'production') {
app.get('/_evlog/logs', (c) => {
return c.json(readMemoryLogs(parseReadMemoryLogsQuery(c.req.query())))
})
}
An agent can now call /_evlog/logs?level=error&limit=50&since=2026-01-01T00:00:00Z and the query params are coerced to the correct types before being passed to readMemoryLogs. Supported query params: store, since, until, level (comma-separated for multiple), limit.
The response is a JSON array of WideEvent objects — the same shape used by every other evlog adapter.
Configuration
Options
| Option | Type | Default | Description |
|---|---|---|---|
maxEvents | number | 1000 | Maximum events to keep in the ring buffer (oldest are dropped) |
store | string | 'default' | Named buffer key — multiple drains sharing the same key share the same buffer |
// Keep only the last 500 events
createMemoryDrain({ maxEvents: 500 })
// Use a named store for isolation
createMemoryDrain({ store: 'my-service' })
Named Stores
Use named stores to isolate events from different services or for testing:
import { createMemoryDrain, readMemoryLogs, clearMemoryLogs } from 'evlog/memory'
// Two separate buffers
const authDrain = createMemoryDrain({ store: 'auth' })
const apiDrain = createMemoryDrain({ store: 'api' })
// Read from a specific store
const authErrors = readMemoryLogs({ store: 'auth', level: 'error' })
// Clear a store (useful in tests)
clearMemoryLogs('auth')
Querying
readMemoryLogs supports the same filtering options as readFsLogs:
import { readMemoryLogs } from 'evlog/memory'
// All events
const all = readMemoryLogs()
// Errors only
const errors = readMemoryLogs({ level: 'error' })
// Last 10 minutes
const recent = readMemoryLogs({
since: new Date(Date.now() - 10 * 60 * 1000),
})
// Custom predicate
const slow = readMemoryLogs({
filter: e => typeof e.duration === 'string' && e.duration.endsWith('s'),
})
// Most recent 50 events
const latest = readMemoryLogs({ limit: 50 })
readMemoryLogs Options
| Option | Type | Description |
|---|---|---|
store | string | Named store to read from (default: 'default') |
since | Date | string | Only events with timestamp >= since |
until | Date | string | Only events with timestamp <= until |
level | LogLevel | LogLevel[] | Filter by level |
filter | (event) => boolean | Custom predicate |
limit | number | Return at most N most-recent matching events |
Combining with Network Drains
Use the memory adapter locally while sending to an observability platform in production:
import { createMemoryDrain } from 'evlog/memory'
import { createAxiomDrain } from 'evlog/axiom'
const memory = createMemoryDrain()
const axiom = createAxiomDrain()
const drain = async (ctx) => {
if (process.env.NODE_ENV === 'development') {
await memory(ctx)
} else {
await axiom(ctx)
}
}
Ring Buffer Behaviour
The buffer is bounded: once it reaches maxEvents, the oldest events are discarded to make room for incoming ones. This means memory usage stays constant regardless of how long the service runs.
Write events 1–5 → [1, 2, 3, 4, 5]
Write event 6 → [2, 3, 4, 5, 6] (1 is dropped)
Write events 7–8 → [4, 5, 6, 7, 8]
Direct API Usage
For advanced use cases, call the underlying helpers directly:
import { writeToMemory, readMemoryLogs, clearMemoryLogs, parseReadMemoryLogsQuery } from 'evlog/memory'
// Write events directly (skips the drain pipeline)
writeToMemory([event], { store: 'default', maxEvents: 1000 })
// Read the current buffer
const events = readMemoryLogs()
// Parse HTTP query params into ReadMemoryLogsOptions
const opts = parseReadMemoryLogsQuery({ level: 'error', limit: '50' })
// → { level: 'error', limit: 50 }
// Reset for tests
clearMemoryLogs()
parseReadMemoryLogsQuery coercion rules
| Query param | Type in ReadMemoryLogsOptions | Notes |
|---|---|---|
store | string | Passed through as-is |
since | string | ISO 8601 string — parsed by readMemoryLogs |
until | string | ISO 8601 string — parsed by readMemoryLogs |
level | LogLevel | LogLevel[] | Comma-separated (error,warn) or repeated array; invalid values are dropped |
limit | number | parseInt; NaN → omitted |
Next Steps
- File System Adapter - Persistent local logs for Node-based runtimes
- NuxtHub Adapter - Database-backed storage for Cloudflare D1
- Pipeline - Add batching and retry to any drain
- Custom Adapters - Build your own adapter