@djangocfg/monitor 2.1.227 → 2.1.229

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.
Files changed (36) hide show
  1. package/README.md +2 -6
  2. package/dist/client.cjs +48 -26
  3. package/dist/client.cjs.map +1 -1
  4. package/dist/client.d.cts +31 -33
  5. package/dist/client.d.ts +31 -33
  6. package/dist/client.mjs +48 -26
  7. package/dist/client.mjs.map +1 -1
  8. package/dist/index.cjs +9 -10
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +29 -33
  11. package/dist/index.d.ts +29 -33
  12. package/dist/index.mjs +9 -10
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/server.cjs +10 -11
  15. package/dist/server.cjs.map +1 -1
  16. package/dist/server.d.cts +29 -33
  17. package/dist/server.d.ts +29 -33
  18. package/dist/server.mjs +10 -11
  19. package/dist/server.mjs.map +1 -1
  20. package/package.json +2 -2
  21. package/src/_api/generated/cfg_monitor/_utils/schemas/FrontendEventIngestRequest.schema.ts +5 -6
  22. package/src/_api/generated/cfg_monitor/_utils/schemas/IngestBatchRequest.schema.ts +2 -2
  23. package/src/_api/generated/cfg_monitor/enums.ts +14 -16
  24. package/src/_api/generated/cfg_monitor/monitor/client.ts +2 -1
  25. package/src/_api/generated/cfg_monitor/monitor/models.ts +16 -18
  26. package/src/_api/generated/cfg_monitor/schema.json +19 -27
  27. package/src/client/capture/console.ts +3 -1
  28. package/src/client/capture/js-errors.ts +7 -0
  29. package/src/client/capture/network.ts +1 -1
  30. package/src/client/capture/validation.ts +1 -1
  31. package/src/client/constants.ts +7 -0
  32. package/src/client/index.ts +2 -1
  33. package/src/client/store/index.ts +28 -2
  34. package/src/client/transport/ingest.ts +5 -9
  35. package/src/client/window.ts +3 -3
  36. package/src/server/index.ts +1 -1
@@ -3,7 +3,7 @@
3
3
  import * as Enums from "../enums";
4
4
 
5
5
  /**
6
- * Wraps a list of events. Enforces max batch size.
6
+ * Batch of up to 50 browser events.
7
7
  *
8
8
  * Request model (no read-only fields).
9
9
  */
@@ -12,25 +12,24 @@ export interface IngestBatchRequest {
12
12
  }
13
13
 
14
14
  /**
15
- * Validates a single event from the browser.
15
+ * Single browser event payload.
16
16
  *
17
17
  * Request model (no read-only fields).
18
18
  */
19
19
  export interface FrontendEventIngestRequest {
20
- /** * `ERROR` - Error
21
- * `WARNING` - Warning
22
- * `INFO` - Info
23
- * `PAGE_VIEW` - Page View
24
- * `PERFORMANCE` - Performance
25
- * `NETWORK_ERROR` - Network Error
26
- * `JS_ERROR` - JS Error
27
- * `CONSOLE` - Console */
20
+ /** * `JS_ERROR` - JS_ERROR
21
+ * `NETWORK_ERROR` - NETWORK_ERROR
22
+ * `ERROR` - ERROR
23
+ * `WARNING` - WARNING
24
+ * `PAGE_VIEW` - PAGE_VIEW
25
+ * `PERFORMANCE` - PERFORMANCE
26
+ * `CONSOLE` - CONSOLE */
28
27
  event_type: Enums.FrontendEventIngestRequestEventType;
29
28
  message: string;
30
- /** * `error` - Error
31
- * `warn` - Warning
32
- * `info` - Info
33
- * `debug` - Debug */
29
+ /** * `error` - error
30
+ * `warning` - warning
31
+ * `info` - info
32
+ * `debug` - debug */
34
33
  level?: Enums.FrontendEventIngestRequestLevel;
35
34
  stack_trace?: string;
36
35
  url?: string;
@@ -38,12 +37,11 @@ export interface FrontendEventIngestRequest {
38
37
  http_status?: number | null;
39
38
  http_method?: string;
40
39
  http_url?: string;
40
+ session_id?: string;
41
41
  user_agent?: string;
42
- session_id?: string | null;
43
- browser_fingerprint?: string;
42
+ build_id?: string;
43
+ environment?: string;
44
44
  extra?: Record<string, any>;
45
45
  project_name?: string;
46
- environment?: string;
47
- build_id?: string;
48
46
  }
49
47
 
@@ -7,7 +7,7 @@
7
7
  "x-django-metadata": {
8
8
  "group": "cfg_monitor",
9
9
  "apps": [
10
- "django_cfg_monitor"
10
+ "django_monitor"
11
11
  ],
12
12
  "generator": "django-client",
13
13
  "generator_version": "1.0.0"
@@ -17,7 +17,7 @@
17
17
  "/cfg/monitor/ingest/": {
18
18
  "post": {
19
19
  "operationId": "cfg_monitor_ingest_create",
20
- "description": "Accepts a batch of up to 50 frontend events. No authentication required.",
20
+ "description": "Accepts a batch of up to 50 frontend events. No authentication required — anonymous visitors can send events.",
21
21
  "summary": "Ingest browser events",
22
22
  "tags": [
23
23
  "monitor"
@@ -58,22 +58,21 @@
58
58
  "schemas": {
59
59
  "FrontendEventIngestRequest": {
60
60
  "type": "object",
61
- "description": "Validates a single event from the browser.",
61
+ "description": "Single browser event payload.",
62
62
  "properties": {
63
63
  "event_type": {
64
64
  "enum": [
65
+ "JS_ERROR",
66
+ "NETWORK_ERROR",
65
67
  "ERROR",
66
68
  "WARNING",
67
- "INFO",
68
69
  "PAGE_VIEW",
69
70
  "PERFORMANCE",
70
- "NETWORK_ERROR",
71
- "JS_ERROR",
72
71
  "CONSOLE"
73
72
  ],
74
73
  "type": "string",
75
- "description": "* `ERROR` - Error\n* `WARNING` - Warning\n* `INFO` - Info\n* `PAGE_VIEW` - Page View\n* `PERFORMANCE` - Performance\n* `NETWORK_ERROR` - Network Error\n* `JS_ERROR` - JS Error\n* `CONSOLE` - Console",
76
- "x-spec-enum-id": "de9da4754700e7fc"
74
+ "description": "* `JS_ERROR` - JS_ERROR\n* `NETWORK_ERROR` - NETWORK_ERROR\n* `ERROR` - ERROR\n* `WARNING` - WARNING\n* `PAGE_VIEW` - PAGE_VIEW\n* `PERFORMANCE` - PERFORMANCE\n* `CONSOLE` - CONSOLE",
75
+ "x-spec-enum-id": "33f6281564c4f844"
77
76
  },
78
77
  "message": {
79
78
  "type": "string",
@@ -83,14 +82,14 @@
83
82
  "level": {
84
83
  "enum": [
85
84
  "error",
86
- "warn",
85
+ "warning",
87
86
  "info",
88
87
  "debug"
89
88
  ],
90
89
  "type": "string",
91
- "description": "* `error` - Error\n* `warn` - Warning\n* `info` - Info\n* `debug` - Debug",
92
- "x-spec-enum-id": "bef029ce9fe8228d",
93
- "default": "info"
90
+ "description": "* `error` - error\n* `warning` - warning\n* `info` - info\n* `debug` - debug",
91
+ "x-spec-enum-id": "00cfb519712ff1ce",
92
+ "default": "error"
94
93
  },
95
94
  "stack_trace": {
96
95
  "type": "string",
@@ -123,25 +122,17 @@
123
122
  "default": "",
124
123
  "maxLength": 2000
125
124
  },
126
- "user_agent": {
125
+ "session_id": {
127
126
  "type": "string",
128
127
  "default": "",
129
- "maxLength": 500
130
- },
131
- "session_id": {
132
- "type": [
133
- "string",
134
- "null"
135
- ],
136
- "format": "uuid"
128
+ "maxLength": 64
137
129
  },
138
- "browser_fingerprint": {
130
+ "user_agent": {
139
131
  "type": "string",
140
132
  "default": "",
141
- "maxLength": 64
133
+ "maxLength": 500
142
134
  },
143
- "extra": {},
144
- "project_name": {
135
+ "build_id": {
145
136
  "type": "string",
146
137
  "default": "",
147
138
  "maxLength": 100
@@ -151,7 +142,8 @@
151
142
  "default": "",
152
143
  "maxLength": 20
153
144
  },
154
- "build_id": {
145
+ "extra": {},
146
+ "project_name": {
155
147
  "type": "string",
156
148
  "default": "",
157
149
  "maxLength": 100
@@ -164,7 +156,7 @@
164
156
  },
165
157
  "IngestBatchRequest": {
166
158
  "type": "object",
167
- "description": "Wraps a list of events. Enforces max batch size.",
159
+ "description": "Batch of up to 50 browser events.",
168
160
  "properties": {
169
161
  "events": {
170
162
  "type": "array",
@@ -1,13 +1,14 @@
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
5
  import { EventType, EventLevel } from '../../_api'
5
6
  import type { FrontendEventIngestRequestEventType, FrontendEventIngestRequestLevel } from '../../_api/generated/cfg_monitor/enums'
6
7
 
7
8
  type ConsoleLevel = 'warn' | 'error'
8
9
 
9
10
  const levelMap: Record<ConsoleLevel, FrontendEventIngestRequestLevel> = {
10
- warn: EventLevel.WARN,
11
+ warn: EventLevel.WARNING,
11
12
  error: EventLevel.ERROR,
12
13
  }
13
14
  const typeMap: Record<ConsoleLevel, FrontendEventIngestRequestEventType> = {
@@ -32,6 +33,7 @@ function stringify(args: unknown[]): string {
32
33
  async function captureConsoleEvent(level: ConsoleLevel, args: unknown[]) {
33
34
  try {
34
35
  const message = stringify(args)
36
+ if (MONITOR_INGEST_PATTERN.test(message)) return
35
37
  const url = typeof window !== 'undefined' ? window.location.href : ''
36
38
  const fingerprint = await computeFingerprint(message, '', url)
37
39
  const { config } = monitorStore.getState()
@@ -1,6 +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
5
  import { EventType, EventLevel } from '../../_api'
5
6
 
6
7
  const MSG_MAX = 2000
@@ -9,6 +10,10 @@ function truncate(s: string, max = MSG_MAX): string {
9
10
  return s.length > max ? s.slice(0, max - 1) + '…' : s
10
11
  }
11
12
 
13
+ function isMonitorOwnError(msg: string, stack?: string): boolean {
14
+ return MONITOR_INGEST_PATTERN.test(msg) || MONITOR_INGEST_PATTERN.test(stack ?? '')
15
+ }
16
+
12
17
  // Hydration mismatches caused by browser extensions (Grammarly, translators, etc.)
13
18
  // These are not application bugs — filtering them prevents alert noise.
14
19
  const HYDRATION_NOISE: RegExp[] = [
@@ -56,6 +61,7 @@ export function installJsErrorCapture(): () => void {
56
61
  const msg = truncate(typeof message === 'string' ? message : String(message))
57
62
  if (isHydrationNoise(msg)) return
58
63
  const stack = error?.stack ?? `at ${source}:${lineno}:${colno}`
64
+ if (isMonitorOwnError(msg, stack)) return
59
65
  const url = window.location.href
60
66
  const fingerprint = await computeFingerprint(msg, stack, url)
61
67
  if (isRecentlySent(fingerprint)) return
@@ -84,6 +90,7 @@ export function installJsErrorCapture(): () => void {
84
90
  : 'Unhandled promise rejection')
85
91
  if (isHydrationNoise(msg)) return
86
92
  const stack = reason instanceof Error ? (reason.stack ?? '') : ''
93
+ if (isMonitorOwnError(msg, stack)) return
87
94
  const url = window.location.href
88
95
  const fingerprint = await computeFingerprint(msg, stack, url)
89
96
  if (isRecentlySent(fingerprint)) return
@@ -15,7 +15,7 @@ export async function monitoredFetch(
15
15
  const { config } = monitorStore.getState()
16
16
  monitorStore.getState().push({
17
17
  event_type: EventType.NETWORK_ERROR,
18
- level: response.status >= 500 ? EventLevel.ERROR : EventLevel.WARN,
18
+ level: response.status >= 500 ? EventLevel.ERROR : EventLevel.WARNING,
19
19
  message: `HTTP ${response.status} ${response.statusText} — ${method} ${url}`,
20
20
  url: typeof window !== 'undefined' ? window.location.href : '',
21
21
  http_status: response.status,
@@ -20,7 +20,7 @@ export function installValidationCapture(): () => void {
20
20
  const rawMsg = `Zod validation error in ${detail.operation}: ${detail.error?.message ?? 'unknown'}`
21
21
  monitorStore.getState().push({
22
22
  event_type: EventType.WARNING,
23
- level: EventLevel.WARN,
23
+ level: EventLevel.WARNING,
24
24
  message: rawMsg.length > 500 ? rawMsg.slice(0, 499) + '…' : rawMsg,
25
25
  url: window.location.href,
26
26
  http_method: detail.method,
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Internal URL patterns for the monitor transport.
3
+ * Used to filter out self-generated errors and prevent capture→flush feedback loops.
4
+ */
5
+
6
+ /** Matches any request to the monitor ingest endpoint */
7
+ export const MONITOR_INGEST_PATTERN = /cfg\/monitor\/ingest/
@@ -27,7 +27,8 @@ import { monitorStore } from './store'
27
27
  import { configureMonitorApi } from '../_api'
28
28
  import type { MonitorConfig, MonitorEvent } from '../types'
29
29
 
30
- export type { MonitorConfig, MonitorEvent, EventType, EventLevel } from '../types'
30
+ export type { MonitorConfig, MonitorEvent } from '../types'
31
+ export { EventType, EventLevel } from '../types'
31
32
  export { monitoredFetch } from './capture/network'
32
33
  export { getSessionId } from './capture/session'
33
34
  export { MonitorProvider } from './MonitorProvider'
@@ -2,10 +2,16 @@ import { createStore } from 'zustand/vanilla'
2
2
  import type { MonitorEvent, MonitorConfig } from '../../types'
3
3
  import { sendBatch } from '../transport/ingest'
4
4
 
5
+ // Circuit breaker: pause flushing after N consecutive transport failures
6
+ const CIRCUIT_BREAKER_THRESHOLD = 3
7
+ const CIRCUIT_BREAKER_COOLDOWN_MS = 60_000 // 1 minute
8
+
5
9
  interface MonitorState {
6
10
  config: MonitorConfig
7
11
  buffer: MonitorEvent[]
8
12
  initialized: boolean
13
+ _consecutiveFailures: number
14
+ _pausedUntil: number
9
15
  push: (event: MonitorEvent) => void
10
16
  flush: (useBeacon?: boolean) => void
11
17
  setConfig: (config: MonitorConfig) => void
@@ -15,6 +21,8 @@ export const monitorStore = createStore<MonitorState>((set, get) => ({
15
21
  config: {},
16
22
  buffer: [],
17
23
  initialized: false,
24
+ _consecutiveFailures: 0,
25
+ _pausedUntil: 0,
18
26
 
19
27
  push(event) {
20
28
  const { config, buffer } = get()
@@ -45,11 +53,29 @@ export const monitorStore = createStore<MonitorState>((set, get) => ({
45
53
  },
46
54
 
47
55
  flush(useBeacon = false) {
48
- const { buffer } = get()
56
+ const { buffer, _pausedUntil } = get()
49
57
  if (buffer.length === 0) return
58
+
59
+ // Circuit breaker: skip sending while paused
60
+ if (Date.now() < _pausedUntil) return
61
+
50
62
  const batch = buffer.slice(0, 50)
51
63
  set({ buffer: buffer.slice(50) })
52
- sendBatch({ events: batch }, useBeacon)
64
+
65
+ sendBatch({ events: batch }, useBeacon).then(
66
+ () => {
67
+ // Success: reset failure counter
68
+ set({ _consecutiveFailures: 0, _pausedUntil: 0 })
69
+ },
70
+ () => {
71
+ // Failure: increment counter and maybe open circuit
72
+ const failures = get()._consecutiveFailures + 1
73
+ const pausedUntil = failures >= CIRCUIT_BREAKER_THRESHOLD
74
+ ? Date.now() + CIRCUIT_BREAKER_COOLDOWN_MS
75
+ : get()._pausedUntil
76
+ set({ _consecutiveFailures: failures, _pausedUntil: pausedUntil })
77
+ }
78
+ )
53
79
  },
54
80
 
55
81
  setConfig(config) {
@@ -18,14 +18,10 @@ export async function sendBatch(
18
18
  ): Promise<void> {
19
19
  if (batch.events.length === 0) return
20
20
 
21
- try {
22
- if (useBeacon) {
23
- syncBeaconBaseUrl()
24
- await monitorApiBeacon.monitor.ingestCreate(batch)
25
- } else {
26
- await monitorApi.monitor.ingestCreate(batch)
27
- }
28
- } catch {
29
- // Transport errors silently ignored — monitoring must never crash the app
21
+ if (useBeacon) {
22
+ syncBeaconBaseUrl()
23
+ await monitorApiBeacon.monitor.ingestCreate(batch)
24
+ } else {
25
+ await monitorApi.monitor.ingestCreate(batch)
30
26
  }
31
27
  }
@@ -72,19 +72,19 @@ export function initWindowMonitor(): void {
72
72
  },
73
73
 
74
74
  warn(message, extra) {
75
- monitorStore.getState().push(makeEvent(EventType.WARNING, EventLevel.WARN, message, extra))
75
+ monitorStore.getState().push(makeEvent(EventType.WARNING, EventLevel.WARNING, message, extra))
76
76
  console.log('[monitor] warn captured →', message)
77
77
  },
78
78
 
79
79
  info(message, extra) {
80
- monitorStore.getState().push(makeEvent(EventType.INFO, EventLevel.INFO, message, extra))
80
+ monitorStore.getState().push(makeEvent(EventType.WARNING, EventLevel.INFO, message, extra))
81
81
  console.log('[monitor] info captured →', message)
82
82
  },
83
83
 
84
84
  network(status, method, url, extra) {
85
85
  const event = makeEvent(
86
86
  EventType.NETWORK_ERROR,
87
- status >= 500 ? EventLevel.ERROR : EventLevel.WARN,
87
+ status >= 500 ? EventLevel.ERROR : EventLevel.WARNING,
88
88
  `${method} ${url} → ${status}`,
89
89
  extra,
90
90
  )
@@ -69,7 +69,7 @@ export const serverMonitor = {
69
69
  ): Promise<void> {
70
70
  await send([withDefaults({
71
71
  event_type: EventType.NETWORK_ERROR,
72
- level: status >= 500 ? EventLevel.ERROR : EventLevel.WARN,
72
+ level: status >= 500 ? EventLevel.ERROR : EventLevel.WARNING,
73
73
  message: `HTTP ${status} — ${method} ${apiUrl}`,
74
74
  url: ctx?.pageUrl ?? '',
75
75
  http_status: status,