Scenarios

Local debugging toolkit

A live event panel + replay-from-disk you can drop into any app — combines the stream server, the fs reader, and a tiny consumer page.

The problem

You're debugging an app and you want to see what's happening on the server side without piping through console.log and remembering to remove the calls afterwards. Bonus : you want to replay what happened in the last hour, not just what's happening now.

The full code

1. Enable the stream server

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    stream: true,
  },
})

For Next.js / standalone / other frameworks, see the stream server page.

2. Add a /debug page

Drop this into app/pages/debug.vue (Nuxt) or wherever your app puts pages :

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
import type { WideEvent } from 'evlog'

const events = shallowRef<WideEvent[]>([])
const status = ref<'connecting' | 'connected' | 'error'>('connecting')
const search = ref('')

let es: EventSource | null = null

async function connect() {
  const { url } = await $fetch<{ url: string | null }>('/api/_evlog/stream-info')
  if (!url) {
    status.value = 'error'
    return
  }

  es = new EventSource(url)
  es.onopen = () => { status.value = 'connected' }
  es.onerror = () => { status.value = 'error' }
  es.onmessage = (e) => {
    const env = JSON.parse(e.data)
    if (env.evlog !== '1') return
    if (env.type === 'event' || env.type === 'replay') {
      events.value = [env.data, ...events.value].slice(0, 500)
    }
  }
}

const filtered = computed(() => {
  const q = search.value.trim().toLowerCase()
  if (!q) return events.value
  return events.value.filter(e => JSON.stringify(e).toLowerCase().includes(q))
})

onMounted(connect)
onBeforeUnmount(() => es?.close())
</script>

<template>
  <div>
    <header>
      <input v-model="search" placeholder="filter…">
      <span>{{ status }}</span>
    </header>
    <table>
      <tr v-for="(e, i) in filtered" :key="i">
        <td>{{ new Date(e.timestamp).toLocaleTimeString() }}</td>
        <td>{{ e.level }}</td>
        <td>{{ e.service }}</td>
        <td>{{ e.action ?? e.message ?? e.path }}</td>
      </tr>
    </table>
  </div>
</template>

3. Replay history before connecting (optional)

Combine with readFsLogs for a "what happened in the last hour" pre-fill :

import { readFile } from 'node:fs/promises'

// At app boot (server side, e.g. instrumentation):
const last = new Date(Date.now() - 60 * 60 * 1000)
// Stream historic events to a file or directly to the same EventSource consumers
// via the in-process stream's drain.

For an end-to-end script that pre-fills then switches to live, see Replay-then-live recipe.

What it gives you

  • Live wide events in a browser tab while you pnpm dev
  • Filter on any field (path, level, custom field)
  • Survives page reloads (SSE auto-reconnects)
  • No SDK, no dependency — EventSource is built into every browser

Where to go next

  • Stream server — the full reference for the underlying mini-server
  • Consumer recipes — variants in vanilla HTML, React, plus a Node CLI
  • FS reader — for offline triage of historic logs without a running server