Node API
Use the high-level Node API when code needs to write, query, tail, and expand a local Leylines store directly.
Open A Store
import { openScopedLogs } from 'leylines'
const logs = openScopedLogs()
Leylines writes to the inferred local store at .leylines/logs.sqlite under the
current working directory.
Leylines is development-only by default. When NODE_ENV is production,
openScopedLogs() returns a disabled handle: it does not create a database,
writes return undefined, and queries return no entries. Check logs.enabled
when application behavior depends on whether an entry was persisted.
Enable production logging only when it is intentional:
const logs = openScopedLogs({ production: true })
Close the handle when the process no longer needs it:
logs.close()
Use an explicit path when a script or test needs an isolated store:
const logs = openScopedLogs({ path: '.leylines/test.sqlite' })
Write Scoped Entries
const logger = logs.logger({
scope: 'worker.queue',
properties: { queue: 'email' },
metadata: { runtime: 'node' },
})
logger.info('job started', {
properties: { jobId: 'job-1', attempt: 1 },
})
logger.error('job failed', {
properties: { jobId: 'job-1' },
error: new Error('smtp unavailable'),
})
Logger properties and metadata are inherited by every entry. Per-entry values merge over inherited values.
The second entry above has queue=email, jobId=job-1, and normalized error
details.
Use Child Loggers
Child loggers keep related workflow context close to the code that emits it:
const root = logs.logger('checkout')
const payment = root.child({
scope: 'payment',
properties: { provider: 'stripe' },
})
payment.warn('authorization retrying', {
properties: { attempt: 2 },
})
The entry scope is checkout.payment.
If the child scope is already fully qualified, Leylines keeps it stable:
root.child({ scope: 'checkout.payment' }).info('captured')
Query Entries
const page = logs.query({
scopePrefix: 'checkout',
minLevel: 'warn',
properties: [{ path: 'request.id', equals: 'req-123' }],
limit: 50,
})
for (const entry of page.entries) {
console.log(entry.timestamp, entry.level, entry.scope, entry.message)
}
Queries are chronological and deterministic. Use before and after cursors
for pagination.
Default queries return up to 50 entries. limit is clamped between 1 and 1000.
Tail New Entries
const controller = new AbortController()
for await (
const entry of logs.tail({ scopePrefix: 'worker' }, { signal: controller.signal })
) {
console.log(entry.message)
}
tail only yields entries appended after subscription.
Abort the signal when the watcher should stop:
controller.abort()
Expand Collapsed Values
When large values are collapsed, query results contain collapsed identifiers. Retrieve the full value with:
const value = logs.expand('<entry-id>:properties.payload')
expand returns undefined when the collapsed value is no longer present, for
example after retention has removed the owning entry.