@orbitai/orbitrace 1.1.9 → 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 +30 -135
- package/dist/index.js +96 -49
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @orbitai/orbitrace
|
|
2
2
|
|
|
3
|
-
Error tracking and monitoring for Node.js
|
|
3
|
+
Error tracking and monitoring for backend Node.js applications.
|
|
4
|
+
|
|
5
|
+
Orbitrace is server-side only. Do not initialize this package in browser or frontend code, because the Orbitrace API key is a backend secret and must not be exposed in public bundles.
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -17,8 +19,6 @@ npm install @orbitai/orbitrace
|
|
|
17
19
|
const Orbitrace = require("@orbitai/orbitrace");
|
|
18
20
|
|
|
19
21
|
function getOrbitraceInstance({ service }) {
|
|
20
|
-
console.log("Initializing Orbitrace for service:", service);
|
|
21
|
-
|
|
22
22
|
Orbitrace.getInstance({
|
|
23
23
|
apiKey: process.env.ORBITRACE_API_KEY,
|
|
24
24
|
orgId: process.env.ORBITRACE_ORG_ID,
|
|
@@ -27,7 +27,7 @@ function getOrbitraceInstance({ service }) {
|
|
|
27
27
|
environment: process.env.APP_ENV,
|
|
28
28
|
service,
|
|
29
29
|
version: process.env.APP_VERSION,
|
|
30
|
-
disabled: process.env.
|
|
30
|
+
disabled: process.env.ORBITRACE_DISABLED === "true",
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
return Orbitrace;
|
|
@@ -38,165 +38,60 @@ module.exports = {
|
|
|
38
38
|
};
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
### 2.
|
|
41
|
+
### 2. Capture Exceptions
|
|
42
42
|
|
|
43
43
|
```javascript
|
|
44
|
-
const { getOrbitraceInstance } = require("
|
|
44
|
+
const { getOrbitraceInstance } = require("./orbitrace");
|
|
45
45
|
const orbitrace = getOrbitraceInstance({ service: "api" });
|
|
46
46
|
|
|
47
|
-
// Capture exceptions
|
|
48
47
|
try {
|
|
49
48
|
// Your code here
|
|
50
49
|
} catch (error) {
|
|
51
50
|
await orbitrace.captureException(error, {
|
|
52
51
|
endpoint: "/users",
|
|
52
|
+
action: "createUser",
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
// Capture messages
|
|
57
|
-
await orbitrace.captureMessage("User registered", {
|
|
58
|
-
userId: "123",
|
|
59
|
-
email: "user@example.com",
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Frontend Setup (Vue 3 + Pinia)
|
|
64
|
-
|
|
65
|
-
### 1. Initialize Orbitrace
|
|
66
|
-
|
|
67
|
-
```javascript
|
|
68
|
-
// orbitrace.js
|
|
69
|
-
import { provide, inject } from "vue";
|
|
70
|
-
import Orbitrace from "@orbitai/orbitrace";
|
|
71
|
-
|
|
72
|
-
export const ORBITRACE_KEY = Symbol("orbitrace");
|
|
73
|
-
|
|
74
|
-
export function getOrbitraceInstance({ service }) {
|
|
75
|
-
Orbitrace.getInstance({
|
|
76
|
-
apiKey: import.meta.env.VITE_ORBITRACE_API_KEY,
|
|
77
|
-
orgId: import.meta.env.VITE_ORBITRACE_ORG_ID,
|
|
78
|
-
projectId: import.meta.env.VITE_ORBITRACE_PROJECT_ID,
|
|
79
|
-
endpoint: import.meta.env.VITE_ORBITRACE_ENDPOINT,
|
|
80
|
-
environment: import.meta.env.VITE_APP_ENV,
|
|
81
|
-
service,
|
|
82
|
-
version: import.meta.env.VITE_APP_VERSION,
|
|
83
|
-
disabled: process.env.APP_ENV !== "production",
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
return orbitrace;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function useOrbitrace() {
|
|
90
|
-
const orbitrace = inject(ORBITRACE_KEY);
|
|
91
|
-
if (!orbitrace) {
|
|
92
|
-
throw new Error("Orbitrace not properly initialized");
|
|
93
|
-
}
|
|
94
|
-
return orbitrace;
|
|
95
|
-
}
|
|
96
55
|
```
|
|
97
56
|
|
|
98
|
-
###
|
|
57
|
+
### 3. Capture Messages
|
|
99
58
|
|
|
100
59
|
```javascript
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
import App from "./App.vue";
|
|
105
|
-
import { getOrbitraceInstance, ORBITRACE_KEY } from "/path/to/orbitrace";
|
|
106
|
-
|
|
107
|
-
const app = createApp(App);
|
|
108
|
-
const pinia = createPinia();
|
|
109
|
-
|
|
110
|
-
// Initialize Orbitrace
|
|
111
|
-
const orbitrace = getOrbitraceInstance({ service: "webapp" });
|
|
112
|
-
|
|
113
|
-
// Make Orbitrace available everywhere
|
|
114
|
-
app.provide(ORBITRACE_KEY, orbitrace);
|
|
115
|
-
|
|
116
|
-
// Add Orbitrace to Pinia
|
|
117
|
-
pinia.use(({ store }) => {
|
|
118
|
-
store.orbitrace = orbitrace;
|
|
60
|
+
await orbitrace.captureMessage("User registered", {
|
|
61
|
+
userId: "123",
|
|
62
|
+
email: "user@example.com",
|
|
119
63
|
});
|
|
120
|
-
|
|
121
|
-
app.use(pinia);
|
|
122
|
-
app.mount("#app");
|
|
123
64
|
```
|
|
124
65
|
|
|
125
|
-
###
|
|
66
|
+
### 4. Delivery Behavior
|
|
126
67
|
|
|
127
|
-
|
|
128
|
-
<script setup>
|
|
129
|
-
import { useOrbitrace } from "/path/to/orbitrace";
|
|
68
|
+
Orbitrace never throws and never blocks your application for long:
|
|
130
69
|
|
|
131
|
-
|
|
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.
|
|
132
74
|
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
// Your code here
|
|
136
|
-
} catch (error) {
|
|
137
|
-
await orbitrace.captureException(error, {
|
|
138
|
-
component: "UserProfile",
|
|
139
|
-
action: "updateProfile",
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Log custom events
|
|
145
|
-
async function trackEvent() {
|
|
146
|
-
await orbitrace.captureMessage("User completed onboarding", {
|
|
147
|
-
userId: "user123",
|
|
148
|
-
completedSteps: ["profile", "preferences"],
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
</script>
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### 4. Usage in Pinia Stores
|
|
75
|
+
In short-lived processes (scripts, CI jobs, serverless functions), call `flush()` before exiting so locally counted repeats are sent:
|
|
155
76
|
|
|
156
77
|
```javascript
|
|
157
|
-
|
|
158
|
-
import { defineStore } from "pinia";
|
|
159
|
-
|
|
160
|
-
export const useUserStore = defineStore("user", {
|
|
161
|
-
state: () => ({
|
|
162
|
-
user: null,
|
|
163
|
-
}),
|
|
164
|
-
actions: {
|
|
165
|
-
async fetchUser() {
|
|
166
|
-
try {
|
|
167
|
-
// Fetch user logic
|
|
168
|
-
} catch (error) {
|
|
169
|
-
await this.orbitrace.captureException(error, {
|
|
170
|
-
store: "userStore",
|
|
171
|
-
action: "fetchUser",
|
|
172
|
-
});
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
});
|
|
78
|
+
await orbitrace.flush();
|
|
178
79
|
```
|
|
179
80
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
For Vite:
|
|
81
|
+
## Environment Variables
|
|
183
82
|
|
|
184
83
|
```env
|
|
185
|
-
|
|
186
|
-
VITE_ORBITRACE_ORG_ID=your-org-id
|
|
187
|
-
VITE_ORBITRACE_PROJECT_ID=your-project-id
|
|
188
|
-
VITE_ORBITRACE_ENDPOINT=your-endpoint
|
|
189
|
-
VITE_APP_ENV=your-environment
|
|
190
|
-
VITE_APP_VERSION=your-version
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
For Node.js:
|
|
194
|
-
|
|
195
|
-
```env
|
|
196
|
-
ORBITRACE_API_KEY=your-api-key
|
|
84
|
+
ORBITRACE_API_KEY=your-backend-api-key
|
|
197
85
|
ORBITRACE_ORG_ID=your-org-id
|
|
198
86
|
ORBITRACE_PROJECT_ID=your-project-id
|
|
199
87
|
ORBITRACE_ENDPOINT=your-endpoint
|
|
200
|
-
APP_ENV=
|
|
201
|
-
APP_VERSION=
|
|
88
|
+
APP_ENV=production
|
|
89
|
+
APP_VERSION=1.0.0
|
|
90
|
+
ORBITRACE_DISABLED=false
|
|
202
91
|
```
|
|
92
|
+
|
|
93
|
+
## Security Model
|
|
94
|
+
|
|
95
|
+
Orbitrace authenticates ingestion requests with `ORBITRACE_API_KEY`. Keep this key only in backend environments such as servers, workers, functions, and CI jobs. Do not use `VITE_`, `NEXT_PUBLIC_`, or other public frontend environment variables for Orbitrace credentials.
|
|
96
|
+
|
|
97
|
+
If this package is initialized in a browser runtime, it throws an error before sending events.
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
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) {
|
|
16
|
+
this.assertServerRuntime();
|
|
5
17
|
this.validateConfig(config);
|
|
6
18
|
|
|
7
19
|
this.#config = {
|
|
@@ -27,72 +39,104 @@ class Orbitrace {
|
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
static
|
|
31
|
-
if (
|
|
42
|
+
static assertServerRuntime() {
|
|
43
|
+
if (typeof window !== "undefined" || typeof document !== "undefined") {
|
|
32
44
|
throw new Error(
|
|
33
|
-
"Orbitrace
|
|
45
|
+
"Orbitrace can only be initialized in backend/server runtimes"
|
|
34
46
|
);
|
|
35
47
|
}
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
static
|
|
39
|
-
|
|
50
|
+
static async captureException(error, metadata = {}) {
|
|
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
|
+
});
|
|
40
58
|
}
|
|
41
59
|
|
|
42
|
-
static async
|
|
43
|
-
this
|
|
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
|
+
}
|
|
44
76
|
|
|
45
77
|
if (this.#config.disabled) {
|
|
46
78
|
console.log("Orbitrace: Logging is disabled");
|
|
47
79
|
return;
|
|
48
80
|
}
|
|
49
81
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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);
|
|
93
|
+
|
|
94
|
+
if (pending) {
|
|
95
|
+
pending.count++;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
61
98
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.error("Orbitrace: Error capturing exception", err);
|
|
99
|
+
if (!this.#canSend()) {
|
|
100
|
+
return;
|
|
65
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);
|
|
66
109
|
}
|
|
67
110
|
|
|
68
|
-
static async
|
|
69
|
-
this.
|
|
111
|
+
static async #flushPending(key) {
|
|
112
|
+
const entry = this.#pending.get(key);
|
|
70
113
|
|
|
71
|
-
if (
|
|
72
|
-
console.log("Orbitrace: Logging is disabled");
|
|
114
|
+
if (!entry) {
|
|
73
115
|
return;
|
|
74
116
|
}
|
|
75
117
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
message,
|
|
79
|
-
metadata: {
|
|
80
|
-
...metadata,
|
|
81
|
-
env: this.#config.environment,
|
|
82
|
-
version: this.#config.version,
|
|
83
|
-
service: this.#config.service,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
118
|
+
this.#pending.delete(key);
|
|
119
|
+
clearTimeout(entry.timer);
|
|
86
120
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.error("Orbitrace: Error capturing message", err);
|
|
121
|
+
if (entry.count > 0 && this.#canSend()) {
|
|
122
|
+
await this.#send(entry.event, entry.payload, entry.count);
|
|
90
123
|
}
|
|
91
124
|
}
|
|
92
125
|
|
|
93
|
-
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) {
|
|
94
138
|
try {
|
|
95
|
-
const
|
|
139
|
+
const response = await fetch(this.#config.endpoint, {
|
|
96
140
|
method: "POST",
|
|
97
141
|
headers: {
|
|
98
142
|
"Content-Type": "application/json",
|
|
@@ -102,24 +146,27 @@ class Orbitrace {
|
|
|
102
146
|
},
|
|
103
147
|
body: JSON.stringify({
|
|
104
148
|
event,
|
|
149
|
+
...(count > 1 && { count }),
|
|
105
150
|
payload,
|
|
106
151
|
}),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
152
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
153
|
+
});
|
|
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;
|
|
111
160
|
}
|
|
112
161
|
|
|
113
|
-
const response = await fetch(this.#config.endpoint, fetchOptions);
|
|
114
|
-
|
|
115
162
|
if (!response.ok) {
|
|
116
|
-
|
|
163
|
+
console.error(`Orbitrace: API responded with status ${response.status}`);
|
|
164
|
+
return;
|
|
117
165
|
}
|
|
118
166
|
|
|
119
167
|
return await response.json();
|
|
120
168
|
} catch (error) {
|
|
121
|
-
console.error("Orbitrace: Error sending data to API", error);
|
|
122
|
-
throw error;
|
|
169
|
+
console.error("Orbitrace: Error sending data to API", error.message);
|
|
123
170
|
}
|
|
124
171
|
}
|
|
125
172
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orbitai/orbitrace",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Error tracking and monitoring for Node.js
|
|
3
|
+
"version": "1.1.11",
|
|
4
|
+
"description": "Error tracking and monitoring for backend Node.js applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|