@statoss/sdk 0.2.0 → 0.3.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/README.md +85 -15
- package/dist/cjs/client.d.ts +5 -1
- package/dist/cjs/client.js +33 -1
- package/dist/cjs/index.d.ts +10 -1
- package/dist/cjs/index.js +11 -6
- package/dist/cjs/levels.d.ts +4 -1
- package/dist/cjs/levels.js +23 -12
- package/dist/cjs/schema.d.ts +199 -0
- package/dist/cjs/schema.js +119 -0
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client.d.ts +5 -1
- package/dist/esm/client.js +33 -1
- package/dist/esm/index.d.ts +10 -1
- package/dist/esm/index.js +9 -5
- package/dist/esm/levels.d.ts +4 -1
- package/dist/esm/levels.js +20 -10
- package/dist/esm/schema.d.ts +199 -0
- package/dist/esm/schema.js +114 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -269,20 +269,88 @@ Adding `--release <release>` to the upload is optional. Deploy the bundles as
|
|
|
269
269
|
`inject` left them: the SDK reads their debug ids and sends them with each
|
|
270
270
|
event. Frames are then shown as source and grouped by their source names.
|
|
271
271
|
|
|
272
|
-
##
|
|
272
|
+
## Your own levels, categories and fields
|
|
273
273
|
|
|
274
274
|
Sentry has five levels: `fatal`, `error`, `warning`, `info` and `debug`.
|
|
275
|
-
StatOSS
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
StatOSS adds levels of your own, categories and fields. They travel as tags
|
|
276
|
+
next to the nearest Sentry level, so Sentry and any Sentry-based tool still
|
|
277
|
+
read the event normally.
|
|
278
|
+
|
|
279
|
+
Declare them with `defineSchema` and give the schema to `init`. The capture
|
|
280
|
+
functions `init` returns take only the schema's names, so a wrong one fails
|
|
281
|
+
to compile.
|
|
278
282
|
|
|
279
283
|
```ts
|
|
280
|
-
StatOSS
|
|
281
|
-
|
|
284
|
+
import * as StatOSS from "@statoss/sdk";
|
|
285
|
+
|
|
286
|
+
export const schema = StatOSS.defineSchema({
|
|
282
287
|
levels: {
|
|
283
|
-
"payment-failed": "error",
|
|
288
|
+
"payment-failed": { like: "error", label: "Payment failed" },
|
|
284
289
|
"slow-query": "warning",
|
|
285
290
|
},
|
|
291
|
+
categories: {
|
|
292
|
+
billing: { label: "Billing", color: "plum" },
|
|
293
|
+
auth: {},
|
|
294
|
+
},
|
|
295
|
+
fields: {
|
|
296
|
+
route: { label: "Route", filterable: true },
|
|
297
|
+
duration_ms: { type: "number", unit: "ms", label: "Duration" },
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const statoss = StatOSS.init({ dsn: process.env.STATOSS_DSN, schema });
|
|
302
|
+
|
|
303
|
+
statoss.captureException(error, {
|
|
304
|
+
level: "payment-failed",
|
|
305
|
+
category: "billing",
|
|
306
|
+
fields: { route: "/pay" },
|
|
307
|
+
});
|
|
308
|
+
// level "error", tags { "statoss.level": "payment-failed",
|
|
309
|
+
// "statoss.category": "billing", route: "/pay" }
|
|
310
|
+
|
|
311
|
+
statoss.captureMessage("Search took 2.4 s", {
|
|
312
|
+
level: "slow-query",
|
|
313
|
+
fields: { duration_ms: 2400 },
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// @ts-expect-error: not a level of this schema
|
|
317
|
+
statoss.captureException(error, { level: "payment-faild" });
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
- A level ranks like `fatal`, `error`, `warning`, `info`, `debug` or
|
|
321
|
+
`trace`: give that alone, or `like` with an optional `severity` (1 to 24,
|
|
322
|
+
inside its range), `label` and `color`. It is sent as the nearest Sentry
|
|
323
|
+
level (`trace` as `debug`; `error` when none is given).
|
|
324
|
+
- A category takes an optional `label`, `color`, `owner` (a team member's
|
|
325
|
+
email) and `hidden`.
|
|
326
|
+
- A field is a tag key, with an optional `label`, `type` (`text` or
|
|
327
|
+
`number`), `unit` and `filterable`. Field values are strings, numbers or
|
|
328
|
+
booleans.
|
|
329
|
+
- Categories and fields can also be lists of names.
|
|
330
|
+
- `rules` set a level or a category for events that match, such as
|
|
331
|
+
`{ subject: "logger", value: "payments", category: "billing" }`. They name
|
|
332
|
+
the schema's own levels and categories.
|
|
333
|
+
- Colours: `crimson`, `red`, `orange`, `amber`, `yellow`, `green`, `aqua`,
|
|
334
|
+
`teal`, `blue`, `violet`, `indigo`, `magenta`, `plum` and `grey`.
|
|
335
|
+
- Names: a level is 1 to 32 characters and a category 1 to 64, of lowercase
|
|
336
|
+
letters, digits, dots, dashes and underscores. A field is any tag key not
|
|
337
|
+
starting `statoss.`.
|
|
338
|
+
|
|
339
|
+
`defineSchema` refuses a wrong name, range, severity, colour or setting at
|
|
340
|
+
compile time (TypeScript 5.0 or later). It returns the schema as given:
|
|
341
|
+
plain data in the shape StatOSS's schema API takes. A function of yours
|
|
342
|
+
that hands a schema to `init` types it as
|
|
343
|
+
`<const S extends SchemaSpec & CheckSchema<S>>(schema: S)`.
|
|
344
|
+
|
|
345
|
+
### Without a schema
|
|
346
|
+
|
|
347
|
+
The module-level functions take any level or category name, and `levels` in
|
|
348
|
+
`init` maps your names to Sentry levels:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
StatOSS.init({
|
|
352
|
+
dsn: "...",
|
|
353
|
+
levels: { "payment-failed": "error", "slow-query": "warning" },
|
|
286
354
|
});
|
|
287
355
|
|
|
288
356
|
StatOSS.captureMessage("Card declined", "payment-failed");
|
|
@@ -295,11 +363,10 @@ StatOSS.captureException(error, {
|
|
|
295
363
|
// level "warning", tags { "statoss.level": "slow-query", "statoss.category": "db.postgres" }
|
|
296
364
|
```
|
|
297
365
|
|
|
298
|
-
-
|
|
299
|
-
`levels`
|
|
300
|
-
-
|
|
301
|
-
|
|
302
|
-
underscores. Others are ignored; with `debug: true` the SDK logs each one.
|
|
366
|
+
- A name in neither `levels` nor the schema is sent as `error`. Given both,
|
|
367
|
+
`levels` wins.
|
|
368
|
+
- Names the SDK cannot send are ignored; with `debug: true` it logs each
|
|
369
|
+
one.
|
|
303
370
|
- StatOSS filters and groups on these tags like any other.
|
|
304
371
|
|
|
305
372
|
## What is captured
|
|
@@ -356,7 +423,8 @@ await StatOSS.withScope(async (scope) => {
|
|
|
356
423
|
## API
|
|
357
424
|
|
|
358
425
|
```ts
|
|
359
|
-
init(options):
|
|
426
|
+
init(options): handle // handle.captureException, captureMessage, captureEvent
|
|
427
|
+
defineSchema(schema): schema // the schema as given, its names as types
|
|
360
428
|
captureException(error, context?): string // returns the event id
|
|
361
429
|
captureMessage(message, levelOrContext?): string
|
|
362
430
|
captureEvent(event, context?): string
|
|
@@ -374,8 +442,9 @@ scope.setUser, setTag, setTags, setExtra, setExtras, setContext, setLevel,
|
|
|
374
442
|
scope.captureException, captureMessage, captureEvent // with this scope
|
|
375
443
|
```
|
|
376
444
|
|
|
377
|
-
`context` is `{ level, tags, extra, contexts, user, fingerprint, category }
|
|
378
|
-
It wins over the scope for
|
|
445
|
+
`context` is `{ level, tags, extra, contexts, user, fingerprint, category }`,
|
|
446
|
+
and on the handle `init` returns also `fields`. It wins over the scope for
|
|
447
|
+
that one event.
|
|
379
448
|
|
|
380
449
|
### Options
|
|
381
450
|
|
|
@@ -392,6 +461,7 @@ It wins over the scope for that one event.
|
|
|
392
461
|
| `sendDefaultPii` | `false` | Record the sender's IP address |
|
|
393
462
|
| `tags` | | Tags on every event |
|
|
394
463
|
| `initialScope` | | `{ tags, extra, contexts, user, level, fingerprint }` |
|
|
464
|
+
| `schema` | | From `defineSchema`: your levels, categories, fields |
|
|
395
465
|
| `levels` | | Your level names and the Sentry level each is sent as |
|
|
396
466
|
| `integrations` | all on | See above |
|
|
397
467
|
| `enabled` | `true` | |
|
package/dist/cjs/client.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { type Logger } from "./logger.js";
|
|
|
2
2
|
import { type RuntimeInfo } from "./runtime.js";
|
|
3
3
|
import { type Scope } from "./scope.js";
|
|
4
4
|
import type { Breadcrumb, BreadcrumbHint, CaptureContext, Event, InitOptions } from "./types.js";
|
|
5
|
+
/** init's options as the client reads them: init may pass a schema. */
|
|
6
|
+
export type ClientOptions = InitOptions & {
|
|
7
|
+
schema?: unknown;
|
|
8
|
+
};
|
|
5
9
|
export declare class Client {
|
|
6
10
|
readonly logger: Logger;
|
|
7
11
|
readonly runtime: RuntimeInfo;
|
|
@@ -20,7 +24,7 @@ export declare class Client {
|
|
|
20
24
|
private reportTimer?;
|
|
21
25
|
private previousSignature?;
|
|
22
26
|
private closed;
|
|
23
|
-
constructor(options:
|
|
27
|
+
constructor(options: ClientOptions);
|
|
24
28
|
/** Installs the integrations that apply to this runtime. */
|
|
25
29
|
setup(): void;
|
|
26
30
|
captureException(error: unknown, context: CaptureContext | undefined, scope: Scope): string;
|
package/dist/cjs/client.js
CHANGED
|
@@ -14,6 +14,7 @@ const handlers_js_1 = require("./integrations/handlers.js");
|
|
|
14
14
|
const levels_js_1 = require("./levels.js");
|
|
15
15
|
const logger_js_1 = require("./logger.js");
|
|
16
16
|
const runtime_js_1 = require("./runtime.js");
|
|
17
|
+
const schema_js_1 = require("./schema.js");
|
|
17
18
|
const scope_js_1 = require("./scope.js");
|
|
18
19
|
const transport_js_1 = require("./transport.js");
|
|
19
20
|
const utils_js_1 = require("./utils.js");
|
|
@@ -54,6 +55,32 @@ function cleanTags(tags) {
|
|
|
54
55
|
}
|
|
55
56
|
return out;
|
|
56
57
|
}
|
|
58
|
+
/** A capture's fields, as tags. Keys starting statoss. are not fields. */
|
|
59
|
+
function fieldTags(fields, logger) {
|
|
60
|
+
const out = {};
|
|
61
|
+
if (fields === undefined || fields === null)
|
|
62
|
+
return out;
|
|
63
|
+
if (!(0, utils_js_1.isObject)(fields) || Array.isArray(fields)) {
|
|
64
|
+
logger.warn('Ignored `fields`: it should be an object such as { route: "/pay" }.');
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
68
|
+
if (value === undefined || value === null)
|
|
69
|
+
continue;
|
|
70
|
+
if (key === "" || key.startsWith("statoss.")) {
|
|
71
|
+
logger.warn(`Ignored field "${key}": fields are not empty and do not start statoss.`);
|
|
72
|
+
}
|
|
73
|
+
else if (typeof value === "string" ||
|
|
74
|
+
typeof value === "number" ||
|
|
75
|
+
typeof value === "boolean") {
|
|
76
|
+
out[key] = value;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
logger.warn(`Ignored field "${key}": its value should be a string, number or boolean.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
57
84
|
function cleanContexts(contexts) {
|
|
58
85
|
const out = {};
|
|
59
86
|
for (const [name, value] of Object.entries(contexts)) {
|
|
@@ -103,7 +130,11 @@ class Client {
|
|
|
103
130
|
this.options = options;
|
|
104
131
|
this.logger = (0, logger_js_1.makeLogger)(options.debug === true);
|
|
105
132
|
this.runtime = (0, runtime_js_1.describeRuntime)();
|
|
106
|
-
|
|
133
|
+
// What `levels` says wins over the schema.
|
|
134
|
+
this.levels = {
|
|
135
|
+
...(0, schema_js_1.readSchema)(options.schema, this.logger),
|
|
136
|
+
...(0, levels_js_1.readLevels)(options.levels, this.logger),
|
|
137
|
+
};
|
|
107
138
|
const rate = options.sampleRate;
|
|
108
139
|
if (rate !== undefined &&
|
|
109
140
|
!(typeof rate === "number" && rate >= 0 && rate <= 1)) {
|
|
@@ -329,6 +360,7 @@ class Client {
|
|
|
329
360
|
const tags = {
|
|
330
361
|
...scope._tags,
|
|
331
362
|
...ctx.tags,
|
|
363
|
+
...fieldTags(ctx.fields, this.logger),
|
|
332
364
|
...base.tags,
|
|
333
365
|
};
|
|
334
366
|
if (choice.custom)
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { CheckSchema, Handle, SchemaSpec } from "./schema.js";
|
|
1
2
|
import type { Breadcrumb, CaptureContext, Event, InitOptions, Level, Primitive, User } from "./types.js";
|
|
2
3
|
export { withScope } from "./hub.js";
|
|
4
|
+
export { defineSchema } from "./schema.js";
|
|
5
|
+
export type { CategoryOf, CategorySettings, CheckSchema, FieldOf, FieldSettings, FieldType, FieldValue, Handle, LevelOf, LevelSettings, Like, RuleMatch, RuleSubject, SchemaColor, SchemaContext, SchemaRule, SchemaSpec, Severity, } from "./schema.js";
|
|
3
6
|
export type { Scope } from "./scope.js";
|
|
4
7
|
export { SDK_NAME, SDK_VERSION } from "./version.js";
|
|
5
8
|
export type { Breadcrumb, BreadcrumbHint, CaptureContext, DebugImage, Event, EventHint, Exception, InitOptions, IntegrationToggles, Level, Mechanism, Primitive, RequestInfo, ScopeData, SdkInfo, SentryLevel, StackFrame, Transport, TransportRequest, TransportResponse, User, } from "./types.js";
|
|
@@ -7,8 +10,14 @@ export type { Breadcrumb, BreadcrumbHint, CaptureContext, DebugImage, Event, Eve
|
|
|
7
10
|
* Starts the SDK. Call it once, as early as you can. Calling it again replaces
|
|
8
11
|
* the earlier setup. Without a valid DSN, or with enabled: false, nothing is
|
|
9
12
|
* sent and nothing is patched.
|
|
13
|
+
*
|
|
14
|
+
* With a `schema` from defineSchema, its levels are sent as the Sentry level
|
|
15
|
+
* each ranks like, and the capture functions it returns take only the
|
|
16
|
+
* schema's levels, categories and fields.
|
|
10
17
|
*/
|
|
11
|
-
export declare function init(options?: InitOptions
|
|
18
|
+
export declare function init<const S extends SchemaSpec & CheckSchema<S> = SchemaSpec>(options?: InitOptions & {
|
|
19
|
+
schema?: S;
|
|
20
|
+
}): Handle<S>;
|
|
12
21
|
/** Sends an error (or anything thrown). Returns the event id. */
|
|
13
22
|
export declare function captureException(error: unknown, context?: CaptureContext): string;
|
|
14
23
|
/**
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SDK_VERSION = exports.SDK_NAME = exports.withScope = void 0;
|
|
3
|
+
exports.SDK_VERSION = exports.SDK_NAME = exports.defineSchema = exports.withScope = void 0;
|
|
4
4
|
exports.init = init;
|
|
5
5
|
exports.captureException = captureException;
|
|
6
6
|
exports.captureMessage = captureMessage;
|
|
@@ -19,14 +19,11 @@ const hub_js_1 = require("./hub.js");
|
|
|
19
19
|
const utils_js_1 = require("./utils.js");
|
|
20
20
|
var hub_js_2 = require("./hub.js");
|
|
21
21
|
Object.defineProperty(exports, "withScope", { enumerable: true, get: function () { return hub_js_2.withScope; } });
|
|
22
|
+
var schema_js_1 = require("./schema.js");
|
|
23
|
+
Object.defineProperty(exports, "defineSchema", { enumerable: true, get: function () { return schema_js_1.defineSchema; } });
|
|
22
24
|
var version_js_1 = require("./version.js");
|
|
23
25
|
Object.defineProperty(exports, "SDK_NAME", { enumerable: true, get: function () { return version_js_1.SDK_NAME; } });
|
|
24
26
|
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_js_1.SDK_VERSION; } });
|
|
25
|
-
/**
|
|
26
|
-
* Starts the SDK. Call it once, as early as you can. Calling it again replaces
|
|
27
|
-
* the earlier setup. Without a valid DSN, or with enabled: false, nothing is
|
|
28
|
-
* sent and nothing is patched.
|
|
29
|
-
*/
|
|
30
27
|
function init(options = {}) {
|
|
31
28
|
try {
|
|
32
29
|
const carrier = (0, hub_js_1.getCarrier)();
|
|
@@ -45,6 +42,7 @@ function init(options = {}) {
|
|
|
45
42
|
(0, utils_js_1.safely)(() => console.error("[StatOSS] init failed:", error));
|
|
46
43
|
}
|
|
47
44
|
}
|
|
45
|
+
return HANDLE;
|
|
48
46
|
}
|
|
49
47
|
/** Sends an error (or anything thrown). Returns the event id. */
|
|
50
48
|
function captureException(error, context) {
|
|
@@ -61,6 +59,13 @@ function captureMessage(message, levelOrContext) {
|
|
|
61
59
|
function captureEvent(event, context) {
|
|
62
60
|
return (0, hub_js_1.captureWith)(undefined, (client, scope) => client.captureEvent(event, context, scope));
|
|
63
61
|
}
|
|
62
|
+
// What init returns: the same functions, typed by the schema. The client
|
|
63
|
+
// reads a context's fields whichever function it came through.
|
|
64
|
+
const HANDLE = Object.freeze({
|
|
65
|
+
captureException,
|
|
66
|
+
captureMessage,
|
|
67
|
+
captureEvent,
|
|
68
|
+
});
|
|
64
69
|
/** Pass null to forget the user. */
|
|
65
70
|
function setUser(user) {
|
|
66
71
|
(0, utils_js_1.safely)(() => (0, hub_js_1.getCurrentScope)().setUser(user));
|
package/dist/cjs/levels.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
2
|
import type { SentryLevel } from "./types.js";
|
|
3
3
|
export declare const SENTRY_LEVELS: readonly SentryLevel[];
|
|
4
|
+
export declare const LEVEL_RULE = "use 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
5
|
+
export declare const CATEGORY_RULE = "use 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
4
6
|
export declare const LEVEL_TAG = "statoss.level";
|
|
5
7
|
export declare const CATEGORY_TAG = "statoss.category";
|
|
6
8
|
export declare function isSentryLevel(value: unknown): value is SentryLevel;
|
|
7
|
-
export declare function
|
|
9
|
+
export declare function isLevelName(value: unknown): value is string;
|
|
10
|
+
export declare function isCategoryName(value: unknown): value is string;
|
|
8
11
|
/** Keeps the entries of init's `levels` that are usable, logging the rest in debug mode. */
|
|
9
12
|
export declare function readLevels(input: unknown, logger: Logger): Record<string, SentryLevel>;
|
|
10
13
|
export interface LevelChoice {
|
package/dist/cjs/levels.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CATEGORY_TAG = exports.LEVEL_TAG = exports.SENTRY_LEVELS = void 0;
|
|
3
|
+
exports.CATEGORY_TAG = exports.LEVEL_TAG = exports.CATEGORY_RULE = exports.LEVEL_RULE = exports.SENTRY_LEVELS = void 0;
|
|
4
4
|
exports.isSentryLevel = isSentryLevel;
|
|
5
|
-
exports.
|
|
5
|
+
exports.isLevelName = isLevelName;
|
|
6
|
+
exports.isCategoryName = isCategoryName;
|
|
6
7
|
exports.readLevels = readLevels;
|
|
7
8
|
exports.chooseLevel = chooseLevel;
|
|
8
9
|
exports.readCategory = readCategory;
|
|
@@ -14,16 +15,26 @@ exports.SENTRY_LEVELS = [
|
|
|
14
15
|
"info",
|
|
15
16
|
"debug",
|
|
16
17
|
];
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Names of your own: lowercase letters, digits, dots, dashes and
|
|
20
|
+
* underscores. A level is at most 32 characters, as the server keeps it; a
|
|
21
|
+
* category at most 64.
|
|
22
|
+
*/
|
|
23
|
+
const LEVEL_RE = /^[a-z0-9._-]{1,32}$/;
|
|
24
|
+
const CATEGORY_RE = /^[a-z0-9._-]{1,64}$/;
|
|
25
|
+
exports.LEVEL_RULE = "use 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
26
|
+
exports.CATEGORY_RULE = "use 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
19
27
|
exports.LEVEL_TAG = "statoss.level";
|
|
20
28
|
exports.CATEGORY_TAG = "statoss.category";
|
|
21
29
|
function isSentryLevel(value) {
|
|
22
30
|
return (typeof value === "string" &&
|
|
23
31
|
exports.SENTRY_LEVELS.includes(value));
|
|
24
32
|
}
|
|
25
|
-
function
|
|
26
|
-
return typeof value === "string" &&
|
|
33
|
+
function isLevelName(value) {
|
|
34
|
+
return typeof value === "string" && LEVEL_RE.test(value);
|
|
35
|
+
}
|
|
36
|
+
function isCategoryName(value) {
|
|
37
|
+
return typeof value === "string" && CATEGORY_RE.test(value);
|
|
27
38
|
}
|
|
28
39
|
/** Keeps the entries of init's `levels` that are usable, logging the rest in debug mode. */
|
|
29
40
|
function readLevels(input, logger) {
|
|
@@ -38,8 +49,8 @@ function readLevels(input, logger) {
|
|
|
38
49
|
if (isSentryLevel(name)) {
|
|
39
50
|
logger.warn(`Ignored level "${name}" in \`levels\`: it is already a Sentry level.`);
|
|
40
51
|
}
|
|
41
|
-
else if (!
|
|
42
|
-
logger.warn(`Ignored level "${name}" in \`levels\`:
|
|
52
|
+
else if (!isLevelName(name)) {
|
|
53
|
+
logger.warn(`Ignored level "${name}" in \`levels\`: ${exports.LEVEL_RULE}.`);
|
|
43
54
|
}
|
|
44
55
|
else if (!isSentryLevel(level)) {
|
|
45
56
|
logger.warn(`Ignored level "${name}" in \`levels\`: map it to fatal, error, warning, info or debug.`);
|
|
@@ -60,18 +71,18 @@ function chooseLevel(candidates, levels, fallback, logger) {
|
|
|
60
71
|
continue;
|
|
61
72
|
if (isSentryLevel(candidate))
|
|
62
73
|
return { level: candidate };
|
|
63
|
-
if (
|
|
74
|
+
if (isLevelName(candidate)) {
|
|
64
75
|
return { level: levels[candidate] ?? "error", custom: candidate };
|
|
65
76
|
}
|
|
66
|
-
logger.warn(`Ignored level "${(0, utils_js_1.safeString)(candidate)}": use a Sentry level or 1 to
|
|
77
|
+
logger.warn(`Ignored level "${(0, utils_js_1.safeString)(candidate)}": use a Sentry level or 1 to 32 lowercase letters, digits, dots, dashes or underscores.`);
|
|
67
78
|
}
|
|
68
79
|
return { level: fallback };
|
|
69
80
|
}
|
|
70
81
|
function readCategory(value, logger) {
|
|
71
82
|
if (value === undefined || value === null)
|
|
72
83
|
return undefined;
|
|
73
|
-
if (
|
|
84
|
+
if (isCategoryName(value))
|
|
74
85
|
return value;
|
|
75
|
-
logger.warn(`Ignored category "${(0, utils_js_1.safeString)(value)}":
|
|
86
|
+
logger.warn(`Ignored category "${(0, utils_js_1.safeString)(value)}": ${exports.CATEGORY_RULE}.`);
|
|
76
87
|
return undefined;
|
|
77
88
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Logger } from "./logger.js";
|
|
2
|
+
import type { CaptureContext, Event, Level, SentryLevel } from "./types.js";
|
|
3
|
+
/** The ranges a level ranks in: Sentry's five and OpenTelemetry's trace. */
|
|
4
|
+
export declare const LIKES: readonly ["fatal", "error", "warning", "info", "debug", "trace"];
|
|
5
|
+
export type Like = (typeof LIKES)[number];
|
|
6
|
+
/** The colours a level or a category takes, by name. */
|
|
7
|
+
export declare const SCHEMA_COLORS: readonly ["crimson", "red", "orange", "amber", "yellow", "green", "aqua", "teal", "blue", "violet", "indigo", "magenta", "plum", "grey"];
|
|
8
|
+
export type SchemaColor = (typeof SCHEMA_COLORS)[number];
|
|
9
|
+
/** What a rule tests: a tag by key, the logger, or one of the event's own fields. */
|
|
10
|
+
export declare const RULE_SUBJECTS: readonly ["tag", "logger", "exception", "culprit", "title", "message"];
|
|
11
|
+
export type RuleSubject = (typeof RULE_SUBJECTS)[number];
|
|
12
|
+
export declare const RULE_MATCHES: readonly ["is", "starts", "contains"];
|
|
13
|
+
export type RuleMatch = (typeof RULE_MATCHES)[number];
|
|
14
|
+
export declare const FIELD_TYPES: readonly ["text", "number"];
|
|
15
|
+
export type FieldType = (typeof FIELD_TYPES)[number];
|
|
16
|
+
/** Each range's severities, as OpenTelemetry numbers them. */
|
|
17
|
+
interface Severities {
|
|
18
|
+
fatal: 21 | 22 | 23 | 24;
|
|
19
|
+
error: 17 | 18 | 19 | 20;
|
|
20
|
+
warning: 13 | 14 | 15 | 16;
|
|
21
|
+
info: 9 | 10 | 11 | 12;
|
|
22
|
+
debug: 5 | 6 | 7 | 8;
|
|
23
|
+
trace: 1 | 2 | 3 | 4;
|
|
24
|
+
}
|
|
25
|
+
export type Severity = Severities[Like];
|
|
26
|
+
/** A level of your own. Null clears a setting. */
|
|
27
|
+
export interface LevelSettings {
|
|
28
|
+
/** The range it ranks in. Without it (and without severity) it ranks as error. */
|
|
29
|
+
like?: Like;
|
|
30
|
+
/** 1 to 24, inside the range of `like`. */
|
|
31
|
+
severity?: Severity;
|
|
32
|
+
label?: string | null;
|
|
33
|
+
color?: SchemaColor | null;
|
|
34
|
+
}
|
|
35
|
+
/** A category. Null clears a setting. */
|
|
36
|
+
export interface CategorySettings {
|
|
37
|
+
label?: string | null;
|
|
38
|
+
color?: SchemaColor | null;
|
|
39
|
+
/** Left out of filters and lists; its events still carry it. */
|
|
40
|
+
hidden?: boolean;
|
|
41
|
+
/** A team member's email. */
|
|
42
|
+
owner?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** A field: a tag key. Null clears a setting. */
|
|
45
|
+
export interface FieldSettings {
|
|
46
|
+
label?: string | null;
|
|
47
|
+
type?: FieldType;
|
|
48
|
+
unit?: string | null;
|
|
49
|
+
/** Gets a picker in Explore's filters. */
|
|
50
|
+
filterable?: boolean;
|
|
51
|
+
}
|
|
52
|
+
type RuleTests = {
|
|
53
|
+
subject: "tag";
|
|
54
|
+
tagKey: string;
|
|
55
|
+
} | {
|
|
56
|
+
subject: Exclude<RuleSubject, "tag">;
|
|
57
|
+
tagKey?: undefined;
|
|
58
|
+
};
|
|
59
|
+
type RuleSets<L extends string, C extends string> = {
|
|
60
|
+
level: L;
|
|
61
|
+
category?: C;
|
|
62
|
+
} | {
|
|
63
|
+
level?: L;
|
|
64
|
+
category: C;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Sets a level, a category or both for events that match: the first rule
|
|
68
|
+
* that matches wins. `match` defaults to "is".
|
|
69
|
+
*/
|
|
70
|
+
export type SchemaRule<L extends string = string, C extends string = string> = RuleTests & {
|
|
71
|
+
match?: RuleMatch;
|
|
72
|
+
value: string;
|
|
73
|
+
} & RuleSets<L, C>;
|
|
74
|
+
/** An app's levels, categories, fields and rules, as the server takes them. */
|
|
75
|
+
export interface SchemaSpec {
|
|
76
|
+
/** Each name with the range it ranks in, or with its settings. */
|
|
77
|
+
levels?: {
|
|
78
|
+
readonly [name: string]: Like | LevelSettings;
|
|
79
|
+
};
|
|
80
|
+
categories?: readonly string[] | {
|
|
81
|
+
readonly [name: string]: CategorySettings;
|
|
82
|
+
};
|
|
83
|
+
fields?: readonly string[] | {
|
|
84
|
+
readonly [name: string]: FieldSettings;
|
|
85
|
+
};
|
|
86
|
+
rules?: readonly SchemaRule[];
|
|
87
|
+
}
|
|
88
|
+
type NamesIn<T> = T extends readonly (infer N)[] ? N & string : T extends object ? keyof T & string : never;
|
|
89
|
+
type LevelNames<S> = S extends {
|
|
90
|
+
levels?: infer L;
|
|
91
|
+
} ? NamesIn<NonNullable<L>> : never;
|
|
92
|
+
type CategoryNames<S> = S extends {
|
|
93
|
+
categories?: infer C;
|
|
94
|
+
} ? NamesIn<NonNullable<C>> : never;
|
|
95
|
+
type FieldNames<S> = S extends {
|
|
96
|
+
fields?: infer F;
|
|
97
|
+
} ? NamesIn<NonNullable<F>> : never;
|
|
98
|
+
/** A Sentry level or a level of the schema. */
|
|
99
|
+
export type LevelOf<S> = string extends LevelNames<S> ? Level : SentryLevel | LevelNames<S>;
|
|
100
|
+
/** A category of the schema. */
|
|
101
|
+
export type CategoryOf<S> = CategoryNames<S>;
|
|
102
|
+
/** A field of the schema. */
|
|
103
|
+
export type FieldOf<S> = FieldNames<S>;
|
|
104
|
+
type NameChar = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "." | "-" | "_";
|
|
105
|
+
/** True for 1 to Max of NameChar. A name only known as string passes. */
|
|
106
|
+
type IsName<S extends string, Max extends number, Seen extends 0[] = []> = string extends S ? true : S extends `${infer C}${infer Rest}` ? C extends NameChar ? Seen["length"] extends Max ? false : IsName<Rest, Max, [...Seen, 0]> : false : Seen extends [] ? false : true;
|
|
107
|
+
type LevelRule = "Level names are 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
108
|
+
type CategoryRule = "Category names are 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
109
|
+
type FieldRule = "Field names are not empty and do not start statoss.";
|
|
110
|
+
type Only<V, Keys extends PropertyKey, Message extends string> = {
|
|
111
|
+
[P in Exclude<keyof V, Keys>]: {
|
|
112
|
+
[M in Message]: true;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
type LevelCheck<V> = V extends string ? Like : {
|
|
116
|
+
like?: Like;
|
|
117
|
+
severity?: V extends {
|
|
118
|
+
like: infer K extends Like;
|
|
119
|
+
} ? Severities[K] : Severity;
|
|
120
|
+
label?: string | null;
|
|
121
|
+
color?: SchemaColor | null;
|
|
122
|
+
} & Only<V, keyof LevelSettings, "A level takes like, severity, label and color">;
|
|
123
|
+
/** Sentry's own levels rank as themselves: a label and a colour only. */
|
|
124
|
+
type BuiltinLevelCheck<K extends SentryLevel, V> = V extends string ? K : {
|
|
125
|
+
like?: K;
|
|
126
|
+
label?: string | null;
|
|
127
|
+
color?: SchemaColor | null;
|
|
128
|
+
} & Only<V, "like" | "label" | "color", "A built-in level takes label and color, and ranks as itself">;
|
|
129
|
+
type CheckLevels<T> = {
|
|
130
|
+
[K in keyof T]: K extends SentryLevel ? BuiltinLevelCheck<K, T[K]> : K extends string ? IsName<K, 32> extends true ? LevelCheck<T[K]> : {
|
|
131
|
+
[M in LevelRule]: true;
|
|
132
|
+
} : never;
|
|
133
|
+
};
|
|
134
|
+
type CheckCategories<T> = T extends readonly unknown[] ? {
|
|
135
|
+
[I in keyof T]: T[I] extends string ? IsName<T[I], 64> extends true ? T[I] : {
|
|
136
|
+
[M in CategoryRule]: true;
|
|
137
|
+
} : string;
|
|
138
|
+
} : {
|
|
139
|
+
[K in keyof T]: K extends string ? IsName<K, 64> extends true ? CategorySettings & Only<T[K], keyof CategorySettings, "A category takes label, color, hidden and owner"> : {
|
|
140
|
+
[M in CategoryRule]: true;
|
|
141
|
+
} : never;
|
|
142
|
+
};
|
|
143
|
+
type IsField<K extends string> = K extends "" | `statoss.${string}` ? false : true;
|
|
144
|
+
type CheckFields<T> = T extends readonly unknown[] ? {
|
|
145
|
+
[I in keyof T]: T[I] extends string ? IsField<T[I]> extends true ? T[I] : {
|
|
146
|
+
[M in FieldRule]: true;
|
|
147
|
+
} : string;
|
|
148
|
+
} : {
|
|
149
|
+
[K in keyof T]: K extends string ? IsField<K> extends true ? FieldSettings & Only<T[K], keyof FieldSettings, "A field takes label, type, unit and filterable"> : {
|
|
150
|
+
[M in FieldRule]: true;
|
|
151
|
+
} : never;
|
|
152
|
+
};
|
|
153
|
+
type CheckRules<T, L extends string, C extends string> = {
|
|
154
|
+
[I in keyof T]: SchemaRule<L, C> & Only<T[I], "subject" | "tagKey" | "match" | "value" | "level" | "category", "A rule takes subject, tagKey, match, value, level and category">;
|
|
155
|
+
};
|
|
156
|
+
/** What defineSchema holds a schema to, read from the schema itself. */
|
|
157
|
+
export type CheckSchema<S> = {
|
|
158
|
+
[K in keyof S]: K extends "levels" ? CheckLevels<S[K]> : K extends "categories" ? CheckCategories<S[K]> : K extends "fields" ? CheckFields<S[K]> : K extends "rules" ? CheckRules<S[K], SentryLevel | LevelNames<S>, CategoryNames<S>> : {
|
|
159
|
+
"A schema takes levels, categories, fields and rules": true;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Declares an app's levels, categories, fields and rules. Returns the schema
|
|
164
|
+
* as given: plain data in the shape the server takes, so it can be sent as
|
|
165
|
+
* it is. Its names become types: pass it to init as `schema` and the capture
|
|
166
|
+
* functions init returns take only these names.
|
|
167
|
+
*/
|
|
168
|
+
export declare function defineSchema<const S extends SchemaSpec & CheckSchema<S>>(schema: S): S;
|
|
169
|
+
/** A field's value; sent as a tag. */
|
|
170
|
+
export type FieldValue = string | number | boolean;
|
|
171
|
+
/** What the capture functions of init's handle take, typed by the schema. */
|
|
172
|
+
export interface SchemaContext<S = SchemaSpec> extends Omit<CaptureContext, "level" | "category"> {
|
|
173
|
+
/** A Sentry level or a level of the schema. */
|
|
174
|
+
level?: LevelOf<S>;
|
|
175
|
+
/** A category of the schema, sent as the `statoss.category` tag. */
|
|
176
|
+
category?: CategoryOf<S>;
|
|
177
|
+
/**
|
|
178
|
+
* Fields of the schema, sent as tags; a schema that declares none takes
|
|
179
|
+
* none here.
|
|
180
|
+
*/
|
|
181
|
+
fields?: [FieldOf<S>] extends [never] ? never : {
|
|
182
|
+
[K in FieldOf<S>]?: FieldValue;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** The capture functions, typed by the schema given to init. */
|
|
186
|
+
export interface Handle<S = SchemaSpec> {
|
|
187
|
+
/** Sends an error (or anything thrown). Returns the event id. */
|
|
188
|
+
captureException(error: unknown, context?: SchemaContext<S>): string;
|
|
189
|
+
/** Sends a message, with a level or a context. Returns the event id. */
|
|
190
|
+
captureMessage(message: string, levelOrContext?: LevelOf<S> | SchemaContext<S>): string;
|
|
191
|
+
/** Sends an event you built in Sentry's schema. Returns the event id. */
|
|
192
|
+
captureEvent(event: Event, context?: SchemaContext<S>): string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The Sentry level each level of init's `schema` is sent as. Built-in
|
|
196
|
+
* levels are left out; names the SDK cannot send are logged in debug mode.
|
|
197
|
+
*/
|
|
198
|
+
export declare function readSchema(schema: unknown, logger: Logger): Record<string, SentryLevel>;
|
|
199
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FIELD_TYPES = exports.RULE_MATCHES = exports.RULE_SUBJECTS = exports.SCHEMA_COLORS = exports.LIKES = void 0;
|
|
4
|
+
exports.defineSchema = defineSchema;
|
|
5
|
+
exports.readSchema = readSchema;
|
|
6
|
+
const levels_js_1 = require("./levels.js");
|
|
7
|
+
const utils_js_1 = require("./utils.js");
|
|
8
|
+
// The server's own lists (src/lib/app-schema.ts, src/lib/schema-colors.ts,
|
|
9
|
+
// src/db/schema.ts), kept equal by a test.
|
|
10
|
+
/** The ranges a level ranks in: Sentry's five and OpenTelemetry's trace. */
|
|
11
|
+
exports.LIKES = [
|
|
12
|
+
"fatal",
|
|
13
|
+
"error",
|
|
14
|
+
"warning",
|
|
15
|
+
"info",
|
|
16
|
+
"debug",
|
|
17
|
+
"trace",
|
|
18
|
+
];
|
|
19
|
+
/** The colours a level or a category takes, by name. */
|
|
20
|
+
exports.SCHEMA_COLORS = [
|
|
21
|
+
"crimson",
|
|
22
|
+
"red",
|
|
23
|
+
"orange",
|
|
24
|
+
"amber",
|
|
25
|
+
"yellow",
|
|
26
|
+
"green",
|
|
27
|
+
"aqua",
|
|
28
|
+
"teal",
|
|
29
|
+
"blue",
|
|
30
|
+
"violet",
|
|
31
|
+
"indigo",
|
|
32
|
+
"magenta",
|
|
33
|
+
"plum",
|
|
34
|
+
"grey",
|
|
35
|
+
];
|
|
36
|
+
/** What a rule tests: a tag by key, the logger, or one of the event's own fields. */
|
|
37
|
+
exports.RULE_SUBJECTS = [
|
|
38
|
+
"tag",
|
|
39
|
+
"logger",
|
|
40
|
+
"exception",
|
|
41
|
+
"culprit",
|
|
42
|
+
"title",
|
|
43
|
+
"message",
|
|
44
|
+
];
|
|
45
|
+
exports.RULE_MATCHES = ["is", "starts", "contains"];
|
|
46
|
+
exports.FIELD_TYPES = ["text", "number"];
|
|
47
|
+
/**
|
|
48
|
+
* Declares an app's levels, categories, fields and rules. Returns the schema
|
|
49
|
+
* as given: plain data in the shape the server takes, so it can be sent as
|
|
50
|
+
* it is. Its names become types: pass it to init as `schema` and the capture
|
|
51
|
+
* functions init returns take only these names.
|
|
52
|
+
*/
|
|
53
|
+
function defineSchema(schema) {
|
|
54
|
+
return schema;
|
|
55
|
+
}
|
|
56
|
+
// At runtime
|
|
57
|
+
/** The Sentry level a level of the schema is sent as. */
|
|
58
|
+
function sentAs(name, value, logger) {
|
|
59
|
+
const settings = (0, utils_js_1.isObject)(value) ? value : {};
|
|
60
|
+
const like = typeof value === "string" ? value : settings.like;
|
|
61
|
+
if ((0, levels_js_1.isSentryLevel)(like))
|
|
62
|
+
return like;
|
|
63
|
+
if (like === "trace")
|
|
64
|
+
return "debug";
|
|
65
|
+
if (like !== undefined || (typeof value !== "string" && !(0, utils_js_1.isObject)(value))) {
|
|
66
|
+
logger.warn(`Level "${name}" in the schema: like should be ${exports.LIKES.join(", ")}; sent as error.`);
|
|
67
|
+
return "error";
|
|
68
|
+
}
|
|
69
|
+
const severity = settings.severity;
|
|
70
|
+
if (typeof severity !== "number" || severity < 1 || severity > 24)
|
|
71
|
+
return "error";
|
|
72
|
+
if (severity >= 21)
|
|
73
|
+
return "fatal";
|
|
74
|
+
if (severity >= 17)
|
|
75
|
+
return "error";
|
|
76
|
+
if (severity >= 13)
|
|
77
|
+
return "warning";
|
|
78
|
+
if (severity >= 9)
|
|
79
|
+
return "info";
|
|
80
|
+
return "debug";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The Sentry level each level of init's `schema` is sent as. Built-in
|
|
84
|
+
* levels are left out; names the SDK cannot send are logged in debug mode.
|
|
85
|
+
*/
|
|
86
|
+
function readSchema(schema, logger) {
|
|
87
|
+
const out = {};
|
|
88
|
+
if (schema === undefined || schema === null)
|
|
89
|
+
return out;
|
|
90
|
+
if (!(0, utils_js_1.isObject)(schema) || Array.isArray(schema)) {
|
|
91
|
+
logger.warn("Ignored `schema`: pass what defineSchema returns.");
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
const { levels, categories } = schema;
|
|
95
|
+
if ((0, utils_js_1.isObject)(levels) && !Array.isArray(levels)) {
|
|
96
|
+
for (const [name, value] of Object.entries(levels)) {
|
|
97
|
+
// A built-in level, given a label or a colour.
|
|
98
|
+
if ((0, levels_js_1.isSentryLevel)(name))
|
|
99
|
+
continue;
|
|
100
|
+
if ((0, levels_js_1.isLevelName)(name))
|
|
101
|
+
out[name] = sentAs(name, value, logger);
|
|
102
|
+
else
|
|
103
|
+
logger.warn(`Ignored level "${name}" in the schema: ${levels_js_1.LEVEL_RULE}.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (levels !== undefined && levels !== null) {
|
|
107
|
+
logger.warn('Ignored the schema\'s levels: they should be an object such as { "payment-failed": "error" }.');
|
|
108
|
+
}
|
|
109
|
+
const names = Array.isArray(categories)
|
|
110
|
+
? categories
|
|
111
|
+
: (0, utils_js_1.isObject)(categories)
|
|
112
|
+
? Object.keys(categories)
|
|
113
|
+
: [];
|
|
114
|
+
for (const name of names) {
|
|
115
|
+
if (!(0, levels_js_1.isCategoryName)(name))
|
|
116
|
+
logger.warn(`Category "${(0, utils_js_1.safeString)(name)}" in the schema cannot be sent: ${levels_js_1.CATEGORY_RULE}.`);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
package/dist/cjs/version.d.ts
CHANGED
package/dist/cjs/version.js
CHANGED
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SDK_PACKAGE = exports.SDK_VERSION = exports.SDK_NAME = void 0;
|
|
4
4
|
exports.SDK_NAME = "statoss.javascript";
|
|
5
5
|
// Kept equal to package.json by a test.
|
|
6
|
-
exports.SDK_VERSION = "0.
|
|
6
|
+
exports.SDK_VERSION = "0.3.0";
|
|
7
7
|
exports.SDK_PACKAGE = "npm:@statoss/sdk";
|
package/dist/esm/client.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { type Logger } from "./logger.js";
|
|
|
2
2
|
import { type RuntimeInfo } from "./runtime.js";
|
|
3
3
|
import { type Scope } from "./scope.js";
|
|
4
4
|
import type { Breadcrumb, BreadcrumbHint, CaptureContext, Event, InitOptions } from "./types.js";
|
|
5
|
+
/** init's options as the client reads them: init may pass a schema. */
|
|
6
|
+
export type ClientOptions = InitOptions & {
|
|
7
|
+
schema?: unknown;
|
|
8
|
+
};
|
|
5
9
|
export declare class Client {
|
|
6
10
|
readonly logger: Logger;
|
|
7
11
|
readonly runtime: RuntimeInfo;
|
|
@@ -20,7 +24,7 @@ export declare class Client {
|
|
|
20
24
|
private reportTimer?;
|
|
21
25
|
private previousSignature?;
|
|
22
26
|
private closed;
|
|
23
|
-
constructor(options:
|
|
27
|
+
constructor(options: ClientOptions);
|
|
24
28
|
/** Installs the integrations that apply to this runtime. */
|
|
25
29
|
setup(): void;
|
|
26
30
|
captureException(error: unknown, context: CaptureContext | undefined, scope: Scope): string;
|
package/dist/esm/client.js
CHANGED
|
@@ -11,6 +11,7 @@ import { installGlobalHandlers } from "./integrations/handlers.js";
|
|
|
11
11
|
import { CATEGORY_TAG, chooseLevel, isSentryLevel, LEVEL_TAG, readCategory, readLevels, } from "./levels.js";
|
|
12
12
|
import { makeLogger } from "./logger.js";
|
|
13
13
|
import { describeRuntime } from "./runtime.js";
|
|
14
|
+
import { readSchema } from "./schema.js";
|
|
14
15
|
import { MAX_BREADCRUMBS } from "./scope.js";
|
|
15
16
|
import { fetchTransport, Sender } from "./transport.js";
|
|
16
17
|
import { isObject, isThenable, normalize, now, safely, safeString, scrubUrl, truncate, unref, uuid4, } from "./utils.js";
|
|
@@ -51,6 +52,32 @@ function cleanTags(tags) {
|
|
|
51
52
|
}
|
|
52
53
|
return out;
|
|
53
54
|
}
|
|
55
|
+
/** A capture's fields, as tags. Keys starting statoss. are not fields. */
|
|
56
|
+
function fieldTags(fields, logger) {
|
|
57
|
+
const out = {};
|
|
58
|
+
if (fields === undefined || fields === null)
|
|
59
|
+
return out;
|
|
60
|
+
if (!isObject(fields) || Array.isArray(fields)) {
|
|
61
|
+
logger.warn('Ignored `fields`: it should be an object such as { route: "/pay" }.');
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
65
|
+
if (value === undefined || value === null)
|
|
66
|
+
continue;
|
|
67
|
+
if (key === "" || key.startsWith("statoss.")) {
|
|
68
|
+
logger.warn(`Ignored field "${key}": fields are not empty and do not start statoss.`);
|
|
69
|
+
}
|
|
70
|
+
else if (typeof value === "string" ||
|
|
71
|
+
typeof value === "number" ||
|
|
72
|
+
typeof value === "boolean") {
|
|
73
|
+
out[key] = value;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
logger.warn(`Ignored field "${key}": its value should be a string, number or boolean.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
54
81
|
function cleanContexts(contexts) {
|
|
55
82
|
const out = {};
|
|
56
83
|
for (const [name, value] of Object.entries(contexts)) {
|
|
@@ -100,7 +127,11 @@ export class Client {
|
|
|
100
127
|
this.options = options;
|
|
101
128
|
this.logger = makeLogger(options.debug === true);
|
|
102
129
|
this.runtime = describeRuntime();
|
|
103
|
-
|
|
130
|
+
// What `levels` says wins over the schema.
|
|
131
|
+
this.levels = {
|
|
132
|
+
...readSchema(options.schema, this.logger),
|
|
133
|
+
...readLevels(options.levels, this.logger),
|
|
134
|
+
};
|
|
104
135
|
const rate = options.sampleRate;
|
|
105
136
|
if (rate !== undefined &&
|
|
106
137
|
!(typeof rate === "number" && rate >= 0 && rate <= 1)) {
|
|
@@ -326,6 +357,7 @@ export class Client {
|
|
|
326
357
|
const tags = {
|
|
327
358
|
...scope._tags,
|
|
328
359
|
...ctx.tags,
|
|
360
|
+
...fieldTags(ctx.fields, this.logger),
|
|
329
361
|
...base.tags,
|
|
330
362
|
};
|
|
331
363
|
if (choice.custom)
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { CheckSchema, Handle, SchemaSpec } from "./schema.js";
|
|
1
2
|
import type { Breadcrumb, CaptureContext, Event, InitOptions, Level, Primitive, User } from "./types.js";
|
|
2
3
|
export { withScope } from "./hub.js";
|
|
4
|
+
export { defineSchema } from "./schema.js";
|
|
5
|
+
export type { CategoryOf, CategorySettings, CheckSchema, FieldOf, FieldSettings, FieldType, FieldValue, Handle, LevelOf, LevelSettings, Like, RuleMatch, RuleSubject, SchemaColor, SchemaContext, SchemaRule, SchemaSpec, Severity, } from "./schema.js";
|
|
3
6
|
export type { Scope } from "./scope.js";
|
|
4
7
|
export { SDK_NAME, SDK_VERSION } from "./version.js";
|
|
5
8
|
export type { Breadcrumb, BreadcrumbHint, CaptureContext, DebugImage, Event, EventHint, Exception, InitOptions, IntegrationToggles, Level, Mechanism, Primitive, RequestInfo, ScopeData, SdkInfo, SentryLevel, StackFrame, Transport, TransportRequest, TransportResponse, User, } from "./types.js";
|
|
@@ -7,8 +10,14 @@ export type { Breadcrumb, BreadcrumbHint, CaptureContext, DebugImage, Event, Eve
|
|
|
7
10
|
* Starts the SDK. Call it once, as early as you can. Calling it again replaces
|
|
8
11
|
* the earlier setup. Without a valid DSN, or with enabled: false, nothing is
|
|
9
12
|
* sent and nothing is patched.
|
|
13
|
+
*
|
|
14
|
+
* With a `schema` from defineSchema, its levels are sent as the Sentry level
|
|
15
|
+
* each ranks like, and the capture functions it returns take only the
|
|
16
|
+
* schema's levels, categories and fields.
|
|
10
17
|
*/
|
|
11
|
-
export declare function init(options?: InitOptions
|
|
18
|
+
export declare function init<const S extends SchemaSpec & CheckSchema<S> = SchemaSpec>(options?: InitOptions & {
|
|
19
|
+
schema?: S;
|
|
20
|
+
}): Handle<S>;
|
|
12
21
|
/** Sends an error (or anything thrown). Returns the event id. */
|
|
13
22
|
export declare function captureException(error: unknown, context?: CaptureContext): string;
|
|
14
23
|
/**
|
package/dist/esm/index.js
CHANGED
|
@@ -2,12 +2,8 @@ import { Client } from "./client.js";
|
|
|
2
2
|
import { captureWith, getCarrier, getCurrentScope, messageContext, } from "./hub.js";
|
|
3
3
|
import { isObject, safely } from "./utils.js";
|
|
4
4
|
export { withScope } from "./hub.js";
|
|
5
|
+
export { defineSchema } from "./schema.js";
|
|
5
6
|
export { SDK_NAME, SDK_VERSION } from "./version.js";
|
|
6
|
-
/**
|
|
7
|
-
* Starts the SDK. Call it once, as early as you can. Calling it again replaces
|
|
8
|
-
* the earlier setup. Without a valid DSN, or with enabled: false, nothing is
|
|
9
|
-
* sent and nothing is patched.
|
|
10
|
-
*/
|
|
11
7
|
export function init(options = {}) {
|
|
12
8
|
try {
|
|
13
9
|
const carrier = getCarrier();
|
|
@@ -26,6 +22,7 @@ export function init(options = {}) {
|
|
|
26
22
|
safely(() => console.error("[StatOSS] init failed:", error));
|
|
27
23
|
}
|
|
28
24
|
}
|
|
25
|
+
return HANDLE;
|
|
29
26
|
}
|
|
30
27
|
/** Sends an error (or anything thrown). Returns the event id. */
|
|
31
28
|
export function captureException(error, context) {
|
|
@@ -42,6 +39,13 @@ export function captureMessage(message, levelOrContext) {
|
|
|
42
39
|
export function captureEvent(event, context) {
|
|
43
40
|
return captureWith(undefined, (client, scope) => client.captureEvent(event, context, scope));
|
|
44
41
|
}
|
|
42
|
+
// What init returns: the same functions, typed by the schema. The client
|
|
43
|
+
// reads a context's fields whichever function it came through.
|
|
44
|
+
const HANDLE = Object.freeze({
|
|
45
|
+
captureException,
|
|
46
|
+
captureMessage,
|
|
47
|
+
captureEvent,
|
|
48
|
+
});
|
|
45
49
|
/** Pass null to forget the user. */
|
|
46
50
|
export function setUser(user) {
|
|
47
51
|
safely(() => getCurrentScope().setUser(user));
|
package/dist/esm/levels.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
2
|
import type { SentryLevel } from "./types.js";
|
|
3
3
|
export declare const SENTRY_LEVELS: readonly SentryLevel[];
|
|
4
|
+
export declare const LEVEL_RULE = "use 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
5
|
+
export declare const CATEGORY_RULE = "use 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
4
6
|
export declare const LEVEL_TAG = "statoss.level";
|
|
5
7
|
export declare const CATEGORY_TAG = "statoss.category";
|
|
6
8
|
export declare function isSentryLevel(value: unknown): value is SentryLevel;
|
|
7
|
-
export declare function
|
|
9
|
+
export declare function isLevelName(value: unknown): value is string;
|
|
10
|
+
export declare function isCategoryName(value: unknown): value is string;
|
|
8
11
|
/** Keeps the entries of init's `levels` that are usable, logging the rest in debug mode. */
|
|
9
12
|
export declare function readLevels(input: unknown, logger: Logger): Record<string, SentryLevel>;
|
|
10
13
|
export interface LevelChoice {
|
package/dist/esm/levels.js
CHANGED
|
@@ -6,16 +6,26 @@ export const SENTRY_LEVELS = [
|
|
|
6
6
|
"info",
|
|
7
7
|
"debug",
|
|
8
8
|
];
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Names of your own: lowercase letters, digits, dots, dashes and
|
|
11
|
+
* underscores. A level is at most 32 characters, as the server keeps it; a
|
|
12
|
+
* category at most 64.
|
|
13
|
+
*/
|
|
14
|
+
const LEVEL_RE = /^[a-z0-9._-]{1,32}$/;
|
|
15
|
+
const CATEGORY_RE = /^[a-z0-9._-]{1,64}$/;
|
|
16
|
+
export const LEVEL_RULE = "use 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
17
|
+
export const CATEGORY_RULE = "use 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
11
18
|
export const LEVEL_TAG = "statoss.level";
|
|
12
19
|
export const CATEGORY_TAG = "statoss.category";
|
|
13
20
|
export function isSentryLevel(value) {
|
|
14
21
|
return (typeof value === "string" &&
|
|
15
22
|
SENTRY_LEVELS.includes(value));
|
|
16
23
|
}
|
|
17
|
-
export function
|
|
18
|
-
return typeof value === "string" &&
|
|
24
|
+
export function isLevelName(value) {
|
|
25
|
+
return typeof value === "string" && LEVEL_RE.test(value);
|
|
26
|
+
}
|
|
27
|
+
export function isCategoryName(value) {
|
|
28
|
+
return typeof value === "string" && CATEGORY_RE.test(value);
|
|
19
29
|
}
|
|
20
30
|
/** Keeps the entries of init's `levels` that are usable, logging the rest in debug mode. */
|
|
21
31
|
export function readLevels(input, logger) {
|
|
@@ -30,8 +40,8 @@ export function readLevels(input, logger) {
|
|
|
30
40
|
if (isSentryLevel(name)) {
|
|
31
41
|
logger.warn(`Ignored level "${name}" in \`levels\`: it is already a Sentry level.`);
|
|
32
42
|
}
|
|
33
|
-
else if (!
|
|
34
|
-
logger.warn(`Ignored level "${name}" in \`levels\`:
|
|
43
|
+
else if (!isLevelName(name)) {
|
|
44
|
+
logger.warn(`Ignored level "${name}" in \`levels\`: ${LEVEL_RULE}.`);
|
|
35
45
|
}
|
|
36
46
|
else if (!isSentryLevel(level)) {
|
|
37
47
|
logger.warn(`Ignored level "${name}" in \`levels\`: map it to fatal, error, warning, info or debug.`);
|
|
@@ -52,18 +62,18 @@ export function chooseLevel(candidates, levels, fallback, logger) {
|
|
|
52
62
|
continue;
|
|
53
63
|
if (isSentryLevel(candidate))
|
|
54
64
|
return { level: candidate };
|
|
55
|
-
if (
|
|
65
|
+
if (isLevelName(candidate)) {
|
|
56
66
|
return { level: levels[candidate] ?? "error", custom: candidate };
|
|
57
67
|
}
|
|
58
|
-
logger.warn(`Ignored level "${safeString(candidate)}": use a Sentry level or 1 to
|
|
68
|
+
logger.warn(`Ignored level "${safeString(candidate)}": use a Sentry level or 1 to 32 lowercase letters, digits, dots, dashes or underscores.`);
|
|
59
69
|
}
|
|
60
70
|
return { level: fallback };
|
|
61
71
|
}
|
|
62
72
|
export function readCategory(value, logger) {
|
|
63
73
|
if (value === undefined || value === null)
|
|
64
74
|
return undefined;
|
|
65
|
-
if (
|
|
75
|
+
if (isCategoryName(value))
|
|
66
76
|
return value;
|
|
67
|
-
logger.warn(`Ignored category "${safeString(value)}":
|
|
77
|
+
logger.warn(`Ignored category "${safeString(value)}": ${CATEGORY_RULE}.`);
|
|
68
78
|
return undefined;
|
|
69
79
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Logger } from "./logger.js";
|
|
2
|
+
import type { CaptureContext, Event, Level, SentryLevel } from "./types.js";
|
|
3
|
+
/** The ranges a level ranks in: Sentry's five and OpenTelemetry's trace. */
|
|
4
|
+
export declare const LIKES: readonly ["fatal", "error", "warning", "info", "debug", "trace"];
|
|
5
|
+
export type Like = (typeof LIKES)[number];
|
|
6
|
+
/** The colours a level or a category takes, by name. */
|
|
7
|
+
export declare const SCHEMA_COLORS: readonly ["crimson", "red", "orange", "amber", "yellow", "green", "aqua", "teal", "blue", "violet", "indigo", "magenta", "plum", "grey"];
|
|
8
|
+
export type SchemaColor = (typeof SCHEMA_COLORS)[number];
|
|
9
|
+
/** What a rule tests: a tag by key, the logger, or one of the event's own fields. */
|
|
10
|
+
export declare const RULE_SUBJECTS: readonly ["tag", "logger", "exception", "culprit", "title", "message"];
|
|
11
|
+
export type RuleSubject = (typeof RULE_SUBJECTS)[number];
|
|
12
|
+
export declare const RULE_MATCHES: readonly ["is", "starts", "contains"];
|
|
13
|
+
export type RuleMatch = (typeof RULE_MATCHES)[number];
|
|
14
|
+
export declare const FIELD_TYPES: readonly ["text", "number"];
|
|
15
|
+
export type FieldType = (typeof FIELD_TYPES)[number];
|
|
16
|
+
/** Each range's severities, as OpenTelemetry numbers them. */
|
|
17
|
+
interface Severities {
|
|
18
|
+
fatal: 21 | 22 | 23 | 24;
|
|
19
|
+
error: 17 | 18 | 19 | 20;
|
|
20
|
+
warning: 13 | 14 | 15 | 16;
|
|
21
|
+
info: 9 | 10 | 11 | 12;
|
|
22
|
+
debug: 5 | 6 | 7 | 8;
|
|
23
|
+
trace: 1 | 2 | 3 | 4;
|
|
24
|
+
}
|
|
25
|
+
export type Severity = Severities[Like];
|
|
26
|
+
/** A level of your own. Null clears a setting. */
|
|
27
|
+
export interface LevelSettings {
|
|
28
|
+
/** The range it ranks in. Without it (and without severity) it ranks as error. */
|
|
29
|
+
like?: Like;
|
|
30
|
+
/** 1 to 24, inside the range of `like`. */
|
|
31
|
+
severity?: Severity;
|
|
32
|
+
label?: string | null;
|
|
33
|
+
color?: SchemaColor | null;
|
|
34
|
+
}
|
|
35
|
+
/** A category. Null clears a setting. */
|
|
36
|
+
export interface CategorySettings {
|
|
37
|
+
label?: string | null;
|
|
38
|
+
color?: SchemaColor | null;
|
|
39
|
+
/** Left out of filters and lists; its events still carry it. */
|
|
40
|
+
hidden?: boolean;
|
|
41
|
+
/** A team member's email. */
|
|
42
|
+
owner?: string | null;
|
|
43
|
+
}
|
|
44
|
+
/** A field: a tag key. Null clears a setting. */
|
|
45
|
+
export interface FieldSettings {
|
|
46
|
+
label?: string | null;
|
|
47
|
+
type?: FieldType;
|
|
48
|
+
unit?: string | null;
|
|
49
|
+
/** Gets a picker in Explore's filters. */
|
|
50
|
+
filterable?: boolean;
|
|
51
|
+
}
|
|
52
|
+
type RuleTests = {
|
|
53
|
+
subject: "tag";
|
|
54
|
+
tagKey: string;
|
|
55
|
+
} | {
|
|
56
|
+
subject: Exclude<RuleSubject, "tag">;
|
|
57
|
+
tagKey?: undefined;
|
|
58
|
+
};
|
|
59
|
+
type RuleSets<L extends string, C extends string> = {
|
|
60
|
+
level: L;
|
|
61
|
+
category?: C;
|
|
62
|
+
} | {
|
|
63
|
+
level?: L;
|
|
64
|
+
category: C;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Sets a level, a category or both for events that match: the first rule
|
|
68
|
+
* that matches wins. `match` defaults to "is".
|
|
69
|
+
*/
|
|
70
|
+
export type SchemaRule<L extends string = string, C extends string = string> = RuleTests & {
|
|
71
|
+
match?: RuleMatch;
|
|
72
|
+
value: string;
|
|
73
|
+
} & RuleSets<L, C>;
|
|
74
|
+
/** An app's levels, categories, fields and rules, as the server takes them. */
|
|
75
|
+
export interface SchemaSpec {
|
|
76
|
+
/** Each name with the range it ranks in, or with its settings. */
|
|
77
|
+
levels?: {
|
|
78
|
+
readonly [name: string]: Like | LevelSettings;
|
|
79
|
+
};
|
|
80
|
+
categories?: readonly string[] | {
|
|
81
|
+
readonly [name: string]: CategorySettings;
|
|
82
|
+
};
|
|
83
|
+
fields?: readonly string[] | {
|
|
84
|
+
readonly [name: string]: FieldSettings;
|
|
85
|
+
};
|
|
86
|
+
rules?: readonly SchemaRule[];
|
|
87
|
+
}
|
|
88
|
+
type NamesIn<T> = T extends readonly (infer N)[] ? N & string : T extends object ? keyof T & string : never;
|
|
89
|
+
type LevelNames<S> = S extends {
|
|
90
|
+
levels?: infer L;
|
|
91
|
+
} ? NamesIn<NonNullable<L>> : never;
|
|
92
|
+
type CategoryNames<S> = S extends {
|
|
93
|
+
categories?: infer C;
|
|
94
|
+
} ? NamesIn<NonNullable<C>> : never;
|
|
95
|
+
type FieldNames<S> = S extends {
|
|
96
|
+
fields?: infer F;
|
|
97
|
+
} ? NamesIn<NonNullable<F>> : never;
|
|
98
|
+
/** A Sentry level or a level of the schema. */
|
|
99
|
+
export type LevelOf<S> = string extends LevelNames<S> ? Level : SentryLevel | LevelNames<S>;
|
|
100
|
+
/** A category of the schema. */
|
|
101
|
+
export type CategoryOf<S> = CategoryNames<S>;
|
|
102
|
+
/** A field of the schema. */
|
|
103
|
+
export type FieldOf<S> = FieldNames<S>;
|
|
104
|
+
type NameChar = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "." | "-" | "_";
|
|
105
|
+
/** True for 1 to Max of NameChar. A name only known as string passes. */
|
|
106
|
+
type IsName<S extends string, Max extends number, Seen extends 0[] = []> = string extends S ? true : S extends `${infer C}${infer Rest}` ? C extends NameChar ? Seen["length"] extends Max ? false : IsName<Rest, Max, [...Seen, 0]> : false : Seen extends [] ? false : true;
|
|
107
|
+
type LevelRule = "Level names are 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
108
|
+
type CategoryRule = "Category names are 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
109
|
+
type FieldRule = "Field names are not empty and do not start statoss.";
|
|
110
|
+
type Only<V, Keys extends PropertyKey, Message extends string> = {
|
|
111
|
+
[P in Exclude<keyof V, Keys>]: {
|
|
112
|
+
[M in Message]: true;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
type LevelCheck<V> = V extends string ? Like : {
|
|
116
|
+
like?: Like;
|
|
117
|
+
severity?: V extends {
|
|
118
|
+
like: infer K extends Like;
|
|
119
|
+
} ? Severities[K] : Severity;
|
|
120
|
+
label?: string | null;
|
|
121
|
+
color?: SchemaColor | null;
|
|
122
|
+
} & Only<V, keyof LevelSettings, "A level takes like, severity, label and color">;
|
|
123
|
+
/** Sentry's own levels rank as themselves: a label and a colour only. */
|
|
124
|
+
type BuiltinLevelCheck<K extends SentryLevel, V> = V extends string ? K : {
|
|
125
|
+
like?: K;
|
|
126
|
+
label?: string | null;
|
|
127
|
+
color?: SchemaColor | null;
|
|
128
|
+
} & Only<V, "like" | "label" | "color", "A built-in level takes label and color, and ranks as itself">;
|
|
129
|
+
type CheckLevels<T> = {
|
|
130
|
+
[K in keyof T]: K extends SentryLevel ? BuiltinLevelCheck<K, T[K]> : K extends string ? IsName<K, 32> extends true ? LevelCheck<T[K]> : {
|
|
131
|
+
[M in LevelRule]: true;
|
|
132
|
+
} : never;
|
|
133
|
+
};
|
|
134
|
+
type CheckCategories<T> = T extends readonly unknown[] ? {
|
|
135
|
+
[I in keyof T]: T[I] extends string ? IsName<T[I], 64> extends true ? T[I] : {
|
|
136
|
+
[M in CategoryRule]: true;
|
|
137
|
+
} : string;
|
|
138
|
+
} : {
|
|
139
|
+
[K in keyof T]: K extends string ? IsName<K, 64> extends true ? CategorySettings & Only<T[K], keyof CategorySettings, "A category takes label, color, hidden and owner"> : {
|
|
140
|
+
[M in CategoryRule]: true;
|
|
141
|
+
} : never;
|
|
142
|
+
};
|
|
143
|
+
type IsField<K extends string> = K extends "" | `statoss.${string}` ? false : true;
|
|
144
|
+
type CheckFields<T> = T extends readonly unknown[] ? {
|
|
145
|
+
[I in keyof T]: T[I] extends string ? IsField<T[I]> extends true ? T[I] : {
|
|
146
|
+
[M in FieldRule]: true;
|
|
147
|
+
} : string;
|
|
148
|
+
} : {
|
|
149
|
+
[K in keyof T]: K extends string ? IsField<K> extends true ? FieldSettings & Only<T[K], keyof FieldSettings, "A field takes label, type, unit and filterable"> : {
|
|
150
|
+
[M in FieldRule]: true;
|
|
151
|
+
} : never;
|
|
152
|
+
};
|
|
153
|
+
type CheckRules<T, L extends string, C extends string> = {
|
|
154
|
+
[I in keyof T]: SchemaRule<L, C> & Only<T[I], "subject" | "tagKey" | "match" | "value" | "level" | "category", "A rule takes subject, tagKey, match, value, level and category">;
|
|
155
|
+
};
|
|
156
|
+
/** What defineSchema holds a schema to, read from the schema itself. */
|
|
157
|
+
export type CheckSchema<S> = {
|
|
158
|
+
[K in keyof S]: K extends "levels" ? CheckLevels<S[K]> : K extends "categories" ? CheckCategories<S[K]> : K extends "fields" ? CheckFields<S[K]> : K extends "rules" ? CheckRules<S[K], SentryLevel | LevelNames<S>, CategoryNames<S>> : {
|
|
159
|
+
"A schema takes levels, categories, fields and rules": true;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Declares an app's levels, categories, fields and rules. Returns the schema
|
|
164
|
+
* as given: plain data in the shape the server takes, so it can be sent as
|
|
165
|
+
* it is. Its names become types: pass it to init as `schema` and the capture
|
|
166
|
+
* functions init returns take only these names.
|
|
167
|
+
*/
|
|
168
|
+
export declare function defineSchema<const S extends SchemaSpec & CheckSchema<S>>(schema: S): S;
|
|
169
|
+
/** A field's value; sent as a tag. */
|
|
170
|
+
export type FieldValue = string | number | boolean;
|
|
171
|
+
/** What the capture functions of init's handle take, typed by the schema. */
|
|
172
|
+
export interface SchemaContext<S = SchemaSpec> extends Omit<CaptureContext, "level" | "category"> {
|
|
173
|
+
/** A Sentry level or a level of the schema. */
|
|
174
|
+
level?: LevelOf<S>;
|
|
175
|
+
/** A category of the schema, sent as the `statoss.category` tag. */
|
|
176
|
+
category?: CategoryOf<S>;
|
|
177
|
+
/**
|
|
178
|
+
* Fields of the schema, sent as tags; a schema that declares none takes
|
|
179
|
+
* none here.
|
|
180
|
+
*/
|
|
181
|
+
fields?: [FieldOf<S>] extends [never] ? never : {
|
|
182
|
+
[K in FieldOf<S>]?: FieldValue;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** The capture functions, typed by the schema given to init. */
|
|
186
|
+
export interface Handle<S = SchemaSpec> {
|
|
187
|
+
/** Sends an error (or anything thrown). Returns the event id. */
|
|
188
|
+
captureException(error: unknown, context?: SchemaContext<S>): string;
|
|
189
|
+
/** Sends a message, with a level or a context. Returns the event id. */
|
|
190
|
+
captureMessage(message: string, levelOrContext?: LevelOf<S> | SchemaContext<S>): string;
|
|
191
|
+
/** Sends an event you built in Sentry's schema. Returns the event id. */
|
|
192
|
+
captureEvent(event: Event, context?: SchemaContext<S>): string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The Sentry level each level of init's `schema` is sent as. Built-in
|
|
196
|
+
* levels are left out; names the SDK cannot send are logged in debug mode.
|
|
197
|
+
*/
|
|
198
|
+
export declare function readSchema(schema: unknown, logger: Logger): Record<string, SentryLevel>;
|
|
199
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { CATEGORY_RULE, isCategoryName, isLevelName, isSentryLevel, LEVEL_RULE, } from "./levels.js";
|
|
2
|
+
import { isObject, safeString } from "./utils.js";
|
|
3
|
+
// The server's own lists (src/lib/app-schema.ts, src/lib/schema-colors.ts,
|
|
4
|
+
// src/db/schema.ts), kept equal by a test.
|
|
5
|
+
/** The ranges a level ranks in: Sentry's five and OpenTelemetry's trace. */
|
|
6
|
+
export const LIKES = [
|
|
7
|
+
"fatal",
|
|
8
|
+
"error",
|
|
9
|
+
"warning",
|
|
10
|
+
"info",
|
|
11
|
+
"debug",
|
|
12
|
+
"trace",
|
|
13
|
+
];
|
|
14
|
+
/** The colours a level or a category takes, by name. */
|
|
15
|
+
export const SCHEMA_COLORS = [
|
|
16
|
+
"crimson",
|
|
17
|
+
"red",
|
|
18
|
+
"orange",
|
|
19
|
+
"amber",
|
|
20
|
+
"yellow",
|
|
21
|
+
"green",
|
|
22
|
+
"aqua",
|
|
23
|
+
"teal",
|
|
24
|
+
"blue",
|
|
25
|
+
"violet",
|
|
26
|
+
"indigo",
|
|
27
|
+
"magenta",
|
|
28
|
+
"plum",
|
|
29
|
+
"grey",
|
|
30
|
+
];
|
|
31
|
+
/** What a rule tests: a tag by key, the logger, or one of the event's own fields. */
|
|
32
|
+
export const RULE_SUBJECTS = [
|
|
33
|
+
"tag",
|
|
34
|
+
"logger",
|
|
35
|
+
"exception",
|
|
36
|
+
"culprit",
|
|
37
|
+
"title",
|
|
38
|
+
"message",
|
|
39
|
+
];
|
|
40
|
+
export const RULE_MATCHES = ["is", "starts", "contains"];
|
|
41
|
+
export const FIELD_TYPES = ["text", "number"];
|
|
42
|
+
/**
|
|
43
|
+
* Declares an app's levels, categories, fields and rules. Returns the schema
|
|
44
|
+
* as given: plain data in the shape the server takes, so it can be sent as
|
|
45
|
+
* it is. Its names become types: pass it to init as `schema` and the capture
|
|
46
|
+
* functions init returns take only these names.
|
|
47
|
+
*/
|
|
48
|
+
export function defineSchema(schema) {
|
|
49
|
+
return schema;
|
|
50
|
+
}
|
|
51
|
+
// At runtime
|
|
52
|
+
/** The Sentry level a level of the schema is sent as. */
|
|
53
|
+
function sentAs(name, value, logger) {
|
|
54
|
+
const settings = isObject(value) ? value : {};
|
|
55
|
+
const like = typeof value === "string" ? value : settings.like;
|
|
56
|
+
if (isSentryLevel(like))
|
|
57
|
+
return like;
|
|
58
|
+
if (like === "trace")
|
|
59
|
+
return "debug";
|
|
60
|
+
if (like !== undefined || (typeof value !== "string" && !isObject(value))) {
|
|
61
|
+
logger.warn(`Level "${name}" in the schema: like should be ${LIKES.join(", ")}; sent as error.`);
|
|
62
|
+
return "error";
|
|
63
|
+
}
|
|
64
|
+
const severity = settings.severity;
|
|
65
|
+
if (typeof severity !== "number" || severity < 1 || severity > 24)
|
|
66
|
+
return "error";
|
|
67
|
+
if (severity >= 21)
|
|
68
|
+
return "fatal";
|
|
69
|
+
if (severity >= 17)
|
|
70
|
+
return "error";
|
|
71
|
+
if (severity >= 13)
|
|
72
|
+
return "warning";
|
|
73
|
+
if (severity >= 9)
|
|
74
|
+
return "info";
|
|
75
|
+
return "debug";
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The Sentry level each level of init's `schema` is sent as. Built-in
|
|
79
|
+
* levels are left out; names the SDK cannot send are logged in debug mode.
|
|
80
|
+
*/
|
|
81
|
+
export function readSchema(schema, logger) {
|
|
82
|
+
const out = {};
|
|
83
|
+
if (schema === undefined || schema === null)
|
|
84
|
+
return out;
|
|
85
|
+
if (!isObject(schema) || Array.isArray(schema)) {
|
|
86
|
+
logger.warn("Ignored `schema`: pass what defineSchema returns.");
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
const { levels, categories } = schema;
|
|
90
|
+
if (isObject(levels) && !Array.isArray(levels)) {
|
|
91
|
+
for (const [name, value] of Object.entries(levels)) {
|
|
92
|
+
// A built-in level, given a label or a colour.
|
|
93
|
+
if (isSentryLevel(name))
|
|
94
|
+
continue;
|
|
95
|
+
if (isLevelName(name))
|
|
96
|
+
out[name] = sentAs(name, value, logger);
|
|
97
|
+
else
|
|
98
|
+
logger.warn(`Ignored level "${name}" in the schema: ${LEVEL_RULE}.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (levels !== undefined && levels !== null) {
|
|
102
|
+
logger.warn('Ignored the schema\'s levels: they should be an object such as { "payment-failed": "error" }.');
|
|
103
|
+
}
|
|
104
|
+
const names = Array.isArray(categories)
|
|
105
|
+
? categories
|
|
106
|
+
: isObject(categories)
|
|
107
|
+
? Object.keys(categories)
|
|
108
|
+
: [];
|
|
109
|
+
for (const name of names) {
|
|
110
|
+
if (!isCategoryName(name))
|
|
111
|
+
logger.warn(`Category "${safeString(name)}" in the schema cannot be sent: ${CATEGORY_RULE}.`);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
package/dist/esm/version.d.ts
CHANGED
package/dist/esm/version.js
CHANGED
package/package.json
CHANGED