<bmx-log-stream>
A log and audit viewer for the browser: millions of lines of server logs, build output, audit trails or CSV exports, read from a file, a URL, a stream or live appends, and searched, filtered and coloured entirely on the page - no log server, nothing sent anywhere.
30 properties · 6 events · 14 methods · 35 parts
Example
field:value or
/regular expressions/, hide levels with the chips, select a line and press Enter for its
fields, and use Export to save what is on show. Email addresses are masked.
Show markup
<div class="row" role="group" aria-label="Example queries">
<bmx-button id="ex-ls-errors" variant="soft">level:>=warn</bmx-button>
<bmx-button id="ex-ls-5xx" variant="soft">status:>=500</bmx-button>
<bmx-button id="ex-ls-clear" variant="ghost">Clear</bmx-button>
</div>
<bmx-log-stream id="ex-ls" label="Order service log" redact="email" style="--bmx-log-stream-height: 24rem; margin-block-start: 1rem"></bmx-log-stream>
<div class="row" style="margin-block-start: 1rem">
<span class="note" id="ex-ls-out">
<strong>Everything runs in the browser.</strong> The log below is made in this page and opened as a file would be;
drop one of your own on the viewer to read it instead. Search with words, <code>field:value</code> or
<code>/regular expressions/</code>, hide levels with the chips, select a line and press <kbd>Enter</kbd> for its
fields, and use <strong>Export</strong> to save what is on show. Email addresses are masked.
</span>
</div>
<script type="module">
await customElements.whenDefined('bmx-log-stream');
const log = document.getElementById('ex-ls');
const out = document.getElementById('ex-ls-out');
// Two thousand JSON lines: an order service, with a stack trace now and then.
const paths = ['/api/orders', '/api/cart', '/api/checkout', '/api/session'];
const lines = [];
for (let i = 0; i < 2000; i += 1) {
const time = new Date(Date.UTC(2026, 8, 28, 9) + i * 1800).toISOString();
const status = i % 97 === 0 ? 503 : i % 23 === 0 ? 404 : 200;
const level = status >= 500 ? 'error' : status >= 400 ? 'warn' : i % 5 === 0 ? 'debug' : 'info';
lines.push(JSON.stringify({ time, level, msg: `GET ${paths[i % 4]}`, status, ms: (i * 37) % 400, email: `customer${i % 40}@example.com` }));
}
await log.load(new Blob([lines.join('\n')], { type: 'application/x-ndjson' }));
const queries = { 'ex-ls-errors': 'level:>=warn', 'ex-ls-5xx': 'status:>=500', 'ex-ls-clear': '' };
for (const [id, query] of Object.entries(queries)) {
document.getElementById(id).addEventListener('click', () => (log.query = query));
}
// Events: what is on show, and the line the reader chose.
log.addEventListener('bmxLogFilter', event => {
const { matches, total } = event.detail;
out.textContent = `bmxLogFilter - ${matches} of ${total} lines match`;
});
log.addEventListener('bmxLogSelect', event => {
out.textContent = `bmxLogSelect - line ${event.detail.line.number}: ${JSON.stringify(event.detail.fields)}`;
});
// Live lines are appended as they arrive, from a WebSocket for example:
// socket.onmessage = message => log.appendLog(message.data);
</script>
READING
The format is detected from the first lines - JSON lines, logfmt, syslog,
Apache and Nginx access logs, CSV and TSV, or plain text - or set with
format, or given as a regular expression with named groups in pattern.
Every line gets its level and time; terminal colour codes are drawn as
colours; a stack trace stays with the line it belongs to and folds away.
FINDING
The search box takes words, "phrases", /regular expressions/, level:error,
status:>=500, -exclusions, OR and brackets. Level chips show and hide
each level with its count, context shows the entries around each match,
and every match is highlighted.
The timeline counts the entries on show over time, errors and warnings in
their own colours; drag across it, or choose with the arrow keys and Enter,
to narrow to that stretch of time. The fields panel lists each field's
commonest values: choose one to filter by it. columns lays structured
lines out as a table.
LIVE
connect() follows a WebSocket or a server-sent event stream, and opens it
again when it drops. Bookmarks mark lines to come back to (M, then ] and [
to step through them), and getState() packs the whole view into a string
for a link that reopens it.
SAFE
Log text is always drawn as text, never as markup, so a line carrying
<script> or <img onerror> shows those characters and runs nothing.
redact masks emails, IP addresses, tokens, keys and card numbers on screen,
in search and in exports.
LARGE
Lines are kept compactly and only the rows in view are drawn; loading runs
in slices, so the page never freezes. A log of fifty thousand lines or more
is searched on a background thread (a worker started from a Blob, so a
Content Security Policy needs worker-src blob:), and typing in the search
box stays smooth; without a worker, or past 128 million characters, the
search runs here in slices instead and finds the same lines.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
bookmarks |
property only | number[] |
[] |
Bookmarked line numbers. |
bookmarksOnly |
bookmarks-only |
boolean |
false |
Show only the bookmarked entries. |
caseSensitive |
case-sensitive |
boolean |
false |
Match the search's letter case. |
columns |
columns |
string |
'' |
Lay the lines out as columns: auto, or field names separated by spaces. @time, @level and @message are the line's own. Empty shows whole lines. |
context |
context |
number |
0 |
Entries to show either side of each match. |
dropFiles |
drop-files |
boolean |
true |
Open a file dropped on the viewer. |
fieldsPanel |
fields-panel |
boolean |
false |
Show the fields panel: each field's commonest values, to filter by. |
fileName |
file-name |
string |
— | The name exported files are saved under. |
follow |
follow |
boolean |
false |
Keep the newest line in view as lines arrive. Scrolling up turns it off. |
format |
format |
BmxLogFormat |
'auto' |
The format of the lines. auto reads it from the first lines. |
label |
label |
string |
— | The accessible name. Default: "Log". |
levels |
levels |
string | readonly string[] |
'' |
Levels to show, separated by spaces: warn error fatal; none for lines with no level. Empty shows all. |
lineNumbers |
line-numbers |
boolean |
true |
Show line numbers. |
liveSrc |
live-src |
string |
— | A WebSocket (ws:, wss:) or server-sent events URL to follow live. |
liveType |
live-type |
'auto' | 'websocket' | 'sse' |
'auto' |
What liveSrc is. auto goes by the URL's scheme. |
maxLines |
max-lines |
number |
0 |
Keep at most this many lines, dropping the oldest: for live logs. 0 keeps all. |
pattern |
pattern |
string |
— | For format="pattern": a regular expression with named groups; level, time and message are understood. |
query |
query |
string |
'' |
The search: words, "phrases", /regex/, field:value, -exclusions, OR. |
redact |
redact |
boolean | string |
false |
Mask personal data and secrets: true for every rule, or rule names: email ip card jwt bearer aws-key secret. |
redactRules |
property only | readonly BmxLogRedactRule[] |
— | Masking rules of your own: { name, pattern, flags?, mask? }. |
regex |
regex |
boolean |
false |
Treat the search as one regular expression. |
requestInit |
property only | RequestInit |
— | Options for fetching src: headers, credentials. |
src |
src |
string |
— | A URL to read the log from, streamed as it downloads. A .gz file is decompressed. |
timeFrom |
time-from |
string | number |
— | Show entries from this time on: an ISO date or milliseconds since 1970. Set by the timeline. |
timeTo |
time-to |
string | number |
— | Show entries before this time. Set by the timeline. |
timeZone |
time-zone |
'utc' | 'local' |
'utc' |
Show times in UTC or in the reader's own time zone. |
timeline |
timeline |
boolean |
true |
Show the timeline over the lines, when they carry times. |
toolbar |
toolbar |
boolean |
true |
Show the toolbar. |
wholeWord |
whole-word |
boolean |
false |
Match the search's words only as whole words. |
wrap |
wrap |
boolean |
false |
Wrap long lines instead of scrolling sideways. |
Events
| Event | Detail | Description |
|---|---|---|
bmxLogBookmark |
BmxLogBookmarkDetail |
A bookmark was added or removed. |
bmxLogConnection |
BmxLogConnectionDetail |
A live connection opened, dropped, is being tried again, or closed. |
bmxLogFilter |
BmxLogFilterDetail |
The lines on show changed. |
bmxLogLoad |
BmxLogLoadDetail |
Text arrived, or the load finished or failed. |
bmxLogSelect |
BmxLogSelectDetail |
A line was chosen: clicked, or reached by the keyboard. |
bmxLogStateChange |
BmxLogStateChangeDetail |
The search, levels, time range, bookmarks, columns or chosen line changed. |
Methods
| Method | Signature | Description |
|---|---|---|
appendLog |
appendLog(text: string | readonly string[]) => Promise<number> |
Adds text or lines, as from a live source. Text without a final line break waits for the rest. |
clear |
clear() => Promise<void> |
Forgets every line. |
connect |
connect(url: string, options?: BmxLogConnectOptions) => Promise<void> |
Follows a live source: a WebSocket (ws:, wss:) or a server-sent events URL. Each message is one or more lines. The connection is opened again when it drops; bmxLogConnection reports each change. |
disconnect |
disconnect() => Promise<void> |
Closes the live connection. The lines already received stay. |
download |
download(format?: BmxLogExportFormat, fileName?: string) => Promise<void> |
Saves the lines on show, under fileName or log. |
exportAs |
exportAs(format?: BmxLogExportFormat) => Promise<Blob> |
The lines on show as a file: text, csv or json, masked as on screen. |
findNext |
findNext() => Promise<number | null> |
Moves to the next match. Resolves to its line number, or null when there is none. |
findPrevious |
findPrevious() => Promise<number | null> |
Moves to the previous match. |
getLine |
getLine(lineNumber: number) => Promise<(BmxLogLine & { fields: Record<string, unknown> | null; }) | null> |
A line by its number, or null when it is not held. |
getState |
getState() => Promise<string> |
What the reader is looking at, as a short URL-safe string: the search, levels, context, time range, bookmarks, columns and the chosen line. Keep it in a link and give it to setState() to open the same view. |
getStats |
getStats() => Promise<BmxLogStats> |
Counts: lines held and shown, matches, characters, and entries per level. |
load |
load(source: BmxLogSource) => Promise<void> |
Reads a log, replacing what is shown: a File or Blob (a dropped or chosen file), a URL, a Response, or a stream. Gzip is recognised and decompressed. |
scrollToLine |
scrollToLine(lineNumber: number) => Promise<boolean> |
Scrolls to a line by its number, selects it, and says whether it is on show. |
setState |
setState(state: string) => Promise<boolean> |
Opens a view saved by getState(). Call it once the log is loaded, so the chosen line can be found. False when the string is not a saved view. |
Slots
| Slot | Description |
|---|---|
empty |
what to show when there are no lines. |
toolbar-end |
extra controls at the end of the toolbar. |
CSS shadow parts
| Part | Description |
|---|---|
body |
|
button |
|
cell |
|
columns-head |
|
context |
|
details |
|
drop-zone |
|
empty |
|
error |
|
export-list |
|
export-option |
|
field |
|
field-value |
|
fields |
|
fields-panel |
|
fold |
|
latest |
|
level |
|
level-chip |
|
level-chip-bookmarks |
|
level-chip-none |
|
line-number |
|
match |
|
match-count |
|
option |
|
query-error |
|
raw |
|
search |
|
search-input |
|
status |
|
text |
|
timeline |
|
timeline-chart |
|
toolbar |
|
viewport |
CSS custom properties
| Property | Description |
|---|---|
--bmx-log-stream-active |
The chosen line. |
--bmx-log-stream-ansi-0 |
Terminal black. -1 to -7 are red, green, yellow, blue, magenta, cyan and white; -8 to -15 their bright forms. |
--bmx-log-stream-ansi-1 |
Terminal red. |
--bmx-log-stream-ansi-10 |
Terminal bright green. |
--bmx-log-stream-ansi-11 |
Terminal bright yellow. |
--bmx-log-stream-ansi-12 |
Terminal bright blue. |
--bmx-log-stream-ansi-13 |
Terminal bright magenta. |
--bmx-log-stream-ansi-14 |
Terminal bright cyan. |
--bmx-log-stream-ansi-15 |
Terminal bright white. @part toolbar - the controls above the lines. @part button - a toolbar button. @part search - the search group. @part search-input - the search field. @part option - the match case, whole word and regular expression toggles. @part match-count - "3 of 12". @part query-error - what is wrong with the search. @part context - the context control. @part export-list - the list of file types under Export. @part export-option - one of them. @part level-chip - a level's toggle. Also level-chip-error and so on, and level-chip-bookmarks. @part timeline - the timeline over the lines. @part timeline-chart - its bars, which choose a stretch of time. @part fields-panel - the fields panel. @part field - one field in it. @part field-value - one of its values, which filters by it. @part columns-head - the column titles, in the column layout. @part cell - one column of a line. @part body - the lines and what lies over them. @part viewport - the scrolling list of lines. @part line-number - a line's number. @part level - a line's level mark. @part fold - the button that folds an entry's following lines. @part text - a line's text. @part match - a search match. @part latest - the "new lines" button while not following. @part drop-zone - shown while a file is dragged over. @part details - the chosen line's fields and text. @part fields - its fields. @part raw - its text. @part status - the counts under the lines. @part empty - what is shown when there are no lines. @part error - the message when a log could not be read. |
--bmx-log-stream-ansi-2 |
Terminal green. |
--bmx-log-stream-ansi-3 |
Terminal yellow. |
--bmx-log-stream-ansi-4 |
Terminal blue. |
--bmx-log-stream-ansi-5 |
Terminal magenta. |
--bmx-log-stream-ansi-6 |
Terminal cyan. |
--bmx-log-stream-ansi-7 |
Terminal white. |
--bmx-log-stream-ansi-8 |
Terminal bright black. |
--bmx-log-stream-ansi-9 |
Terminal bright red. |
--bmx-log-stream-background |
Behind the lines. |
--bmx-log-stream-debug |
The debug level's colour. |
--bmx-log-stream-error |
The error level's colour. |
--bmx-log-stream-fatal |
The fatal level's colour. |
--bmx-log-stream-font |
The lines' font. A monospace font. |
--bmx-log-stream-font-size |
The lines' size. |
--bmx-log-stream-height |
How tall the viewer is. |
--bmx-log-stream-info |
The info level's colour. |
--bmx-log-stream-match |
A search match. |
--bmx-log-stream-text |
The lines' text. |
--bmx-log-stream-trace |
The trace level's colour. Also -debug, -info, -warn, -error, -fatal. |
--bmx-log-stream-warn |
The warning level's colour. |