@tracelog/lib 0.7.2 → 0.8.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/README.md +18 -1
- package/dist/browser/tracelog.esm.js +687 -655
- package/dist/browser/tracelog.esm.js.map +1 -1
- package/dist/browser/tracelog.js +2 -2
- package/dist/browser/tracelog.js.map +1 -1
- package/dist/cjs/constants/config.constants.d.ts +1 -1
- package/dist/cjs/constants/config.constants.js +2 -2
- package/dist/cjs/constants/error.constants.d.ts +6 -0
- package/dist/cjs/constants/error.constants.js +10 -1
- package/dist/cjs/managers/sender.manager.d.ts +2 -0
- package/dist/cjs/managers/sender.manager.js +74 -9
- package/dist/cjs/types/error.types.d.ts +11 -0
- package/dist/cjs/types/error.types.js +22 -0
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.js +1 -0
- package/dist/esm/constants/config.constants.d.ts +1 -1
- package/dist/esm/constants/config.constants.js +2 -2
- package/dist/esm/constants/error.constants.d.ts +6 -0
- package/dist/esm/constants/error.constants.js +9 -0
- package/dist/esm/managers/sender.manager.d.ts +2 -0
- package/dist/esm/managers/sender.manager.js +76 -11
- package/dist/esm/types/error.types.d.ts +11 -0
- package/dist/esm/types/error.types.js +18 -0
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.js +1 -0
- package/package.json +9 -1
|
@@ -7,7 +7,7 @@ export declare const DUPLICATE_EVENT_THRESHOLD_MS = 500;
|
|
|
7
7
|
export declare const EVENT_SENT_INTERVAL_MS = 10000;
|
|
8
8
|
export declare const SCROLL_DEBOUNCE_TIME_MS = 250;
|
|
9
9
|
export declare const DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;
|
|
10
|
-
export declare const EVENT_EXPIRY_HOURS =
|
|
10
|
+
export declare const EVENT_EXPIRY_HOURS = 2;
|
|
11
11
|
export declare const EVENT_PERSISTENCE_MAX_AGE_MS: number;
|
|
12
12
|
export declare const MAX_EVENTS_QUEUE_LENGTH = 100;
|
|
13
13
|
export declare const MAX_RETRIES = 3;
|
|
@@ -16,8 +16,8 @@ exports.EVENT_SENT_INTERVAL_MS = 10000; // 10 seconds
|
|
|
16
16
|
exports.SCROLL_DEBOUNCE_TIME_MS = 250;
|
|
17
17
|
exports.DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;
|
|
18
18
|
// Event expiry
|
|
19
|
-
exports.EVENT_EXPIRY_HOURS =
|
|
20
|
-
exports.EVENT_PERSISTENCE_MAX_AGE_MS =
|
|
19
|
+
exports.EVENT_EXPIRY_HOURS = 2;
|
|
20
|
+
exports.EVENT_PERSISTENCE_MAX_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
21
21
|
// ============================================================================
|
|
22
22
|
// LIMITS & RETRIES
|
|
23
23
|
// ============================================================================
|
|
@@ -32,3 +32,9 @@ export declare const MAX_TRACKED_ERRORS_HARD_LIMIT: number;
|
|
|
32
32
|
* Controls what percentage of errors are actually reported
|
|
33
33
|
*/
|
|
34
34
|
export declare const DEFAULT_ERROR_SAMPLING_RATE = 0.1;
|
|
35
|
+
/**
|
|
36
|
+
* Time window for throttling permanent error logs in milliseconds
|
|
37
|
+
* Same error status codes are logged at most once per this window
|
|
38
|
+
* Prevents console spam when backend repeatedly returns 4xx errors
|
|
39
|
+
*/
|
|
40
|
+
export declare const PERMANENT_ERROR_LOG_THROTTLE_MS = 60000;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Centralizes patterns and limits for error tracking and data protection
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.DEFAULT_ERROR_SAMPLING_RATE = exports.MAX_TRACKED_ERRORS_HARD_LIMIT = exports.MAX_TRACKED_ERRORS = exports.ERROR_SUPPRESSION_WINDOW_MS = exports.MAX_ERROR_MESSAGE_LENGTH = exports.PII_PATTERNS = void 0;
|
|
7
|
+
exports.PERMANENT_ERROR_LOG_THROTTLE_MS = exports.DEFAULT_ERROR_SAMPLING_RATE = exports.MAX_TRACKED_ERRORS_HARD_LIMIT = exports.MAX_TRACKED_ERRORS = exports.ERROR_SUPPRESSION_WINDOW_MS = exports.MAX_ERROR_MESSAGE_LENGTH = exports.PII_PATTERNS = void 0;
|
|
8
8
|
// ============================================================================
|
|
9
9
|
// PII SANITIZATION PATTERNS
|
|
10
10
|
// ============================================================================
|
|
@@ -59,3 +59,12 @@ exports.MAX_TRACKED_ERRORS_HARD_LIMIT = exports.MAX_TRACKED_ERRORS * 2;
|
|
|
59
59
|
* Controls what percentage of errors are actually reported
|
|
60
60
|
*/
|
|
61
61
|
exports.DEFAULT_ERROR_SAMPLING_RATE = 0.1; // 10% of errors
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// PERMANENT ERROR LOGGING
|
|
64
|
+
// ============================================================================
|
|
65
|
+
/**
|
|
66
|
+
* Time window for throttling permanent error logs in milliseconds
|
|
67
|
+
* Same error status codes are logged at most once per this window
|
|
68
|
+
* Prevents console spam when backend repeatedly returns 4xx errors
|
|
69
|
+
*/
|
|
70
|
+
exports.PERMANENT_ERROR_LOG_THROTTLE_MS = 60000; // 1 minute
|
|
@@ -10,6 +10,7 @@ export declare class SenderManager extends StateManager {
|
|
|
10
10
|
private retryTimeoutId;
|
|
11
11
|
private retryCount;
|
|
12
12
|
private isRetrying;
|
|
13
|
+
private lastPermanentErrorLog;
|
|
13
14
|
constructor(storeManager: StorageManager);
|
|
14
15
|
private getQueueStorageKey;
|
|
15
16
|
sendEventsQueueSync(body: BaseEventsQueueDto): boolean;
|
|
@@ -33,5 +34,6 @@ export declare class SenderManager extends StateManager {
|
|
|
33
34
|
private simulateSuccessfulSend;
|
|
34
35
|
private isSendBeaconAvailable;
|
|
35
36
|
private clearRetryTimeout;
|
|
37
|
+
private logPermanentError;
|
|
36
38
|
}
|
|
37
39
|
export {};
|
|
@@ -11,6 +11,7 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
11
11
|
this.retryTimeoutId = null;
|
|
12
12
|
this.retryCount = 0;
|
|
13
13
|
this.isRetrying = false;
|
|
14
|
+
this.lastPermanentErrorLog = null;
|
|
14
15
|
this.storeManager = storeManager;
|
|
15
16
|
}
|
|
16
17
|
getQueueStorageKey() {
|
|
@@ -42,17 +43,31 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
42
43
|
(0, utils_1.log)('warn', 'Failed to persist events, attempting immediate send');
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
try {
|
|
47
|
+
const success = await this.send(body);
|
|
48
|
+
if (success) {
|
|
49
|
+
this.clearPersistedEvents();
|
|
50
|
+
this.resetRetryState();
|
|
51
|
+
callbacks?.onSuccess?.(body.events.length, body.events, body);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.scheduleRetry(body, callbacks);
|
|
55
|
+
callbacks?.onFailure?.();
|
|
56
|
+
}
|
|
57
|
+
return success;
|
|
50
58
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Permanent errors should not be retried
|
|
61
|
+
if (error instanceof types_1.PermanentError) {
|
|
62
|
+
this.logPermanentError('Permanent error, not retrying', error);
|
|
63
|
+
this.clearPersistedEvents();
|
|
64
|
+
this.resetRetryState();
|
|
65
|
+
callbacks?.onFailure?.();
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// Re-throw unexpected errors
|
|
69
|
+
throw error;
|
|
54
70
|
}
|
|
55
|
-
return success;
|
|
56
71
|
}
|
|
57
72
|
async recoverPersistedEvents(callbacks) {
|
|
58
73
|
try {
|
|
@@ -74,6 +89,14 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
catch (error) {
|
|
92
|
+
// Permanent errors should clear persisted events immediately
|
|
93
|
+
if (error instanceof types_1.PermanentError) {
|
|
94
|
+
this.logPermanentError('Permanent error during recovery, clearing persisted events', error);
|
|
95
|
+
this.clearPersistedEvents();
|
|
96
|
+
this.resetRetryState();
|
|
97
|
+
callbacks?.onFailure?.();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
77
100
|
(0, utils_1.log)('error', 'Failed to recover persisted events', { error });
|
|
78
101
|
this.clearPersistedEvents();
|
|
79
102
|
}
|
|
@@ -105,6 +128,10 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
105
128
|
return response.ok;
|
|
106
129
|
}
|
|
107
130
|
catch (error) {
|
|
131
|
+
// Re-throw PermanentError to be caught by caller
|
|
132
|
+
if (error instanceof types_1.PermanentError) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
108
135
|
(0, utils_1.log)('error', 'Send request failed', {
|
|
109
136
|
error,
|
|
110
137
|
data: {
|
|
@@ -130,6 +157,13 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
130
157
|
},
|
|
131
158
|
});
|
|
132
159
|
if (!response.ok) {
|
|
160
|
+
// 4xx errors are permanent client errors - don't retry
|
|
161
|
+
const isPermanentError = response.status >= 400 && response.status < 500;
|
|
162
|
+
if (isPermanentError) {
|
|
163
|
+
// Note: Logging handled by caller with throttling to prevent spam
|
|
164
|
+
throw new types_1.PermanentError(`HTTP ${response.status}: ${response.statusText}`, response.status);
|
|
165
|
+
}
|
|
166
|
+
// 5xx or other errors - retry
|
|
133
167
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
134
168
|
}
|
|
135
169
|
return response;
|
|
@@ -265,6 +299,25 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
265
299
|
this.scheduleRetry(body, originalCallbacks);
|
|
266
300
|
}
|
|
267
301
|
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
// If it's a permanent error, don't retry
|
|
304
|
+
if (error instanceof types_1.PermanentError) {
|
|
305
|
+
this.logPermanentError('Permanent error detected during retry, giving up', error);
|
|
306
|
+
this.clearPersistedEvents();
|
|
307
|
+
this.resetRetryState();
|
|
308
|
+
originalCallbacks?.onFailure?.();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// For other errors, continue normal retry flow
|
|
312
|
+
if (this.retryCount >= constants_1.MAX_RETRIES) {
|
|
313
|
+
this.clearPersistedEvents();
|
|
314
|
+
this.resetRetryState();
|
|
315
|
+
originalCallbacks?.onFailure?.();
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
this.scheduleRetry(body, originalCallbacks);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
268
321
|
finally {
|
|
269
322
|
this.isRetrying = false;
|
|
270
323
|
}
|
|
@@ -287,5 +340,17 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
287
340
|
this.retryTimeoutId = null;
|
|
288
341
|
}
|
|
289
342
|
}
|
|
343
|
+
logPermanentError(context, error) {
|
|
344
|
+
const now = Date.now();
|
|
345
|
+
const shouldLog = !this.lastPermanentErrorLog ||
|
|
346
|
+
this.lastPermanentErrorLog.statusCode !== error.statusCode ||
|
|
347
|
+
now - this.lastPermanentErrorLog.timestamp >= constants_1.PERMANENT_ERROR_LOG_THROTTLE_MS;
|
|
348
|
+
if (shouldLog) {
|
|
349
|
+
(0, utils_1.log)('error', context, {
|
|
350
|
+
data: { status: error.statusCode, message: error.message },
|
|
351
|
+
});
|
|
352
|
+
this.lastPermanentErrorLog = { statusCode: error.statusCode, timestamp: now };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
290
355
|
}
|
|
291
356
|
exports.SenderManager = SenderManager;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for TraceLog
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a permanent HTTP error (4xx) that should not be retried
|
|
6
|
+
* Examples: 400 Bad Request, 403 Forbidden, 404 Not Found
|
|
7
|
+
*/
|
|
8
|
+
export declare class PermanentError extends Error {
|
|
9
|
+
readonly statusCode?: number | undefined;
|
|
10
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Custom error types for TraceLog
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PermanentError = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Represents a permanent HTTP error (4xx) that should not be retried
|
|
9
|
+
* Examples: 400 Bad Request, 403 Forbidden, 404 Not Found
|
|
10
|
+
*/
|
|
11
|
+
class PermanentError extends Error {
|
|
12
|
+
constructor(message, statusCode) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.name = 'PermanentError';
|
|
16
|
+
// Maintain proper stack trace for where our error was thrown (only available on V8)
|
|
17
|
+
if (Error.captureStackTrace) {
|
|
18
|
+
Error.captureStackTrace(this, PermanentError);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.PermanentError = PermanentError;
|
package/dist/cjs/types/index.js
CHANGED
|
@@ -18,6 +18,7 @@ __exportStar(require("./common.types"), exports);
|
|
|
18
18
|
__exportStar(require("./config.types"), exports);
|
|
19
19
|
__exportStar(require("./device.types"), exports);
|
|
20
20
|
__exportStar(require("./emitter.types"), exports);
|
|
21
|
+
__exportStar(require("./error.types"), exports);
|
|
21
22
|
__exportStar(require("./event.types"), exports);
|
|
22
23
|
__exportStar(require("./log.types"), exports);
|
|
23
24
|
__exportStar(require("./mode.types"), exports);
|
|
@@ -7,7 +7,7 @@ export declare const DUPLICATE_EVENT_THRESHOLD_MS = 500;
|
|
|
7
7
|
export declare const EVENT_SENT_INTERVAL_MS = 10000;
|
|
8
8
|
export declare const SCROLL_DEBOUNCE_TIME_MS = 250;
|
|
9
9
|
export declare const DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;
|
|
10
|
-
export declare const EVENT_EXPIRY_HOURS =
|
|
10
|
+
export declare const EVENT_EXPIRY_HOURS = 2;
|
|
11
11
|
export declare const EVENT_PERSISTENCE_MAX_AGE_MS: number;
|
|
12
12
|
export declare const MAX_EVENTS_QUEUE_LENGTH = 100;
|
|
13
13
|
export declare const MAX_RETRIES = 3;
|
|
@@ -12,8 +12,8 @@ export const EVENT_SENT_INTERVAL_MS = 10000; // 10 seconds
|
|
|
12
12
|
export const SCROLL_DEBOUNCE_TIME_MS = 250;
|
|
13
13
|
export const DEFAULT_VISIBILITY_TIMEOUT_MS = 2000;
|
|
14
14
|
// Event expiry
|
|
15
|
-
export const EVENT_EXPIRY_HOURS =
|
|
16
|
-
export const EVENT_PERSISTENCE_MAX_AGE_MS =
|
|
15
|
+
export const EVENT_EXPIRY_HOURS = 2;
|
|
16
|
+
export const EVENT_PERSISTENCE_MAX_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// LIMITS & RETRIES
|
|
19
19
|
// ============================================================================
|
|
@@ -32,3 +32,9 @@ export declare const MAX_TRACKED_ERRORS_HARD_LIMIT: number;
|
|
|
32
32
|
* Controls what percentage of errors are actually reported
|
|
33
33
|
*/
|
|
34
34
|
export declare const DEFAULT_ERROR_SAMPLING_RATE = 0.1;
|
|
35
|
+
/**
|
|
36
|
+
* Time window for throttling permanent error logs in milliseconds
|
|
37
|
+
* Same error status codes are logged at most once per this window
|
|
38
|
+
* Prevents console spam when backend repeatedly returns 4xx errors
|
|
39
|
+
*/
|
|
40
|
+
export declare const PERMANENT_ERROR_LOG_THROTTLE_MS = 60000;
|
|
@@ -56,3 +56,12 @@ export const MAX_TRACKED_ERRORS_HARD_LIMIT = MAX_TRACKED_ERRORS * 2;
|
|
|
56
56
|
* Controls what percentage of errors are actually reported
|
|
57
57
|
*/
|
|
58
58
|
export const DEFAULT_ERROR_SAMPLING_RATE = 0.1; // 10% of errors
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// PERMANENT ERROR LOGGING
|
|
61
|
+
// ============================================================================
|
|
62
|
+
/**
|
|
63
|
+
* Time window for throttling permanent error logs in milliseconds
|
|
64
|
+
* Same error status codes are logged at most once per this window
|
|
65
|
+
* Prevents console spam when backend repeatedly returns 4xx errors
|
|
66
|
+
*/
|
|
67
|
+
export const PERMANENT_ERROR_LOG_THROTTLE_MS = 60000; // 1 minute
|
|
@@ -10,6 +10,7 @@ export declare class SenderManager extends StateManager {
|
|
|
10
10
|
private retryTimeoutId;
|
|
11
11
|
private retryCount;
|
|
12
12
|
private isRetrying;
|
|
13
|
+
private lastPermanentErrorLog;
|
|
13
14
|
constructor(storeManager: StorageManager);
|
|
14
15
|
private getQueueStorageKey;
|
|
15
16
|
sendEventsQueueSync(body: BaseEventsQueueDto): boolean;
|
|
@@ -33,5 +34,6 @@ export declare class SenderManager extends StateManager {
|
|
|
33
34
|
private simulateSuccessfulSend;
|
|
34
35
|
private isSendBeaconAvailable;
|
|
35
36
|
private clearRetryTimeout;
|
|
37
|
+
private logPermanentError;
|
|
36
38
|
}
|
|
37
39
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { QUEUE_KEY, EVENT_EXPIRY_HOURS, MAX_RETRIES, RETRY_DELAY_MS, REQUEST_TIMEOUT_MS } from '../constants';
|
|
2
|
-
import { SpecialApiUrl } from '../types';
|
|
1
|
+
import { QUEUE_KEY, EVENT_EXPIRY_HOURS, MAX_RETRIES, RETRY_DELAY_MS, REQUEST_TIMEOUT_MS, PERMANENT_ERROR_LOG_THROTTLE_MS, } from '../constants';
|
|
2
|
+
import { SpecialApiUrl, PermanentError } from '../types';
|
|
3
3
|
import { log } from '../utils';
|
|
4
4
|
import { StateManager } from './state.manager';
|
|
5
5
|
export class SenderManager extends StateManager {
|
|
@@ -8,6 +8,7 @@ export class SenderManager extends StateManager {
|
|
|
8
8
|
this.retryTimeoutId = null;
|
|
9
9
|
this.retryCount = 0;
|
|
10
10
|
this.isRetrying = false;
|
|
11
|
+
this.lastPermanentErrorLog = null;
|
|
11
12
|
this.storeManager = storeManager;
|
|
12
13
|
}
|
|
13
14
|
getQueueStorageKey() {
|
|
@@ -39,17 +40,31 @@ export class SenderManager extends StateManager {
|
|
|
39
40
|
log('warn', 'Failed to persist events, attempting immediate send');
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
try {
|
|
44
|
+
const success = await this.send(body);
|
|
45
|
+
if (success) {
|
|
46
|
+
this.clearPersistedEvents();
|
|
47
|
+
this.resetRetryState();
|
|
48
|
+
callbacks?.onSuccess?.(body.events.length, body.events, body);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.scheduleRetry(body, callbacks);
|
|
52
|
+
callbacks?.onFailure?.();
|
|
53
|
+
}
|
|
54
|
+
return success;
|
|
47
55
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Permanent errors should not be retried
|
|
58
|
+
if (error instanceof PermanentError) {
|
|
59
|
+
this.logPermanentError('Permanent error, not retrying', error);
|
|
60
|
+
this.clearPersistedEvents();
|
|
61
|
+
this.resetRetryState();
|
|
62
|
+
callbacks?.onFailure?.();
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
// Re-throw unexpected errors
|
|
66
|
+
throw error;
|
|
51
67
|
}
|
|
52
|
-
return success;
|
|
53
68
|
}
|
|
54
69
|
async recoverPersistedEvents(callbacks) {
|
|
55
70
|
try {
|
|
@@ -71,6 +86,14 @@ export class SenderManager extends StateManager {
|
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
88
|
catch (error) {
|
|
89
|
+
// Permanent errors should clear persisted events immediately
|
|
90
|
+
if (error instanceof PermanentError) {
|
|
91
|
+
this.logPermanentError('Permanent error during recovery, clearing persisted events', error);
|
|
92
|
+
this.clearPersistedEvents();
|
|
93
|
+
this.resetRetryState();
|
|
94
|
+
callbacks?.onFailure?.();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
74
97
|
log('error', 'Failed to recover persisted events', { error });
|
|
75
98
|
this.clearPersistedEvents();
|
|
76
99
|
}
|
|
@@ -102,6 +125,10 @@ export class SenderManager extends StateManager {
|
|
|
102
125
|
return response.ok;
|
|
103
126
|
}
|
|
104
127
|
catch (error) {
|
|
128
|
+
// Re-throw PermanentError to be caught by caller
|
|
129
|
+
if (error instanceof PermanentError) {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
105
132
|
log('error', 'Send request failed', {
|
|
106
133
|
error,
|
|
107
134
|
data: {
|
|
@@ -127,6 +154,13 @@ export class SenderManager extends StateManager {
|
|
|
127
154
|
},
|
|
128
155
|
});
|
|
129
156
|
if (!response.ok) {
|
|
157
|
+
// 4xx errors are permanent client errors - don't retry
|
|
158
|
+
const isPermanentError = response.status >= 400 && response.status < 500;
|
|
159
|
+
if (isPermanentError) {
|
|
160
|
+
// Note: Logging handled by caller with throttling to prevent spam
|
|
161
|
+
throw new PermanentError(`HTTP ${response.status}: ${response.statusText}`, response.status);
|
|
162
|
+
}
|
|
163
|
+
// 5xx or other errors - retry
|
|
130
164
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
131
165
|
}
|
|
132
166
|
return response;
|
|
@@ -262,6 +296,25 @@ export class SenderManager extends StateManager {
|
|
|
262
296
|
this.scheduleRetry(body, originalCallbacks);
|
|
263
297
|
}
|
|
264
298
|
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
// If it's a permanent error, don't retry
|
|
301
|
+
if (error instanceof PermanentError) {
|
|
302
|
+
this.logPermanentError('Permanent error detected during retry, giving up', error);
|
|
303
|
+
this.clearPersistedEvents();
|
|
304
|
+
this.resetRetryState();
|
|
305
|
+
originalCallbacks?.onFailure?.();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
// For other errors, continue normal retry flow
|
|
309
|
+
if (this.retryCount >= MAX_RETRIES) {
|
|
310
|
+
this.clearPersistedEvents();
|
|
311
|
+
this.resetRetryState();
|
|
312
|
+
originalCallbacks?.onFailure?.();
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
this.scheduleRetry(body, originalCallbacks);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
265
318
|
finally {
|
|
266
319
|
this.isRetrying = false;
|
|
267
320
|
}
|
|
@@ -284,4 +337,16 @@ export class SenderManager extends StateManager {
|
|
|
284
337
|
this.retryTimeoutId = null;
|
|
285
338
|
}
|
|
286
339
|
}
|
|
340
|
+
logPermanentError(context, error) {
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
const shouldLog = !this.lastPermanentErrorLog ||
|
|
343
|
+
this.lastPermanentErrorLog.statusCode !== error.statusCode ||
|
|
344
|
+
now - this.lastPermanentErrorLog.timestamp >= PERMANENT_ERROR_LOG_THROTTLE_MS;
|
|
345
|
+
if (shouldLog) {
|
|
346
|
+
log('error', context, {
|
|
347
|
+
data: { status: error.statusCode, message: error.message },
|
|
348
|
+
});
|
|
349
|
+
this.lastPermanentErrorLog = { statusCode: error.statusCode, timestamp: now };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
287
352
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for TraceLog
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a permanent HTTP error (4xx) that should not be retried
|
|
6
|
+
* Examples: 400 Bad Request, 403 Forbidden, 404 Not Found
|
|
7
|
+
*/
|
|
8
|
+
export declare class PermanentError extends Error {
|
|
9
|
+
readonly statusCode?: number | undefined;
|
|
10
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for TraceLog
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a permanent HTTP error (4xx) that should not be retried
|
|
6
|
+
* Examples: 400 Bad Request, 403 Forbidden, 404 Not Found
|
|
7
|
+
*/
|
|
8
|
+
export class PermanentError extends Error {
|
|
9
|
+
constructor(message, statusCode) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.name = 'PermanentError';
|
|
13
|
+
// Maintain proper stack trace for where our error was thrown (only available on V8)
|
|
14
|
+
if (Error.captureStackTrace) {
|
|
15
|
+
Error.captureStackTrace(this, PermanentError);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/esm/types/index.js
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tracelog/lib",
|
|
3
3
|
"description": "JavaScript library for web analytics and real-time event tracking",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.8.0",
|
|
6
6
|
"main": "./dist/cjs/public-api.js",
|
|
7
7
|
"module": "./dist/esm/public-api.js",
|
|
8
8
|
"types": "./dist/esm/public-api.d.ts",
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
"import": "./dist/esm/public-api.js",
|
|
16
16
|
"require": "./dist/cjs/public-api.js"
|
|
17
17
|
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/nacorga/tracelog-lib.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/nacorga/tracelog-lib#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/nacorga/tracelog-lib/issues"
|
|
25
|
+
},
|
|
18
26
|
"publishConfig": {
|
|
19
27
|
"access": "public"
|
|
20
28
|
},
|