@johnboxcodes/boxlogger 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/dist/index.js ADDED
@@ -0,0 +1,1147 @@
1
+ /**
2
+ * NodeLogger - Backend Logger with Pluggable Storage
3
+ *
4
+ * A lightweight, Sentry-compatible logger with multiple storage backends.
5
+ * Implements the top 5 Sentry functions for Next.js production apps.
6
+ *
7
+ * @packageDocumentation
8
+ *
9
+ * @example Quick Start
10
+ * ```typescript
11
+ * import * as Sentry from '@nodelogger/core';
12
+ *
13
+ * // Initialize with SQLite storage
14
+ * await Sentry.init('sqlite', { filename: './logs.db' });
15
+ *
16
+ * // 1. captureException - The Error Workhorse
17
+ * try {
18
+ * await riskyOperation();
19
+ * } catch (error) {
20
+ * Sentry.captureException(error, {
21
+ * tags: { section: 'payment', userId: '123' },
22
+ * extra: { endpoint: '/api/charge', amount: 99.99 },
23
+ * level: 'error',
24
+ * });
25
+ * }
26
+ *
27
+ * // 2. captureMessage - Custom Alerts
28
+ * Sentry.captureMessage('User reached payment limit', 'warning');
29
+ * Sentry.captureMessage('High-value transaction', {
30
+ * level: 'info',
31
+ * tags: { transactionType: 'purchase' },
32
+ * extra: { amount: 5000 },
33
+ * });
34
+ *
35
+ * // 3. setUser - User Context
36
+ * Sentry.setUser({
37
+ * id: user.id,
38
+ * email: user.email,
39
+ * segment: user.subscriptionTier,
40
+ * ip_address: '{{auto}}',
41
+ * });
42
+ *
43
+ * // 4. addBreadcrumb - Event Trail
44
+ * Sentry.addBreadcrumb({
45
+ * category: 'navigation',
46
+ * message: 'Navigated to checkout',
47
+ * level: 'info',
48
+ * data: { from: '/cart', to: '/checkout' },
49
+ * });
50
+ *
51
+ * // 5. withScope - Isolated Context
52
+ * Sentry.withScope((scope) => {
53
+ * scope.setTag('transaction', 'payment');
54
+ * scope.setExtra('orderId', orderId);
55
+ * scope.setFingerprint(['payment', orderId]);
56
+ * Sentry.captureException(error);
57
+ * });
58
+ * ```
59
+ */
60
+ import { randomUUID } from 'node:crypto';
61
+ import { Logger } from './logger.js';
62
+ import { MemoryStoreProvider } from './stores/memory.js';
63
+ import { SQLiteStoreProvider } from './stores/sqlite.js';
64
+ import { Scope, getCurrentScope, getGlobalScope, getIsolationScope, configureScope, withScope as withScopeInternal, resetScopes, } from './scope.js';
65
+ // ============================================================================
66
+ // Global Singleton Logger
67
+ // ============================================================================
68
+ let _instance = null;
69
+ let _store = null;
70
+ let _beforeSend = null;
71
+ let _beforeSendMessage = null;
72
+ let _ignoreErrors = [];
73
+ let _sampleRate = 1.0;
74
+ let _messagesSampleRate = 1.0;
75
+ let _activeTransaction = null;
76
+ /**
77
+ * Initialize the global logger singleton
78
+ *
79
+ * @param provider - Storage provider type ('sqlite', 'memory', 'mongodb')
80
+ * @param options - Configuration options
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // SQLite (persistent)
85
+ * await init('sqlite', { filename: './logs.db' });
86
+ *
87
+ * // Memory (development/testing)
88
+ * await init('memory');
89
+ *
90
+ * // MongoDB (future)
91
+ * await init('mongodb', { uri: 'mongodb://localhost/logs' });
92
+ * ```
93
+ */
94
+ export async function init(provider = 'memory', options = {}) {
95
+ // Close existing instance if any
96
+ if (_instance) {
97
+ await close();
98
+ }
99
+ // Reset scopes
100
+ resetScopes();
101
+ // Create store based on provider type
102
+ switch (provider) {
103
+ case 'sqlite':
104
+ _store = new SQLiteStoreProvider({
105
+ filename: options.filename ?? ':memory:',
106
+ });
107
+ break;
108
+ case 'mongodb':
109
+ throw new Error('MongoDB provider not yet implemented');
110
+ case 'memory':
111
+ default:
112
+ _store = new MemoryStoreProvider();
113
+ break;
114
+ }
115
+ await _store.init();
116
+ // Create logger instance
117
+ _instance = new Logger({
118
+ store: _store,
119
+ service: options.service,
120
+ environment: options.environment ?? process.env.NODE_ENV ?? 'development',
121
+ release: options.release,
122
+ minLevel: options.minLevel ?? 'info',
123
+ enableSessions: options.enableSessions ?? false,
124
+ defaultMetadata: options.defaultMetadata,
125
+ });
126
+ // Store ignoreErrors patterns
127
+ _ignoreErrors = options.ignoreErrors ?? [];
128
+ // Store sample rates
129
+ _sampleRate = options.sampleRate ?? 1.0;
130
+ _messagesSampleRate = options.messagesSampleRate ?? 1.0;
131
+ // Store beforeSend hooks
132
+ _beforeSend = options.beforeSend ?? null;
133
+ _beforeSendMessage = options.beforeSendMessage ?? null;
134
+ if (options.debug) {
135
+ console.log('[NodeLogger] Initialized with provider:', provider);
136
+ }
137
+ }
138
+ /**
139
+ * Create a new logger instance with its own store
140
+ *
141
+ * Factory function for creating isolated logger instances.
142
+ *
143
+ * @param provider - Storage provider type
144
+ * @param options - Configuration options
145
+ * @returns Logger instance
146
+ */
147
+ export async function create(provider = 'memory', options = {}) {
148
+ let store;
149
+ switch (provider) {
150
+ case 'sqlite':
151
+ store = new SQLiteStoreProvider({
152
+ filename: options.filename ?? ':memory:',
153
+ });
154
+ break;
155
+ case 'mongodb':
156
+ throw new Error('MongoDB provider not yet implemented');
157
+ case 'memory':
158
+ default:
159
+ store = new MemoryStoreProvider();
160
+ break;
161
+ }
162
+ await store.init();
163
+ return new Logger({
164
+ store,
165
+ service: options.service,
166
+ environment: options.environment ?? process.env.NODE_ENV ?? 'development',
167
+ release: options.release,
168
+ minLevel: options.minLevel ?? 'info',
169
+ enableSessions: options.enableSessions ?? false,
170
+ defaultMetadata: options.defaultMetadata,
171
+ });
172
+ }
173
+ /**
174
+ * Close the global logger and release resources
175
+ */
176
+ export async function close() {
177
+ if (_instance) {
178
+ await _instance.close();
179
+ _instance = null;
180
+ }
181
+ if (_store) {
182
+ _store = null;
183
+ }
184
+ _ignoreErrors = [];
185
+ _sampleRate = 1.0;
186
+ _messagesSampleRate = 1.0;
187
+ _beforeSend = null;
188
+ _beforeSendMessage = null;
189
+ _activeTransaction = null;
190
+ resetScopes();
191
+ }
192
+ /**
193
+ * Check if the global logger is initialized
194
+ */
195
+ export function isInitialized() {
196
+ return _instance !== null && _store !== null && _store.isReady();
197
+ }
198
+ // ============================================================================
199
+ // TOP 5 SENTRY FUNCTIONS FOR PRODUCTION APPS
200
+ // ============================================================================
201
+ // ----------------------------------------------------------------------------
202
+ // 1. captureException() - The Error Workhorse
203
+ // ----------------------------------------------------------------------------
204
+ /**
205
+ * Capture an exception (Sentry-compatible)
206
+ *
207
+ * This is the most used Sentry function. It captures errors with full stack
208
+ * traces and context information.
209
+ *
210
+ * @param error - Error object or error message string
211
+ * @param captureContext - Additional context (tags, extra, level, fingerprint)
212
+ * @returns Event ID (UUID)
213
+ *
214
+ * @example Basic usage
215
+ * ```typescript
216
+ * try {
217
+ * await riskyOperation();
218
+ * } catch (error) {
219
+ * Sentry.captureException(error);
220
+ * }
221
+ * ```
222
+ *
223
+ * @example With full context (production pattern)
224
+ * ```typescript
225
+ * try {
226
+ * await fetchUserProfile(userId);
227
+ * } catch (error) {
228
+ * Sentry.captureException(error, {
229
+ * tags: {
230
+ * section: 'user-profile',
231
+ * userId,
232
+ * },
233
+ * extra: {
234
+ * endpoint: '/api/users/profile',
235
+ * timestamp: Date.now(),
236
+ * },
237
+ * level: 'error',
238
+ * });
239
+ * }
240
+ * ```
241
+ */
242
+ export function captureException(error, captureContext) {
243
+ ensureInitialized();
244
+ // Apply sampling - return empty string if event is dropped
245
+ if (Math.random() >= _sampleRate) {
246
+ return '';
247
+ }
248
+ const eventId = randomUUID();
249
+ // Convert error to Error object if string
250
+ const err = typeof error === 'string' ? new Error(error) : error;
251
+ // Check if this error should be ignored based on ignoreErrors patterns
252
+ const errorMessage = err?.message ?? String(error);
253
+ if (shouldIgnoreError(errorMessage)) {
254
+ return eventId;
255
+ }
256
+ const scope = getCurrentScope();
257
+ // Apply capture context to a temporary scope
258
+ const tempScope = new Scope(scope);
259
+ if (captureContext) {
260
+ tempScope.applyContext(captureContext);
261
+ }
262
+ // Determine level
263
+ const level = mapSeverityToLogLevel(captureContext?.level ?? 'error');
264
+ // Build metadata from scope
265
+ const metadata = tempScope.toMetadata();
266
+ // Attach traceId and spanId from active transaction if present
267
+ if (_activeTransaction) {
268
+ metadata.traceId = _activeTransaction.traceId;
269
+ metadata.spanId = _activeTransaction.spanId;
270
+ }
271
+ // Build event for beforeSend hook
272
+ let event = {
273
+ id: eventId,
274
+ timestamp: new Date().toISOString(),
275
+ level,
276
+ message: err.message,
277
+ metadata: {
278
+ ...metadata,
279
+ error: {
280
+ type: err.name,
281
+ message: err.message,
282
+ stack: err.stack,
283
+ },
284
+ },
285
+ };
286
+ // Call beforeSend hook if set
287
+ if (_beforeSend) {
288
+ const result = _beforeSend(event, { originalException: err });
289
+ if (result === null) {
290
+ // Event was dropped
291
+ return '';
292
+ }
293
+ event = result;
294
+ }
295
+ // Log the exception using the (possibly modified) event data
296
+ _instance.exception(err, undefined, event.metadata);
297
+ return eventId;
298
+ }
299
+ // ----------------------------------------------------------------------------
300
+ // 2. captureMessage() - Custom Alerts
301
+ // ----------------------------------------------------------------------------
302
+ /**
303
+ * Capture a message (Sentry-compatible)
304
+ *
305
+ * Used for logging important events that aren't errors but need visibility,
306
+ * such as security events or business logic anomalies.
307
+ *
308
+ * @param message - Message to capture
309
+ * @param captureContextOrLevel - Severity level string OR full context object
310
+ * @returns Event ID (UUID)
311
+ *
312
+ * @example Simple message
313
+ * ```typescript
314
+ * Sentry.captureMessage('User reached payment limit');
315
+ * ```
316
+ *
317
+ * @example With severity level
318
+ * ```typescript
319
+ * Sentry.captureMessage('Suspicious login attempt detected', 'warning');
320
+ * ```
321
+ *
322
+ * @example With full context
323
+ * ```typescript
324
+ * Sentry.captureMessage('High-value transaction completed', {
325
+ * level: 'info',
326
+ * tags: {
327
+ * transactionType: 'purchase',
328
+ * amount: 'high',
329
+ * },
330
+ * extra: {
331
+ * orderId,
332
+ * amount: 5000,
333
+ * userTier: 'premium',
334
+ * },
335
+ * });
336
+ * ```
337
+ */
338
+ export function captureMessage(message, captureContextOrLevel) {
339
+ ensureInitialized();
340
+ // Apply sampling - return empty string if event is dropped
341
+ if (Math.random() >= _messagesSampleRate) {
342
+ return '';
343
+ }
344
+ const eventId = randomUUID();
345
+ const scope = getCurrentScope();
346
+ let level = 'info';
347
+ let captureContext;
348
+ // Handle overloaded parameter
349
+ if (typeof captureContextOrLevel === 'string') {
350
+ // It's a severity level
351
+ level = mapSeverityToLogLevel(captureContextOrLevel);
352
+ }
353
+ else if (captureContextOrLevel) {
354
+ // It's a full context object
355
+ captureContext = captureContextOrLevel;
356
+ if (captureContext.level) {
357
+ level = mapSeverityToLogLevel(captureContext.level);
358
+ }
359
+ }
360
+ // Apply capture context to a temporary scope
361
+ const tempScope = new Scope(scope);
362
+ if (captureContext) {
363
+ tempScope.applyContext(captureContext);
364
+ }
365
+ // Build metadata from scope
366
+ const metadata = tempScope.toMetadata();
367
+ // Attach traceId and spanId from active transaction if present
368
+ if (_activeTransaction) {
369
+ metadata.traceId = _activeTransaction.traceId;
370
+ metadata.spanId = _activeTransaction.spanId;
371
+ }
372
+ // Build event for beforeSendMessage hook
373
+ let event = {
374
+ id: eventId,
375
+ timestamp: new Date().toISOString(),
376
+ level,
377
+ message,
378
+ metadata,
379
+ };
380
+ // Call beforeSendMessage hook if set
381
+ if (_beforeSendMessage) {
382
+ const result = _beforeSendMessage(event, { originalMessage: message });
383
+ if (result === null) {
384
+ // Event was dropped
385
+ return '';
386
+ }
387
+ event = result;
388
+ }
389
+ // Log the message using the (possibly modified) event data
390
+ _instance.log(event.level, event.message, event.metadata);
391
+ return eventId;
392
+ }
393
+ // ----------------------------------------------------------------------------
394
+ // 3. setUser() - User Context
395
+ // ----------------------------------------------------------------------------
396
+ /**
397
+ * Set user context (Sentry-compatible)
398
+ *
399
+ * Essential for tracking which users are affected by errors.
400
+ * Critical for production debugging.
401
+ *
402
+ * @param user - User info or null to clear
403
+ *
404
+ * @example After user authentication
405
+ * ```typescript
406
+ * function setSentryUser(user: User) {
407
+ * Sentry.setUser({
408
+ * id: user.id,
409
+ * email: user.email,
410
+ * username: user.username,
411
+ * ip_address: '{{auto}}', // Auto-detect IP
412
+ * });
413
+ * }
414
+ * ```
415
+ *
416
+ * @example On logout
417
+ * ```typescript
418
+ * function clearSentryUser() {
419
+ * Sentry.setUser(null);
420
+ * }
421
+ * ```
422
+ *
423
+ * @example With segment data
424
+ * ```typescript
425
+ * Sentry.setUser({
426
+ * id: user.id,
427
+ * email: user.email,
428
+ * segment: user.subscriptionTier, // 'free' | 'pro' | 'enterprise'
429
+ * plan: user.planType,
430
+ * });
431
+ * ```
432
+ */
433
+ export function setUser(user) {
434
+ ensureInitialized();
435
+ // Handle {{auto}} IP address
436
+ if (user && user.ip_address === '{{auto}}') {
437
+ // In a real backend scenario, you'd get this from the request
438
+ // For now, we just leave it as a marker
439
+ user = { ...user, ip_address: '{{auto}}' };
440
+ }
441
+ // Set on global scope
442
+ getGlobalScope().setUser(user);
443
+ getCurrentScope().setUser(user);
444
+ // Also update logger default metadata
445
+ if (!_instance['config'].defaultMetadata) {
446
+ _instance['config'].defaultMetadata = {};
447
+ }
448
+ if (user === null) {
449
+ delete _instance['config'].defaultMetadata.user;
450
+ }
451
+ else {
452
+ _instance['config'].defaultMetadata.user = user;
453
+ }
454
+ }
455
+ // ----------------------------------------------------------------------------
456
+ // 4. addBreadcrumb() - Event Trail
457
+ // ----------------------------------------------------------------------------
458
+ /**
459
+ * Add a breadcrumb (Sentry-compatible)
460
+ *
461
+ * Creates a trail of events leading up to an error.
462
+ * Invaluable for understanding what happened before a crash.
463
+ *
464
+ * @param breadcrumb - Breadcrumb data
465
+ *
466
+ * @example Navigation breadcrumb
467
+ * ```typescript
468
+ * function trackNavigation(url: string) {
469
+ * Sentry.addBreadcrumb({
470
+ * category: 'navigation',
471
+ * message: `Navigated to ${url}`,
472
+ * level: 'info',
473
+ * data: {
474
+ * from: window.location.pathname,
475
+ * to: url,
476
+ * },
477
+ * });
478
+ * }
479
+ * ```
480
+ *
481
+ * @example API call breadcrumb
482
+ * ```typescript
483
+ * Sentry.addBreadcrumb({
484
+ * category: 'api',
485
+ * message: 'API request started',
486
+ * level: 'info',
487
+ * data: { endpoint, method: 'GET' },
488
+ * });
489
+ * ```
490
+ *
491
+ * @example User action breadcrumb
492
+ * ```typescript
493
+ * Sentry.addBreadcrumb({
494
+ * category: 'ui.click',
495
+ * message: 'User clicked checkout button',
496
+ * level: 'info',
497
+ * data: {
498
+ * cartItems: 5,
499
+ * totalAmount: 129.99,
500
+ * },
501
+ * });
502
+ * ```
503
+ */
504
+ export function addBreadcrumb(breadcrumb) {
505
+ ensureInitialized();
506
+ // Add timestamp if not provided (Sentry uses seconds since epoch)
507
+ const crumb = {
508
+ ...breadcrumb,
509
+ timestamp: breadcrumb.timestamp ?? Date.now() / 1000,
510
+ };
511
+ // Add to both global and current scope
512
+ getGlobalScope().addBreadcrumb(crumb);
513
+ getCurrentScope().addBreadcrumb(crumb);
514
+ }
515
+ // ----------------------------------------------------------------------------
516
+ // 5. withScope() - Isolated Context
517
+ // ----------------------------------------------------------------------------
518
+ /**
519
+ * Run code with an isolated scope (Sentry-compatible)
520
+ *
521
+ * Creates a temporary scope that doesn't pollute the global scope.
522
+ * This is the preferred way to add context to specific errors.
523
+ *
524
+ * @param callback - Function to run with isolated scope
525
+ * @returns Result of the callback
526
+ *
527
+ * @example Basic usage
528
+ * ```typescript
529
+ * function processPayment(orderId: string, amount: number) {
530
+ * return Sentry.withScope((scope) => {
531
+ * scope.setTag('transaction', 'payment');
532
+ * scope.setExtra('orderId', orderId);
533
+ * scope.setExtra('amount', amount);
534
+ * scope.setFingerprint(['payment', orderId]);
535
+ *
536
+ * try {
537
+ * return executePayment(orderId, amount);
538
+ * } catch (error) {
539
+ * Sentry.captureException(error);
540
+ * throw error;
541
+ * }
542
+ * });
543
+ * }
544
+ * ```
545
+ *
546
+ * @example Async operations
547
+ * ```typescript
548
+ * async function handleUserAction(userId: string, action: string) {
549
+ * return Sentry.withScope(async (scope) => {
550
+ * scope.setUser({ id: userId });
551
+ * scope.setTag('action', action);
552
+ *
553
+ * const result = await performAction(userId, action);
554
+ * return result;
555
+ * });
556
+ * }
557
+ * ```
558
+ */
559
+ export function withScope(callback) {
560
+ return withScopeInternal(callback);
561
+ }
562
+ // ============================================================================
563
+ // Additional Sentry-Compatible Functions
564
+ // ============================================================================
565
+ /**
566
+ * Configure the global scope (Sentry-compatible)
567
+ *
568
+ * Modifies the global scope that affects all future events.
569
+ * Use sparingly - prefer withScope for isolated context.
570
+ *
571
+ * @param callback - Function to configure the scope
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * Sentry.configureScope((scope) => {
576
+ * scope.setTag('environment', 'production');
577
+ * scope.setTag('release', process.env.APP_VERSION);
578
+ * });
579
+ * ```
580
+ */
581
+ export { configureScope };
582
+ // ----------------------------------------------------------------------------
583
+ // captureEvent() - Low-level Event Capture
584
+ // ----------------------------------------------------------------------------
585
+ /**
586
+ * Capture a raw event (Sentry-compatible)
587
+ *
588
+ * This is a low-level function that captures a raw event object.
589
+ * Use this for maximum control over event data, such as manually constructed
590
+ * exception events or custom event formats.
591
+ *
592
+ * @param event - The raw Sentry event object
593
+ * @returns Event ID (UUID)
594
+ *
595
+ * @example Basic message event
596
+ * ```typescript
597
+ * Sentry.captureEvent({
598
+ * message: 'Manual event',
599
+ * level: 'info',
600
+ * tags: { source: 'manual' },
601
+ * });
602
+ * ```
603
+ *
604
+ * @example Exception event
605
+ * ```typescript
606
+ * Sentry.captureEvent({
607
+ * message: 'Something went wrong',
608
+ * level: 'error',
609
+ * exception: {
610
+ * values: [{
611
+ * type: 'Error',
612
+ * value: 'Connection failed',
613
+ * stacktrace: { frames: [] },
614
+ * }],
615
+ * },
616
+ * });
617
+ * ```
618
+ *
619
+ * @example With full context
620
+ * ```typescript
621
+ * Sentry.captureEvent({
622
+ * message: 'Custom event',
623
+ * level: 'warning',
624
+ * tags: { module: 'payments' },
625
+ * extra: { orderId: '123', amount: 99.99 },
626
+ * user: { id: 'user-123', email: 'user@example.com' },
627
+ * contexts: {
628
+ * payment: { processor: 'stripe', status: 'failed' },
629
+ * },
630
+ * fingerprint: ['payment', 'stripe', 'failed'],
631
+ * });
632
+ * ```
633
+ */
634
+ export function captureEvent(event) {
635
+ ensureInitialized();
636
+ // Generate event_id if not provided
637
+ const eventId = event.event_id ?? randomUUID();
638
+ // Get current scope
639
+ const scope = getCurrentScope();
640
+ // Create a temporary scope starting from current scope
641
+ const tempScope = new Scope(scope);
642
+ // Apply event data to scope (event data takes precedence over scope)
643
+ if (event.tags) {
644
+ tempScope.setTags(event.tags);
645
+ }
646
+ if (event.extra) {
647
+ tempScope.setExtras(event.extra);
648
+ }
649
+ if (event.user) {
650
+ tempScope.setUser(event.user);
651
+ }
652
+ if (event.level) {
653
+ tempScope.setLevel(event.level);
654
+ }
655
+ if (event.fingerprint) {
656
+ tempScope.setFingerprint(event.fingerprint);
657
+ }
658
+ if (event.contexts) {
659
+ for (const [name, ctx] of Object.entries(event.contexts)) {
660
+ tempScope.setContext(name, ctx);
661
+ }
662
+ }
663
+ if (event.breadcrumbs) {
664
+ for (const crumb of event.breadcrumbs) {
665
+ tempScope.addBreadcrumb(crumb);
666
+ }
667
+ }
668
+ // Build metadata from scope
669
+ const metadata = tempScope.toMetadata();
670
+ // Determine log level
671
+ const level = mapSeverityToLogLevel(event.level ?? 'info');
672
+ // Determine message
673
+ let message = event.message ?? '';
674
+ // If there's an exception, handle it
675
+ if (event.exception?.values?.length) {
676
+ const exc = event.exception.values[0];
677
+ const excMessage = exc.value || exc.type || 'Unknown error';
678
+ // Store exception info in metadata
679
+ metadata.error = {
680
+ type: exc.type,
681
+ message: exc.value,
682
+ stack: exc.stacktrace?.frames
683
+ ? exc.stacktrace.frames
684
+ .map((f) => ` at ${f.function || '<anonymous>'} (${f.filename || 'unknown'}:${f.lineno || 0}:${f.colno || 0})`)
685
+ .join('\n')
686
+ : undefined,
687
+ };
688
+ // If no message provided, use exception message
689
+ if (!message) {
690
+ message = `${exc.type}: ${excMessage}`;
691
+ }
692
+ }
693
+ // Ensure we have some message
694
+ if (!message) {
695
+ message = 'Event captured';
696
+ }
697
+ // Log the event
698
+ _instance.log(level, message, metadata);
699
+ return eventId;
700
+ }
701
+ /**
702
+ * Set a single tag on the global scope
703
+ */
704
+ export function setTag(key, value) {
705
+ ensureInitialized();
706
+ getGlobalScope().setTag(key, value);
707
+ getCurrentScope().setTag(key, value);
708
+ // Also update logger default metadata
709
+ if (!_instance['config'].defaultMetadata) {
710
+ _instance['config'].defaultMetadata = {};
711
+ }
712
+ if (!_instance['config'].defaultMetadata.tags) {
713
+ _instance['config'].defaultMetadata.tags = {};
714
+ }
715
+ _instance['config'].defaultMetadata.tags[key] = value;
716
+ }
717
+ /**
718
+ * Set multiple tags
719
+ */
720
+ export function setTags(tags) {
721
+ for (const [key, value] of Object.entries(tags)) {
722
+ setTag(key, value);
723
+ }
724
+ }
725
+ /**
726
+ * Set extra data on the global scope
727
+ */
728
+ export function setExtra(key, value) {
729
+ ensureInitialized();
730
+ getGlobalScope().setExtra(key, value);
731
+ getCurrentScope().setExtra(key, value);
732
+ // Also update logger default metadata
733
+ if (!_instance['config'].defaultMetadata) {
734
+ _instance['config'].defaultMetadata = {};
735
+ }
736
+ if (!_instance['config'].defaultMetadata.extra) {
737
+ _instance['config'].defaultMetadata.extra = {};
738
+ }
739
+ _instance['config'].defaultMetadata.extra[key] = value;
740
+ }
741
+ /**
742
+ * Set multiple extras
743
+ */
744
+ export function setExtras(extras) {
745
+ for (const [key, value] of Object.entries(extras)) {
746
+ setExtra(key, value);
747
+ }
748
+ }
749
+ /**
750
+ * Set a named context (Sentry-compatible)
751
+ *
752
+ * @param name - Context name (e.g., 'browser', 'os', 'device', 'custom')
753
+ * @param context - Context data or null to clear
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * Sentry.setContext('payment', {
758
+ * processor: 'stripe',
759
+ * orderId: '12345',
760
+ * amount: 99.99,
761
+ * });
762
+ * ```
763
+ */
764
+ export function setContext(name, context) {
765
+ ensureInitialized();
766
+ getGlobalScope().setContext(name, context);
767
+ getCurrentScope().setContext(name, context);
768
+ }
769
+ // ============================================================================
770
+ // Classic Logging API
771
+ // ============================================================================
772
+ /**
773
+ * Log a fatal error
774
+ */
775
+ export function fatal(message, metadata) {
776
+ ensureInitialized();
777
+ _instance.fatal(message, mergeWithScopeMetadata(metadata));
778
+ }
779
+ /**
780
+ * Log an error
781
+ */
782
+ export function error(message, metadata) {
783
+ ensureInitialized();
784
+ _instance.error(message, mergeWithScopeMetadata(metadata));
785
+ }
786
+ /**
787
+ * Log a warning
788
+ */
789
+ export function warn(message, metadata) {
790
+ ensureInitialized();
791
+ _instance.warn(message, mergeWithScopeMetadata(metadata));
792
+ }
793
+ /**
794
+ * Log an info message
795
+ */
796
+ export function info(message, metadata) {
797
+ ensureInitialized();
798
+ _instance.info(message, mergeWithScopeMetadata(metadata));
799
+ }
800
+ /**
801
+ * Log a debug message
802
+ */
803
+ export function debug(message, metadata) {
804
+ ensureInitialized();
805
+ _instance.debug(message, mergeWithScopeMetadata(metadata));
806
+ }
807
+ /**
808
+ * Log a trace message
809
+ */
810
+ export function trace(message, metadata) {
811
+ ensureInitialized();
812
+ _instance.trace(message, mergeWithScopeMetadata(metadata));
813
+ }
814
+ /**
815
+ * Log an exception
816
+ */
817
+ export function exception(err, message, metadata) {
818
+ ensureInitialized();
819
+ _instance.exception(err, message, mergeWithScopeMetadata(metadata));
820
+ }
821
+ /**
822
+ * Generic log method
823
+ */
824
+ export function log(level, message, metadata) {
825
+ ensureInitialized();
826
+ _instance.log(level, message, mergeWithScopeMetadata(metadata));
827
+ }
828
+ // ============================================================================
829
+ // Session Management
830
+ // ============================================================================
831
+ export async function startSession(attributes) {
832
+ ensureInitialized();
833
+ return _instance.startSession(attributes);
834
+ }
835
+ export async function endSession(status) {
836
+ ensureInitialized();
837
+ await _instance.endSession(status);
838
+ }
839
+ export function getCurrentSession() {
840
+ if (!_instance)
841
+ return null;
842
+ return _instance.getCurrentSession();
843
+ }
844
+ // ============================================================================
845
+ // Query Methods
846
+ // ============================================================================
847
+ export async function getLogs(filter) {
848
+ ensureInitialized();
849
+ return _instance.getLogs(filter);
850
+ }
851
+ export async function getSessions(filter) {
852
+ ensureInitialized();
853
+ return _instance.getSessions(filter);
854
+ }
855
+ export async function getStats() {
856
+ ensureInitialized();
857
+ return _instance.getStats();
858
+ }
859
+ // ============================================================================
860
+ // Configuration
861
+ // ============================================================================
862
+ export function setMinLevel(level) {
863
+ ensureInitialized();
864
+ _instance.setMinLevel(level);
865
+ }
866
+ export function getMinLevel() {
867
+ ensureInitialized();
868
+ return _instance.getMinLevel();
869
+ }
870
+ export function isLevelEnabled(level) {
871
+ if (!_instance)
872
+ return false;
873
+ return _instance.isLevelEnabled(level);
874
+ }
875
+ export function child(name, defaultMetadata) {
876
+ ensureInitialized();
877
+ return _instance.child(name, defaultMetadata);
878
+ }
879
+ // ============================================================================
880
+ // Helpers
881
+ // ============================================================================
882
+ function ensureInitialized() {
883
+ if (!_instance || !_store) {
884
+ throw new Error('NodeLogger not initialized. Call init() first.');
885
+ }
886
+ }
887
+ /**
888
+ * Check if an error message matches any ignoreErrors pattern
889
+ */
890
+ function shouldIgnoreError(message) {
891
+ for (const pattern of _ignoreErrors) {
892
+ if (typeof pattern === 'string') {
893
+ // Substring match for strings
894
+ if (message.includes(pattern)) {
895
+ return true;
896
+ }
897
+ }
898
+ else if (pattern instanceof RegExp) {
899
+ // RegExp match
900
+ if (pattern.test(message)) {
901
+ return true;
902
+ }
903
+ }
904
+ }
905
+ return false;
906
+ }
907
+ /**
908
+ * Map Sentry severity level to LogLevel
909
+ */
910
+ function mapSeverityToLogLevel(severity) {
911
+ switch (severity) {
912
+ case 'fatal':
913
+ return 'fatal';
914
+ case 'error':
915
+ return 'error';
916
+ case 'warning':
917
+ return 'warn';
918
+ case 'log':
919
+ case 'info':
920
+ return 'info';
921
+ case 'debug':
922
+ return 'debug';
923
+ default:
924
+ return 'info';
925
+ }
926
+ }
927
+ /**
928
+ * Generate a random hex ID of specified length
929
+ */
930
+ function generateHexId(length) {
931
+ const bytes = new Uint8Array(length / 2);
932
+ crypto.getRandomValues(bytes);
933
+ return Array.from(bytes)
934
+ .map((b) => b.toString(16).padStart(2, '0'))
935
+ .join('');
936
+ }
937
+ // ============================================================================
938
+ // Performance Monitoring - Transaction Class
939
+ // ============================================================================
940
+ /**
941
+ * Transaction implementation for performance monitoring (Sentry-compatible)
942
+ *
943
+ * @remarks
944
+ * Transactions represent a unit of work and can contain measurements
945
+ * and contextual data. Logs captured during an active transaction
946
+ * automatically get traceId and spanId attached.
947
+ *
948
+ * @example
949
+ * ```typescript
950
+ * const transaction = Sentry.startTransaction({ name: 'checkout', op: 'http.server' });
951
+ * transaction.setMeasurement('ttfb', 250, 'millisecond');
952
+ * // ... do work ...
953
+ * transaction.finish();
954
+ * ```
955
+ */
956
+ export class Transaction {
957
+ name;
958
+ op;
959
+ description;
960
+ traceId;
961
+ spanId;
962
+ startTimestamp;
963
+ endTimestamp;
964
+ status;
965
+ tags;
966
+ data;
967
+ measurements;
968
+ constructor(context) {
969
+ this.name = context.name;
970
+ this.op = context.op;
971
+ this.description = context.description;
972
+ this.tags = context.tags ? { ...context.tags } : {};
973
+ this.data = {};
974
+ this.measurements = {};
975
+ this.traceId = generateHexId(32);
976
+ this.spanId = generateHexId(16);
977
+ this.startTimestamp = Date.now();
978
+ }
979
+ /**
980
+ * Set a tag on the transaction
981
+ */
982
+ setTag(key, value) {
983
+ if (!this.tags) {
984
+ this.tags = {};
985
+ }
986
+ this.tags[key] = value;
987
+ }
988
+ /**
989
+ * Set arbitrary data on the transaction
990
+ */
991
+ setData(key, value) {
992
+ if (!this.data) {
993
+ this.data = {};
994
+ }
995
+ this.data[key] = value;
996
+ }
997
+ /**
998
+ * Set a performance measurement
999
+ */
1000
+ setMeasurement(name, value, unit) {
1001
+ if (!this.measurements) {
1002
+ this.measurements = {};
1003
+ }
1004
+ this.measurements[name] = { value, unit };
1005
+ }
1006
+ /**
1007
+ * Set the transaction status
1008
+ */
1009
+ setStatus(status) {
1010
+ this.status = status;
1011
+ }
1012
+ /**
1013
+ * Finish the transaction and calculate duration
1014
+ */
1015
+ finish() {
1016
+ this.endTimestamp = Date.now();
1017
+ // Clear the active transaction if this is it
1018
+ if (_activeTransaction === this) {
1019
+ _activeTransaction = null;
1020
+ }
1021
+ // Set status to 'ok' if not already set
1022
+ if (!this.status) {
1023
+ this.status = 'ok';
1024
+ }
1025
+ }
1026
+ }
1027
+ /**
1028
+ * Start a new transaction for performance monitoring (Sentry-compatible)
1029
+ *
1030
+ * @param context - Transaction context with name and optional operation type
1031
+ * @returns Transaction object with methods to add measurements and finish
1032
+ *
1033
+ * @example
1034
+ * ```typescript
1035
+ * const transaction = Sentry.startTransaction({
1036
+ * name: 'checkout',
1037
+ * op: 'http.server',
1038
+ * });
1039
+ * transaction.setMeasurement('ttfb', 250, 'millisecond');
1040
+ * // ... do work ...
1041
+ * transaction.finish();
1042
+ * ```
1043
+ */
1044
+ export function startTransaction(context) {
1045
+ const transaction = new Transaction(context);
1046
+ _activeTransaction = transaction;
1047
+ return transaction;
1048
+ }
1049
+ /**
1050
+ * Get the currently active transaction (if any)
1051
+ *
1052
+ * @returns The active transaction or null
1053
+ */
1054
+ export function getActiveTransaction() {
1055
+ return _activeTransaction;
1056
+ }
1057
+ /**
1058
+ * Merge provided metadata with scope metadata and active transaction context
1059
+ */
1060
+ function mergeWithScopeMetadata(metadata) {
1061
+ const scopeMetadata = getCurrentScope().toMetadata();
1062
+ // Start with scope metadata
1063
+ const result = {
1064
+ ...scopeMetadata,
1065
+ ...metadata,
1066
+ tags: {
1067
+ ...scopeMetadata.tags,
1068
+ ...metadata?.tags,
1069
+ },
1070
+ extra: {
1071
+ ...scopeMetadata.extra,
1072
+ ...metadata?.extra,
1073
+ },
1074
+ };
1075
+ // Attach traceId and spanId from active transaction if present
1076
+ if (_activeTransaction) {
1077
+ result.traceId = _activeTransaction.traceId;
1078
+ result.spanId = _activeTransaction.spanId;
1079
+ }
1080
+ return result;
1081
+ }
1082
+ // ============================================================================
1083
+ // Re-exports
1084
+ // ============================================================================
1085
+ // Core classes
1086
+ export { Logger, createLogger } from './logger.js';
1087
+ export { Scope } from './scope.js';
1088
+ // Store providers
1089
+ export { MemoryStoreProvider } from './stores/memory.js';
1090
+ export { SQLiteStoreProvider } from './stores/sqlite.js';
1091
+ export { BaseStoreProvider } from './stores/base.js';
1092
+ // Scope utilities
1093
+ export { getCurrentScope, getGlobalScope, getIsolationScope, withScopeAsync, } from './scope.js';
1094
+ export { LogLevelValue } from './types.js';
1095
+ // Default export for convenience (Sentry-style)
1096
+ export default {
1097
+ // Initialization
1098
+ init,
1099
+ create,
1100
+ close,
1101
+ isInitialized,
1102
+ // Top 5 Sentry Functions
1103
+ captureException,
1104
+ captureMessage,
1105
+ setUser,
1106
+ addBreadcrumb,
1107
+ withScope,
1108
+ // Additional Sentry Functions
1109
+ configureScope,
1110
+ captureEvent,
1111
+ setTag,
1112
+ setTags,
1113
+ setExtra,
1114
+ setExtras,
1115
+ setContext,
1116
+ // Classic Logging API
1117
+ fatal,
1118
+ error,
1119
+ warn,
1120
+ info,
1121
+ debug,
1122
+ trace,
1123
+ exception,
1124
+ log,
1125
+ // Sessions
1126
+ startSession,
1127
+ endSession,
1128
+ getCurrentSession,
1129
+ // Queries
1130
+ getLogs,
1131
+ getSessions,
1132
+ getStats,
1133
+ // Config
1134
+ setMinLevel,
1135
+ getMinLevel,
1136
+ isLevelEnabled,
1137
+ child,
1138
+ // Scope Access
1139
+ getCurrentScope,
1140
+ getGlobalScope,
1141
+ getIsolationScope,
1142
+ // Performance Monitoring
1143
+ startTransaction,
1144
+ getActiveTransaction,
1145
+ Transaction,
1146
+ };
1147
+ //# sourceMappingURL=index.js.map