Circular in-memory buffer +
consoleproxy. Two variants: main thread and worker.
Module logger | Source packages/front/fw/src/core/logger.js | Deps none | Worker-safe partial (main: no, worker: yes via logger.worker)
fw.log is the main-thread instance, created automatically in main.js if ENV.LOG = true. This component is not resolved via runtime.resolve() — it is instantiated directly by the framework bootstrap.
Resolve
// Not resolved via runtime — direct access from the fw object:
import fw from './fw/main.js';
const log = fw.log;
// Returns: { get, setLen, clear, __push, subscribe }
API
logger.main(dev) — main thread variant
Creates the main-thread logger and replaces window.console with a non-modifiable Proxy.
| Param | Type | Description |
|---|---|---|
dev |
boolean |
If true, also forwards to the native console |
Returns: { get, setLen, clear, __push, subscribe }
logger.worker(dev, port) — worker variant
Creates the worker-side logger. Proxies self.console, forwards entries via port.postMessage. Used internally by worker-helper.js. Worker logs appear in fw.log.get() on the main thread.
| Param | Type | Description |
|---|---|---|
dev |
boolean |
If true, also forwards to the native worker console |
port |
MessagePort |
Channel to the main thread |
Returns: { get, setLen, clear, subscribe }
Common methods
| Method | Signature | Returns |
|---|---|---|
get() |
() => LogEntry[] |
Snapshot of the current buffer |
setLen(n) |
(n: number) => void |
Max buffer size (1–65536, default 1024) |
clear() |
() => void |
Empties the buffer |
subscribe(fn) |
(fn: (LogEntry) => void) => () => void |
Registers a single listener; returns unsubscribe |
__push(entry) (main only) |
(LogEntry) => void |
Internal use — receives worker logs |
log.subscribe(fn)
Registers a listener called synchronously after each push.
- Single slot: if a listener is already active,
subscribe(fn2)throws'logger: subscriber already registered'. Callunsubscribe()first. - No replay: previous entries are not replayed. Combine with
get()to bootstrap a store. - Silenced errors: if the listener throws an exception, it is caught silently.
__pushalso notifies (main only): any addition via__push(receiving worker logs) triggers the listener.
Returns unsubscribe: () => void — idempotent.
Examples
Read and clear the buffer
import fw from './fw/main.js';
const { log } = fw;
const entries = log.get();
entries.forEach(e => console.log(e.ts, e.lvl, e.data));
log.setLen(4096); // max 65536
log.clear();
Persist each entry to IndexedDB
import fw from './fw/main.js';
const { log } = fw;
// Bootstrap: persist entries already present
log.get().forEach(entry => idb.put('logs', entry));
// Subscribe for future entries
const unsubscribe = log.subscribe(function (entry) {
idb.put('logs', entry).catch(function (_) {
// do not re-log here to avoid a loop
});
});
// Later
// unsubscribe();
Worker Usage
logger.worker is injected automatically into each worker by worker-helper.js when ENV.LOG = true. Worker logs appear in fw.log.get() on the main thread via log.__push.
const worker = fw.createWorker(
function({ libs }) {
// console.log/warn/error inside the worker are intercepted
// and forwarded to the main thread automatically
console.log('from the worker', libs.hex.fromBytes(new Uint8Array([255])));
},
{ dependencies: ['hex'] }
);
Typedef: LogEntry
{
ts: number, // timestamp Date.now()
lvl: string, // 'log' | 'info' | 'warn' | 'error' | ...
data: any[], // logged arguments (serialised on the worker side)
stack: string // stack trace (formatted as an array of lines)
}
Notes
window.consoleis replaced by a non-modifiable Proxy (configurable: false, writable: false).- Node / worker-shaped realm fallback:
logger.main(dev)resolves the console holder environment-aware — whentypeof window === 'undefined'(bare Node, or a worker-shaped global with nowindow), it operates onglobalThis.consoleand skips the Proxy install entirely. Node's console is non-configurable territory and is never seized; logging works as a plain passthrough and@awacloud/fw's entry point (main.js, which unconditionally callslogger.main(ENV.DEV)at import time) can be imported under bare Node without throwing. The browser path (windowdefined) is unchanged — same non-writable, non-configurable Proxy install as before. - Worker logs are serialised (Error, Map, Set, Function, circular → JSON-safe representation via
JSON.stringifywith a custom replacer). Date.nowin the logger is affected by the granularity imposed bysanity/base.js(rounded to 100 ms).logger.workeris stringified via.toString()before injection into the Worker — the function must remain self-contained (no closure over external variables).
See also
- worker-helper — automatic logger injection into workers
- sanity/base — impact on
Date.nowtemporal resolution