Log SDK
@parallelsandbox/log is a small browser SDK. Add it to any page you run inside a box (or anywhere else) and its errors and logs land in a log project you own. Your agent then reads them with logs_search, logs_errors and logs_tail instead of asking you to paste console output.
Projects and keys
A log project is created with POST https://log.parallelsandbox.com/v1/projects, using your tenant API key:
curl -fsS -X POST https://log.parallelsandbox.com/v1/projects \
-H "Authorization: Bearer psbx_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "example-web", "origins": ["http://localhost:8080"] }'
The response carries the project id and the write key. A project has:
- a project id,
prj_..., used in every query; - a write key,
pw_..., which the browser sends with every batch. It can only write into this project, so it is safe to put in a page; - an origin allow list: batches from other origins are rejected;
- a rate limit per key.
Each project is its own log group. Logs are kept without expiry.
Install
From npm, for bundlers:
npm install @parallelsandbox/log
Or without a bundler, through an import map:
<script type="importmap">
{ "imports": { "@parallelsandbox/log": "https://cdn.jsdelivr.net/npm/@parallelsandbox/log/+esm" } }
</script>
Initialise
import { init } from '@parallelsandbox/log';
init({
project: 'prj_...', // project id
writeKey: 'pw_...', // write key
endpoint: 'https://log.parallelsandbox.com', // log server
release: 'a1b2c3d', // build id; must match the release used when uploading source maps
});
Call it once, as early as possible. After init:
- uncaught errors (
window.onerror), unhandled promise rejections andconsole.errorare captured with their stack; - entries are batched and sent every few seconds and on page hide;
- every entry carries
release, the page URL, the user agent and, when the page is served from a box, the box id, sologs_errorscan be filtered byboxId.
Manual calls
import { log, captureError, flush } from '@parallelsandbox/log';
log('info', 'checkout started', { cart: 3 });
log('warn', 'slow response', { ms: 2400 });
try {
riskyThing();
} catch (err) {
captureError(err, { step: 'riskyThing' });
}
await flush(); // before navigating away in a test
level is info, warn or error. extra is any JSON-serialisable object and is searchable with logs_search.
Source maps
Minified stacks are useless to an agent. Upload the source maps of every build under the same release you pass to init, and logs_errors returns stacks resolved to original files and lines.
curl -fsS -X POST https://log.parallelsandbox.com/v1/projects/prj_.../sourcemaps \
-H "Authorization: Bearer psbx_YOUR_API_KEY" \
-F "release=a1b2c3d" \
-F "path=assets/app-ALQGRFWG.js" \
-F "map=@dist/assets/app-ALQGRFWG.js.map;type=application/json"
One request per map. path is the script path as it appears in the stack (relative to the page origin). Uploading uses the tenant API key, not the write key. example-web-with-logs has a script that does this for a whole dist/ directory.
Query from the agent
logs_errors { "project": "prj_...", "since": "30m", "boxId": "<box id>" }
logs_search { "project": "prj_...", "since": "1h", "query": "checkout" }
logs_tail { "project": "prj_...", "boxId": "<box id>" }
Rows include ts, level, message, release, url, boxId, extra, and for errors stack and stackResolved.
Cost
Log ingestion and retention are metered like everything else, at cost times 1.5:
| Usage | Credits |
|---|---|
| Written | 1,140 per GB |
| Stored | 49.5 per GB per month |
A page that reports a few hundred errors a day costs well under one credit a month.