@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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/dist/index.d.ts +607 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1147 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +180 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +389 -0
- package/dist/logger.js.map +1 -0
- package/dist/scope.d.ts +242 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +373 -0
- package/dist/scope.js.map +1 -0
- package/dist/stores/base.d.ts +127 -0
- package/dist/stores/base.d.ts.map +1 -0
- package/dist/stores/base.js +288 -0
- package/dist/stores/base.js.map +1 -0
- package/dist/stores/index.d.ts +12 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +12 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/stores/memory.d.ts +131 -0
- package/dist/stores/memory.d.ts.map +1 -0
- package/dist/stores/memory.js +284 -0
- package/dist/stores/memory.js.map +1 -0
- package/dist/stores/sqlite.d.ts +204 -0
- package/dist/stores/sqlite.d.ts.map +1 -0
- package/dist/stores/sqlite.js +608 -0
- package/dist/stores/sqlite.js.map +1 -0
- package/dist/types.d.ts +607 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/package.json +84 -0
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
|