devlog-ui 0.1.0
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/LICENSE +21 -0
- package/README.md +673 -0
- package/dist/index.cjs +1721 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +796 -0
- package/dist/index.js +3529 -0
- package/dist/index.js.map +1 -0
- package/dist/noop.cjs +2 -0
- package/dist/noop.cjs.map +1 -0
- package/dist/noop.d.ts +682 -0
- package/dist/noop.js +132 -0
- package/dist/noop.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Remer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# LogView
|
|
2
|
+
|
|
3
|
+
A lightweight, browser-based dev logger with a beautiful debug UI. Zero dependencies, framework-agnostic, production-safe.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📋 **Structured Logging** - Replace `console.log` with type-safe, structured logs
|
|
8
|
+
- 🔍 **Source Location** - Automatic file, line, and function tracking
|
|
9
|
+
- 🎨 **Debug UI** - Shadow DOM overlay with filter, search, and pop-out window
|
|
10
|
+
- 🚀 **Zero Production Overhead** - Tree-shakeable no-op export for production builds
|
|
11
|
+
- 🔒 **Crash-Resistant** - Never throws, never breaks your app
|
|
12
|
+
- ⚡ **Global Error Capture** - Automatically catch uncaught errors and unhandled rejections
|
|
13
|
+
- 💾 **Persistence & Crash Recovery** - Survive page crashes with automatic log persistence
|
|
14
|
+
- 🎯 **Spans & Grouping** - Group related logs with timing and nested spans
|
|
15
|
+
- 🏷️ **Context & Tags** - Attach requestId, userId, or any context to logs
|
|
16
|
+
- 📤 **Export & Share** - Copy logs as JSON or text for bug reports
|
|
17
|
+
- 🔄 **Visual Diff** - Compare objects with color-coded change visualization
|
|
18
|
+
- 🌐 **Network Capture** - Automatic Fetch/XHR request tracking with spans
|
|
19
|
+
- 📊 **Timeline View** - Canvas-based visualization of logs and spans over time
|
|
20
|
+
- 🌐 **Framework-Agnostic** - Works with React, Vue, Svelte, vanilla JS, or any framework
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install devlogger
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { logger, DevLoggerUI } from 'devlogger';
|
|
32
|
+
|
|
33
|
+
// Initialize the UI (once, at app start)
|
|
34
|
+
DevLoggerUI.init();
|
|
35
|
+
|
|
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'));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## API Reference
|
|
44
|
+
|
|
45
|
+
### Logger
|
|
46
|
+
|
|
47
|
+
The `logger` singleton provides four log levels:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Debug - verbose development info
|
|
51
|
+
logger.debug(message: string, ...data: unknown[]): void
|
|
52
|
+
|
|
53
|
+
// Info - general information
|
|
54
|
+
logger.info(message: string, ...data: unknown[]): void
|
|
55
|
+
|
|
56
|
+
// Warning - potential issues
|
|
57
|
+
logger.warn(message: string, ...data: unknown[]): void
|
|
58
|
+
|
|
59
|
+
// Error - errors and exceptions
|
|
60
|
+
logger.error(message: string, ...data: unknown[]): void
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Configuration
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
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
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Other Methods
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Get all logs (readonly array)
|
|
77
|
+
const logs = logger.getLogs();
|
|
78
|
+
|
|
79
|
+
// Clear all logs
|
|
80
|
+
logger.clear();
|
|
81
|
+
|
|
82
|
+
// Subscribe to new logs
|
|
83
|
+
const unsubscribe = logger.subscribe((log: LogEvent) => {
|
|
84
|
+
console.log('New log:', log);
|
|
85
|
+
});
|
|
86
|
+
unsubscribe(); // Stop receiving logs
|
|
87
|
+
|
|
88
|
+
// Get current session ID
|
|
89
|
+
const sessionId = logger.getSessionId();
|
|
90
|
+
|
|
91
|
+
// Get current config
|
|
92
|
+
const config = logger.getConfig();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Spans (Log Grouping)
|
|
96
|
+
|
|
97
|
+
Group related logs together with timing and status:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
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 });
|
|
104
|
+
|
|
105
|
+
// End successfully
|
|
106
|
+
span.end(); // status: 'success', duration calculated
|
|
107
|
+
|
|
108
|
+
// Or end with error
|
|
109
|
+
span.fail('Network timeout'); // status: 'error'
|
|
110
|
+
span.fail(new Error('Timeout')); // also logs the error
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Nested Spans
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const requestSpan = logger.span('HTTP Request', { requestId: 'abc-123' });
|
|
117
|
+
|
|
118
|
+
const fetchSpan = requestSpan.span('Fetch Data');
|
|
119
|
+
fetchSpan.info('Fetching...');
|
|
120
|
+
fetchSpan.end();
|
|
121
|
+
|
|
122
|
+
const processSpan = requestSpan.span('Process Data');
|
|
123
|
+
processSpan.info('Processing...');
|
|
124
|
+
processSpan.end();
|
|
125
|
+
|
|
126
|
+
requestSpan.end(); // Parent span ends after children
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Span Methods
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// Get all spans
|
|
133
|
+
const spans = logger.getSpans();
|
|
134
|
+
|
|
135
|
+
// Get specific span
|
|
136
|
+
const span = logger.getSpan(spanId);
|
|
137
|
+
|
|
138
|
+
// Get logs belonging to a span
|
|
139
|
+
const spanLogs = logger.getSpanLogs(spanId);
|
|
140
|
+
|
|
141
|
+
// Subscribe to span events
|
|
142
|
+
const unsub = logger.subscribeSpans((span) => {
|
|
143
|
+
if (span.status === 'error') {
|
|
144
|
+
console.log(`Span ${span.name} failed after ${span.duration}ms`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Context (Tags)
|
|
150
|
+
|
|
151
|
+
Attach contextual information to logs for filtering and correlation:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Set global context (attached to ALL logs)
|
|
155
|
+
logger.setGlobalContext({ env: 'development', build: '1.2.3' });
|
|
156
|
+
|
|
157
|
+
// Update global context
|
|
158
|
+
logger.updateGlobalContext({ userId: 'user-456' });
|
|
159
|
+
|
|
160
|
+
// Clear global context
|
|
161
|
+
logger.clearGlobalContext();
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Context-Bound Logger
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Create a logger with specific context
|
|
168
|
+
const reqLogger = logger.withContext({ requestId: 'req-123' });
|
|
169
|
+
reqLogger.info('Request started'); // includes requestId
|
|
170
|
+
|
|
171
|
+
// Chain contexts
|
|
172
|
+
const userLogger = reqLogger.withContext({ userId: 'user-456' });
|
|
173
|
+
userLogger.info('User action'); // includes both requestId and userId
|
|
174
|
+
|
|
175
|
+
// Context loggers can also create spans
|
|
176
|
+
const span = reqLogger.span('Process Request');
|
|
177
|
+
span.info('Processing...'); // inherits requestId
|
|
178
|
+
span.end();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Export
|
|
182
|
+
|
|
183
|
+
Export logs for sharing, bug reports, or analysis:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// Export as JSON (pretty printed)
|
|
187
|
+
const json = logger.exportLogs({ format: 'json' });
|
|
188
|
+
|
|
189
|
+
// Export as compact JSON
|
|
190
|
+
const compact = logger.exportLogs({ format: 'json', pretty: false });
|
|
191
|
+
|
|
192
|
+
// Export as human-readable text
|
|
193
|
+
const text = logger.exportLogs({ format: 'text' });
|
|
194
|
+
|
|
195
|
+
// Filter exports
|
|
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"
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Copy to clipboard
|
|
204
|
+
const success = await logger.copyLogs({ format: 'json' });
|
|
205
|
+
if (success) {
|
|
206
|
+
console.log('Logs copied!');
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Visual Diff
|
|
211
|
+
|
|
212
|
+
Compare objects and log changes with color-coded visualization:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// Log a diff with automatic change detection
|
|
216
|
+
const oldConfig = { theme: 'light', fontSize: 14 };
|
|
217
|
+
const newConfig = { theme: 'dark', fontSize: 14, language: 'en' };
|
|
218
|
+
|
|
219
|
+
const diff = logger.diff('Config updated', oldConfig, newConfig);
|
|
220
|
+
// Logs with visual diff: +1 added, ~1 changed
|
|
221
|
+
|
|
222
|
+
console.log(diff.summary);
|
|
223
|
+
// { added: 1, removed: 0, changed: 1, unchanged: 1 }
|
|
224
|
+
|
|
225
|
+
// Specify log level
|
|
226
|
+
logger.diff('Breaking change', oldApi, newApi, 'warn');
|
|
227
|
+
|
|
228
|
+
// Compute diff without logging
|
|
229
|
+
const result = logger.computeDiff(objA, objB);
|
|
230
|
+
if (result.summary.changed > 0) {
|
|
231
|
+
logger.warn('Objects differ!', result.changes);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Diff Utilities
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { computeDiff, createDiffResult, hasChanges, formatValue } from 'devlogger';
|
|
239
|
+
|
|
240
|
+
// Low-level diff computation
|
|
241
|
+
const changes = computeDiff(oldObj, newObj);
|
|
242
|
+
// Returns array of { path, type, oldValue, newValue }
|
|
243
|
+
|
|
244
|
+
// Full diff result with summary
|
|
245
|
+
const result = createDiffResult(oldObj, newObj);
|
|
246
|
+
// { changes: [...], summary: { added, removed, changed, unchanged } }
|
|
247
|
+
|
|
248
|
+
// Quick check for any changes
|
|
249
|
+
if (hasChanges(result)) {
|
|
250
|
+
console.log('Objects are different');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Format values for display
|
|
254
|
+
formatValue({ a: 1 }); // "{a: 1}"
|
|
255
|
+
formatValue([1, 2, 3, 4, 5]); // "[5 items]"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Network Capture
|
|
259
|
+
|
|
260
|
+
Automatically track Fetch and XHR requests with spans:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { NetworkCapture } from 'devlogger';
|
|
264
|
+
|
|
265
|
+
// Install at app start
|
|
266
|
+
NetworkCapture.install();
|
|
267
|
+
|
|
268
|
+
// All fetch calls are now automatically logged
|
|
269
|
+
await fetch('/api/users'); // Creates a span with timing
|
|
270
|
+
|
|
271
|
+
// With configuration
|
|
272
|
+
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',
|
|
281
|
+
/\.hot-update\./,
|
|
282
|
+
/sockjs/,
|
|
283
|
+
],
|
|
284
|
+
context: { service: 'api' } // Context for all network logs
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Add ignore patterns dynamically
|
|
288
|
+
NetworkCapture.addIgnorePattern('/health');
|
|
289
|
+
|
|
290
|
+
// Check status
|
|
291
|
+
NetworkCapture.isActive();
|
|
292
|
+
NetworkCapture.getConfig();
|
|
293
|
+
|
|
294
|
+
// Uninstall and restore original fetch/XHR
|
|
295
|
+
NetworkCapture.uninstall();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Network requests create spans automatically:
|
|
299
|
+
```
|
|
300
|
+
[info] GET /api/users
|
|
301
|
+
└─ span: "GET /api/users" (234ms, success)
|
|
302
|
+
├─ status: 200
|
|
303
|
+
├─ response: { users: [...] }
|
|
304
|
+
└─ headers: { content-type: "application/json" }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Timeline
|
|
308
|
+
|
|
309
|
+
Visualize logs and spans on a canvas-based timeline:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { createTimeline, Timeline } from 'devlogger';
|
|
313
|
+
|
|
314
|
+
// Create timeline in a container
|
|
315
|
+
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
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Update time window
|
|
325
|
+
timeline.setTimeWindow(30000); // Show last 30 seconds
|
|
326
|
+
|
|
327
|
+
// Cleanup when done
|
|
328
|
+
timeline.destroy();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Timeline features:
|
|
332
|
+
- Color-coded log markers (debug/info/warn/error)
|
|
333
|
+
- Span bars with duration and nesting
|
|
334
|
+
- Hover tooltips with details
|
|
335
|
+
- Auto-scroll to follow new logs
|
|
336
|
+
- Time axis with tick marks
|
|
337
|
+
|
|
338
|
+
### DevLoggerUI
|
|
339
|
+
|
|
340
|
+
The UI overlay provides a visual interface for viewing logs:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Initialize (creates Shadow DOM host)
|
|
344
|
+
DevLoggerUI.init();
|
|
345
|
+
|
|
346
|
+
// Show/hide panel
|
|
347
|
+
DevLoggerUI.open();
|
|
348
|
+
DevLoggerUI.close();
|
|
349
|
+
DevLoggerUI.toggle();
|
|
350
|
+
|
|
351
|
+
// Open in separate window
|
|
352
|
+
DevLoggerUI.popout();
|
|
353
|
+
DevLoggerUI.closePopout();
|
|
354
|
+
DevLoggerUI.isPopoutOpen();
|
|
355
|
+
|
|
356
|
+
// Filter logs programmatically
|
|
357
|
+
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
|
|
361
|
+
});
|
|
362
|
+
DevLoggerUI.getFilter();
|
|
363
|
+
DevLoggerUI.clearFilter();
|
|
364
|
+
|
|
365
|
+
// Cleanup
|
|
366
|
+
DevLoggerUI.destroy();
|
|
367
|
+
|
|
368
|
+
// State checks
|
|
369
|
+
DevLoggerUI.isVisible();
|
|
370
|
+
DevLoggerUI.isInitialized();
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Keyboard Shortcut
|
|
374
|
+
|
|
375
|
+
Press `Ctrl+Shift+L` to toggle the debug panel.
|
|
376
|
+
|
|
377
|
+
### ErrorCapture
|
|
378
|
+
|
|
379
|
+
Automatically capture uncaught errors and unhandled promise rejections:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { ErrorCapture } from 'devlogger';
|
|
383
|
+
|
|
384
|
+
// Install at app start
|
|
385
|
+
ErrorCapture.install();
|
|
386
|
+
|
|
387
|
+
// With custom configuration
|
|
388
|
+
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
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Check if active
|
|
396
|
+
ErrorCapture.isActive();
|
|
397
|
+
|
|
398
|
+
// Get current config
|
|
399
|
+
ErrorCapture.getConfig();
|
|
400
|
+
|
|
401
|
+
// Uninstall and restore original handlers
|
|
402
|
+
ErrorCapture.uninstall();
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
All captured errors are automatically logged as `error` level with full stack traces.
|
|
406
|
+
|
|
407
|
+
### LogPersistence
|
|
408
|
+
|
|
409
|
+
Persist logs to survive page crashes and enable crash recovery:
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import { LogPersistence, logger } from 'devlogger';
|
|
413
|
+
|
|
414
|
+
// Enable persistence at app start
|
|
415
|
+
LogPersistence.enable();
|
|
416
|
+
|
|
417
|
+
// Rehydrate logs from previous session
|
|
418
|
+
const count = LogPersistence.rehydrate();
|
|
419
|
+
if (LogPersistence.hadCrash()) {
|
|
420
|
+
logger.warn(`Recovered ${count} logs from previous crash`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// With custom configuration
|
|
424
|
+
LogPersistence.enable({
|
|
425
|
+
storage: 'session', // 'session' (sessionStorage) or 'local' (localStorage)
|
|
426
|
+
maxPersisted: 500, // Max logs to persist
|
|
427
|
+
debounceMs: 100 // Debounce writes for performance
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Check if active
|
|
431
|
+
LogPersistence.isActive();
|
|
432
|
+
|
|
433
|
+
// Get persisted logs without importing
|
|
434
|
+
const logs = LogPersistence.getPersistedLogs();
|
|
435
|
+
|
|
436
|
+
// Clear persisted logs
|
|
437
|
+
LogPersistence.clear();
|
|
438
|
+
|
|
439
|
+
// Disable persistence
|
|
440
|
+
LogPersistence.disable();
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Logs are persisted automatically after each new log (debounced). On page unload, logs are saved synchronously to ensure no data loss.
|
|
444
|
+
|
|
445
|
+
## Production Build
|
|
446
|
+
|
|
447
|
+
For production, import from `devlogger/noop` to completely eliminate logging code via tree-shaking:
|
|
448
|
+
|
|
449
|
+
### Vite
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
// vite.config.ts
|
|
453
|
+
export default defineConfig({
|
|
454
|
+
resolve: {
|
|
455
|
+
alias: {
|
|
456
|
+
'devlogger': process.env.NODE_ENV === 'production'
|
|
457
|
+
? 'devlogger/noop'
|
|
458
|
+
: 'devlogger'
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Webpack
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
// webpack.config.js
|
|
468
|
+
module.exports = {
|
|
469
|
+
resolve: {
|
|
470
|
+
alias: {
|
|
471
|
+
'devlogger': process.env.NODE_ENV === 'production'
|
|
472
|
+
? 'devlogger/noop'
|
|
473
|
+
: 'devlogger'
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### esbuild
|
|
480
|
+
|
|
481
|
+
```javascript
|
|
482
|
+
// build.js
|
|
483
|
+
require('esbuild').build({
|
|
484
|
+
alias: {
|
|
485
|
+
'devlogger': process.env.NODE_ENV === 'production'
|
|
486
|
+
? 'devlogger/noop'
|
|
487
|
+
: 'devlogger'
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The `noop` export provides the same API but all functions are no-ops, resulting in zero runtime overhead after tree-shaking.
|
|
493
|
+
|
|
494
|
+
## Types
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
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';
|
|
505
|
+
type LogContext = Record<string, string | number | boolean>;
|
|
506
|
+
|
|
507
|
+
interface Source {
|
|
508
|
+
file: string;
|
|
509
|
+
line: number;
|
|
510
|
+
column?: number;
|
|
511
|
+
function?: string;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
interface LogEvent {
|
|
515
|
+
id: string;
|
|
516
|
+
timestamp: number;
|
|
517
|
+
level: LogLevel;
|
|
518
|
+
message: string;
|
|
519
|
+
data: unknown[];
|
|
520
|
+
source: Source;
|
|
521
|
+
sessionId: string;
|
|
522
|
+
context?: LogContext; // Attached context/tags
|
|
523
|
+
spanId?: string; // Parent span ID
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
interface SpanEvent {
|
|
527
|
+
id: string;
|
|
528
|
+
name: string;
|
|
529
|
+
startTime: number;
|
|
530
|
+
endTime?: number;
|
|
531
|
+
duration?: number;
|
|
532
|
+
status: SpanStatus;
|
|
533
|
+
parentId?: string; // For nested spans
|
|
534
|
+
context?: LogContext;
|
|
535
|
+
source: Source;
|
|
536
|
+
sessionId: string;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
interface LoggerConfig {
|
|
540
|
+
maxLogs?: number;
|
|
541
|
+
minLevel?: LogLevel;
|
|
542
|
+
enabled?: boolean;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
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
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
interface FilterState {
|
|
554
|
+
levels: Set<LogLevel>;
|
|
555
|
+
search: string;
|
|
556
|
+
file: string;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
interface ErrorCaptureConfig {
|
|
560
|
+
captureErrors?: boolean;
|
|
561
|
+
captureRejections?: boolean;
|
|
562
|
+
errorPrefix?: string;
|
|
563
|
+
rejectionPrefix?: string;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
interface PersistenceConfig {
|
|
567
|
+
storage?: 'session' | 'local';
|
|
568
|
+
maxPersisted?: number;
|
|
569
|
+
debounceMs?: number;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Diff types
|
|
573
|
+
type DiffChangeType = 'added' | 'removed' | 'changed' | 'unchanged';
|
|
574
|
+
|
|
575
|
+
interface DiffEntry {
|
|
576
|
+
path: string; // e.g., "user.profile.name"
|
|
577
|
+
type: DiffChangeType;
|
|
578
|
+
oldValue?: unknown;
|
|
579
|
+
newValue?: unknown;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
interface DiffResult {
|
|
583
|
+
changes: DiffEntry[];
|
|
584
|
+
summary: {
|
|
585
|
+
added: number;
|
|
586
|
+
removed: number;
|
|
587
|
+
changed: number;
|
|
588
|
+
unchanged: number;
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
interface NetworkCaptureConfig {
|
|
593
|
+
captureFetch?: boolean;
|
|
594
|
+
captureXHR?: boolean;
|
|
595
|
+
includeHeaders?: boolean;
|
|
596
|
+
includeBody?: boolean;
|
|
597
|
+
includeResponse?: boolean;
|
|
598
|
+
maxResponseLength?: number;
|
|
599
|
+
ignorePatterns?: (string | RegExp)[];
|
|
600
|
+
context?: LogContext;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
interface TimelineConfig {
|
|
604
|
+
container: HTMLElement | string;
|
|
605
|
+
timeWindow?: number;
|
|
606
|
+
refreshInterval?: number;
|
|
607
|
+
showSpans?: boolean;
|
|
608
|
+
showLogs?: boolean;
|
|
609
|
+
height?: number;
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Architecture
|
|
614
|
+
|
|
615
|
+
```
|
|
616
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
617
|
+
│ Your Application │
|
|
618
|
+
│ │
|
|
619
|
+
│ logger.info('message', data) ───────────────────────┐ │
|
|
620
|
+
│ │ │
|
|
621
|
+
└─────────────────────────────────────────────────────────│────┘
|
|
622
|
+
│
|
|
623
|
+
┌─────────────────────────────────────────────────────────▼────┐
|
|
624
|
+
│ LoggerCore (Singleton) │
|
|
625
|
+
│ │
|
|
626
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
627
|
+
│ │ Capture │──│ Enrich │──│ Store │──│ Notify │ │
|
|
628
|
+
│ │ Source │ │ Metadata │ │ (FIFO) │ │ Subs │ │
|
|
629
|
+
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
|
|
630
|
+
│ │ │
|
|
631
|
+
└──────────────────────────────────────────────────│──────────┘
|
|
632
|
+
│
|
|
633
|
+
┌────────────────────────────────────────┤
|
|
634
|
+
│ │
|
|
635
|
+
▼ ▼
|
|
636
|
+
┌─────────────────────┐ ┌─────────────────────────┐
|
|
637
|
+
│ DevLoggerUI │◄────────────►│ Pop-out Window │
|
|
638
|
+
│ (Shadow DOM) │ Broadcast │ (Separate Window) │
|
|
639
|
+
│ │ Channel │ │
|
|
640
|
+
│ ┌───────────────┐ │ │ ┌───────────────────┐ │
|
|
641
|
+
│ │ Filter Bar │ │ │ │ Synced Logs │ │
|
|
642
|
+
│ │ Log List │ │ │ │ Clear Button │ │
|
|
643
|
+
│ │ Toggle Button │ │ │ │ Connection Status │ │
|
|
644
|
+
│ └───────────────┘ │ │ └───────────────────┘ │
|
|
645
|
+
└─────────────────────┘ └─────────────────────────┘
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Design Principles
|
|
649
|
+
|
|
650
|
+
1. **Zero-Throw Policy** - The logger never throws exceptions. If something goes wrong internally, it fails silently to avoid breaking your app.
|
|
651
|
+
|
|
652
|
+
2. **UI-Agnostic Core** - The `LoggerCore` has no knowledge of the UI. It only manages logs and notifies subscribers.
|
|
653
|
+
|
|
654
|
+
3. **Shadow DOM Isolation** - The UI uses Shadow DOM to prevent CSS conflicts with your application.
|
|
655
|
+
|
|
656
|
+
4. **Strict Decoupling** - The logger and UI are completely independent. You can use the logger without the UI, or create your own UI using the `subscribe()` API.
|
|
657
|
+
|
|
658
|
+
5. **No Side Effects on Import** - Importing the logger doesn't create any DOM elements or start any listeners. You must explicitly call `DevLoggerUI.init()`.
|
|
659
|
+
|
|
660
|
+
## Browser Support
|
|
661
|
+
|
|
662
|
+
- Chrome/Edge 80+
|
|
663
|
+
- Firefox 78+
|
|
664
|
+
- Safari 14+
|
|
665
|
+
|
|
666
|
+
Requires support for:
|
|
667
|
+
- Shadow DOM
|
|
668
|
+
- BroadcastChannel
|
|
669
|
+
- ES2020+
|
|
670
|
+
|
|
671
|
+
## License
|
|
672
|
+
|
|
673
|
+
MIT
|