Package: i45-jslogger
Current Version: v2.0.0
Next Major Version: v3.0.0 (Planned)
Document Date: December 23, 2025
i45-jslogger is a browser-based logging library designed for development debugging and async timing analysis. It provides persistent localStorage logging with CustomEvent dispatching, enabling developers to debug complex async workflows (like React useEffect chains) where traditional console logging is insufficient.
Core Value Proposition:
- Persistent logs survive page refreshes and crashes
- Guaranteed execution order via synchronous localStorage writes
- Multi-destination routing via custom clients (APIs, analytics, monitoring)
- Event-driven architecture for building reactive debug UIs
Primary Use Cases:
- React/Vue/Angular useEffect/lifecycle debugging
- Async operation timing analysis
- Race condition investigation
- Intermittent bug reproduction
- Multi-tab interaction debugging
1. Solid Foundation
- ✅ Clean API with method chaining
- ✅ CustomEvent dispatching with namespace (
i45-logger:) - ✅ localStorage persistence for crash recovery
- ✅ Custom client system for multi-destination logging
- ✅ Environment detection (browser/Node.js)
- ✅ Comprehensive test coverage
- ✅ Storage limits via
maxEventsproperty
2. Developer Experience
- ✅ Zero configuration required
- ✅ TypeScript definitions included
- ✅ Clear documentation with examples
- ✅ Semantic versioning with migration guides
3. Reliability
- ✅ Client error isolation (one client failure doesn't break others)
- ✅ Proper error handling and validation
- ✅ Breaking changes clearly documented
- ✅ Syntax errors in documentation - FIXED in v2.0.0
- ✅ Build process - FIXED in v2.0.0 (proper Rollup bundling)
- ✅ TypeScript definitions - FIXED in v2.0.0 (complete .d.ts)
- ✅ Environment detection - FIXED in v2.0.0
- ✅ addClient() return value - FIXED in v2.0.0 (returns boolean)
- ✅ Test coverage - COMPLETED in v2.0.0
- ✅ Storage limits - COMPLETED in v2.0.0 (
maxEvents) - ✅ Error handling - COMPLETED in v2.0.0
- ✅ Event namespacing - COMPLETED in v2.0.0 (
i45-logger:prefix) - ⏸️ Separate concerns - DEFERRED to v3.0.0 (architectural refactoring)
- ⏭️ Full Node.js compatibility - Future
- ⏭️ Plugin architecture - v3.0.0 target
- ⏭️ Async client support - v3.0.0 target
- ⏭️ Live demo and playground - Future
- ⏭️ Performance optimizations - v3.0.0+
Motivation: Enable grouping logs by user session and analyzing async operation timing.
Features:
// Session tracking across page refreshes
const logger = new Logger({
enableSessionTracking: true, // Default: true
sessionIdStorage: 'sessionStorage' // or 'localStorage'
});
// Each log entry includes:
{
sessionId: "abc-123-def", // Persists across page refreshes
pageLoadId: "xyz-789-uvw", // Unique per page load
timestamp: 1703354001234, // High-resolution timestamp
level: "info",
message: "Component mounted",
args: [...]
}
// Query helpers
logger.getEventsBySession(); // All logs in current session
logger.getEventsByPageLoad(); // All logs in current page load
logger.getSessionId(); // Current session ID
logger.getPageLoadId(); // Current page load IDBenefits:
- ✅ Group logs across page refreshes for crash analysis
- ✅ Distinguish between sessions in multi-tab scenarios
- ✅ Analyze timing relationships in async workflows
- ✅ Trace user journeys through multiple page loads
Implementation:
- Use
crypto.randomUUID()for ID generation - Store session ID in sessionStorage (default) or localStorage (configurable)
- Generate new page load ID on each instantiation
- Add query helpers for filtering by session/page load
Motivation: Provide more granular logging for different use cases.
Proposed Levels:
logger.trace("Function entry: fetchUser()"); // Verbose debugging
logger.debug("Variable state:", { userId: 123 }); // Development details
logger.log("User action recorded"); // Standard logging (existing)
logger.info("Payment processed"); // Informational (existing)
logger.warn("Rate limit approaching"); // Warning (existing)
logger.error("Database connection failed"); // Error (existing)
logger.critical("System shutdown imminent"); // Critical/FatalLevel Hierarchy:
TRACE < DEBUG < LOG < INFO < WARN < ERROR < CRITICAL
Configuration:
const logger = new Logger({
minLevel: "info", // Only log INFO and above (suppress DEBUG/TRACE)
});
// Or runtime control
logger.setMinLevel("debug"); // Enable debug loggingBenefits:
- ✅ Filter noise in production (only WARN+)
- ✅ Enable verbose debugging in development (TRACE+)
- ✅ Distinguish critical vs standard errors
- ✅ Better log aggregation and filtering
Implementation:
- Add
trace(),debug(),critical()methods - Add level filtering configuration
- Update CustomEvent names (
i45-logger:TRACE, etc.) - Update storage format to include level enum
Motivation: Organize logs by feature/module for better analysis.
Proposed API:
// Create categorized loggers
const authLogger = logger.category("auth");
const paymentLogger = logger.category("payments");
const cartLogger = logger.category("cart");
// Logs include category
authLogger.error("Login failed");
// → [auth] Login failed
paymentLogger.info("Payment processing");
// → [payments] Payment processing
// Query by category
logger.getEventsByCategory("auth");
// Hierarchical categories
const stripeLogger = paymentLogger.category("stripe");
stripeLogger.warn("Retry attempt 3");
// → [payments:stripe] Retry attempt 3Configuration:
const logger = new Logger({
enableCategories: true,
categoryFormat: "[{category}]", // Customizable prefix
categorySeparator: ":", // For hierarchies
});
// Filter by category
logger.filterCategories = ["auth", "payments"]; // Only these categoriesBenefits:
- ✅ Organize logs in large applications
- ✅ Filter by feature during debugging
- ✅ Track specific workflows (auth, checkout, etc.)
- ✅ Better log analysis and reporting
Implementation:
- Add
category(name)method returning new Logger instance - Share event storage across category instances
- Add category field to log entries
- Add category-based filtering
Motivation: Use localStorage for ordering guarantees, IndexedDB for capacity.
Hybrid Approach:
const logger = new Logger({
maxEvents: 200, // Keep 200 recent in localStorage
enableArchival: true, // Archive old logs to IndexedDB
archivalThreshold: 150, // Archive when >150 logs
archivalStrategy: "auto", // or 'manual', 'time-based'
});
// Manual archival
await logger.archiveToIndexedDB();
// Query archived logs
const archived = await logger.getArchivedLogs({
sessionId: "abc-123",
startTime: yesterday,
endTime: today,
level: "error",
});Architecture:
User Action
↓
logger.info("Action")
↓
localStorage (immediate, ordered) ← For recent debugging
↓ (async archival when >150 logs)
IndexedDB (large capacity) ← For long-term analysis
Benefits:
- ✅ localStorage: Guaranteed order + crash recovery
- ✅ IndexedDB: Large capacity (50MB+) for long sessions
- ✅ Automatic cleanup of localStorage (prevents quota errors)
- ✅ Query capabilities for archived logs
Implementation:
- Add IndexedDB wrapper class
- Implement archival trigger logic
- Add query API for archived logs
- Provide custom client pattern as alternative
Documentation Note: Provide example implementation of IndexedDB client for users who want manual control:
// Optional: DIY IndexedDB client using i45 DataContext
import { Logger } from "i45-jslogger";
import { DataContext, StorageLocations } from "i45";
class IndexedDBLoggerClient {
constructor() {
this.context = new DataContext({
storageKey: "ApplicationLogs",
storageLocation: StorageLocations.IndexedDB,
});
}
async #storeLog(level, message, ...args) {
const logs = await this.context.retrieve();
logs.push({
timestamp: Date.now(),
level,
message,
args,
});
// Keep last 10,000 logs
if (logs.length > 10000) logs.shift();
await this.context.store(logs);
}
log(msg, ...args) {
this.#storeLog("log", msg, ...args);
}
info(msg, ...args) {
this.#storeLog("info", msg, ...args);
}
warn(msg, ...args) {
this.#storeLog("warn", msg, ...args);
}
error(msg, ...args) {
this.#storeLog("error", msg, ...args);
}
}Motivation: See logs from other tabs in real-time debug dashboards.
Proposed Implementation:
const logger = new Logger({
enableCrossTabSync: true, // Default: false (opt-in)
});
// Listen for other tabs' logs
window.addEventListener("i45-logger:cross-tab-update", (e) => {
console.log("Another tab logged:", e.detail.newEvents);
updateDebugUI(e.detail.newEvents);
});Implementation:
class Logger {
#initCrossTabSync() {
// Use browser's built-in storage event
window.addEventListener("storage", (e) => {
if (e.key !== "i45-logger-events") return;
window.dispatchEvent(
new CustomEvent("i45-logger:cross-tab-update", {
detail: {
newEvents: JSON.parse(e.newValue || "[]"),
oldEvents: JSON.parse(e.oldValue || "[]"),
},
})
);
});
}
}Benefits:
- ✅ Minimal code (uses native
storageevent) - ✅ Opt-in (no overhead unless enabled)
- ✅ Useful for OAuth popups, multi-tab testing
- ✅ Build debug dashboards that aggregate tab logs
Use Cases:
- Debug admin panel monitoring user tabs
- OAuth/SSO popup window debugging
- WebSocket connection coordination
- Multi-tab integration testing
Motivation: Make async debugging easier with framework-specific utilities.
React Hook Wrapper:
import { useTrackedEffect } from "i45-jslogger/react";
// Automatic timing and error tracking
useTrackedEffect(
"FetchUser",
() => {
fetchUser().then(setUser);
},
[]
);
// Logs:
// → [FetchUser] START { deps: [], pageLoadId: "..." }
// → [FetchUser] MOUNTED { duration: "234.56ms" }
// → [FetchUser] CLEANUP (on unmount)Operation Tracking:
// Track async operation lifecycle
const opId = logger.startOperation("FetchOrders");
try {
const orders = await fetchOrders();
logger.endOperation(opId, { count: orders.length });
} catch (error) {
logger.failOperation(opId, error);
}
// Logs:
// → ▶ START FetchOrders { opId: "abc-123" }
// → ✓ COMPLETE FetchOrders (456.78ms) { opId: "abc-123", result: {...} }
// or
// → ✗ FAILED FetchOrders (123.45ms) { opId: "abc-123", error: "..." }Benefits:
- ✅ Track React useEffect execution order
- ✅ Validate component dependency chains (A → B → C)
- ✅ Measure operation durations automatically
- ✅ Detect StrictMode double-execution patterns
Motivation: Help developers analyze and share debugging sessions.
Export Utilities:
// Export session logs
const sessionData = logger.exportSession({
format: "json", // or 'csv', 'markdown'
sessionId: "abc-123",
includeArchived: true,
});
downloadFile("debug-session.json", sessionData);
// Import logs for analysis
logger.importSession(sessionData);Analysis Helpers:
// Timing analysis
const analysis = logger.analyzeTimings({
sessionId: "abc-123",
groupBy: "category",
});
console.log(analysis);
// {
// "auth": { avgDuration: 234ms, operations: 5 },
// "payments": { avgDuration: 567ms, operations: 3 }
// }
// Dependency validation
const valid = logger.validateDependencies([
{ name: "FetchUser", dependsOn: [] },
{ name: "FetchOrders", dependsOn: ["FetchUser"] },
{ name: "ProcessPayment", dependsOn: ["FetchOrders"] },
]);
// Returns: { valid: true, violations: [] }Benefits:
- ✅ Share debug sessions with team
- ✅ Attach logs to bug reports
- ✅ Automated timing analysis
- ✅ Dependency chain validation
Current State (v2.0.0):
// Monolithic Logger class
class Logger {
#events = [];
#clients = new Set();
#loggingEnabled = true;
// Everything in one class
}Proposed (v3.0.0):
// Separated concerns
class Logger {
#storage; // EventStorage
#dispatcher; // EventDispatcher
#clientManager; // ClientManager
#console; // ConsoleManager
constructor(options = {}) {
this.#storage = new EventStorage(options.storage);
this.#dispatcher = new EventDispatcher(options.dispatcher);
this.#clientManager = new ClientManager(options.clients);
this.#console = new ConsoleManager(options.console);
}
}Module Structure:
/src
/logger.js # Main Logger class
/storage/
EventStorage.js # localStorage + in-memory
IndexedDBStorage.js # IndexedDB archival
/events/
EventDispatcher.js # CustomEvent handling
/clients/
ClientManager.js # External client management
/console/
ConsoleManager.js # Console output
/utils/
SessionManager.js # Session/PageLoad ID tracking
CategoryManager.js # Category/namespace handling
TimingAnalyzer.js # Performance tracking
Benefits:
- ✅ Better testability (isolated unit tests)
- ✅ Tree-shaking (unused modules excluded)
- ✅ Extensibility (swap implementations)
- ✅ Maintainability (clear responsibilities)
Philosophy: v2.0.0 just introduced breaking changes. Give the API time to mature (12-18 months) before v3.0.0, and provide comprehensive migration support when the time comes.
Timeline Overview:
v2.0.0 (Dec 2025) - Current release, stable
v2.1.0-2.4.0 (Q1-Q3 2026) - Additive features only, no breaking changes
v2.5.0 (Q3 2026) - Deprecation warnings begin
v2.8.0 (Q4 2026) - Dual API support, migration tools
v3.0.0 (Q1-Q2 2027) - Major refactoring (12-18 months after v2.0.0)
v2.x LTS (Q1-Q3 2027) - Security/critical fixes for 6 months
Introduce new architecture alongside old:
// v2.5.0 - new modular approach (opt-in)
class Logger {
#deprecationWarningShown = false;
constructor(options = {}) {
// New modular architecture (preferred)
if (options.modules) {
this.#storage = options.modules.storage || new EventStorage();
this.#dispatcher = options.modules.dispatcher || new EventDispatcher();
this.#clientManager = options.modules.clients || new ClientManager();
} else {
// Old monolithic approach (deprecated but works)
this.#events = [];
this.#clients = new Set();
// Emit one-time deprecation warning
if (!this.#deprecationWarningShown) {
console.warn(
"[i45-jslogger] Deprecation Notice: Monolithic Logger architecture " +
"will be removed in v3.0.0 (planned Q1 2027). " +
"Migrate to modular architecture for better performance and tree-shaking. " +
"Migration guide: https://github.com/xnodeoncode/i45-jslogger/blob/master/docs/v3-migration.md"
);
this.#deprecationWarningShown = true;
sessionStorage.setItem("i45-logger-deprecation-shown", "true");
}
}
}
}What users see:
- Existing code works unchanged
- Console warning (once per session) with link to migration guide
- No functionality broken
- 6+ months to plan migration
Provide side-by-side compatibility:
// v2.8.0 - both APIs work simultaneously
import { Logger } from "i45-jslogger"; // Legacy (still works)
import { ModularLogger } from "i45-jslogger/v3"; // New API (opt-in)
// Old code continues working
const legacyLogger = new Logger();
legacyLogger.info("Still works");
// New code ready for v3.0.0
const newLogger = new ModularLogger({
storage: new EventStorage({ maxEvents: 200 }),
dispatcher: new EventDispatcher({ namespace: "i45-logger:" }),
clients: new ClientManager(),
});
newLogger.info("Future-ready");
// Adapter for gradual migration
import { createV3Adapter } from "i45-jslogger/adapters";
const adaptedLogger = createV3Adapter(legacyLogger);
// Acts like v3 API but uses v2 internallyBenefits:
- Test v3 API without committing
- Gradual migration (file-by-file)
- Rollback if issues arise
- Both versions in same codebase
CLI Migration Assistant:
# Install migration tool
npm install -g @i45-jslogger/migrate
# Scan project for compatibility
npx @i45-jslogger/migrate scan
# Output:
# ✓ 15 files compatible with v3.0.0
# ⚠️ 3 files need updates:
# - src/logger.js:12 - Use modular constructor
# - src/debug.js:45 - Replace direct #events access
# - src/utils/log.js:89 - Update client registration pattern
#
# Run 'npx @i45-jslogger/migrate fix' to auto-migrate
# Apply automated fixes
npx @i45-jslogger/migrate fix
# Review changes
git diff
# Output shows before/after for each fileCodemod Script:
// Transforms v2 code to v3 automatically
// Before (v2.x):
const logger = new Logger();
logger.loggingEnabled = true;
logger.maxEvents = 100;
// After (v3.0.0) - auto-transformed:
const logger = new Logger({
storage: new EventStorage({
maxEvents: 100,
}),
dispatcher: new EventDispatcher({
enabled: true,
}),
});Manual Migration Guide:
Comprehensive documentation with:
- Side-by-side code examples (before/after)
- Common patterns and their v3 equivalents
- Breaking change checklist
- Troubleshooting FAQ
- Performance comparison
- Rollback instructions
Before Release:
- All v2.x features work in v3.0.0 (via adapter if needed)
- Migration guide complete with real-world examples
- Automated codemod tested on 10+ real projects
- v3.0.0-beta released 2+ months before stable
- Breaking changes clearly documented
- Community feedback incorporated
- Performance benchmarks show no regression
- Rollback procedure documented
Breaking Changes (v3.0.0):
- Remove monolithic constructor pattern
- Remove direct property access to internal storage
- Require module-based architecture for advanced features
- Drop Node.js <16 support (if needed)
- Remove v1.x compatibility shims
What Stays Compatible:
- Basic logging API (
log(),info(),warn(),error()) - CustomEvent naming (
i45-logger:*) - localStorage format (can read v2.x logs)
- Custom client interface
- Session tracking (introduced in v2.1.0)
v2.x LTS (6 months after v3.0.0 release):
v3.0.0 released (Q1 2027)
↓
v2.x enters LTS mode
↓
Q1-Q3 2027: Security fixes and critical bugs only
↓
Q4 2027: v2.x officially EOL (end-of-life)
What's supported:
- ✅ Security vulnerabilities
- ✅ Critical bugs (data loss, crashes)
- ✅ npm package remains available
- ❌ No new features
- ❌ No non-critical bug fixes
- ❌ No dependency updates (except security)
Communication:
- Clear EOL date announced 6+ months in advance
- Pinning guide:
npm install i45-jslogger@^2.8.0 - Archive branch maintained:
v2-maintenance - GitHub issues labeled:
v2-lts-only
Documentation:
- Migration guide:
/docs/migration/v2-to-v3.md - Video walkthrough (10-15 minutes)
- Blog post series (3-4 articles)
- FAQ document
- Before/after example repository
Tooling:
- Automated codemod (JavaScript/TypeScript)
- VS Code extension with inline migration hints
- ESLint plugin to warn about deprecated patterns
- GitHub Action to check compatibility
Community Support:
- GitHub Discussions: Migration Q&A
- Discord channel: #v3-migration
- Office hours: Weekly Zoom sessions during transition
- Case studies from early adopters
- Bounty program for community-contributed migration guides
Testing Strategy:
- Beta testing program (volunteers get early access)
- Canary releases:
npm install i45-jslogger@canary - Side-by-side comparison tools
- Performance regression testing
- Automated compatibility test suite
1. Transparency
- Document every breaking change
- Explain why changes are necessary
- Show before/after code examples
- Provide cost/benefit analysis
2. Automation
- Codemods for mechanical changes
- CLI tools for scanning and reporting
- CI/CD integration for compatibility checks
- Minimal manual effort required
3. Time
- Minimum 12 months between major versions
- Minimum 6 months deprecation warning
- Minimum 2 months beta testing
- 6 months LTS support after new major
4. Escape Hatches
- Keep v2.x available indefinitely on npm
- Document version pinning strategies
- Provide adapter/polyfill layers
- Clear rollback procedures
5. Community First
- Gather feedback before finalizing changes
- Prioritize real-world use cases
- Support users during migration
- Celebrate successful migrations
Motivation: Extensibility without bloating core package.
API Design:
// Plugin interface
interface LoggerPlugin {
name: string;
install(logger: Logger): void;
uninstall?(logger: Logger): void;
}
// Usage
const logger = new Logger();
logger.use(apiLoggerPlugin);
logger.use(filterPlugin);
logger.use(performancePlugin);Built-in Plugins (Separate Packages):
@i45-jslogger/plugin-api- API logging client@i45-jslogger/plugin-filter- Message filtering/sanitization@i45-jslogger/plugin-sentry- Sentry integration@i45-jslogger/plugin-analytics- Google Analytics events@i45-jslogger/plugin-performance- Performance.mark() integration
Example Plugin:
// API Logger Plugin
const apiLoggerPlugin = {
name: "api-logger",
install(logger) {
logger.on("error", async (event) => {
await fetch("/api/errors", {
method: "POST",
body: JSON.stringify(event),
});
});
},
};Benefits:
- ✅ Core stays lightweight
- ✅ Community can build plugins
- ✅ Easy integration with third-party services
- ✅ Pay-for-what-you-use approach
Motivation: Inspired by .NET middleware patterns, enable automatic logging of user interactions, API calls, navigation, and framework lifecycle events without polluting the codebase with explicit logging calls. This provides the same benefits as ASP.NET middleware/filters - centralized logging with rich context.
Configuration-Based Approach:
const logger = new Logger({
middleware: {
// Global event interception
dom: {
captureClicks: true,
captureFormSubmissions: true,
captureInputChanges: false, // Privacy consideration
includeElementPath: true, // div.container > button#submit
excludeSelectors: [".no-log", "[data-no-log]"],
sanitizeInputs: ["password", "ssn", "credit-card"],
},
// API call interception (Fetch/XHR)
fetch: {
enabled: true,
includeHeaders: false, // Privacy
includeRequestBody: false, // Privacy
includeResponseBody: false, // Privacy
includeTimings: true,
excludeUrls: [/\/analytics/, "/api/metrics"],
sanitizeUrls: true, // Remove tokens from URLs
},
// Navigation interception (SPA routing)
navigation: {
enabled: true,
captureHashChange: true,
capturePushState: true,
captureReplaceState: true,
capturePopState: true,
includeReferrer: true,
},
// Error interception
errors: {
captureUnhandledRejections: true,
captureGlobalErrors: true,
captureConsoleErrors: true,
includeStackTrace: true,
},
// Performance monitoring
performance: {
captureNavigation: true,
captureResources: false, // Can be verbose
captureLongTasks: true,
longTaskThreshold: 50, // ms
captureLayoutShifts: true,
},
},
});Benefits:
- ✅ Zero code pollution - configure once, works everywhere
- ✅ Consistent logging - all events captured uniformly
- ✅ Rich context - automatic capture of DOM state, user actions, timing
- ✅ Privacy controls - built-in sanitization and exclusion rules
- ✅ Opt-in/opt-out - granular control over what's captured
Implementation:
class DOMMiddleware {
#logger;
#config;
constructor(logger, config) {
this.#logger = logger;
this.#config = config;
this.#attachListeners();
}
#attachListeners() {
if (this.#config.captureClicks) {
document.addEventListener("click", this.#handleClick.bind(this), true);
}
if (this.#config.captureFormSubmissions) {
document.addEventListener("submit", this.#handleSubmit.bind(this), true);
}
}
#handleClick(event) {
const target = event.target;
// Check exclusions
if (this.#shouldExclude(target)) return;
this.#logger.info("[middleware:click]", {
element: target.tagName.toLowerCase(),
id: target.id,
className: target.className,
text: target.textContent?.trim().substring(0, 50),
elementPath: this.#getElementPath(target),
timestamp: Date.now(),
});
}
#handleSubmit(event) {
const form = event.target;
if (this.#shouldExclude(form)) return;
const formData = new FormData(form);
const data = {};
for (const [key, value] of formData.entries()) {
// Sanitize sensitive fields
if (this.#config.sanitizeInputs.includes(key)) {
data[key] = "[REDACTED]";
} else {
data[key] = value;
}
}
this.#logger.info("[middleware:form]", {
action: form.action,
method: form.method,
data,
timestamp: Date.now(),
});
}
#shouldExclude(element) {
return this.#config.excludeSelectors.some(
(selector) => element.matches?.(selector) || element.closest?.(selector)
);
}
#getElementPath(element) {
if (!this.#config.includeElementPath) return undefined;
const path = [];
let current = element;
while (current && current !== document.body) {
let selector = current.tagName.toLowerCase();
if (current.id) selector += `#${current.id}`;
else if (current.className)
selector += `.${current.className.split(" ")[0]}`;
path.unshift(selector);
current = current.parentElement;
}
return path.join(" > ");
}
}Auto-Logged Events:
// User clicks button
// → [middleware:click] { element: "button", id: "submit-btn", text: "Save Order", elementPath: "div.checkout > form#order-form > button#submit-btn" }
// User submits form
// → [middleware:form] { action: "/api/orders", method: "POST", data: { email: "user@example.com", password: "[REDACTED]" } }Implementation:
class FetchMiddleware {
#logger;
#config;
#originalFetch;
#originalXHROpen;
constructor(logger, config) {
this.#logger = logger;
this.#config = config;
this.#interceptFetch();
this.#interceptXHR();
}
#interceptFetch() {
this.#originalFetch = window.fetch;
const self = this;
window.fetch = async function (...args) {
const url = typeof args[0] === "string" ? args[0] : args[0].url;
const options = args[1] || {};
// Check exclusions
if (self.#shouldExcludeUrl(url)) {
return self.#originalFetch.apply(this, args);
}
const startTime = performance.now();
const requestId = crypto.randomUUID();
self.#logger.info("[middleware:fetch:request]", {
requestId,
url: self.#sanitizeUrl(url),
method: options.method || "GET",
headers: self.#config.includeHeaders ? options.headers : undefined,
body: self.#config.includeRequestBody ? options.body : undefined,
});
try {
const response = await self.#originalFetch.apply(this, args);
const duration = performance.now() - startTime;
self.#logger.info("[middleware:fetch:response]", {
requestId,
url: self.#sanitizeUrl(url),
status: response.status,
statusText: response.statusText,
duration: `${duration.toFixed(2)}ms`,
ok: response.ok,
});
return response;
} catch (error) {
const duration = performance.now() - startTime;
self.#logger.error("[middleware:fetch:error]", {
requestId,
url: self.#sanitizeUrl(url),
error: error.message,
duration: `${duration.toFixed(2)}ms`,
});
throw error;
}
};
}
#interceptXHR() {
this.#originalXHROpen = XMLHttpRequest.prototype.open;
const self = this;
XMLHttpRequest.prototype.open = function (method, url, ...args) {
if (!self.#shouldExcludeUrl(url)) {
const requestId = crypto.randomUUID();
const startTime = performance.now();
this.addEventListener("load", function () {
const duration = performance.now() - startTime;
self.#logger.info("[middleware:xhr:response]", {
requestId,
url: self.#sanitizeUrl(url),
method,
status: this.status,
duration: `${duration.toFixed(2)}ms`,
});
});
this.addEventListener("error", function () {
const duration = performance.now() - startTime;
self.#logger.error("[middleware:xhr:error]", {
requestId,
url: self.#sanitizeUrl(url),
method,
duration: `${duration.toFixed(2)}ms`,
});
});
}
return self.#originalXHROpen.apply(this, [method, url, ...args]);
};
}
#shouldExcludeUrl(url) {
return this.#config.excludeUrls.some((pattern) => {
if (pattern instanceof RegExp) return pattern.test(url);
return url.includes(pattern);
});
}
#sanitizeUrl(url) {
if (!this.#config.sanitizeUrls) return url;
// Remove common token patterns
return url
.replace(/([?&]token=)[^&]+/, "$1[REDACTED]")
.replace(/([?&]api_key=)[^&]+/, "$1[REDACTED]")
.replace(/([?&]access_token=)[^&]+/, "$1[REDACTED]");
}
}Auto-Logged API Calls:
// fetch('/api/users/123')
// → [middleware:fetch:request] { requestId: "abc-123", url: "/api/users/123", method: "GET" }
// → [middleware:fetch:response] { requestId: "abc-123", status: 200, duration: "234.56ms", ok: true }
// Failed API call
// → [middleware:fetch:request] { requestId: "def-456", url: "/api/orders", method: "POST" }
// → [middleware:fetch:error] { requestId: "def-456", error: "Network Error", duration: "5012.34ms" }Implementation:
class NavigationMiddleware {
#logger;
#config;
#originalPushState;
#originalReplaceState;
constructor(logger, config) {
this.#logger = logger;
this.#config = config;
this.#interceptHistory();
this.#attachListeners();
}
#interceptHistory() {
this.#originalPushState = history.pushState;
this.#originalReplaceState = history.replaceState;
const self = this;
if (this.#config.capturePushState) {
history.pushState = function (state, title, url) {
self.#logger.info("[middleware:navigation:pushState]", {
from: window.location.pathname,
to: url,
state,
referrer: self.#config.includeReferrer
? document.referrer
: undefined,
});
return self.#originalPushState.apply(this, arguments);
};
}
if (this.#config.captureReplaceState) {
history.replaceState = function (state, title, url) {
self.#logger.info("[middleware:navigation:replaceState]", {
from: window.location.pathname,
to: url,
state,
});
return self.#originalReplaceState.apply(this, arguments);
};
}
}
#attachListeners() {
if (this.#config.capturePopState) {
window.addEventListener("popstate", (event) => {
this.#logger.info("[middleware:navigation:popstate]", {
url: window.location.pathname,
state: event.state,
});
});
}
if (this.#config.captureHashChange) {
window.addEventListener("hashchange", (event) => {
this.#logger.info("[middleware:navigation:hashchange]", {
from: event.oldURL,
to: event.newURL,
});
});
}
}
}Auto-Logged Navigation:
// SPA navigation (React Router, Vue Router, etc.)
// → [middleware:navigation:pushState] { from: "/dashboard", to: "/users/123", state: {...} }
// Browser back/forward
// → [middleware:navigation:popstate] { url: "/dashboard", state: {...} }
// Hash navigation
// → [middleware:navigation:hashchange] { from: "#home", to: "#settings" }Approach 1: React Profiler (Lightweight, No Code Changes)
const logger = new Logger({
middleware: {
react: {
enabled: true,
mode: "profiler", // Use React's built-in Profiler
includeProps: true,
excludeComponents: ["Button", "Icon", /^UI/],
minRenderTime: 16, // Only log if render > 16ms
},
},
});
// Logger automatically wraps your app:
function App() {
return (
<Profiler id="App" onRender={logger.handleReactRender}>
<YourApp />
</Profiler>
);
}
// Auto-logged:
// → [react:mount] { component: "UserDashboard", duration: "23.45ms", props: {...} }
// → [react:update] { component: "OrderList", duration: "45.67ms", propsChanged: ["userId"] }Approach 2: createElement Interception (Global)
const logger = new Logger({
middleware: {
react: {
enabled: true,
mode: "createElement", // Monkey-patch React.createElement
logCreation: false, // Too verbose for production
logLifecycle: true,
},
},
});
// Behind the scenes:
const originalCreateElement = React.createElement;
React.createElement = function (type, props, ...children) {
if (typeof type === "function") {
const componentName = type.name || type.displayName || "Anonymous";
// Only log on first render (not every re-render)
if (logger.#isFirstRender(componentName)) {
logger.trace("[react:createElement]", {
component: componentName,
props: logger.#sanitizeProps(props),
});
}
}
return originalCreateElement.call(this, type, props, ...children);
};Approach 3: Custom Hook (Opt-In Per Component)
// One-time provider setup
import { LoggerProvider } from "i45-jslogger/react";
function App() {
return (
<LoggerProvider logger={logger}>
<YourApp />
</LoggerProvider>
);
}
// In components you want to log (opt-in):
import { useComponentLogger } from "i45-jslogger/react";
function UserProfile({ userId }) {
useComponentLogger("UserProfile", { userId }); // One line!
const [user, setUser] = useState(null);
useEffect(() => {
// Automatically logged as "[UserProfile] Effect: fetchUser"
fetchUser(userId).then(setUser);
}, [userId]);
return <div>Profile for {user?.name}</div>;
}
// Auto-logged:
// → [UserProfile] Mount { props: { userId: 123 } }
// → [UserProfile] Effect: fetchUser { userId: 123 }
// → [UserProfile] Update { propsChanged: ["userId"], newProps: { userId: 456 } }
// → [UserProfile] Unmountconst logger = new Logger({
middleware: {
vue: {
enabled: true,
logLifecycle: true,
logDataChanges: true,
excludeComponents: ["BaseButton", "BaseIcon"],
},
},
});
// Automatically intercepts Vue lifecycle hooks:
app.mixin({
mounted() {
logger.info("[vue:mounted]", {
component: this.$options.name,
props: this.$props,
});
},
updated() {
logger.info("[vue:updated]", {
component: this.$options.name,
});
},
unmounted() {
logger.info("[vue:unmounted]", {
component: this.$options.name,
});
},
});// Injectable service with HTTP interceptor
import { HttpInterceptor } from '@angular/common/http';
import { Logger } from 'i45-jslogger';
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
constructor(private logger: Logger) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const startTime = Date.now();
this.logger.info('[angular:http:request]', {
url: req.url,
method: req.method,
});
return next.handle(req).pipe(
tap(
response => {
const duration = Date.now() - startTime;
this.logger.info('[angular:http:response]', {
url: req.url,
status: response.status,
duration: `${duration}ms`,
});
},
error => {
const duration = Date.now() - startTime;
this.logger.error('[angular:http:error]', {
url: req.url,
error: error.message,
duration: `${duration}ms`,
});
}
)
);
}
}Babel Plugin:
// babel.config.js
module.exports = {
plugins: [
[
"i45-jslogger/babel-plugin",
{
enabled: process.env.NODE_ENV === "development",
framework: "react",
components: ["*"], // or specific patterns
excludeComponents: ["Button", "Icon"],
logLifecycle: true,
logProps: true,
},
],
],
};
// Before transformation:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
return <div>Profile</div>;
}
// After transformation (automatic):
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
logger.info("[UserProfile] Mounted", { props: { userId } });
return () => logger.info("[UserProfile] Unmounted");
}, []);
useEffect(() => {
logger.info("[UserProfile] Effect Start", { deps: [userId] });
fetchUser(userId).then(setUser);
}, [userId]);
return <div>Profile</div>;
}Vite Plugin:
// vite.config.js
import { defineConfig } from "vite";
import { i45LoggerPlugin } from "i45-jslogger/vite";
export default defineConfig({
plugins: [
i45LoggerPlugin({
framework: "react",
autoInstrument: true,
include: ["src/**/*.jsx", "src/**/*.tsx"],
exclude: ["src/components/ui/**"],
mode: "development", // Only in dev mode
}),
],
});Webpack Loader:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(jsx|tsx)$/,
use: [
"babel-loader",
{
loader: "i45-jslogger/webpack-loader",
options: {
framework: "react",
autoInstrument: true,
excludeComponents: ["Button", "Icon"],
},
},
],
},
],
},
};Automatic Performance Monitoring:
const logger = new Logger({
middleware: {
performance: {
captureNavigation: true,
captureResources: false, // Can be very verbose
captureLongTasks: true,
longTaskThreshold: 50, // ms
captureLayoutShifts: true,
captureLargestContentfulPaint: true,
captureFirstInputDelay: true,
},
},
});
// Behind the scenes:
class PerformanceMiddleware {
#logger;
#config;
constructor(logger, config) {
this.#logger = logger;
this.#config = config;
this.#initObservers();
}
#initObservers() {
// Navigation timing
if (this.#config.captureNavigation) {
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.#logger.info("[performance:navigation]", {
type: entry.type,
duration: `${entry.duration.toFixed(2)}ms`,
domContentLoaded: `${
entry.domContentLoadedEventEnd - entry.domContentLoadedEventStart
}ms`,
loadComplete: `${entry.loadEventEnd - entry.loadEventStart}ms`,
});
}
}).observe({ entryTypes: ["navigation"] });
}
// Long tasks (blocking the main thread)
if (this.#config.captureLongTasks) {
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration >= this.#config.longTaskThreshold) {
this.#logger.warn("[performance:longtask]", {
duration: `${entry.duration.toFixed(2)}ms`,
startTime: entry.startTime,
attribution: entry.attribution,
});
}
}
}).observe({ entryTypes: ["longtask"] });
}
// Layout shifts (visual stability)
if (this.#config.captureLayoutShifts) {
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
this.#logger.info("[performance:layout-shift]", {
value: entry.value,
cumulativeScore: clsValue,
});
}
}
}).observe({ entryTypes: ["layout-shift"] });
}
// Largest Contentful Paint (LCP)
if (this.#config.captureLargestContentfulPaint) {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.#logger.info("[performance:lcp]", {
renderTime: `${lastEntry.renderTime.toFixed(2)}ms`,
element: lastEntry.element?.tagName,
url: lastEntry.url,
});
}).observe({ entryTypes: ["largest-contentful-paint"] });
}
// First Input Delay (FID)
if (this.#config.captureFirstInputDelay) {
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.#logger.info("[performance:fid]", {
delay: `${entry.processingStart - entry.startTime}ms`,
eventType: entry.name,
});
}
}).observe({ entryTypes: ["first-input"] });
}
}
}Auto-Logged Performance Metrics:
// Page load
// → [performance:navigation] { type: "navigate", duration: "1234.56ms", domContentLoaded: "567.89ms", loadComplete: "234.56ms" }
// Long task detected
// → [performance:longtask] { duration: "123.45ms", startTime: 5678.90 }
// Layout shift
// → [performance:layout-shift] { value: 0.15, cumulativeScore: 0.15 }
// Largest Contentful Paint
// → [performance:lcp] { renderTime: "2345.67ms", element: "IMG", url: "/hero.jpg" }
// First Input Delay
// → [performance:fid] { delay: "12.34ms", eventType: "click" }Global Error Handling:
const logger = new Logger({
middleware: {
errors: {
captureUnhandledRejections: true,
captureGlobalErrors: true,
captureConsoleErrors: true,
includeStackTrace: true,
includeUserAgent: true,
},
},
});
// Implementation:
class ErrorMiddleware {
#logger;
#config;
constructor(logger, config) {
this.#logger = logger;
this.#config = config;
this.#attachHandlers();
}
#attachHandlers() {
if (this.#config.captureUnhandledRejections) {
window.addEventListener("unhandledrejection", (event) => {
this.#logger.error("[middleware:unhandled-rejection]", {
reason: event.reason?.message || event.reason,
promise: event.promise,
stack: this.#config.includeStackTrace
? event.reason?.stack
: undefined,
userAgent: this.#config.includeUserAgent
? navigator.userAgent
: undefined,
});
});
}
if (this.#config.captureGlobalErrors) {
window.addEventListener("error", (event) => {
this.#logger.error("[middleware:global-error]", {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: this.#config.includeStackTrace
? event.error?.stack
: undefined,
});
});
}
if (this.#config.captureConsoleErrors) {
const originalError = console.error;
console.error = (...args) => {
this.#logger.error("[middleware:console-error]", {
args: args.map((arg) =>
typeof arg === "object" ? JSON.stringify(arg) : String(arg)
),
});
originalError.apply(console, args);
};
}
}
}Built-In Sanitization:
const logger = new Logger({
middleware: {
privacy: {
// Automatic PII detection and redaction
sanitizePasswords: true, // Any field with "password" in name
sanitizeCreditCards: true, // Detect credit card patterns
sanitizeTokens: true, // Remove tokens from URLs and headers
sanitizeEmails: false, // Keep emails (unless specified)
// Custom sanitization rules
customRules: [
{ pattern: /ssn|social_security/i, replacement: "[SSN-REDACTED]" },
{ pattern: /api_key=[\w-]+/g, replacement: "api_key=[REDACTED]" },
{ pattern: /\b\d{16}\b/g, replacement: "[CARD-REDACTED]" }, // Credit cards
],
// Data retention
maxStorageDays: 7, // Auto-delete logs older than 7 days
clearOnLogout: true, // Clear logs when user logs out
// Consent management
requireConsent: true, // Don't log until user consents
consentStorageKey: "i45-logger-consent",
},
},
});
// Check consent before logging
if (!logger.hasConsent()) {
logger.requestConsent(); // Show UI prompt
}
// User grants consent
logger.grantConsent();
// User revokes consent (stops logging + clears data)
logger.revokeConsent();1. Production Bug Investigation
// Silent logging in production (no console output)
const logger = new Logger({
suppressConsole: true,
middleware: {
dom: { captureClicks: true, captureForms: true },
fetch: { enabled: true, includeTimings: true },
errors: { captureUnhandledRejections: true },
},
});
// When user reports a bug, export their session
window.addEventListener("unhandledrejection", async () => {
const sessionData = await logger.exportSession();
await sendToSupportTeam(sessionData);
// Support team can replay the exact sequence of events
});2. A/B Testing Analysis
// Track user interactions for experiment analysis
const logger = new Logger({
middleware: {
dom: {
captureClicks: true,
includeElementPath: true,
},
},
});
// All clicks automatically logged with context
// → [middleware:click] { element: "button", id: "checkout-btn-variant-a", ... }
// → [middleware:click] { element: "button", id: "checkout-btn-variant-b", ... }
// Analyze which variant gets more clicks
const clicks = logger
.getEvents()
.filter((e) => e.category === "middleware:click");3. Multi-Tab Debugging
// Debug OAuth popup flow
const logger = new Logger({
enableCrossTabSync: true,
middleware: {
navigation: { enabled: true },
fetch: { enabled: true },
},
});
// Main window
logger.info("Opening OAuth popup");
window.open("/oauth", "oauth", "width=600,height=400");
// Popup window (same logger config)
// → [middleware:navigation] popup loaded
// → [middleware:fetch:request] /oauth/authorize
// → [middleware:fetch:response] status: 302
// → [middleware:navigation] redirect to /callback
// Main window receives all popup logs via cross-tab sync!4. Performance Regression Testing
// Automatically track Core Web Vitals
const logger = new Logger({
middleware: {
performance: {
captureLargestContentfulPaint: true,
captureFirstInputDelay: true,
captureLayoutShifts: true,
captureLongTasks: true,
},
},
});
// After each deployment, compare metrics
const currentMetrics = logger.analyzePerformance();
const baselineMetrics = loadFromBaseline();
if (currentMetrics.lcp > baselineMetrics.lcp * 1.1) {
alert("LCP regression detected!");
}5. React useEffect Debugging
// Track all component lifecycle automatically
const logger = new Logger({
middleware: {
react: {
enabled: true,
mode: "profiler",
includeProps: true,
},
},
});
// After mount, analyze dependency chain
const events = logger.getEventsBySession();
// → [react:mount] UserDashboard
// → [react:mount] OrderList { props: { userId: undefined } } ← Bug! Should have userId
// → [react:update] OrderList { propsChanged: ["userId"] } ← Fixed after parent updatedPhase 1: Core Middleware (v3.0.0)
- ✅ DOM event interception (clicks, forms)
- ✅ Fetch/XHR interception
- ✅ Navigation interception (history API)
- ✅ Error interception (unhandled rejections, global errors)
- ✅ Privacy controls (sanitization, exclusions)
Phase 2: Framework Integrations (v3.1.0)
- ✅ React Profiler integration
- ✅ React custom hook (
useComponentLogger) ⚠️ Vue mixin (optional)⚠️ Angular interceptor (optional)
Phase 3: Performance Monitoring (v3.2.0)
- ✅ PerformanceObserver integration
- ✅ Core Web Vitals tracking (LCP, FID, CLS)
- ✅ Long task detection
- ✅ Resource timing (opt-in)
Phase 4: Build-Time Tooling (v3.3.0+)
⚠️ Babel plugin (optional, advanced users)⚠️ Vite plugin (optional, advanced users)⚠️ Webpack loader (optional, advanced users)
Priority: Medium (powerful feature, but optional for core functionality)
Development Mode (Verbose Logging):
const logger = new Logger({
loggingEnabled: true,
suppressConsole: false,
minLevel: "trace",
middleware: {
dom: { captureClicks: true, captureForms: true },
fetch: { enabled: true, includeHeaders: true, includeTimings: true },
navigation: { enabled: true },
errors: { captureUnhandledRejections: true, captureGlobalErrors: true },
performance: { captureLongTasks: true, captureLayoutShifts: true },
react: { enabled: true, mode: "profiler", includeProps: true },
},
});Production Mode (Silent, Error-Only):
const logger = new Logger({
loggingEnabled: true,
suppressConsole: true, // No console output
minLevel: "error", // Only errors
middleware: {
dom: { captureClicks: false, captureForms: false },
fetch: { enabled: true, includeHeaders: false, includeTimings: true },
navigation: { enabled: false },
errors: { captureUnhandledRejections: true, captureGlobalErrors: true },
performance: { captureLongTasks: false },
privacy: {
sanitizePasswords: true,
sanitizeCreditCards: true,
sanitizeTokens: true,
maxStorageDays: 7,
},
},
});Testing Mode (API & Performance Focus):
const logger = new Logger({
middleware: {
fetch: {
enabled: true,
includeHeaders: true,
includeRequestBody: true,
includeResponseBody: true,
includeTimings: true,
},
performance: {
captureNavigation: true,
captureLongTasks: true,
longTaskThreshold: 50,
},
errors: { captureUnhandledRejections: true },
},
});Breaking Changes - Major Release
Phase 1 (Essential):
- ✅ Session & timing tracking (sessionId, pageLoadId)
- ✅ Additional log levels (trace, debug, critical)
- ✅ Categories & namespaces
Phase 2 (Important):
- ✅ IndexedDB archival strategy
⚠️ Optional: Lightweight cross-tab sync
Phase 4 (Architecture):
- ✅ Separation of concerns refactoring
- ✅ Backward compatibility adapter
Feature Release
- React/framework debugging helpers
- Log export & analysis tools
Plugin System
- Plugin architecture
- Core plugins (@i45-jslogger/plugin-*)
Middleware & Auto-Instrumentation - Part 1
- DOM event interception (clicks, forms)
- Fetch/XHR interception
- Navigation interception (history API)
- Error interception (unhandled rejections, global errors)
- Privacy controls (sanitization, exclusions)
Middleware & Auto-Instrumentation - Part 2
- React Profiler integration
- React custom hook (
useComponentLogger) - Performance monitoring (Core Web Vitals, long tasks)
- Vue mixin (optional)
- Angular interceptor (optional)
Advanced Tooling & Enhancements
- Babel plugin for automatic instrumentation
- Vite plugin for automatic instrumentation
- Webpack loader for automatic instrumentation
- Performance optimizations
- Live demo & playground
- Full Node.js compatibility
Breaking Changes:
- Constructor signature changes (config object expanded)
- Internal module structure (shouldn't affect public API)
- Deprecated features removed (if any)
Migration Guide:
// v2.0.0
const logger = new Logger();
logger.enableLogging = true;
logger.maxEvents = 100;
// v3.0.0 (backward compatible)
const logger = new Logger({
loggingEnabled: true,
maxEvents: 100,
enableSessionTracking: true, // NEW
enableCategories: true, // NEW
});
// Or continue using v2 style (still works)
const logger = new Logger();
logger.loggingEnabled = true;
logger.maxEvents = 100;Tools Provided:
- Automated codemod script
- Migration CLI:
npx @i45-jslogger/migrate - Deprecation warnings with upgrade instructions
- Side-by-side compatibility for 1 minor version
- All v2.0.0 features work in v3.0.0
- Performance: No regression, target 10-20% improvement
- Bundle size: Core under 5KB gzipped
- Test coverage: 95%+ including new features
- Documentation: Complete with examples
- Migration guide: Step-by-step with codemods
- TypeScript: Full type definitions for all features
- npm download trends
- GitHub issues/PRs
- Community feedback (surveys)
- Adoption rate metrics
- Performance benchmarks
- Session ID Persistence: sessionStorage (default) vs localStorage?
- IndexedDB Priority: Built-in vs custom client pattern only?
- Cross-tab Sync: Essential feature or niche use case?
- Plugin System: Separate packages or monorepo?
- Breaking Changes: How aggressive should v3.0.0 refactoring be?
Feedback Welcome:
- GitHub Issues: Feature requests
- GitHub Discussions: Architecture proposals
- Pull Requests: Proof-of-concept implementations
Problem: Component B depends on data from Component A, but timing is unclear.
Solution:
// Component A
useEffect(() => {
logger.info("A: START fetch user");
fetchUser().then((user) => {
logger.info("A: COMPLETE", { userId: user.id });
setUser(user);
});
}, []);
// Component B (depends on A)
useEffect(() => {
if (!user) {
logger.info("B: SKIPPED - no user");
return;
}
logger.info("B: START fetch orders", { userId: user.id });
fetchOrders(user.id).then((orders) => {
logger.info("B: COMPLETE", { count: orders.length });
setOrders(orders);
});
}, [user]);
// After crash, analyze logs:
const logs = logger.getEventsBySession();
// Shows: A START → B SKIPPED → A COMPLETE → B START → B COMPLETEProblem: OAuth popup closes before you can see what happened.
Solution:
// Main window
const logger = new Logger({ enableCrossTabSync: true });
logger.info("Opening OAuth popup");
// Popup window (same logger instance)
logger.info("OAuth callback received");
logger.info("Token saved");
window.close();
// Main window still has logs from popup!
const allLogs = logger.getEventsBySession();
// Includes popup logs even after it closedProblem: Users report intermittent errors, but you can't reproduce.
Solution:
// Production logger with archival
const logger = new Logger({
suppressConsole: true, // Silent in production
maxEvents: 200, // Recent logs in localStorage
enableArchival: true, // Long-term storage
enableSessionTracking: true,
});
// When error occurs, export session
window.addEventListener("unhandledrejection", async (e) => {
logger.error("Unhandled rejection", { error: e.reason });
// Export logs for support team
const sessionData = await logger.exportSession({ includeArchived: true });
await sendToSupport(sessionData);
});Last Updated: December 23, 2025
Next Review: Q1 2026 (before v3.0.0 development begins)
Maintainer: Project Lead
This roadmap is a living document and will be updated based on:
- Community feedback
- Technical discoveries during implementation
- Changing ecosystem requirements
- User demand and adoption patterns
Let's build the future of browser debugging together! 🚀