@statoss/sdk 0.2.0 → 0.4.0
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/CHANGELOG.md +41 -0
- package/README.md +151 -23
- package/dist/cjs/client.d.ts +29 -2
- package/dist/cjs/client.js +211 -4
- package/dist/cjs/envelope.d.ts +20 -1
- package/dist/cjs/envelope.js +41 -1
- package/dist/cjs/index.d.ts +19 -2
- package/dist/cjs/index.js +19 -6
- package/dist/cjs/levels.d.ts +4 -1
- package/dist/cjs/levels.js +23 -12
- package/dist/cjs/logs.d.ts +133 -0
- package/dist/cjs/logs.js +235 -0
- package/dist/cjs/schema.d.ts +211 -0
- package/dist/cjs/schema.js +159 -0
- package/dist/cjs/transport.d.ts +6 -1
- package/dist/cjs/transport.js +23 -7
- package/dist/cjs/types.d.ts +9 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client.d.ts +29 -2
- package/dist/esm/client.js +212 -5
- package/dist/esm/envelope.d.ts +20 -1
- package/dist/esm/envelope.js +37 -1
- package/dist/esm/index.d.ts +19 -2
- package/dist/esm/index.js +17 -5
- package/dist/esm/levels.d.ts +4 -1
- package/dist/esm/levels.js +20 -10
- package/dist/esm/logs.d.ts +133 -0
- package/dist/esm/logs.js +219 -0
- package/dist/esm/schema.d.ts +211 -0
- package/dist/esm/schema.js +153 -0
- package/dist/esm/transport.d.ts +6 -1
- package/dist/esm/transport.js +23 -7
- package/dist/esm/types.d.ts +9 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { CATEGORY_RULE, isCategoryName, isLevelName, isSentryLevel, LEVEL_RULE, } from "./levels.js";
|
|
2
|
+
import { LOG_SEVERITY, logLevelOf, rangeOfSeverity, } from "./logs.js";
|
|
3
|
+
import { isObject, safeString } from "./utils.js";
|
|
4
|
+
// The server's own lists (src/lib/app-schema.ts, src/lib/schema-colors.ts,
|
|
5
|
+
// src/db/schema.ts), kept equal by a test.
|
|
6
|
+
/** The ranges a level ranks in: Sentry's five and OpenTelemetry's trace. */
|
|
7
|
+
export const LIKES = [
|
|
8
|
+
"fatal",
|
|
9
|
+
"error",
|
|
10
|
+
"warning",
|
|
11
|
+
"info",
|
|
12
|
+
"debug",
|
|
13
|
+
"trace",
|
|
14
|
+
];
|
|
15
|
+
/** The colours a level or a category takes, by name. */
|
|
16
|
+
export const SCHEMA_COLORS = [
|
|
17
|
+
"crimson",
|
|
18
|
+
"red",
|
|
19
|
+
"orange",
|
|
20
|
+
"amber",
|
|
21
|
+
"yellow",
|
|
22
|
+
"green",
|
|
23
|
+
"aqua",
|
|
24
|
+
"teal",
|
|
25
|
+
"blue",
|
|
26
|
+
"violet",
|
|
27
|
+
"indigo",
|
|
28
|
+
"magenta",
|
|
29
|
+
"plum",
|
|
30
|
+
"grey",
|
|
31
|
+
];
|
|
32
|
+
/** What a rule tests: a tag by key, the logger, or one of the event's own fields. */
|
|
33
|
+
export const RULE_SUBJECTS = [
|
|
34
|
+
"tag",
|
|
35
|
+
"logger",
|
|
36
|
+
"exception",
|
|
37
|
+
"culprit",
|
|
38
|
+
"title",
|
|
39
|
+
"message",
|
|
40
|
+
];
|
|
41
|
+
export const RULE_MATCHES = ["is", "starts", "contains"];
|
|
42
|
+
export const FIELD_TYPES = ["text", "number"];
|
|
43
|
+
/**
|
|
44
|
+
* Declares an app's levels, categories, fields and rules. Returns the schema
|
|
45
|
+
* as given: plain data in the shape the server takes, so it can be sent as
|
|
46
|
+
* it is. Its names become types: pass it to init as `schema` and the capture
|
|
47
|
+
* functions init returns take only these names.
|
|
48
|
+
*/
|
|
49
|
+
export function defineSchema(schema) {
|
|
50
|
+
return schema;
|
|
51
|
+
}
|
|
52
|
+
// At runtime
|
|
53
|
+
/** The Sentry level a level of the schema is sent as. */
|
|
54
|
+
function sentAs(name, value, logger) {
|
|
55
|
+
const settings = isObject(value) ? value : {};
|
|
56
|
+
const like = typeof value === "string" ? value : settings.like;
|
|
57
|
+
if (isSentryLevel(like))
|
|
58
|
+
return like;
|
|
59
|
+
if (like === "trace")
|
|
60
|
+
return "debug";
|
|
61
|
+
if (like !== undefined || (typeof value !== "string" && !isObject(value))) {
|
|
62
|
+
logger.warn(`Level "${name}" in the schema: like should be ${LIKES.join(", ")}; sent as error.`);
|
|
63
|
+
return "error";
|
|
64
|
+
}
|
|
65
|
+
const severity = settings.severity;
|
|
66
|
+
if (typeof severity !== "number" || severity < 1 || severity > 24)
|
|
67
|
+
return "error";
|
|
68
|
+
if (severity >= 21)
|
|
69
|
+
return "fatal";
|
|
70
|
+
if (severity >= 17)
|
|
71
|
+
return "error";
|
|
72
|
+
if (severity >= 13)
|
|
73
|
+
return "warning";
|
|
74
|
+
if (severity >= 9)
|
|
75
|
+
return "info";
|
|
76
|
+
return "debug";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* How a line at each level of init's `schema` is logged: at its range's
|
|
80
|
+
* level (trace stays trace), with its severity when it gives one inside
|
|
81
|
+
* that range. Names readSchema refuses are left out.
|
|
82
|
+
*/
|
|
83
|
+
export function schemaLogLevels(schema) {
|
|
84
|
+
const out = {};
|
|
85
|
+
if (!isObject(schema) || Array.isArray(schema))
|
|
86
|
+
return out;
|
|
87
|
+
const { levels } = schema;
|
|
88
|
+
if (!isObject(levels) || Array.isArray(levels))
|
|
89
|
+
return out;
|
|
90
|
+
for (const [name, value] of Object.entries(levels)) {
|
|
91
|
+
if (isSentryLevel(name) || !isLevelName(name))
|
|
92
|
+
continue;
|
|
93
|
+
const settings = isObject(value) ? value : {};
|
|
94
|
+
const like = typeof value === "string" ? value : settings.like;
|
|
95
|
+
const given = settings.severity;
|
|
96
|
+
const severity = typeof given === "number" &&
|
|
97
|
+
Number.isInteger(given) &&
|
|
98
|
+
given >= 1 &&
|
|
99
|
+
given <= 24
|
|
100
|
+
? given
|
|
101
|
+
: undefined;
|
|
102
|
+
let level = "error";
|
|
103
|
+
if (typeof like === "string" && LIKES.includes(like))
|
|
104
|
+
level = logLevelOf(like) ?? "error";
|
|
105
|
+
else if (like === undefined && severity !== undefined)
|
|
106
|
+
level = rangeOfSeverity(severity);
|
|
107
|
+
out[name] = {
|
|
108
|
+
level,
|
|
109
|
+
severity: severity !== undefined && rangeOfSeverity(severity) === level
|
|
110
|
+
? severity
|
|
111
|
+
: LOG_SEVERITY[level],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The Sentry level each level of init's `schema` is sent as. Built-in
|
|
118
|
+
* levels are left out; names the SDK cannot send are logged in debug mode.
|
|
119
|
+
*/
|
|
120
|
+
export function readSchema(schema, logger) {
|
|
121
|
+
const out = {};
|
|
122
|
+
if (schema === undefined || schema === null)
|
|
123
|
+
return out;
|
|
124
|
+
if (!isObject(schema) || Array.isArray(schema)) {
|
|
125
|
+
logger.warn("Ignored `schema`: pass what defineSchema returns.");
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
const { levels, categories } = schema;
|
|
129
|
+
if (isObject(levels) && !Array.isArray(levels)) {
|
|
130
|
+
for (const [name, value] of Object.entries(levels)) {
|
|
131
|
+
// A built-in level, given a label or a colour.
|
|
132
|
+
if (isSentryLevel(name))
|
|
133
|
+
continue;
|
|
134
|
+
if (isLevelName(name))
|
|
135
|
+
out[name] = sentAs(name, value, logger);
|
|
136
|
+
else
|
|
137
|
+
logger.warn(`Ignored level "${name}" in the schema: ${LEVEL_RULE}.`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (levels !== undefined && levels !== null) {
|
|
141
|
+
logger.warn('Ignored the schema\'s levels: they should be an object such as { "payment-failed": "error" }.');
|
|
142
|
+
}
|
|
143
|
+
const names = Array.isArray(categories)
|
|
144
|
+
? categories
|
|
145
|
+
: isObject(categories)
|
|
146
|
+
? Object.keys(categories)
|
|
147
|
+
: [];
|
|
148
|
+
for (const name of names) {
|
|
149
|
+
if (!isCategoryName(name))
|
|
150
|
+
logger.warn(`Category "${safeString(name)}" in the schema cannot be sent: ${CATEGORY_RULE}.`);
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
package/dist/esm/transport.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { type Envelope } from "./envelope.js";
|
|
|
2
2
|
import type { Logger } from "./logger.js";
|
|
3
3
|
import type { Transport } from "./types.js";
|
|
4
4
|
export declare const QUEUE_LIMIT = 30;
|
|
5
|
+
/** Of the queue, what log envelopes may take, so a flood of lines never pushes out an error. */
|
|
6
|
+
export declare const LOG_QUEUE_LIMIT = 10;
|
|
5
7
|
export declare const CONCURRENCY = 5;
|
|
6
8
|
/** The default transport: fetch, called on globalThis (Workers refuse any other this). */
|
|
7
9
|
export declare function fetchTransport(browserLike: boolean): Transport;
|
|
@@ -16,12 +18,15 @@ export interface SenderOptions {
|
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Sends envelopes in order of arrival, a few at a time, keeping at most 30
|
|
19
|
-
* waiting or in flight
|
|
21
|
+
* waiting or in flight, of them at most 10 of log lines. Whatever cannot
|
|
22
|
+
* be sent is counted for client reports.
|
|
20
23
|
*/
|
|
21
24
|
export declare class Sender {
|
|
22
25
|
private readonly options;
|
|
23
26
|
private readonly queue;
|
|
24
27
|
private active;
|
|
28
|
+
/** Log envelopes waiting or in flight. */
|
|
29
|
+
private logs;
|
|
25
30
|
private limits;
|
|
26
31
|
private waiters;
|
|
27
32
|
private keepaliveBytes;
|
package/dist/esm/transport.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { itemCategory, serializeEnvelope } from "./envelope.js";
|
|
1
|
+
import { isLogEnvelope, itemCategory, itemQuantity, serializeEnvelope, } from "./envelope.js";
|
|
2
2
|
import { isRateLimited, updateRateLimits, } from "./ratelimit.js";
|
|
3
3
|
import { safely, unref } from "./utils.js";
|
|
4
4
|
export const QUEUE_LIMIT = 30;
|
|
5
|
+
/** Of the queue, what log envelopes may take, so a flood of lines never pushes out an error. */
|
|
6
|
+
export const LOG_QUEUE_LIMIT = 10;
|
|
5
7
|
export const CONCURRENCY = 5;
|
|
6
8
|
const REQUEST_TIMEOUT_MS = 30000;
|
|
7
9
|
// Browsers refuse keepalive requests past 64 KiB in flight in total.
|
|
@@ -57,13 +59,16 @@ function header(response, name) {
|
|
|
57
59
|
}
|
|
58
60
|
/**
|
|
59
61
|
* Sends envelopes in order of arrival, a few at a time, keeping at most 30
|
|
60
|
-
* waiting or in flight
|
|
62
|
+
* waiting or in flight, of them at most 10 of log lines. Whatever cannot
|
|
63
|
+
* be sent is counted for client reports.
|
|
61
64
|
*/
|
|
62
65
|
export class Sender {
|
|
63
66
|
constructor(options) {
|
|
64
67
|
this.options = options;
|
|
65
68
|
this.queue = [];
|
|
66
69
|
this.active = 0;
|
|
70
|
+
/** Log envelopes waiting or in flight. */
|
|
71
|
+
this.logs = 0;
|
|
67
72
|
this.limits = {};
|
|
68
73
|
this.waiters = [];
|
|
69
74
|
this.keepaliveBytes = 0;
|
|
@@ -73,11 +78,15 @@ export class Sender {
|
|
|
73
78
|
const allowed = this.withoutLimited(envelope);
|
|
74
79
|
if (!allowed)
|
|
75
80
|
return;
|
|
76
|
-
|
|
81
|
+
const log = isLogEnvelope(allowed);
|
|
82
|
+
if (this.queue.length + this.active >= QUEUE_LIMIT ||
|
|
83
|
+
(log && this.logs >= LOG_QUEUE_LIMIT)) {
|
|
77
84
|
this.options.logger.warn("Dropped an envelope: the send queue is full.");
|
|
78
85
|
this.lose(allowed, "queue_overflow");
|
|
79
86
|
return;
|
|
80
87
|
}
|
|
88
|
+
if (log)
|
|
89
|
+
this.logs++;
|
|
81
90
|
this.queue.push(allowed);
|
|
82
91
|
this.pump();
|
|
83
92
|
}
|
|
@@ -126,7 +135,7 @@ export class Sender {
|
|
|
126
135
|
if (!isRateLimited(this.limits, category, now))
|
|
127
136
|
return true;
|
|
128
137
|
if (category !== "internal")
|
|
129
|
-
this.options.recordLoss("ratelimit_backoff", category);
|
|
138
|
+
this.options.recordLoss("ratelimit_backoff", category, itemQuantity(item));
|
|
130
139
|
return false;
|
|
131
140
|
});
|
|
132
141
|
if (items.length < envelope[1].length) {
|
|
@@ -136,13 +145,20 @@ export class Sender {
|
|
|
136
145
|
}
|
|
137
146
|
pump() {
|
|
138
147
|
while (this.active < CONCURRENCY && this.queue.length > 0) {
|
|
148
|
+
const waited = this.queue.shift();
|
|
149
|
+
const log = isLogEnvelope(waited);
|
|
139
150
|
// A 429 may have come in while it waited.
|
|
140
|
-
const next = this.withoutLimited(
|
|
141
|
-
if (!next)
|
|
151
|
+
const next = this.withoutLimited(waited);
|
|
152
|
+
if (!next) {
|
|
153
|
+
if (log)
|
|
154
|
+
this.logs--;
|
|
142
155
|
continue;
|
|
156
|
+
}
|
|
143
157
|
this.active++;
|
|
144
158
|
const done = () => {
|
|
145
159
|
this.active--;
|
|
160
|
+
if (log)
|
|
161
|
+
this.logs--;
|
|
146
162
|
this.pump();
|
|
147
163
|
};
|
|
148
164
|
void this.deliver(next).then(done, done);
|
|
@@ -207,7 +223,7 @@ export class Sender {
|
|
|
207
223
|
const category = itemCategory(item);
|
|
208
224
|
// A lost client report is not reported again.
|
|
209
225
|
if (category !== "internal")
|
|
210
|
-
this.options.recordLoss(reason, category);
|
|
226
|
+
this.options.recordLoss(reason, category, itemQuantity(item));
|
|
211
227
|
}
|
|
212
228
|
}
|
|
213
229
|
}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Log } from "./logs.js";
|
|
1
2
|
/** The five levels Sentry knows. */
|
|
2
3
|
export type SentryLevel = "fatal" | "error" | "warning" | "info" | "debug";
|
|
3
4
|
/**
|
|
@@ -174,7 +175,10 @@ export interface InitOptions {
|
|
|
174
175
|
beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | null;
|
|
175
176
|
/** Defaults to 100, which is also the most kept. */
|
|
176
177
|
maxBreadcrumbs?: number;
|
|
177
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* When true, browsers ask the server to record the sender's IP address,
|
|
180
|
+
* and log lines carry the scope's user. Defaults to false.
|
|
181
|
+
*/
|
|
178
182
|
sendDefaultPii?: boolean;
|
|
179
183
|
/** Tags on every event. */
|
|
180
184
|
tags?: Record<string, Primitive>;
|
|
@@ -187,4 +191,8 @@ export interface InitOptions {
|
|
|
187
191
|
integrations?: IntegrationToggles;
|
|
188
192
|
/** Your own level names and the Sentry level each is sent as, such as { "payment-failed": "error" }. */
|
|
189
193
|
levels?: Record<string, SentryLevel>;
|
|
194
|
+
/** Sends the lines of `logger`, as Sentry's enableLogs does. Defaults to false. */
|
|
195
|
+
logs?: boolean;
|
|
196
|
+
/** Change or drop (return null) a log line before it is sent. */
|
|
197
|
+
beforeSendLog?: (log: Log) => Log | null;
|
|
190
198
|
}
|
package/dist/esm/version.d.ts
CHANGED
package/dist/esm/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statoss/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Error tracking for StatOSS: browsers, Node, Bun, Deno, Cloudflare Workers and Vercel Edge. Speaks Sentry's protocol.",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Error tracking and logs for StatOSS: browsers, Node, Bun, Deno, Cloudflare Workers and Vercel Edge. Speaks Sentry's protocol.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "StatOSS",
|
|
7
7
|
"homepage": "https://statoss.com",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"statoss",
|
|
15
15
|
"errors",
|
|
16
16
|
"error-tracking",
|
|
17
|
+
"logs",
|
|
17
18
|
"sentry",
|
|
18
19
|
"monitoring"
|
|
19
20
|
],
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"files": [
|
|
39
40
|
"dist",
|
|
40
41
|
"README.md",
|
|
42
|
+
"CHANGELOG.md",
|
|
41
43
|
"LICENSE"
|
|
42
44
|
],
|
|
43
45
|
"engines": {
|