@djangocfg/monitor 2.1.238 → 2.1.240
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/client.cjs +24 -22
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +4 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.mjs +24 -22
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/package.json +2 -2
- package/src/client/capture/console.ts +4 -4
- package/src/client/capture/js-errors.ts +4 -5
- package/src/client/capture/network.ts +5 -4
- package/src/client/capture/validation.ts +5 -4
- package/src/client/constants.ts +6 -0
- package/src/client/store/index.ts +9 -11
- package/src/types/config.ts +4 -0
package/dist/index.d.cts
CHANGED
|
@@ -85,6 +85,10 @@ interface MonitorConfig {
|
|
|
85
85
|
captureJsErrors?: boolean;
|
|
86
86
|
/** Log debug info to console. Default: false */
|
|
87
87
|
debug?: boolean;
|
|
88
|
+
/** Deduplication TTL in ms for per-capture-source filtering. Default: 5000 */
|
|
89
|
+
dedupeTtl?: number;
|
|
90
|
+
/** Deduplication TTL in ms for the global store-level filter (cross-source). Default: 30000 */
|
|
91
|
+
dedupeStoreTtl?: number;
|
|
88
92
|
}
|
|
89
93
|
interface ServerMonitorConfig {
|
|
90
94
|
/** Base URL for the django-cfg backend (absolute URL required on server) */
|
package/dist/index.d.ts
CHANGED
|
@@ -85,6 +85,10 @@ interface MonitorConfig {
|
|
|
85
85
|
captureJsErrors?: boolean;
|
|
86
86
|
/** Log debug info to console. Default: false */
|
|
87
87
|
debug?: boolean;
|
|
88
|
+
/** Deduplication TTL in ms for per-capture-source filtering. Default: 5000 */
|
|
89
|
+
dedupeTtl?: number;
|
|
90
|
+
/** Deduplication TTL in ms for the global store-level filter (cross-source). Default: 30000 */
|
|
91
|
+
dedupeStoreTtl?: number;
|
|
88
92
|
}
|
|
89
93
|
interface ServerMonitorConfig {
|
|
90
94
|
/** Base URL for the django-cfg backend (absolute URL required on server) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/monitor",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.240",
|
|
4
4
|
"description": "Browser error and event monitoring SDK for django-cfg backends. Captures JS errors, network failures, console logs, and performance metrics.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
86
|
+
"@djangocfg/typescript-config": "^2.1.240",
|
|
87
87
|
"p-retry": "^6.2.0",
|
|
88
88
|
"@types/node": "^24.7.2",
|
|
89
89
|
"@types/react": "^19.1.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computeFingerprint } from './fingerprint'
|
|
2
2
|
import { getSessionId } from './session'
|
|
3
3
|
import { monitorStore } from '../store'
|
|
4
|
-
import { MONITOR_INGEST_PATTERN } from '../constants'
|
|
4
|
+
import { MONITOR_INGEST_PATTERN, DEFAULT_DEDUPE_TTL } from '../constants'
|
|
5
5
|
import { EventType, EventLevel } from '../../_api'
|
|
6
6
|
import type { FrontendEventIngestRequestEventType, FrontendEventIngestRequestLevel } from '../../_api/generated/cfg_monitor/enums'
|
|
7
7
|
|
|
@@ -18,14 +18,14 @@ const typeMap: Record<ConsoleLevel, FrontendEventIngestRequestEventType> = {
|
|
|
18
18
|
|
|
19
19
|
const ARG_MAX = 500
|
|
20
20
|
|
|
21
|
-
// Dedup: skip re-sending the same console message within TTL
|
|
22
|
-
const CONSOLE_DEDUP_TTL = 5 * 60 * 1000 // 5 minutes
|
|
21
|
+
// Dedup: skip re-sending the same console message within TTL (read from config)
|
|
23
22
|
const recentConsoleFingerprints = new Map<string, number>()
|
|
24
23
|
|
|
25
24
|
function isRecentConsole(fingerprint: string): boolean {
|
|
25
|
+
const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
|
|
26
26
|
const now = Date.now()
|
|
27
27
|
const last = recentConsoleFingerprints.get(fingerprint)
|
|
28
|
-
if (last !== undefined && now - last <
|
|
28
|
+
if (last !== undefined && now - last < ttl) return true
|
|
29
29
|
recentConsoleFingerprints.set(fingerprint, now)
|
|
30
30
|
if (recentConsoleFingerprints.size > 100) {
|
|
31
31
|
const oldest = [...recentConsoleFingerprints.entries()].sort((a, b) => a[1] - b[1])[0]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computeFingerprint } from './fingerprint'
|
|
2
2
|
import { getSessionId } from './session'
|
|
3
3
|
import { monitorStore } from '../store'
|
|
4
|
-
import { MONITOR_INGEST_PATTERN } from '../constants'
|
|
4
|
+
import { MONITOR_INGEST_PATTERN, DEFAULT_DEDUPE_TTL } from '../constants'
|
|
5
5
|
import { EventType, EventLevel } from '../../_api'
|
|
6
6
|
|
|
7
7
|
const MSG_MAX = 2000
|
|
@@ -29,17 +29,16 @@ function isHydrationNoise(msg: string): boolean {
|
|
|
29
29
|
return HYDRATION_NOISE.some((p) => p.test(msg))
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// Client-side dedup: skip re-sending the same fingerprint within
|
|
32
|
+
// Client-side dedup: skip re-sending the same fingerprint within dedupeTtl.
|
|
33
33
|
// Protects against crash-loop pages flooding the ingest endpoint.
|
|
34
|
-
const CLIENT_DEDUP_TTL = 5 * 60 * 1000 // 5 minutes
|
|
35
34
|
const recentFingerprints = new Map<string, number>()
|
|
36
35
|
|
|
37
36
|
function isRecentlySent(fingerprint: string): boolean {
|
|
37
|
+
const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
|
|
38
38
|
const now = Date.now()
|
|
39
39
|
const last = recentFingerprints.get(fingerprint)
|
|
40
|
-
if (last !== undefined && now - last <
|
|
40
|
+
if (last !== undefined && now - last < ttl) return true
|
|
41
41
|
recentFingerprints.set(fingerprint, now)
|
|
42
|
-
// Cap map size to avoid memory growth — evict oldest entry when over limit
|
|
43
42
|
if (recentFingerprints.size > 100) {
|
|
44
43
|
const oldest = [...recentFingerprints.entries()].sort((a, b) => a[1] - b[1])[0]
|
|
45
44
|
recentFingerprints.delete(oldest[0])
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { getSessionId } from './session'
|
|
2
2
|
import { monitorStore } from '../store'
|
|
3
|
+
import { DEFAULT_DEDUPE_TTL } from '../constants'
|
|
3
4
|
import { EventType, EventLevel } from '../../_api'
|
|
4
5
|
|
|
5
|
-
// Dedup: skip re-sending the same network error within TTL
|
|
6
|
-
const NETWORK_DEDUP_TTL = 5_000 // 5 seconds
|
|
6
|
+
// Dedup: skip re-sending the same network error within TTL (read from config)
|
|
7
7
|
const recentNetworkErrors = new Map<string, number>()
|
|
8
8
|
|
|
9
9
|
function isRecentNetwork(key: string): boolean {
|
|
10
|
+
const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
|
|
10
11
|
const now = Date.now()
|
|
11
12
|
const last = recentNetworkErrors.get(key)
|
|
12
|
-
if (last !== undefined && now - last <
|
|
13
|
+
if (last !== undefined && now - last < ttl) return true
|
|
13
14
|
recentNetworkErrors.set(key, now)
|
|
14
15
|
if (recentNetworkErrors.size > 100) {
|
|
15
16
|
for (const [k, ts] of recentNetworkErrors) {
|
|
16
|
-
if (now - ts >
|
|
17
|
+
if (now - ts > ttl) recentNetworkErrors.delete(k)
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
return false
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { getSessionId } from './session'
|
|
2
2
|
import { monitorStore } from '../store'
|
|
3
|
+
import { DEFAULT_DEDUPE_TTL } from '../constants'
|
|
3
4
|
import { EventType, EventLevel } from '../../_api'
|
|
4
5
|
|
|
5
|
-
// Dedup: skip re-sending the same validation error within TTL
|
|
6
|
-
const VALIDATION_DEDUP_TTL = 10_000 // 10 seconds
|
|
6
|
+
// Dedup: skip re-sending the same validation error within TTL (read from config)
|
|
7
7
|
const recentValidations = new Map<string, number>()
|
|
8
8
|
|
|
9
9
|
function isRecentValidation(key: string): boolean {
|
|
10
|
+
const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
|
|
10
11
|
const now = Date.now()
|
|
11
12
|
const last = recentValidations.get(key)
|
|
12
|
-
if (last !== undefined && now - last <
|
|
13
|
+
if (last !== undefined && now - last < ttl) return true
|
|
13
14
|
recentValidations.set(key, now)
|
|
14
15
|
if (recentValidations.size > 50) {
|
|
15
16
|
for (const [k, ts] of recentValidations) {
|
|
16
|
-
if (now - ts >
|
|
17
|
+
if (now - ts > ttl) recentValidations.delete(k)
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
return false
|
package/src/client/constants.ts
CHANGED
|
@@ -5,3 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
/** Matches any request to the monitor ingest endpoint */
|
|
7
7
|
export const MONITOR_INGEST_PATTERN = /cfg\/monitor\/ingest/
|
|
8
|
+
|
|
9
|
+
/** Default deduplication TTL for per-capture-source filtering (ms) */
|
|
10
|
+
export const DEFAULT_DEDUPE_TTL = 5_000
|
|
11
|
+
|
|
12
|
+
/** Default deduplication TTL for global store-level filtering (ms) */
|
|
13
|
+
export const DEFAULT_DEDUPE_STORE_TTL = 30_000
|
|
@@ -2,6 +2,7 @@ import { createStore } from 'zustand/vanilla'
|
|
|
2
2
|
import type { MonitorEvent, MonitorConfig } from '../../types'
|
|
3
3
|
import { sendBatch } from '../transport/ingest'
|
|
4
4
|
import { MONITOR_VERSION } from '../utils/env'
|
|
5
|
+
import { DEFAULT_DEDUPE_STORE_TTL } from '../constants'
|
|
5
6
|
|
|
6
7
|
// Circuit breaker: pause flushing after N consecutive transport failures
|
|
7
8
|
const CIRCUIT_BREAKER_THRESHOLD = 3
|
|
@@ -10,25 +11,22 @@ const CIRCUIT_BREAKER_COOLDOWN_MS = 60_000 // 1 minute
|
|
|
10
11
|
// Global deduplication: skip pushing identical events within this window.
|
|
11
12
|
// Protects against multiple capture sources (js-errors, console, ErrorTrackingProvider)
|
|
12
13
|
// all firing for the same underlying error.
|
|
13
|
-
const STORE_DEDUP_TTL = 5_000 // 5 seconds
|
|
14
14
|
const STORE_DEDUP_MAX = 200
|
|
15
15
|
const _recentPushKeys = new Map<string, number>()
|
|
16
16
|
|
|
17
17
|
function _pushDedupeKey(event: MonitorEvent): string {
|
|
18
|
-
// Build a key from event_type + level + message (first 100 chars) + url
|
|
19
18
|
const msg = (event.message ?? '').slice(0, 100)
|
|
20
19
|
return `${event.event_type}:${event.level}:${msg}:${event.http_url ?? event.url ?? ''}`
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
function _isRecentPush(key: string): boolean {
|
|
22
|
+
function _isRecentPush(key: string, ttl: number): boolean {
|
|
24
23
|
const now = Date.now()
|
|
25
24
|
const last = _recentPushKeys.get(key)
|
|
26
|
-
if (last !== undefined && now - last <
|
|
25
|
+
if (last !== undefined && now - last < ttl) return true
|
|
27
26
|
_recentPushKeys.set(key, now)
|
|
28
|
-
// Evict old entries when map grows too large
|
|
29
27
|
if (_recentPushKeys.size > STORE_DEDUP_MAX) {
|
|
30
28
|
for (const [k, ts] of _recentPushKeys) {
|
|
31
|
-
if (now - ts >
|
|
29
|
+
if (now - ts > ttl) _recentPushKeys.delete(k)
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
return false
|
|
@@ -54,10 +52,10 @@ export const monitorStore = createStore<MonitorState>((set, get) => ({
|
|
|
54
52
|
|
|
55
53
|
push(event) {
|
|
56
54
|
// Global dedup: skip if an identical event was pushed recently
|
|
57
|
-
const dedupeKey = _pushDedupeKey(event)
|
|
58
|
-
if (_isRecentPush(dedupeKey)) return
|
|
59
|
-
|
|
60
55
|
const { config, buffer } = get()
|
|
56
|
+
const storeTtl = config.dedupeStoreTtl ?? DEFAULT_DEDUPE_STORE_TTL
|
|
57
|
+
const dedupeKey = _pushDedupeKey(event)
|
|
58
|
+
if (_isRecentPush(dedupeKey, storeTtl)) return
|
|
61
59
|
const maxSize = config.maxBufferSize ?? 20
|
|
62
60
|
const sanitized: MonitorEvent = {
|
|
63
61
|
build_id: event.build_id ?? config.buildId ?? `sdk:${MONITOR_VERSION}`,
|
|
@@ -92,8 +90,8 @@ export const monitorStore = createStore<MonitorState>((set, get) => ({
|
|
|
92
90
|
// Circuit breaker: skip sending while paused
|
|
93
91
|
if (Date.now() < _pausedUntil) return
|
|
94
92
|
|
|
95
|
-
const batch = buffer.slice(0,
|
|
96
|
-
set({ buffer: buffer.slice(
|
|
93
|
+
const batch = buffer.slice(0, 25)
|
|
94
|
+
set({ buffer: buffer.slice(25) })
|
|
97
95
|
|
|
98
96
|
sendBatch({ events: batch }, useBeacon).then(
|
|
99
97
|
() => {
|
package/src/types/config.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface MonitorConfig {
|
|
|
21
21
|
captureJsErrors?: boolean
|
|
22
22
|
/** Log debug info to console. Default: false */
|
|
23
23
|
debug?: boolean
|
|
24
|
+
/** Deduplication TTL in ms for per-capture-source filtering. Default: 5000 */
|
|
25
|
+
dedupeTtl?: number
|
|
26
|
+
/** Deduplication TTL in ms for the global store-level filter (cross-source). Default: 30000 */
|
|
27
|
+
dedupeStoreTtl?: number
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export interface ServerMonitorConfig {
|