@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.
- package/README.md +2 -6
- package/dist/client.cjs +48 -26
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +31 -33
- package/dist/client.d.ts +31 -33
- package/dist/client.mjs +48 -26
- package/dist/client.mjs.map +1 -1
- package/dist/index.cjs +9 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -33
- package/dist/index.d.ts +29 -33
- package/dist/index.mjs +9 -10
- package/dist/index.mjs.map +1 -1
- package/dist/server.cjs +10 -11
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +29 -33
- package/dist/server.d.ts +29 -33
- package/dist/server.mjs +10 -11
- package/dist/server.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/cfg_monitor/_utils/schemas/FrontendEventIngestRequest.schema.ts +5 -6
- package/src/_api/generated/cfg_monitor/_utils/schemas/IngestBatchRequest.schema.ts +2 -2
- package/src/_api/generated/cfg_monitor/enums.ts +14 -16
- package/src/_api/generated/cfg_monitor/monitor/client.ts +2 -1
- package/src/_api/generated/cfg_monitor/monitor/models.ts +16 -18
- package/src/_api/generated/cfg_monitor/schema.json +19 -27
- package/src/client/capture/console.ts +3 -1
- package/src/client/capture/js-errors.ts +7 -0
- package/src/client/capture/network.ts +1 -1
- package/src/client/capture/validation.ts +1 -1
- package/src/client/constants.ts +7 -0
- package/src/client/index.ts +2 -1
- package/src/client/store/index.ts +28 -2
- package/src/client/transport/ingest.ts +5 -9
- package/src/client/window.ts +3 -3
- package/src/server/index.ts +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import * as Enums from "../enums";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
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
|
-
*
|
|
15
|
+
* Single browser event payload.
|
|
16
16
|
*
|
|
17
17
|
* Request model (no read-only fields).
|
|
18
18
|
*/
|
|
19
19
|
export interface FrontendEventIngestRequest {
|
|
20
|
-
/** * `
|
|
21
|
-
* `
|
|
22
|
-
* `
|
|
23
|
-
* `
|
|
24
|
-
* `
|
|
25
|
-
* `
|
|
26
|
-
* `
|
|
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` -
|
|
31
|
-
* `
|
|
32
|
-
* `info` -
|
|
33
|
-
* `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
|
-
|
|
43
|
-
|
|
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
|
-
"
|
|
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": "
|
|
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": "* `
|
|
76
|
-
"x-spec-enum-id": "
|
|
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
|
-
"
|
|
85
|
+
"warning",
|
|
87
86
|
"info",
|
|
88
87
|
"debug"
|
|
89
88
|
],
|
|
90
89
|
"type": "string",
|
|
91
|
-
"description": "* `error` -
|
|
92
|
-
"x-spec-enum-id": "
|
|
93
|
-
"default": "
|
|
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
|
-
"
|
|
125
|
+
"session_id": {
|
|
127
126
|
"type": "string",
|
|
128
127
|
"default": "",
|
|
129
|
-
"maxLength":
|
|
130
|
-
},
|
|
131
|
-
"session_id": {
|
|
132
|
-
"type": [
|
|
133
|
-
"string",
|
|
134
|
-
"null"
|
|
135
|
-
],
|
|
136
|
-
"format": "uuid"
|
|
128
|
+
"maxLength": 64
|
|
137
129
|
},
|
|
138
|
-
"
|
|
130
|
+
"user_agent": {
|
|
139
131
|
"type": "string",
|
|
140
132
|
"default": "",
|
|
141
|
-
"maxLength":
|
|
133
|
+
"maxLength": 500
|
|
142
134
|
},
|
|
143
|
-
"
|
|
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
|
-
"
|
|
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": "
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
package/src/client/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|
package/src/client/window.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
87
|
+
status >= 500 ? EventLevel.ERROR : EventLevel.WARNING,
|
|
88
88
|
`${method} ${url} → ${status}`,
|
|
89
89
|
extra,
|
|
90
90
|
)
|
package/src/server/index.ts
CHANGED
|
@@ -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.
|
|
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,
|