Browser Logging Without the Bloat
Most frontend logging setups involve installing an SDK, configuring a bundler plugin, and wiring up a provider at the root of your app. By the time you’re done, you’ve added 40KB to your bundle and you’re still not sure if it’s actually capturing the errors that matter.
Loguro takes a different approach. One <script> tag in your <head> and you get:
- Console error/warning capture
- Uncaught JS errors and unhandled promise rejections
- Failed network requests (fetch + XHR)
- Core Web Vitals (LCP, CLS, FID/INP)
- Long task detection
- Rage click detection
- Page context on every log (URL, viewport, user agent)
No bundler. No npm install. No framework coupling.
Setup
Create a browser API key in your project — browser keys are safe to expose in client-side code because they’re locked to a list of allowed origins. The ingest service validates the Origin header on every request.
# Via command palette
--keys::create:browser:my-frontend When prompted, add your allowed origins: https://myapp.com, http://localhost:3000.
Then drop the snippet in your <head>:
<script
src="https://logu.ro/loguro.js"
data-api-key="YOUR_BROWSER_KEY"
></script> That’s it. Errors start flowing immediately.
What gets captured automatically
Console output
All console methods are intercepted by default and forwarded as structured logs. The original calls still fire — your browser DevTools are unaffected.
console.error('Payment failed', { userId: 42 }); // → level: error
console.warn('Slow query detected'); // → level: warning
console.info('User signed in'); // → level: info
console.log('Cart updated'); // → level: debug
console.debug('Cache miss', { key: 'user:42' }); // → level: debug Uncaught exceptions
JS errors that bubble to window.onerror are captured with filename, line, and column:
{
"level": "error",
"message": "Cannot read properties of undefined (reading 'id')",
"context": {
"source": "window.onerror",
"filename": "https://myapp.com/assets/checkout.js",
"line": 142,
"col": 18
}
} Unhandled promise rejections
fetch('/api/orders').then(res => res.json()); // forgot to .catch()
// → captured automatically as level: error Failed network requests
Every fetch and XMLHttpRequest that returns 4xx/5xx or fails at the network level is logged with URL, method, status, and duration:
{
"level": "error",
"message": "Network request failed: POST /api/checkout",
"context": {
"source": "fetch",
"url": "/api/checkout",
"method": "POST",
"status": 503,
"duration_ms": 4821
}
} Page context (on every log)
Every log automatically includes where the user was and what they were running:
{
"context": {
"url": "https://myapp.com/checkout",
"path": "/checkout",
"referrer": "https://myapp.com/cart",
"viewport": "1440x900",
"user_agent": "Mozilla/5.0 ..."
}
} Core Web Vitals
LCP, CLS, and FID/INP are captured via PerformanceObserver and logged as info events — queryable in the console like any other log:
context.metric:"LCP" context.value_ms:>2500
context.metric:"CLS" context.value:>0.1 Long tasks
Any JS task blocking the main thread for more than 50ms is logged as a warning. These are the frames that cause jank:
{
"level": "warning",
"message": "Long task detected",
"context": { "source": "performance", "duration_ms": 312, "start_time_ms": 1840 }
} Rage clicks
Three or more clicks on the same element within 500ms signals something isn’t working. Captured as a warning with the element and path:
{
"level": "warning",
"message": "Rage click detected",
"context": {
"source": "rage-clicks",
"element": "BUTTON",
"element_id": "submit-payment",
"clicks": 4,
"path": "/checkout"
}
} Manual logging
window.Loguro is available globally for logging application events:
// After a successful action
Loguro.info('Checkout completed', { orderId: 'ord_9xk2', total: 99.99 });
// Custom error with context
Loguro.error('Payment declined', { gateway: 'stripe', code: 'card_declined', userId: 42 });
// Track slow operations
const start = Date.now();
await processOrder();
Loguro.info('Order processed', { duration_ms: Date.now() - start }); Controlling what gets captured
Two attributes give you full control:
data-intercept — console capture
<!-- Default: captures everything -->
<script src="https://logu.ro/loguro.js"
data-api-key="..."
data-intercept="all">
</script>
<!-- Only errors and warnings, skip info/debug -->
<script src="https://logu.ro/loguro.js"
data-api-key="..."
data-intercept="error,warning">
</script>
<!-- No console capture, manual Loguro.* calls only -->
<script src="https://logu.ro/loguro.js"
data-api-key="..."
data-intercept="none">
</script> | Value | Captures |
|---|---|
error | console.error, uncaught errors, unhandled rejections |
warning | console.warn |
info | console.info |
debug | console.log, console.debug |
data-capture — automatic features
<!-- Disable everything, manual only -->
<script src="https://logu.ro/loguro.js"
data-api-key="..."
data-intercept="none"
data-capture="none">
</script>
<!-- Network monitoring and context only -->
<script src="https://logu.ro/loguro.js"
data-api-key="..."
data-capture="network,context">
</script>
<!-- Everything including breadcrumbs (click trail sent with errors) -->
<script src="https://logu.ro/loguro.js"
data-api-key="..."
data-capture="context,network,performance,vitals,rage-clicks,breadcrumbs">
</script> | Feature | Default | What it does |
|---|---|---|
context | ✅ | URL, path, referrer, viewport, user agent on every log |
network | ✅ | Failed fetch/XHR with status and duration |
performance | ✅ | Long tasks + page load timing |
vitals | ✅ | LCP, CLS, FID/INP |
rage-clicks | ✅ | 3+ rapid clicks on the same element |
breadcrumbs | ❌ | Click and network trail sent alongside errors |
Resilience
The snippet is built to never interfere with your app:
- Circuit breaker — stops sending after 5 consecutive failures, retries after 60s
- Rate limiter — caps at 1,000 logs/minute client-side (ingest hard limit is 10k/min per key)
- 5s timeout on every request via
AbortController - Fire-and-forget —
fetchis never awaited, nothing blocks the main thread
What’s next
- Read the Getting Started guide for server-side logging and batch ingestion
- See Query Syntax to filter by
context.metric,context.path,context.status, and more