Vite And Browser
Use the Vite integration when browser-side development events should be captured in the same local store as Node and CLI entries.
Configure The Plugin
// vite.config.ts
import { defineConfig } from 'vite'
import { leylines } from 'leylines/vite'
export default defineConfig({
plugins: [
leylines({
endpoint: '/__scoped_logs',
scope: 'browser',
captureConsole: ['warn', 'error'],
captureErrors: true,
captureRejections: true,
stripProduction: true,
}),
],
})
In serve mode, the plugin:
- opens the inferred local store
- registers a browser log ingestion endpoint
- injects
logger.connect(...)into HTML - optionally captures console calls, uncaught errors, and unhandled rejections
In recognized test environments, the plugin does none of this by default. The
runtime guard also disables direct leylines/browser connections and Tauri log
forwarding, so non-Vite browser tests do not send requests or install capture
hooks. This runtime behavior covers more test setups than source stripping and
leaves production stripping as a separate build-size choice.
Instrumentation tests can exercise the complete plugin and browser path with an explicit opt-in:
leylines({
test: true,
path: '.leylines/instrumentation-test.sqlite',
})
Production build capture is disabled by default. Use production: true only
when production browser capture is intentional.
leylines({
production: true,
captureConsole: ['error'],
})
The default endpoint is /__scoped_logs, and the default browser scope is
browser.
Use stripProduction: true when application logger calls should be removed from
production modules:
leylines({
stripProduction: true,
})
The source rewriter removes standalone browser logger calls after static
leylines/browser imports:
import { logger } from 'leylines/browser'
logger.info('router', 'route loaded')
logger.warn('checkout', 'submit retrying', { attempt: 2 })
Remaining logger references are replaced with a local no-op logger so unusual usage still builds without sending entries.
Vite Logger Capture
Capture Vite's own dev-server warnings and errors when agents need structured diagnostics instead of terminal output:
leylines({
viteLogger: {
scope: 'dev.vite',
levels: ['warn', 'error'],
},
})
Captured entries keep Vite mode, command, logger method, and Rollup/Vite error
context such as plugin name, hook, module id, source location, frame, and stack
when Vite provides them. Terminal output still goes through Vite's normal
logger. Captured Vite warn and error calls keep those Leylines levels.
When info is included in levels, Vite info calls are stored as Leylines
debug entries, so default queries stay focused on diagnostics:
ley --scope-prefix dev.vite --min-level warn --json
Request the captured Vite info entries explicitly:
ley --scope-prefix dev.vite --include-debug --json
For the default dev.vite scope, captureViteLogger is a shorthand:
leylines({
captureViteLogger: ['warn', 'error'],
})
Set the shorthand to true to capture Vite info, warn, and error. The
captured info entries still use the Leylines debug level:
leylines({
captureViteLogger: true,
})
Write Browser Entries
import { logger } from 'leylines/browser'
logger.info('router', 'route loaded', { route: '/settings' })
logger.warn('checkout', 'submit retrying', {
attempt: 2,
})
The exported logger is a side-effect-free singleton. Importing it does not
patch console methods, add event listeners, or send network requests. The Vite
plugin connects it during page load.
Before connection, logger writes are ignored. After the Vite plugin injects
logger.connect(...), browser entries are posted to the configured endpoint.
The first argument is the entry scope, and the second argument is the event
message. Keep scopes stable around the product area or component, such as
router or checkout.payment; put the event action in the message.
Tauri Log Forwarding
Install Tauri's log plugin in apps that should forward native-side records into the Vite plugin's local ingestion endpoint:
pnpm add @tauri-apps/plugin-log
Then attach forwarding from app startup code:
import { attachTauriLogger } from 'leylines/tauri'
const detachTauriLogs = attachTauriLogger({
scope: 'tauri',
metadata: { windowLabel: 'main' },
})
Use test: true only in instrumentation tests that need to exercise Tauri log
forwarding:
const detachTestLogs = attachTauriLogger({ test: true })
Application tests leave it omitted, so no native listener is attached.
The Vite plugin connects leylines/browser before application modules run, so
Tauri records sent through attachTauriLogger are posted to the same
/__scoped_logs endpoint as browser entries. The default scope is tauri, and
each entry includes properties.source: 'tauri.log'.
Call the returned function when forwarding should stop:
detachTauriLogs()
Manual Connection
For non-Vite browser runtimes, connect the singleton explicitly:
import { logger } from 'leylines/browser'
logger.connect({
endpoint: '/__scoped_logs',
scope: 'browser',
captureConsole: false,
captureErrors: true,
captureRejections: true,
})
Tests that intentionally verify the direct browser transport opt in on the connection:
logger.connect({ endpoint: '/__scoped_logs', test: true })
Repeated connect calls reconfigure the singleton without stacking duplicate
console or error capture hooks.
Console Capture
captureConsole accepts true or selected levels:
leylines({
captureConsole: ['warn', 'error'],
})
Captured console entries are still written to the original console method, then
sent to Leylines with properties.console: true.
Error Capture
captureErrors records uncaught browser errors. captureRejections records
unhandled promise rejections. Both default to true for the injected browser
logger.
leylines({
captureErrors: true,
captureRejections: true,
})
Inspect Browser Logs
ley --scope router
ley --scope-prefix browser
Use the Vite logger scope separately when you enabled Vite logger capture:
ley --scope-prefix dev.vite --min-level warn --json