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