@orbitai/orbitrace 1.1.10 → 1.1.11
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 +15 -0
- package/dist/index.js +92 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,21 @@ await orbitrace.captureMessage("User registered", {
|
|
|
63
63
|
});
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
### 4. Delivery Behavior
|
|
67
|
+
|
|
68
|
+
Orbitrace never throws and never blocks your application for long:
|
|
69
|
+
|
|
70
|
+
- Each request times out after 5 seconds. Failures are logged to the console and swallowed.
|
|
71
|
+
- Repeats of the same event (same message and stack) within 10 seconds are counted locally and sent as one request with an occurrence count, so an error raised in a loop costs at most two requests per 10 seconds.
|
|
72
|
+
- At most 10 requests are in flight at once; events beyond that are dropped.
|
|
73
|
+
- When the API answers `429`, events are dropped until its `Retry-After` period ends.
|
|
74
|
+
|
|
75
|
+
In short-lived processes (scripts, CI jobs, serverless functions), call `flush()` before exiting so locally counted repeats are sent:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
await orbitrace.flush();
|
|
79
|
+
```
|
|
80
|
+
|
|
66
81
|
## Environment Variables
|
|
67
82
|
|
|
68
83
|
```env
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
// Reporting must never slow down or break the host application, so every failure is logged and swallowed.
|
|
2
|
+
const REQUEST_TIMEOUT_MS = 5000;
|
|
3
|
+
// Repeats of the same event inside this window are counted locally and sent once as a single request.
|
|
4
|
+
const DEDUPE_WINDOW_MS = 10000;
|
|
5
|
+
// Caps concurrent requests so a loop of distinct errors cannot exhaust the host's sockets or memory.
|
|
6
|
+
const MAX_IN_FLIGHT = 10;
|
|
7
|
+
const DEFAULT_RETRY_AFTER_SECONDS = 60;
|
|
8
|
+
|
|
1
9
|
class Orbitrace {
|
|
2
10
|
static #config = null;
|
|
11
|
+
static #pending = new Map();
|
|
12
|
+
static #inFlight = new Set();
|
|
13
|
+
static #pausedUntil = 0;
|
|
3
14
|
|
|
4
15
|
static getInstance(config) {
|
|
5
16
|
this.assertServerRuntime();
|
|
@@ -28,14 +39,6 @@ class Orbitrace {
|
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
static checkInitialized() {
|
|
32
|
-
if (!this.#config) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
"Orbitrace must be initialized with Orbitrace.getInstance() before use"
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
42
|
static assertServerRuntime() {
|
|
40
43
|
if (typeof window !== "undefined" || typeof document !== "undefined") {
|
|
41
44
|
throw new Error(
|
|
@@ -45,57 +48,93 @@ class Orbitrace {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
static async captureException(error, metadata = {}) {
|
|
48
|
-
|
|
51
|
+
const message = error?.message || error?.name || String(error);
|
|
52
|
+
|
|
53
|
+
return this.#capture("capture_exception", {
|
|
54
|
+
message,
|
|
55
|
+
stack: error?.stack || message,
|
|
56
|
+
metadata,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static async captureMessage(message, metadata = {}) {
|
|
61
|
+
return this.#capture("capture_message", { message, metadata });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Sends counts still held by the dedupe window and waits for in-flight requests.
|
|
65
|
+
// Call it before a short-lived process or serverless invocation ends.
|
|
66
|
+
static async flush() {
|
|
67
|
+
await Promise.all([...this.#pending.keys()].map((key) => this.#flushPending(key)));
|
|
68
|
+
await Promise.all(this.#inFlight);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static async #capture(event, { metadata, ...fields }) {
|
|
72
|
+
if (!this.#config) {
|
|
73
|
+
console.error("Orbitrace: call Orbitrace.getInstance() before capturing events");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
49
76
|
|
|
50
77
|
if (this.#config.disabled) {
|
|
51
78
|
console.log("Orbitrace: Logging is disabled");
|
|
52
79
|
return;
|
|
53
80
|
}
|
|
54
81
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
const payload = {
|
|
83
|
+
...fields,
|
|
84
|
+
metadata: {
|
|
85
|
+
...metadata,
|
|
86
|
+
env: this.#config.environment,
|
|
87
|
+
version: this.#config.version,
|
|
88
|
+
service: this.#config.service,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
const key = JSON.stringify([event, payload.message, payload.stack ?? null]);
|
|
92
|
+
const pending = this.#pending.get(key);
|
|
66
93
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
if (pending) {
|
|
95
|
+
pending.count++;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!this.#canSend()) {
|
|
100
|
+
return;
|
|
70
101
|
}
|
|
102
|
+
|
|
103
|
+
const entry = { event, payload, count: 0 };
|
|
104
|
+
entry.timer = setTimeout(() => this.#flushPending(key), DEDUPE_WINDOW_MS);
|
|
105
|
+
entry.timer.unref?.();
|
|
106
|
+
this.#pending.set(key, entry);
|
|
107
|
+
|
|
108
|
+
return this.#send(event, payload, 1);
|
|
71
109
|
}
|
|
72
110
|
|
|
73
|
-
static async
|
|
74
|
-
this.
|
|
111
|
+
static async #flushPending(key) {
|
|
112
|
+
const entry = this.#pending.get(key);
|
|
75
113
|
|
|
76
|
-
if (
|
|
77
|
-
console.log("Orbitrace: Logging is disabled");
|
|
114
|
+
if (!entry) {
|
|
78
115
|
return;
|
|
79
116
|
}
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
message,
|
|
84
|
-
metadata: {
|
|
85
|
-
...metadata,
|
|
86
|
-
env: this.#config.environment,
|
|
87
|
-
version: this.#config.version,
|
|
88
|
-
service: this.#config.service,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
118
|
+
this.#pending.delete(key);
|
|
119
|
+
clearTimeout(entry.timer);
|
|
91
120
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.error("Orbitrace: Error capturing message", err);
|
|
121
|
+
if (entry.count > 0 && this.#canSend()) {
|
|
122
|
+
await this.#send(entry.event, entry.payload, entry.count);
|
|
95
123
|
}
|
|
96
124
|
}
|
|
97
125
|
|
|
98
|
-
static
|
|
126
|
+
static #canSend() {
|
|
127
|
+
return Date.now() >= this.#pausedUntil && this.#inFlight.size < MAX_IN_FLIGHT;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static #send(event, payload, count) {
|
|
131
|
+
const request = this.#post(event, payload, count).finally(() => this.#inFlight.delete(request));
|
|
132
|
+
this.#inFlight.add(request);
|
|
133
|
+
|
|
134
|
+
return request;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static async #post(event, payload, count) {
|
|
99
138
|
try {
|
|
100
139
|
const response = await fetch(this.#config.endpoint, {
|
|
101
140
|
method: "POST",
|
|
@@ -107,18 +146,27 @@ class Orbitrace {
|
|
|
107
146
|
},
|
|
108
147
|
body: JSON.stringify({
|
|
109
148
|
event,
|
|
149
|
+
...(count > 1 && { count }),
|
|
110
150
|
payload,
|
|
111
151
|
}),
|
|
152
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
112
153
|
});
|
|
113
154
|
|
|
155
|
+
if (response.status === 429) {
|
|
156
|
+
const retryAfterSeconds = Number(response.headers.get("retry-after")) || DEFAULT_RETRY_AFTER_SECONDS;
|
|
157
|
+
this.#pausedUntil = Date.now() + retryAfterSeconds * 1000;
|
|
158
|
+
console.warn(`Orbitrace: rate limited, dropping events for ${retryAfterSeconds}s`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
114
162
|
if (!response.ok) {
|
|
115
|
-
|
|
163
|
+
console.error(`Orbitrace: API responded with status ${response.status}`);
|
|
164
|
+
return;
|
|
116
165
|
}
|
|
117
166
|
|
|
118
167
|
return await response.json();
|
|
119
168
|
} catch (error) {
|
|
120
|
-
console.error("Orbitrace: Error sending data to API", error);
|
|
121
|
-
throw error;
|
|
169
|
+
console.error("Orbitrace: Error sending data to API", error.message);
|
|
122
170
|
}
|
|
123
171
|
}
|
|
124
172
|
}
|