@statoss/sdk 0.1.1 → 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 CHANGED
@@ -252,20 +252,105 @@ The Edge runtime has no global error hook either: capture where you handle
252
252
  errors and `await StatOSS.flush(2000)` (or pass it to `waitUntil`) before
253
253
  returning. In Next.js, the `instrumentation.ts` above covers it.
254
254
 
255
- ## Custom levels and categories
255
+ ## Source maps
256
+
257
+ After the build, give the bundles and their maps debug ids, then upload the
258
+ maps with Sentry's CLI:
259
+
260
+ ```sh
261
+ npx @sentry/cli sourcemaps inject ./dist
262
+
263
+ SENTRY_URL=https://statoss.com \
264
+ SENTRY_AUTH_TOKEN=<a StatOSS API key with write access> \
265
+ npx @sentry/cli sourcemaps upload --org statoss --project <app slug> ./dist
266
+ ```
267
+
268
+ Adding `--release <release>` to the upload is optional. Deploy the bundles as
269
+ `inject` left them: the SDK reads their debug ids and sends them with each
270
+ event. Frames are then shown as source and grouped by their source names.
271
+
272
+ ## Your own levels, categories and fields
256
273
 
257
274
  Sentry has five levels: `fatal`, `error`, `warning`, `info` and `debug`.
258
- StatOSS lets you use your own words as well, and adds a category. Both are
259
- sent as tags next to the nearest Sentry level, so Sentry and any Sentry-based
260
- tool still read the event normally.
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.
261
282
 
262
283
  ```ts
263
- StatOSS.init({
264
- dsn: "...",
284
+ import * as StatOSS from "@statoss/sdk";
285
+
286
+ export const schema = StatOSS.defineSchema({
265
287
  levels: {
266
- "payment-failed": "error",
288
+ "payment-failed": { like: "error", label: "Payment failed" },
267
289
  "slow-query": "warning",
268
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" },
269
354
  });
270
355
 
271
356
  StatOSS.captureMessage("Card declined", "payment-failed");
@@ -278,11 +363,10 @@ StatOSS.captureException(error, {
278
363
  // level "warning", tags { "statoss.level": "slow-query", "statoss.category": "db.postgres" }
279
364
  ```
280
365
 
281
- - `level` takes a Sentry level or any name of your own. A name that is not in
282
- `levels` is sent as `error`.
283
- - `category` sets the `statoss.category` tag.
284
- - Names are 1 to 64 characters of lowercase letters, digits, dots, dashes and
285
- 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.
286
370
  - StatOSS filters and groups on these tags like any other.
287
371
 
288
372
  ## What is captured
@@ -339,7 +423,8 @@ await StatOSS.withScope(async (scope) => {
339
423
  ## API
340
424
 
341
425
  ```ts
342
- init(options): void
426
+ init(options): handle // handle.captureException, captureMessage, captureEvent
427
+ defineSchema(schema): schema // the schema as given, its names as types
343
428
  captureException(error, context?): string // returns the event id
344
429
  captureMessage(message, levelOrContext?): string
345
430
  captureEvent(event, context?): string
@@ -357,8 +442,9 @@ scope.setUser, setTag, setTags, setExtra, setExtras, setContext, setLevel,
357
442
  scope.captureException, captureMessage, captureEvent // with this scope
358
443
  ```
359
444
 
360
- `context` is `{ level, tags, extra, contexts, user, fingerprint, category }`.
361
- It wins over the scope for that one event.
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.
362
448
 
363
449
  ### Options
364
450
 
@@ -375,6 +461,7 @@ It wins over the scope for that one event.
375
461
  | `sendDefaultPii` | `false` | Record the sender's IP address |
376
462
  | `tags` | | Tags on every event |
377
463
  | `initialScope` | | `{ tags, extra, contexts, user, level, fingerprint }` |
464
+ | `schema` | | From `defineSchema`: your levels, categories, fields |
378
465
  | `levels` | | Your level names and the Sentry level each is sent as |
379
466
  | `integrations` | all on | See above |
380
467
  | `enabled` | `true` | |
@@ -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: InitOptions);
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;
@@ -44,7 +48,10 @@ export declare class Client {
44
48
  /** Merges scope, context and options into the event, in Sentry's field order. */
45
49
  private prepare;
46
50
  private process;
47
- /** Makes what beforeSend handed back valid again: a Sentry level, string tags, a sane size. */
51
+ /**
52
+ * Makes what beforeSend handed back valid again (a Sentry level, string
53
+ * tags, a sane size) and names the debug ids of the files its frames ran in.
54
+ */
48
55
  private finish;
49
56
  private send;
50
57
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Client = void 0;
4
+ const debug_ids_js_1 = require("./debug-ids.js");
4
5
  const dsn_js_1 = require("./dsn.js");
5
6
  const envelope_js_1 = require("./envelope.js");
6
7
  const event_js_1 = require("./event.js");
@@ -13,6 +14,7 @@ const handlers_js_1 = require("./integrations/handlers.js");
13
14
  const levels_js_1 = require("./levels.js");
14
15
  const logger_js_1 = require("./logger.js");
15
16
  const runtime_js_1 = require("./runtime.js");
17
+ const schema_js_1 = require("./schema.js");
16
18
  const scope_js_1 = require("./scope.js");
17
19
  const transport_js_1 = require("./transport.js");
18
20
  const utils_js_1 = require("./utils.js");
@@ -53,6 +55,32 @@ function cleanTags(tags) {
53
55
  }
54
56
  return out;
55
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
+ }
56
84
  function cleanContexts(contexts) {
57
85
  const out = {};
58
86
  for (const [name, value] of Object.entries(contexts)) {
@@ -102,7 +130,11 @@ class Client {
102
130
  this.options = options;
103
131
  this.logger = (0, logger_js_1.makeLogger)(options.debug === true);
104
132
  this.runtime = (0, runtime_js_1.describeRuntime)();
105
- this.levels = (0, levels_js_1.readLevels)(options.levels, this.logger);
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
+ };
106
138
  const rate = options.sampleRate;
107
139
  if (rate !== undefined &&
108
140
  !(typeof rate === "number" && rate >= 0 && rate <= 1)) {
@@ -328,6 +360,7 @@ class Client {
328
360
  const tags = {
329
361
  ...scope._tags,
330
362
  ...ctx.tags,
363
+ ...fieldTags(ctx.fields, this.logger),
331
364
  ...base.tags,
332
365
  };
333
366
  if (choice.custom)
@@ -434,7 +467,10 @@ class Client {
434
467
  }
435
468
  this.send(this.finish(final));
436
469
  }
437
- /** Makes what beforeSend handed back valid again: a Sentry level, string tags, a sane size. */
470
+ /**
471
+ * Makes what beforeSend handed back valid again (a Sentry level, string
472
+ * tags, a sane size) and names the debug ids of the files its frames ran in.
473
+ */
438
474
  finish(event) {
439
475
  if (!(0, levels_js_1.isSentryLevel)(event.level)) {
440
476
  const choice = (0, levels_js_1.chooseLevel)([event.level], this.levels, "error", this.logger);
@@ -444,6 +480,7 @@ class Client {
444
480
  }
445
481
  if ((0, utils_js_1.isObject)(event.tags))
446
482
  event.tags = cleanTags(event.tags);
483
+ (0, debug_ids_js_1.addDebugImages)(event);
447
484
  const size = () => (0, utils_js_1.safely)(() => JSON.stringify(event).length, 0) ?? 0;
448
485
  if (size() > MAX_EVENT_CHARS) {
449
486
  this.logger.warn("The event is over 1 MB; sending it without breadcrumbs and extra.");
@@ -0,0 +1,16 @@
1
+ import type { Event } from "./types.js";
2
+ /**
3
+ * Debug ids by the abs_path of the bundle they belong to, as Sentry's
4
+ * getFilenameToDebugIdMap reads them. `sentry-cli sourcemaps inject` and
5
+ * Sentry's bundler plugins put a snippet in each bundle that sets
6
+ * `globalThis._sentryDebugIds[new Error().stack] = "<debug id>"`, so the
7
+ * newest frame of each key ran in that bundle. Read again only when the
8
+ * number of entries changes (or the object is replaced).
9
+ */
10
+ export declare function debugIdsByFile(): ReadonlyMap<string, string>;
11
+ /**
12
+ * Adds `{ type: "sourcemap", code_file, debug_id }` to event.debug_meta.images
13
+ * for each file in the event's frames that has a debug id, after the images
14
+ * already there, so the server can map those frames to source. Never throws.
15
+ */
16
+ export declare function addDebugImages(event: Event): void;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.debugIdsByFile = debugIdsByFile;
4
+ exports.addDebugImages = addDebugImages;
5
+ const stack_js_1 = require("./stack.js");
6
+ const utils_js_1 = require("./utils.js");
7
+ const NONE = new Map();
8
+ // The file each stack string names, so each is parsed once.
9
+ const fileOfStack = new Map();
10
+ let lastIds;
11
+ let lastCount = -1;
12
+ let lastMap = NONE;
13
+ /**
14
+ * Debug ids by the abs_path of the bundle they belong to, as Sentry's
15
+ * getFilenameToDebugIdMap reads them. `sentry-cli sourcemaps inject` and
16
+ * Sentry's bundler plugins put a snippet in each bundle that sets
17
+ * `globalThis._sentryDebugIds[new Error().stack] = "<debug id>"`, so the
18
+ * newest frame of each key ran in that bundle. Read again only when the
19
+ * number of entries changes (or the object is replaced).
20
+ */
21
+ function debugIdsByFile() {
22
+ const ids = globalThis._sentryDebugIds;
23
+ if (!(0, utils_js_1.isObject)(ids) || Array.isArray(ids))
24
+ return NONE;
25
+ const keys = Object.keys(ids);
26
+ if (ids === lastIds && keys.length === lastCount)
27
+ return lastMap;
28
+ const map = new Map();
29
+ for (const key of keys) {
30
+ const id = ids[key];
31
+ if (typeof id !== "string" || id === "")
32
+ continue;
33
+ if (!fileOfStack.has(key))
34
+ fileOfStack.set(key, newestFile(key));
35
+ const file = fileOfStack.get(key);
36
+ if (file)
37
+ map.set(file, id);
38
+ }
39
+ lastIds = ids;
40
+ lastCount = keys.length;
41
+ lastMap = map;
42
+ return map;
43
+ }
44
+ // As Sentry does: walking from the newest frame, the first that names a file.
45
+ function newestFile(stack) {
46
+ const frames = (0, stack_js_1.parseStack)(stack);
47
+ for (let i = frames.length - 1; i >= 0; i--) {
48
+ const file = frames[i].abs_path;
49
+ if (file)
50
+ return file;
51
+ }
52
+ return undefined;
53
+ }
54
+ // Sentry's schema allows { values: [...] } or a bare list.
55
+ function valuesOf(value) {
56
+ if (Array.isArray(value))
57
+ return value;
58
+ if ((0, utils_js_1.isObject)(value) && Array.isArray(value.values))
59
+ return value.values;
60
+ return [];
61
+ }
62
+ /** The abs_path of every frame in the event's exceptions and threads. */
63
+ function framePaths(event) {
64
+ const paths = new Set();
65
+ const holders = [...valuesOf(event.exception), ...valuesOf(event.threads)];
66
+ for (const holder of holders) {
67
+ if (!(0, utils_js_1.isObject)(holder) || !(0, utils_js_1.isObject)(holder.stacktrace))
68
+ continue;
69
+ const frames = holder.stacktrace.frames;
70
+ if (!Array.isArray(frames))
71
+ continue;
72
+ for (const frame of frames) {
73
+ if ((0, utils_js_1.isObject)(frame) && typeof frame.abs_path === "string")
74
+ paths.add(frame.abs_path);
75
+ }
76
+ }
77
+ return paths;
78
+ }
79
+ /**
80
+ * Adds `{ type: "sourcemap", code_file, debug_id }` to event.debug_meta.images
81
+ * for each file in the event's frames that has a debug id, after the images
82
+ * already there, so the server can map those frames to source. Never throws.
83
+ */
84
+ function addDebugImages(event) {
85
+ try {
86
+ const ids = debugIdsByFile();
87
+ if (!ids.size)
88
+ return;
89
+ const meta = (0, utils_js_1.isObject)(event.debug_meta)
90
+ ? event.debug_meta
91
+ : {};
92
+ const images = Array.isArray(meta.images)
93
+ ? [...meta.images]
94
+ : [];
95
+ const named = new Set();
96
+ for (const image of images) {
97
+ if ((0, utils_js_1.isObject)(image) && image.type === "sourcemap")
98
+ named.add(image.code_file);
99
+ }
100
+ const before = images.length;
101
+ for (const path of framePaths(event)) {
102
+ const id = ids.get(path);
103
+ if (id !== undefined && !named.has(path))
104
+ images.push({ type: "sourcemap", code_file: path, debug_id: id });
105
+ }
106
+ if (images.length > before)
107
+ event.debug_meta = { ...meta, images: images };
108
+ }
109
+ catch {
110
+ // A malformed _sentryDebugIds or event costs the images, nothing more.
111
+ }
112
+ }
@@ -1,14 +1,23 @@
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
- export type { Breadcrumb, BreadcrumbHint, CaptureContext, Event, EventHint, Exception, InitOptions, IntegrationToggles, Level, Mechanism, Primitive, RequestInfo, ScopeData, SdkInfo, SentryLevel, StackFrame, Transport, TransportRequest, TransportResponse, User, } from "./types.js";
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";
6
9
  /**
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): void;
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));
@@ -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 isValidName(value: unknown): value is string;
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 {
@@ -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.isValidName = isValidName;
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
- /** Custom level and category names: what the server accepts as tag values to filter on. */
18
- const NAME_RE = /^[a-z0-9._-]{1,64}$/;
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 isValidName(value) {
26
- return typeof value === "string" && NAME_RE.test(value);
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 (!isValidName(name)) {
42
- logger.warn(`Ignored level "${name}" in \`levels\`: use 1 to 64 lowercase letters, digits, dots, dashes or underscores.`);
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 (isValidName(candidate)) {
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 64 lowercase letters, digits, dots, dashes or underscores.`);
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 (isValidName(value))
84
+ if (isCategoryName(value))
74
85
  return value;
75
- logger.warn(`Ignored category "${(0, utils_js_1.safeString)(value)}": use 1 to 64 lowercase letters, digits, dots, dashes or underscores.`);
86
+ logger.warn(`Ignored category "${(0, utils_js_1.safeString)(value)}": ${exports.CATEGORY_RULE}.`);
76
87
  return undefined;
77
88
  }