Skip to content

Live Logs (WebSocket) ​

In development, once the websocket subsystem is enabled, Frogger broadcasts every log it ingests over a WebSocket. The useFroggerWebSocket() composable lets you subscribe to that stream from your app — perfect for building a live console, a debug overlay, or a custom dashboard. The live-stream is opt-in (see enabling it below).

Dev only

The live-stream WebSocket handler is registered only in development. There is no production log-reading path — in production, logs are written to rotated JSON-lines files on disk (see Transports). useFroggerWebSocket() is also only auto-imported when both websocket and serverModule are enabled. serverModule is on by default, but the live-stream websocket is opt-in — enable it explicitly:

ts
// nuxt.config.ts
export default defineNuxtConfig({
    frogger: { websocket: true }        // or preset: 'full'
})

See Presets for the full preset, which turns on the websocket along with the other opt-in subsystems.

Quick start ​

useFroggerWebSocket() returns a fluent builder. Pick a channel and some filters, attach handlers, then call connect():

vue
<script setup lang="ts">
const socket = useFroggerWebSocket()
    .channel('main')                       
    .levels([0, 1, 2, 3])                  
    .onMessage((ws, message) => {          
        if (message.type === 'log') {      
            console.log('logs:', message.data) 
        }                                  
    })                                     
    .connect()                             
</script>

Incoming log frames have the shape { type: 'log', channel, timestamp, data, meta }, where data is an array of LoggerObject — a single frame may carry a batch of logs.

Filtering ​

Every filter is chainable. Combine as many as you like — they're ANDed together.

MethodFilters byExample
.channel(name)Channel name (default 'main').channel('payments')
.levels(levels)Log levels (numbers or names).levels([0, 1]) / .levels(['error', 'warn'])
.type(types)Log type(s).type(['error', 'fatal'])
.sources(sources)Source app name(s).sources(['my-api'])
.filters(obj)Several at once.filters({ level: [0, 1], source: ['api'] })
ts
const socket = useFroggerWebSocket()
    .channel('main')
    .levels(['error', 'warn'])     // only errors and warnings
    .sources(['checkout-service']) // from one app
    .onMessage((ws, message) => { /* ... */ })
    .connect()

Handlers ​

ts
useFroggerWebSocket()
    .onConnected((ws) => console.log('connected'))
    .onMessage((ws, message) => { /* every message */ })
    .onError((ws, event) => console.warn('socket error', event))
    .connect()

Reactive state & control ​

The returned instance exposes reactive state and imperative controls:

ts
const socket = useFroggerWebSocket().channel('main')
socket.connect()

socket.status      // Ref<WebSocketStatus> — 'connecting' | 'open' | 'closed' | 'timeout'
socket.lastMessage // Ref<LogWebSocketMessage | null>
socket.ws          // Ref<WebSocket | undefined>

socket.send({ type: 'ping' }) // send a message
socket.close()                // close the connection

Reconnecting with new filters

To change filters at runtime, close() the current socket and build a fresh useFroggerWebSocket() with the new filters, then connect() again. See the playground's live-logs page for a worked example with checkboxes that rebuild the subscription.

A minimal live console ​

vue
<script setup lang="ts">
import { ref } from 'vue'

// Each entry in `message.data` is a LoggerObject. A minimal shape for display:
interface LogEntry { type: string; msg: string; lvl: number; ctx?: Record<string, any> }

const logs = ref<LogEntry[]>([])

useFroggerWebSocket()
    .channel('main')
    .levels([0, 1, 2, 3, 4, 5])
    .onMessage((ws, message) => {
        if (message.type === 'log' && Array.isArray(message.data)) {
            logs.value.unshift(...message.data)
            logs.value = logs.value.slice(0, 200)
        }
    })
    .connect()
</script>

<template>
    <ul>
        <li v-for="(log, i) in logs" :key="i">
            <strong>{{ log.type }}</strong> {{ log.msg }}
        </li>
    </ul>
</template>

Configuration ​

The live-stream is off by default. Turn it on with websocket: true (sensible defaults) or the full preset. Passing an object both enables it and configures the route under the websocket module option:

ts
export default defineNuxtConfig({
    modules: ['nuxt-frogger'],
    frogger: {
        websocket: {
            route: '/api/_frogger/dev-ws', // default
            defaultChannel: 'main',
            // Gate who may open the socket (return false to reject):
            upgrade: (request) => true,
        },
    },
})

Bursts are coalesced, not dropped

Each channel is throttled to one frame per 100 ms. Logs arriving inside that window are buffered and replayed on the next boundary rather than discarded, so a burst arrives late rather than not at all. Past 500 buffered rows the oldest are dropped and counted in getStatus().droppedRows — a live tail is more useful showing the newest lines.

See the Configuration page for the full websocket option reference.