devlog-ui 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Devlog-UI
2
2
 
3
- A lightweight, browser-based dev logger with a beautiful debug UI. Zero dependencies, framework-agnostic, production-safe.
3
+ A feature-rich, browser-based dev logger with a beautiful debug UI. Zero dependencies, framework-agnostic, production-safe.
4
4
 
5
5
  ## Features
6
6
 
@@ -28,16 +28,16 @@ npm install devlogger
28
28
  ## Quick Start
29
29
 
30
30
  ```typescript
31
- import { logger, DevLoggerUI } from 'devlogger';
31
+ import { logger, DevLoggerUI } from "devlogger";
32
32
 
33
33
  // Initialize the UI (once, at app start)
34
34
  DevLoggerUI.init();
35
35
 
36
36
  // Log messages with automatic source tracking
37
- logger.info('App started');
38
- logger.debug('Loading config', { theme: 'dark' });
39
- logger.warn('Cache miss', { key: 'user_prefs' });
40
- logger.error('API failed', new Error('Network timeout'));
37
+ logger.info("App started");
38
+ logger.debug("Loading config", { theme: "dark" });
39
+ logger.warn("Cache miss", { key: "user_prefs" });
40
+ logger.error("API failed", new Error("Network timeout"));
41
41
  ```
42
42
 
43
43
  ## API Reference
@@ -64,9 +64,9 @@ logger.error(message: string, ...data: unknown[]): void
64
64
 
65
65
  ```typescript
66
66
  logger.configure({
67
- maxLogs: 1000, // Max logs in memory (FIFO rotation)
68
- minLevel: 'debug', // Minimum level: 'debug' | 'info' | 'warn' | 'error'
69
- enabled: true, // Enable/disable logging
67
+ maxLogs: 1000, // Max logs in memory (FIFO rotation)
68
+ minLevel: "debug", // Minimum level: 'debug' | 'info' | 'warn' | 'error'
69
+ enabled: true, // Enable/disable logging
70
70
  });
71
71
  ```
72
72
 
@@ -81,7 +81,7 @@ logger.clear();
81
81
 
82
82
  // Subscribe to new logs
83
83
  const unsubscribe = logger.subscribe((log: LogEvent) => {
84
- console.log('New log:', log);
84
+ console.log("New log:", log);
85
85
  });
86
86
  unsubscribe(); // Stop receiving logs
87
87
 
@@ -98,29 +98,29 @@ Group related logs together with timing and status:
98
98
 
99
99
  ```typescript
100
100
  // Create a span for an operation
101
- const span = logger.span('Load user profile');
102
- span.info('Fetching from API...');
103
- span.debug('Request payload', { userId: 123 });
101
+ const span = logger.span("Load user profile");
102
+ span.info("Fetching from API...");
103
+ span.debug("Request payload", { userId: 123 });
104
104
 
105
105
  // End successfully
106
106
  span.end(); // status: 'success', duration calculated
107
107
 
108
108
  // Or end with error
109
- span.fail('Network timeout'); // status: 'error'
110
- span.fail(new Error('Timeout')); // also logs the error
109
+ span.fail("Network timeout"); // status: 'error'
110
+ span.fail(new Error("Timeout")); // also logs the error
111
111
  ```
112
112
 
113
113
  #### Nested Spans
114
114
 
115
115
  ```typescript
116
- const requestSpan = logger.span('HTTP Request', { requestId: 'abc-123' });
116
+ const requestSpan = logger.span("HTTP Request", { requestId: "abc-123" });
117
117
 
118
- const fetchSpan = requestSpan.span('Fetch Data');
119
- fetchSpan.info('Fetching...');
118
+ const fetchSpan = requestSpan.span("Fetch Data");
119
+ fetchSpan.info("Fetching...");
120
120
  fetchSpan.end();
121
121
 
122
- const processSpan = requestSpan.span('Process Data');
123
- processSpan.info('Processing...');
122
+ const processSpan = requestSpan.span("Process Data");
123
+ processSpan.info("Processing...");
124
124
  processSpan.end();
125
125
 
126
126
  requestSpan.end(); // Parent span ends after children
@@ -140,7 +140,7 @@ const spanLogs = logger.getSpanLogs(spanId);
140
140
 
141
141
  // Subscribe to span events
142
142
  const unsub = logger.subscribeSpans((span) => {
143
- if (span.status === 'error') {
143
+ if (span.status === "error") {
144
144
  console.log(`Span ${span.name} failed after ${span.duration}ms`);
145
145
  }
146
146
  });
@@ -152,10 +152,10 @@ Attach contextual information to logs for filtering and correlation:
152
152
 
153
153
  ```typescript
154
154
  // Set global context (attached to ALL logs)
155
- logger.setGlobalContext({ env: 'development', build: '1.2.3' });
155
+ logger.setGlobalContext({ env: "development", build: "1.2.3" });
156
156
 
157
157
  // Update global context
158
- logger.updateGlobalContext({ userId: 'user-456' });
158
+ logger.updateGlobalContext({ userId: "user-456" });
159
159
 
160
160
  // Clear global context
161
161
  logger.clearGlobalContext();
@@ -165,16 +165,16 @@ logger.clearGlobalContext();
165
165
 
166
166
  ```typescript
167
167
  // Create a logger with specific context
168
- const reqLogger = logger.withContext({ requestId: 'req-123' });
169
- reqLogger.info('Request started'); // includes requestId
168
+ const reqLogger = logger.withContext({ requestId: "req-123" });
169
+ reqLogger.info("Request started"); // includes requestId
170
170
 
171
171
  // Chain contexts
172
- const userLogger = reqLogger.withContext({ userId: 'user-456' });
173
- userLogger.info('User action'); // includes both requestId and userId
172
+ const userLogger = reqLogger.withContext({ userId: "user-456" });
173
+ userLogger.info("User action"); // includes both requestId and userId
174
174
 
175
175
  // Context loggers can also create spans
176
- const span = reqLogger.span('Process Request');
177
- span.info('Processing...'); // inherits requestId
176
+ const span = reqLogger.span("Process Request");
177
+ span.info("Processing..."); // inherits requestId
178
178
  span.end();
179
179
  ```
180
180
 
@@ -184,26 +184,26 @@ Export logs for sharing, bug reports, or analysis:
184
184
 
185
185
  ```typescript
186
186
  // Export as JSON (pretty printed)
187
- const json = logger.exportLogs({ format: 'json' });
187
+ const json = logger.exportLogs({ format: "json" });
188
188
 
189
189
  // Export as compact JSON
190
- const compact = logger.exportLogs({ format: 'json', pretty: false });
190
+ const compact = logger.exportLogs({ format: "json", pretty: false });
191
191
 
192
192
  // Export as human-readable text
193
- const text = logger.exportLogs({ format: 'text' });
193
+ const text = logger.exportLogs({ format: "text" });
194
194
 
195
195
  // Filter exports
196
196
  const filtered = logger.exportLogs({
197
- format: 'json',
198
- levels: ['warn', 'error'], // Only warnings and errors
199
- lastMs: 30000, // Last 30 seconds
200
- search: 'user', // Contains "user"
197
+ format: "json",
198
+ levels: ["warn", "error"], // Only warnings and errors
199
+ lastMs: 30000, // Last 30 seconds
200
+ search: "user", // Contains "user"
201
201
  });
202
202
 
203
203
  // Copy to clipboard
204
- const success = await logger.copyLogs({ format: 'json' });
204
+ const success = await logger.copyLogs({ format: "json" });
205
205
  if (success) {
206
- console.log('Logs copied!');
206
+ console.log("Logs copied!");
207
207
  }
208
208
  ```
209
209
 
@@ -213,29 +213,34 @@ Compare objects and log changes with color-coded visualization:
213
213
 
214
214
  ```typescript
215
215
  // Log a diff with automatic change detection
216
- const oldConfig = { theme: 'light', fontSize: 14 };
217
- const newConfig = { theme: 'dark', fontSize: 14, language: 'en' };
216
+ const oldConfig = { theme: "light", fontSize: 14 };
217
+ const newConfig = { theme: "dark", fontSize: 14, language: "en" };
218
218
 
219
- const diff = logger.diff('Config updated', oldConfig, newConfig);
219
+ const diff = logger.diff("Config updated", oldConfig, newConfig);
220
220
  // Logs with visual diff: +1 added, ~1 changed
221
221
 
222
222
  console.log(diff.summary);
223
223
  // { added: 1, removed: 0, changed: 1, unchanged: 1 }
224
224
 
225
225
  // Specify log level
226
- logger.diff('Breaking change', oldApi, newApi, 'warn');
226
+ logger.diff("Breaking change", oldApi, newApi, "warn");
227
227
 
228
228
  // Compute diff without logging
229
229
  const result = logger.computeDiff(objA, objB);
230
230
  if (result.summary.changed > 0) {
231
- logger.warn('Objects differ!', result.changes);
231
+ logger.warn("Objects differ!", result.changes);
232
232
  }
233
233
  ```
234
234
 
235
235
  #### Diff Utilities
236
236
 
237
237
  ```typescript
238
- import { computeDiff, createDiffResult, hasChanges, formatValue } from 'devlogger';
238
+ import {
239
+ computeDiff,
240
+ createDiffResult,
241
+ hasChanges,
242
+ formatValue,
243
+ } from "devlogger";
239
244
 
240
245
  // Low-level diff computation
241
246
  const changes = computeDiff(oldObj, newObj);
@@ -247,7 +252,7 @@ const result = createDiffResult(oldObj, newObj);
247
252
 
248
253
  // Quick check for any changes
249
254
  if (hasChanges(result)) {
250
- console.log('Objects are different');
255
+ console.log("Objects are different");
251
256
  }
252
257
 
253
258
  // Format values for display
@@ -260,32 +265,33 @@ formatValue([1, 2, 3, 4, 5]); // "[5 items]"
260
265
  Automatically track Fetch and XHR requests with spans:
261
266
 
262
267
  ```typescript
263
- import { NetworkCapture } from 'devlogger';
268
+ import { NetworkCapture } from "devlogger";
264
269
 
265
270
  // Install at app start
266
271
  NetworkCapture.install();
267
272
 
268
273
  // All fetch calls are now automatically logged
269
- await fetch('/api/users'); // Creates a span with timing
274
+ await fetch("/api/users"); // Creates a span with timing
270
275
 
271
276
  // With configuration
272
277
  NetworkCapture.install({
273
- captureFetch: true, // Hook into fetch (default: true)
274
- captureXHR: true, // Hook into XHR (default: true)
275
- includeHeaders: true, // Log request headers (default: false)
276
- includeBody: true, // Log request body (default: false)
277
- includeResponse: true, // Log response body (default: false)
278
- maxResponseLength: 5000, // Max response chars to capture
279
- ignorePatterns: [ // URLs to ignore
280
- '/analytics',
278
+ captureFetch: true, // Hook into fetch (default: true)
279
+ captureXHR: true, // Hook into XHR (default: true)
280
+ includeHeaders: true, // Log request headers (default: false)
281
+ includeBody: true, // Log request body (default: false)
282
+ includeResponse: true, // Log response body (default: false)
283
+ maxResponseLength: 5000, // Max response chars to capture
284
+ ignorePatterns: [
285
+ // URLs to ignore
286
+ "/analytics",
281
287
  /\.hot-update\./,
282
288
  /sockjs/,
283
289
  ],
284
- context: { service: 'api' } // Context for all network logs
290
+ context: { service: "api" }, // Context for all network logs
285
291
  });
286
292
 
287
293
  // Add ignore patterns dynamically
288
- NetworkCapture.addIgnorePattern('/health');
294
+ NetworkCapture.addIgnorePattern("/health");
289
295
 
290
296
  // Check status
291
297
  NetworkCapture.isActive();
@@ -296,6 +302,7 @@ NetworkCapture.uninstall();
296
302
  ```
297
303
 
298
304
  Network requests create spans automatically:
305
+
299
306
  ```
300
307
  [info] GET /api/users
301
308
  └─ span: "GET /api/users" (234ms, success)
@@ -309,16 +316,16 @@ Network requests create spans automatically:
309
316
  Visualize logs and spans on a canvas-based timeline:
310
317
 
311
318
  ```typescript
312
- import { createTimeline, Timeline } from 'devlogger';
319
+ import { createTimeline, Timeline } from "devlogger";
313
320
 
314
321
  // Create timeline in a container
315
322
  const timeline = createTimeline({
316
- container: '#timeline-container', // CSS selector or HTMLElement
317
- timeWindow: 60000, // Show last 60 seconds
318
- refreshInterval: 100, // Refresh rate in ms
319
- showSpans: true, // Display span bars
320
- showLogs: true, // Display log markers
321
- height: 200, // Canvas height in pixels
323
+ container: "#timeline-container", // CSS selector or HTMLElement
324
+ timeWindow: 60000, // Show last 60 seconds
325
+ refreshInterval: 100, // Refresh rate in ms
326
+ showSpans: true, // Display span bars
327
+ showLogs: true, // Display log markers
328
+ height: 200, // Canvas height in pixels
322
329
  });
323
330
 
324
331
  // Update time window
@@ -329,6 +336,7 @@ timeline.destroy();
329
336
  ```
330
337
 
331
338
  Timeline features:
339
+
332
340
  - Color-coded log markers (debug/info/warn/error)
333
341
  - Span bars with duration and nesting
334
342
  - Hover tooltips with details
@@ -355,9 +363,9 @@ DevLoggerUI.isPopoutOpen();
355
363
 
356
364
  // Filter logs programmatically
357
365
  DevLoggerUI.setFilter({
358
- levels: new Set(['warn', 'error']), // Show only warnings and errors
359
- search: 'api', // Text search
360
- file: 'utils', // Filter by file name
366
+ levels: new Set(["warn", "error"]), // Show only warnings and errors
367
+ search: "api", // Text search
368
+ file: "utils", // Filter by file name
361
369
  });
362
370
  DevLoggerUI.getFilter();
363
371
  DevLoggerUI.clearFilter();
@@ -379,17 +387,17 @@ Press `Ctrl+Shift+L` to toggle the debug panel.
379
387
  Automatically capture uncaught errors and unhandled promise rejections:
380
388
 
381
389
  ```typescript
382
- import { ErrorCapture } from 'devlogger';
390
+ import { ErrorCapture } from "devlogger";
383
391
 
384
392
  // Install at app start
385
393
  ErrorCapture.install();
386
394
 
387
395
  // With custom configuration
388
396
  ErrorCapture.install({
389
- captureErrors: true, // Capture window.onerror (default: true)
390
- captureRejections: true, // Capture unhandledrejection (default: true)
391
- errorPrefix: '[ERROR]', // Prefix for error messages
392
- rejectionPrefix: '[REJECT]' // Prefix for rejection messages
397
+ captureErrors: true, // Capture window.onerror (default: true)
398
+ captureRejections: true, // Capture unhandledrejection (default: true)
399
+ errorPrefix: "[ERROR]", // Prefix for error messages
400
+ rejectionPrefix: "[REJECT]", // Prefix for rejection messages
393
401
  });
394
402
 
395
403
  // Check if active
@@ -409,7 +417,7 @@ All captured errors are automatically logged as `error` level with full stack tr
409
417
  Persist logs to survive page crashes and enable crash recovery:
410
418
 
411
419
  ```typescript
412
- import { LogPersistence, logger } from 'devlogger';
420
+ import { LogPersistence, logger } from "devlogger";
413
421
 
414
422
  // Enable persistence at app start
415
423
  LogPersistence.enable();
@@ -422,9 +430,9 @@ if (LogPersistence.hadCrash()) {
422
430
 
423
431
  // With custom configuration
424
432
  LogPersistence.enable({
425
- storage: 'session', // 'session' (sessionStorage) or 'local' (localStorage)
426
- maxPersisted: 500, // Max logs to persist
427
- debounceMs: 100 // Debounce writes for performance
433
+ storage: "session", // 'session' (sessionStorage) or 'local' (localStorage)
434
+ maxPersisted: 500, // Max logs to persist
435
+ debounceMs: 100, // Debounce writes for performance
428
436
  });
429
437
 
430
438
  // Check if active
@@ -453,11 +461,10 @@ For production, import from `devlogger/noop` to completely eliminate logging cod
453
461
  export default defineConfig({
454
462
  resolve: {
455
463
  alias: {
456
- 'devlogger': process.env.NODE_ENV === 'production'
457
- ? 'devlogger/noop'
458
- : 'devlogger'
459
- }
460
- }
464
+ devlogger:
465
+ process.env.NODE_ENV === "production" ? "devlogger/noop" : "devlogger",
466
+ },
467
+ },
461
468
  });
462
469
  ```
463
470
 
@@ -468,11 +475,10 @@ export default defineConfig({
468
475
  module.exports = {
469
476
  resolve: {
470
477
  alias: {
471
- 'devlogger': process.env.NODE_ENV === 'production'
472
- ? 'devlogger/noop'
473
- : 'devlogger'
474
- }
475
- }
478
+ devlogger:
479
+ process.env.NODE_ENV === "production" ? "devlogger/noop" : "devlogger",
480
+ },
481
+ },
476
482
  };
477
483
  ```
478
484
 
@@ -480,12 +486,11 @@ module.exports = {
480
486
 
481
487
  ```javascript
482
488
  // build.js
483
- require('esbuild').build({
489
+ require("esbuild").build({
484
490
  alias: {
485
- 'devlogger': process.env.NODE_ENV === 'production'
486
- ? 'devlogger/noop'
487
- : 'devlogger'
488
- }
491
+ devlogger:
492
+ process.env.NODE_ENV === "production" ? "devlogger/noop" : "devlogger",
493
+ },
489
494
  });
490
495
  ```
491
496
 
@@ -495,13 +500,25 @@ The `noop` export provides the same API but all functions are no-ops, resulting
495
500
 
496
501
  ```typescript
497
502
  import type {
498
- LogEvent, LogLevel, LoggerConfig, Source, FilterState,
499
- ErrorCaptureConfig, LogContext, SpanEvent, SpanStatus, ExportOptions,
500
- DiffEntry, DiffResult, DiffChangeType, NetworkCaptureConfig, TimelineConfig
501
- } from 'devlogger';
502
-
503
- type LogLevel = 'debug' | 'info' | 'warn' | 'error';
504
- type SpanStatus = 'running' | 'success' | 'error';
503
+ LogEvent,
504
+ LogLevel,
505
+ LoggerConfig,
506
+ Source,
507
+ FilterState,
508
+ ErrorCaptureConfig,
509
+ LogContext,
510
+ SpanEvent,
511
+ SpanStatus,
512
+ ExportOptions,
513
+ DiffEntry,
514
+ DiffResult,
515
+ DiffChangeType,
516
+ NetworkCaptureConfig,
517
+ TimelineConfig,
518
+ } from "devlogger";
519
+
520
+ type LogLevel = "debug" | "info" | "warn" | "error";
521
+ type SpanStatus = "running" | "success" | "error";
505
522
  type LogContext = Record<string, string | number | boolean>;
506
523
 
507
524
  interface Source {
@@ -519,8 +536,8 @@ interface LogEvent {
519
536
  data: unknown[];
520
537
  source: Source;
521
538
  sessionId: string;
522
- context?: LogContext; // Attached context/tags
523
- spanId?: string; // Parent span ID
539
+ context?: LogContext; // Attached context/tags
540
+ spanId?: string; // Parent span ID
524
541
  }
525
542
 
526
543
  interface SpanEvent {
@@ -530,7 +547,7 @@ interface SpanEvent {
530
547
  endTime?: number;
531
548
  duration?: number;
532
549
  status: SpanStatus;
533
- parentId?: string; // For nested spans
550
+ parentId?: string; // For nested spans
534
551
  context?: LogContext;
535
552
  source: Source;
536
553
  sessionId: string;
@@ -543,11 +560,11 @@ interface LoggerConfig {
543
560
  }
544
561
 
545
562
  interface ExportOptions {
546
- format?: 'json' | 'text';
547
- lastMs?: number; // Filter by time
548
- levels?: LogLevel[]; // Filter by levels
549
- search?: string; // Filter by text
550
- pretty?: boolean; // Pretty print JSON
563
+ format?: "json" | "text";
564
+ lastMs?: number; // Filter by time
565
+ levels?: LogLevel[]; // Filter by levels
566
+ search?: string; // Filter by text
567
+ pretty?: boolean; // Pretty print JSON
551
568
  }
552
569
 
553
570
  interface FilterState {
@@ -564,16 +581,16 @@ interface ErrorCaptureConfig {
564
581
  }
565
582
 
566
583
  interface PersistenceConfig {
567
- storage?: 'session' | 'local';
584
+ storage?: "session" | "local";
568
585
  maxPersisted?: number;
569
586
  debounceMs?: number;
570
587
  }
571
588
 
572
589
  // Diff types
573
- type DiffChangeType = 'added' | 'removed' | 'changed' | 'unchanged';
590
+ type DiffChangeType = "added" | "removed" | "changed" | "unchanged";
574
591
 
575
592
  interface DiffEntry {
576
- path: string; // e.g., "user.profile.name"
593
+ path: string; // e.g., "user.profile.name"
577
594
  type: DiffChangeType;
578
595
  oldValue?: unknown;
579
596
  newValue?: unknown;
@@ -664,6 +681,7 @@ interface TimelineConfig {
664
681
  - Safari 14+
665
682
 
666
683
  Requires support for:
684
+
667
685
  - Shadow DOM
668
686
  - BroadcastChannel
669
687
  - ES2020+