@bro-code/scribe-web
v2026.1.0
Published
Scribe for the browser - records what the app logs in the same ScribeRecord shape as @bro-code/scribe, mirrors it to the console, and ships batches of records to a service callback (e.g. an HTTP endpoint). Can capture uncaught errors and unhandled rejecti
Maintainers
Readme
@bro-code/scribe-web
Scribe for the browser. It has the call signatures of @bro-code/scribe and records in its ScribeRecord shape, but a browser has no stdout to capture, so it builds the text itself, writes it to the console and queues the records for a service callback that ships them in batches (to an HTTP endpoint, say). It can also record uncaught errors and unhandled promise rejections.
@bro-code/logger and @bro-code/scribe read files when they load, so they cannot be bundled for a page. This package imports only scribe's types (@bro-code/logger is a development dependency, for the test that checks the level table) and keeps its own copy of that table.
Installation
npm install @bro-code/scribe-webUsage
import scribe from '@bro-code/scribe-web';
scribe.setService(async (records, final) => {
await fetch('/api/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ records }),
keepalive: final,
});
});
scribe.captureGlobalErrors();
scribe.info('Loaded'); // console only
scribe.error('Could not load the save', 0, null, err); // console, and queued for the serviceCommonJS
const scribe = require('@bro-code/scribe-web').default;
scribe.info('Loaded');By default only error records are queued for the service; the console shows info and above. Records queued before setService is called wait for it.
API
new BroScribeWeb(options?)
The default export is an instance made with no options.
| Option | Default | |
|---|---|---|
| level | info | Minimum level written to the console. |
| serviceLevel | error | Minimum level queued for the service. silent queues nothing. |
| tabSize | 2 | Spaces per indent level. |
| flushIntervalMs | 5000 | How long records wait before a batch is sent. |
| maxBatch | 20 | Most records per service call; a full batch is sent at once. |
| maxPending | 200 | Most records held; the oldest are dropped past it. |
| maxOutputLength | 4000 | Longest output; longer is cut and ends with (truncated). |
| repeatWindowMs | 30000 | See Repeats. |
| errorMessages | keep | drop sends errors to the service without their messages. See Error messages. |
| onServiceError | | (err, records) => void, called when the service throws or rejects. |
Logging
log, info, warn, debug, error, json and code, each (message, tab = 0, options?, ...args) and chainable, as in @bro-code/logger. The text is:
- the label (
[INFO],[WARN],[DEBUG],[ERROR]; none forlog), then the message indentedtablevels - each follow-on argument on its own line, one level deeper than the one before; arrays one item per line
- an
Erroras its stack (orname: messagewhen it has none), other objects as JSON jsonrecords the compact JSON of the data andcodethe raw source, ignoring follow-on arguments
The options are ScribeWebLogOptions: BroLogOptions and a kind. Of BroLogOptions, label, showLabel, tabSize, bullets, bulletChar, numbered and silent are honoured; colours, borders, padding, timestamps and wrapping are terminal-only and ignored. A record has no trailing newline.
kind is a name of the app's own ('multiplayer', 'uncaught') put on the record queued for the service, so the service can sort or filter by it; the console output is the same with or without it. A record from a call with no kind has no kind field.
scribe.error('Host: the room could not be opened', 0, { kind: 'multiplayer' }, `${err.type}: ${err.message}`);Console: error goes to console.error, warn to console.warn, debug to console.debug, the rest to console.log.
Error messages
With errorMessages: 'drop', every Error in a record queued for the service, as the message, an argument or an array item, is written as its name and stack only. The message is left out, and so is the copy of it a V8 (Chrome, Edge) stack opens with, even a message over several lines. A Firefox or Safari stack has no such line and is kept as it is, after the name. An error with no stack is its name only. Values that are not errors are written as usual, so text the app builds itself is sent as it is.
The console still gets the whole error. The service's copy is rendered a second time only when the record is going to the service.
const scribe = new BroScribeWeb({ errorMessages: 'drop' });
scribe.error('Could not save', 0, null, new TypeError('Entry "Rent" is invalid'));
// console: [ERROR] Could not save\n TypeError: Entry "Rent" is invalid\n at save (app.js:3:4)
// service: [ERROR] Could not save\n TypeError\n at save (app.js:3:4)setService(fn | null)
fn is (records: ScribeWebRecord[], final: boolean) => void | Promise<void>, where ScribeWebRecord is ScribeRecord with an optional kind. final is true when the page is being hidden or unloaded; use a send that survives that (fetch with keepalive, navigator.sendBeacon). Passing null detaches the service and leaves the queue as it is.
While a service is set:
- the queue is sent on a
flushIntervalMstimer, and at once whenmaxBatchrecords wait - one call is in flight at a time, and a failed batch is reported through
onServiceErrorand not retried - on
pagehide, and whenvisibilitychangeturns the page hidden, everything left is sent withfinal: true, a batch per call and without waiting for a call in flight
The listeners exist only while a service is set. The package does nothing when imported.
flush(final = false)
Sends waiting records now. Resolves when the call has finished; it never rejects. During a call in flight it resolves with that call.
clear()
Empties the queue.
captureGlobalErrors(options?)
Listens on window for error and unhandledrejection and records each as an error: the message, filename:lineno:colno, and the error's stack (or the rejection reason's). They are not written to the console again, since the browser already prints them. Returns a function that removes both listeners. Without a window it does nothing.
options is ScribeWebCaptureOptions, { kind?: string }: the kind given to every record it makes.
Under errorMessages: 'drop', the browser's message is not used:
- an uncaught error is
Uncaught <name>, thenfilename:lineno:colnoand the error as its name and stack. A thrown value that is not an error gives its type in place of the name (Uncaught string), and nothing more; a cross-originScript error.isUncaught null. - an unhandled rejection is
Unhandled promise rejection, then the reason as its name and stack when it is an error, or its type only otherwise (string,object,undefined,null).
level, serviceLevel
Getters and setters for the two levels.
Types
The package exports BroScribeWebOptions, ScribeWebServiceFn,
ScribeWebRecord, ScribeWebLogOptions, ScribeWebCaptureOptions,
ScribeWebErrorMessages, ScribeRecord, ScribeMethod, LogLevel, and
BroLogOptions as types. The scribe record and logger types are
re-exported from @bro-code/scribe.
Repeats
An identical record (same method, kind and output) within repeatWindowMs of the last one queued is not queued again; the count held back goes out in the next batch as <output> (repeated N more times), with the same kind. An error thrown every frame costs one record and one count.
Dropped records
When the queue overflows maxPending, the next batch begins with a warn record, -- N record(s) dropped --, for each kind that lost records, carrying that kind (and no kind for records that had none).
License
MIT - see LICENSE.
Changelog
[2026.1.0] - 2026-11-10
Added
errorMessagesoption:dropwrites every error in a record queued for the service as its name and stack, without its message (nor the copy a V8 stack opens with), while the console keeps the whole error. Defaultkeep, as before.- Under
drop,captureGlobalErrorsrecords an uncaught error asUncaught <name>in place of the browser's message, and a thrown or rejected value that is not an error as its type only. kindon the log options (ScribeWebLogOptions), carried on the queued record (ScribeWebRecord); repeats fold only within a kind, and dropped records are counted per kind, a line for each.captureGlobalErrors({ kind })gives its records a kind (ScribeWebCaptureOptions).- Types
ScribeWebRecord,ScribeWebLogOptions,ScribeWebCaptureOptionsandScribeWebErrorMessages.
[2026.0.0] - 2026-10-27
Added
- Initial release.
BroScribeWebclass with@bro-code/scribe's call signatures (log,info,warn,debug,error,json,code) andScribeRecordas its record.- Console output filtered by
level; records queued for the service filtered byserviceLevel(defaulterror). setServicewith batched sending on an interval and on a full batch, one call in flight, afinalflush onpagehideandvisibilitychange, andonServiceError.- A bounded queue (
maxPending) that reports dropped records, and folding of identical records into(repeated N more times). captureGlobalErrorsfor uncaught errors and unhandled rejections.maxOutputLengthtruncation.
