@statoss/sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOG_CONTENT_TYPE = void 0;
3
4
  exports.eventEnvelope = eventEnvelope;
4
5
  exports.clientReportEnvelope = clientReportEnvelope;
6
+ exports.logEnvelope = logEnvelope;
5
7
  exports.itemCategory = itemCategory;
8
+ exports.itemQuantity = itemQuantity;
9
+ exports.isLogEnvelope = isLogEnvelope;
6
10
  exports.serializeEnvelope = serializeEnvelope;
7
11
  const dsn_js_1 = require("./dsn.js");
8
12
  const utils_js_1 = require("./utils.js");
9
13
  const version_js_1 = require("./version.js");
14
+ exports.LOG_CONTENT_TYPE = "application/vnd.sentry.items.log+json";
10
15
  function sdkHeader() {
11
16
  return { name: version_js_1.SDK_NAME, version: version_js_1.SDK_VERSION };
12
17
  }
@@ -31,9 +36,44 @@ function clientReportEnvelope(discarded, dsn, timestamp = Date.now() / 1000) {
31
36
  [[{ type: "client_report" }, { timestamp, discarded_events: discarded }]],
32
37
  ];
33
38
  }
39
+ /** Log lines in one envelope, as Sentry's logger sends them. */
40
+ function logEnvelope(logs, dsn) {
41
+ return [
42
+ {
43
+ sent_at: new Date().toISOString(),
44
+ dsn: (0, dsn_js_1.dsnToString)(dsn),
45
+ sdk: sdkHeader(),
46
+ },
47
+ [
48
+ [
49
+ {
50
+ type: "log",
51
+ item_count: logs.length,
52
+ content_type: exports.LOG_CONTENT_TYPE,
53
+ },
54
+ { version: 2, items: logs },
55
+ ],
56
+ ],
57
+ ];
58
+ }
34
59
  /** Sentry's rate-limit category for an envelope item. */
35
60
  function itemCategory(item) {
36
- return item[0].type === "event" ? "error" : "internal";
61
+ switch (item[0].type) {
62
+ case "event":
63
+ return "error";
64
+ case "log":
65
+ return "log_item";
66
+ default:
67
+ return "internal";
68
+ }
69
+ }
70
+ /** How many things an item holds, as client reports count them. */
71
+ function itemQuantity(item) {
72
+ return item[0].type === "log" ? item[0].item_count : 1;
73
+ }
74
+ /** True for an envelope of log lines. */
75
+ function isLogEnvelope(envelope) {
76
+ return envelope[1].some((item) => item[0].type === "log");
37
77
  }
38
78
  function stringify(payload) {
39
79
  try {
@@ -1,14 +1,25 @@
1
+ import { type AppLogger } from "./logs.js";
2
+ import type { CheckSchema, Handle, SchemaSpec } from "./schema.js";
1
3
  import type { Breadcrumb, CaptureContext, Event, InitOptions, Level, Primitive, User } from "./types.js";
2
4
  export { withScope } from "./hub.js";
5
+ export type { AppLogger, Log, LogAttributes, LogAttributeValue, LogLevel, LogLevelOf, LogMessage, SerializedLog, } from "./logs.js";
6
+ export { defineSchema } from "./schema.js";
7
+ export type { CategoryOf, CategorySettings, CheckSchema, FieldOf, FieldSettings, FieldType, FieldValue, Handle, LevelOf, LevelSettings, Like, RuleMatch, RuleSubject, SchemaColor, SchemaContext, SchemaRule, SchemaSpec, Severity, } from "./schema.js";
3
8
  export type { Scope } from "./scope.js";
4
9
  export { SDK_NAME, SDK_VERSION } from "./version.js";
5
10
  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
11
  /**
7
12
  * Starts the SDK. Call it once, as early as you can. Calling it again replaces
8
13
  * the earlier setup. Without a valid DSN, or with enabled: false, nothing is
9
- * sent and nothing is patched.
14
+ * sent and nothing is patched. With `logs: true`, `logger`'s lines are sent.
15
+ *
16
+ * With a `schema` from defineSchema, its levels are sent as the Sentry level
17
+ * each ranks like, and the capture functions and logger it returns take
18
+ * only the schema's levels, categories and fields.
10
19
  */
11
- export declare function init(options?: InitOptions): void;
20
+ export declare function init<const S extends SchemaSpec & CheckSchema<S> = SchemaSpec>(options?: InitOptions & {
21
+ schema?: S;
22
+ }): Handle<S>;
12
23
  /** Sends an error (or anything thrown). Returns the event id. */
13
24
  export declare function captureException(error: unknown, context?: CaptureContext): string;
14
25
  /**
@@ -18,6 +29,12 @@ export declare function captureException(error: unknown, context?: CaptureContex
18
29
  export declare function captureMessage(message: string, levelOrContext?: Level | CaptureContext): string;
19
30
  /** Sends an event you built in Sentry's schema. Returns the event id. */
20
31
  export declare function captureEvent(event: Event, context?: CaptureContext): string;
32
+ /**
33
+ * Log lines, sent when init has `logs: true`: logger.info("Paid", { orderId: 7 }),
34
+ * logger.warn(logger.fmt`Took ${ms} ms`), logger.log("payment-failed", "Card declined").
35
+ * Lines go out in batches; flush() and close() send what waits.
36
+ */
37
+ export declare const logger: AppLogger;
21
38
  /** Pass null to forget the user. */
22
39
  export declare function setUser(user: User | null): void;
23
40
  export declare function setTag(key: string, value: Primitive): void;
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.logger = 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;
@@ -16,17 +16,15 @@ exports.close = close;
16
16
  exports.lastEventId = lastEventId;
17
17
  const client_js_1 = require("./client.js");
18
18
  const hub_js_1 = require("./hub.js");
19
+ const logs_js_1 = require("./logs.js");
19
20
  const utils_js_1 = require("./utils.js");
20
21
  var hub_js_2 = require("./hub.js");
21
22
  Object.defineProperty(exports, "withScope", { enumerable: true, get: function () { return hub_js_2.withScope; } });
23
+ var schema_js_1 = require("./schema.js");
24
+ Object.defineProperty(exports, "defineSchema", { enumerable: true, get: function () { return schema_js_1.defineSchema; } });
22
25
  var version_js_1 = require("./version.js");
23
26
  Object.defineProperty(exports, "SDK_NAME", { enumerable: true, get: function () { return version_js_1.SDK_NAME; } });
24
27
  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
28
  function init(options = {}) {
31
29
  try {
32
30
  const carrier = (0, hub_js_1.getCarrier)();
@@ -45,6 +43,7 @@ function init(options = {}) {
45
43
  (0, utils_js_1.safely)(() => console.error("[StatOSS] init failed:", error));
46
44
  }
47
45
  }
46
+ return HANDLE;
48
47
  }
49
48
  /** Sends an error (or anything thrown). Returns the event id. */
50
49
  function captureException(error, context) {
@@ -61,6 +60,20 @@ function captureMessage(message, levelOrContext) {
61
60
  function captureEvent(event, context) {
62
61
  return (0, hub_js_1.captureWith)(undefined, (client, scope) => client.captureEvent(event, context, scope));
63
62
  }
63
+ /**
64
+ * Log lines, sent when init has `logs: true`: logger.info("Paid", { orderId: 7 }),
65
+ * logger.warn(logger.fmt`Took ${ms} ms`), logger.log("payment-failed", "Card declined").
66
+ * Lines go out in batches; flush() and close() send what waits.
67
+ */
68
+ exports.logger = (0, logs_js_1.makeAppLogger)((level, message, attributes) => (0, utils_js_1.safely)(() => (0, hub_js_1.getCarrier)().client?.captureLog(level, message, attributes, (0, hub_js_1.getCurrentScope)())));
69
+ // What init returns: the same functions, typed by the schema. The client
70
+ // reads a context's fields whichever function it came through.
71
+ const HANDLE = Object.freeze({
72
+ captureException,
73
+ captureMessage,
74
+ captureEvent,
75
+ logger: exports.logger,
76
+ });
64
77
  /** Pass null to forget the user. */
65
78
  function setUser(user) {
66
79
  (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
  }
@@ -0,0 +1,133 @@
1
+ import type { Logger } from "./logger.js";
2
+ import type { LevelOf, SchemaSpec } from "./schema.js";
3
+ import type { SentryLevel } from "./types.js";
4
+ /**
5
+ * Log lines, as Sentry's logger sends them: `log` items in envelopes, each
6
+ * line with a level, a body and typed attributes
7
+ * (https://develop.sentry.dev/sdk/telemetry/logs/).
8
+ */
9
+ /** The levels of a log line: OpenTelemetry's six ranges, as Sentry's logger names them. */
10
+ export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
11
+ export declare const LOG_LEVELS: readonly LogLevel[];
12
+ /** What intake keeps of a line (docs/logs.md, In), in UTF-8 bytes: a body, and an attribute's value. */
13
+ export declare const MAX_BODY_BYTES: number;
14
+ export declare const MAX_ATTRIBUTE_BYTES: number;
15
+ /** A cut body's size in bytes before the cut, as intake records it. */
16
+ export declare const TRUNCATED = "statoss.truncated";
17
+ /** Each level's severity number: the start of its OpenTelemetry range. */
18
+ export declare const LOG_SEVERITY: Readonly<Record<LogLevel, number>>;
19
+ export declare function isLogLevel(value: unknown): value is LogLevel;
20
+ /** The level a range (a schema's `like`) or a Sentry level is logged at. */
21
+ export declare function logLevelOf(range: string): LogLevel | undefined;
22
+ /** The range a severity number falls in. */
23
+ export declare function rangeOfSeverity(severity: number): LogLevel;
24
+ /** A message made by `logger.fmt`: the text, and the template and values it came from. */
25
+ export interface LogMessage {
26
+ /** The message, its values filled in. */
27
+ readonly text: string;
28
+ /** The template, each value written %s, as Sentry's logger writes it. */
29
+ readonly template: string;
30
+ readonly values: readonly unknown[];
31
+ toString(): string;
32
+ }
33
+ /**
34
+ * A message with parameters: logger.info(logger.fmt`Paid ${amount} for ${order}`).
35
+ * The line's body is the text; the template and each value go along as
36
+ * the attributes sentry.message.template and sentry.message.parameter.<n>.
37
+ */
38
+ export declare function fmt(strings: TemplateStringsArray, ...values: unknown[]): LogMessage;
39
+ export declare function isLogMessage(value: unknown): value is LogMessage;
40
+ /** A line's attributes: strings, numbers and booleans are sent as they are, anything else as JSON. */
41
+ export type LogAttributes = Record<string, unknown>;
42
+ /** A log line before it is sent, as beforeSendLog sees it. */
43
+ export interface Log {
44
+ level: LogLevel;
45
+ /** The message, with its values filled in. */
46
+ message: string;
47
+ attributes: LogAttributes;
48
+ /** 1 to 24, inside the level's range. */
49
+ severityNumber: number;
50
+ /** Seconds since the epoch, with fractions. */
51
+ timestamp: number;
52
+ }
53
+ export type LogAttributeValue = {
54
+ value: string;
55
+ type: "string";
56
+ } | {
57
+ value: number;
58
+ type: "integer";
59
+ } | {
60
+ value: number;
61
+ type: "double";
62
+ } | {
63
+ value: boolean;
64
+ type: "boolean";
65
+ };
66
+ /** A log line as an envelope's log item holds it. */
67
+ export interface SerializedLog {
68
+ timestamp: number;
69
+ level: LogLevel;
70
+ body: string;
71
+ /** 32 lower-case hex digits; Sentry's Relay drops a line without one. */
72
+ trace_id: string;
73
+ severity_number: number;
74
+ attributes: Record<string, LogAttributeValue>;
75
+ }
76
+ /** An attribute's value as Sentry types it; undefined and null are left out. */
77
+ export declare function serializeAttribute(value: unknown): LogAttributeValue | undefined;
78
+ /** A string's length in UTF-8 bytes. */
79
+ export declare function utf8Length(s: string): number;
80
+ /**
81
+ * A string cut to at most `max` UTF-8 bytes, the cut marked "...", as
82
+ * intake's cut writes it (intake/normalize.ts), never inside a character.
83
+ */
84
+ export declare function cutBytes(s: string, max: number): string;
85
+ /**
86
+ * A body past 64 KiB is cut there, with its size in bytes as
87
+ * statoss.truncated, as intake cuts it and records it: one huge line must
88
+ * not take the batch it is sent in down with it.
89
+ */
90
+ export declare function cutBody(body: string, attributes: Record<string, LogAttributeValue>): string;
91
+ /** The message, or a value in it, as text: objects as JSON. */
92
+ export declare function bodyOf(message: unknown): string;
93
+ /** A level of the app's own, as a line is logged at it. */
94
+ export interface OwnLogLevel {
95
+ level: LogLevel;
96
+ severity: number;
97
+ }
98
+ export interface LogLevelChoice {
99
+ level: LogLevel;
100
+ severity: number;
101
+ /** The app's own level, when the line names one. */
102
+ custom?: string;
103
+ }
104
+ /**
105
+ * The level a line is logged at. One of the six (or a Sentry level) is
106
+ * itself; an app's own level is its range's, with its severity and its
107
+ * name for statoss.level; a name nobody declared ranks as error, as an
108
+ * event's does. Anything else is logged as info.
109
+ */
110
+ export declare function chooseLogLevel(level: unknown, own: Record<string, OwnLogLevel>, logger: Logger): LogLevelChoice;
111
+ /** The ranges of init's `levels`: a Sentry level each. */
112
+ export declare function ownLevelsFromSentry(levels: Record<string, SentryLevel>): Record<string, OwnLogLevel>;
113
+ /** What `logger` and the handle's `logger` take as a level, typed by the schema. */
114
+ export type LogLevelOf<S = SchemaSpec> = LogLevel | LevelOf<S>;
115
+ /** The logger: one method per level, any level by name, and templates. */
116
+ export interface AppLogger<S = SchemaSpec> {
117
+ trace(message: string | LogMessage, attributes?: LogAttributes): void;
118
+ debug(message: string | LogMessage, attributes?: LogAttributes): void;
119
+ info(message: string | LogMessage, attributes?: LogAttributes): void;
120
+ warn(message: string | LogMessage, attributes?: LogAttributes): void;
121
+ error(message: string | LogMessage, attributes?: LogAttributes): void;
122
+ fatal(message: string | LogMessage, attributes?: LogAttributes): void;
123
+ /**
124
+ * A line at a level by name: one of the six, a Sentry level, or one of
125
+ * the app's own (with a schema, only its names). An own level is sent at
126
+ * its range's level with the attribute statoss.level.
127
+ */
128
+ log(level: LogLevelOf<S>, message: string | LogMessage, attributes?: LogAttributes): void;
129
+ /** A message with parameters: logger.fmt`Paid ${amount}`. */
130
+ fmt(strings: TemplateStringsArray, ...values: unknown[]): LogMessage;
131
+ }
132
+ /** A logger that hands each line to `capture`. */
133
+ export declare function makeAppLogger(capture: (level: unknown, message: unknown, attributes: LogAttributes | undefined) => void): AppLogger;
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOG_SEVERITY = exports.TRUNCATED = exports.MAX_ATTRIBUTE_BYTES = exports.MAX_BODY_BYTES = exports.LOG_LEVELS = void 0;
4
+ exports.isLogLevel = isLogLevel;
5
+ exports.logLevelOf = logLevelOf;
6
+ exports.rangeOfSeverity = rangeOfSeverity;
7
+ exports.fmt = fmt;
8
+ exports.isLogMessage = isLogMessage;
9
+ exports.serializeAttribute = serializeAttribute;
10
+ exports.utf8Length = utf8Length;
11
+ exports.cutBytes = cutBytes;
12
+ exports.cutBody = cutBody;
13
+ exports.bodyOf = bodyOf;
14
+ exports.chooseLogLevel = chooseLogLevel;
15
+ exports.ownLevelsFromSentry = ownLevelsFromSentry;
16
+ exports.makeAppLogger = makeAppLogger;
17
+ const levels_js_1 = require("./levels.js");
18
+ const utils_js_1 = require("./utils.js");
19
+ exports.LOG_LEVELS = [
20
+ "trace",
21
+ "debug",
22
+ "info",
23
+ "warn",
24
+ "error",
25
+ "fatal",
26
+ ];
27
+ /** What intake keeps of a line (docs/logs.md, In), in UTF-8 bytes: a body, and an attribute's value. */
28
+ exports.MAX_BODY_BYTES = 64 * 1024;
29
+ exports.MAX_ATTRIBUTE_BYTES = 8 * 1024;
30
+ /** A cut body's size in bytes before the cut, as intake records it. */
31
+ exports.TRUNCATED = "statoss.truncated";
32
+ /** Each level's severity number: the start of its OpenTelemetry range. */
33
+ exports.LOG_SEVERITY = {
34
+ trace: 1,
35
+ debug: 5,
36
+ info: 9,
37
+ warn: 13,
38
+ error: 17,
39
+ fatal: 21,
40
+ };
41
+ function isLogLevel(value) {
42
+ return (typeof value === "string" &&
43
+ exports.LOG_LEVELS.includes(value));
44
+ }
45
+ /** The level a range (a schema's `like`) or a Sentry level is logged at. */
46
+ function logLevelOf(range) {
47
+ if (range === "warning")
48
+ return "warn";
49
+ return isLogLevel(range) ? range : undefined;
50
+ }
51
+ /** The range a severity number falls in. */
52
+ function rangeOfSeverity(severity) {
53
+ if (severity >= 21)
54
+ return "fatal";
55
+ if (severity >= 17)
56
+ return "error";
57
+ if (severity >= 13)
58
+ return "warn";
59
+ if (severity >= 9)
60
+ return "info";
61
+ if (severity >= 5)
62
+ return "debug";
63
+ return "trace";
64
+ }
65
+ /**
66
+ * A message with parameters: logger.info(logger.fmt`Paid ${amount} for ${order}`).
67
+ * The line's body is the text; the template and each value go along as
68
+ * the attributes sentry.message.template and sentry.message.parameter.<n>.
69
+ */
70
+ function fmt(strings, ...values) {
71
+ const parts = Array.from(strings, (s, i) => s ?? strings.raw[i]);
72
+ let text = parts[0];
73
+ for (let i = 1; i < parts.length; i++)
74
+ text += `${bodyOf(values[i - 1])}${parts[i]}`;
75
+ return Object.freeze({
76
+ text,
77
+ template: parts.map((s) => s.replace(/%/g, "%%")).join("%s"),
78
+ values: Object.freeze([...values]),
79
+ toString: () => text,
80
+ });
81
+ }
82
+ function isLogMessage(value) {
83
+ return ((0, utils_js_1.isObject)(value) &&
84
+ typeof value.text === "string" &&
85
+ typeof value.template === "string" &&
86
+ Array.isArray(value.values));
87
+ }
88
+ /** An attribute's value as Sentry types it; undefined and null are left out. */
89
+ function serializeAttribute(value) {
90
+ if (value === undefined || value === null)
91
+ return undefined;
92
+ if (typeof value === "string")
93
+ return { value: cutBytes(value, exports.MAX_ATTRIBUTE_BYTES), type: "string" };
94
+ if (typeof value === "boolean")
95
+ return { value, type: "boolean" };
96
+ if (typeof value === "number") {
97
+ if (!Number.isFinite(value))
98
+ return { value: (0, utils_js_1.safeString)(value), type: "string" };
99
+ return Number.isInteger(value)
100
+ ? { value, type: "integer" }
101
+ : { value, type: "double" };
102
+ }
103
+ if (typeof value === "bigint") {
104
+ const n = Number(value);
105
+ return Number.isSafeInteger(n)
106
+ ? { value: n, type: "integer" }
107
+ : { value: value.toString(), type: "string" };
108
+ }
109
+ const text = value instanceof String
110
+ ? String(value)
111
+ : ((0, utils_js_1.safely)(() => JSON.stringify((0, utils_js_1.normalize)(value, 4, 1024, 100))) ??
112
+ (0, utils_js_1.safeString)(value));
113
+ return { value: cutBytes(text, exports.MAX_ATTRIBUTE_BYTES), type: "string" };
114
+ }
115
+ /** UTF-8 bytes of one UTF-16 unit at i, and how many units it takes. */
116
+ function utf8At(s, i) {
117
+ const c = s.charCodeAt(i);
118
+ if (c < 0x80)
119
+ return [1, 1];
120
+ if (c < 0x800)
121
+ return [2, 1];
122
+ if (c >= 0xd800 &&
123
+ c <= 0xdbff &&
124
+ i + 1 < s.length &&
125
+ (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00)
126
+ return [4, 2];
127
+ // A lone surrogate is written as U+FFFD, three bytes.
128
+ return [3, 1];
129
+ }
130
+ /** A string's length in UTF-8 bytes. */
131
+ function utf8Length(s) {
132
+ let n = 0;
133
+ for (let i = 0; i < s.length;) {
134
+ const [bytes, units] = utf8At(s, i);
135
+ n += bytes;
136
+ i += units;
137
+ }
138
+ return n;
139
+ }
140
+ /**
141
+ * A string cut to at most `max` UTF-8 bytes, the cut marked "...", as
142
+ * intake's cut writes it (intake/normalize.ts), never inside a character.
143
+ */
144
+ function cutBytes(s, max) {
145
+ if (s.length * 3 <= max || utf8Length(s) <= max)
146
+ return s;
147
+ let n = 0;
148
+ let i = 0;
149
+ while (i < s.length) {
150
+ const [bytes, units] = utf8At(s, i);
151
+ if (n + bytes > max - 3)
152
+ break;
153
+ n += bytes;
154
+ i += units;
155
+ }
156
+ // As intake: a lone high surrogate does not end a cut.
157
+ if (i > 0 && /[\ud800-\udbff]/.test(s[i - 1]))
158
+ i--;
159
+ return `${s.slice(0, i)}...`;
160
+ }
161
+ /**
162
+ * A body past 64 KiB is cut there, with its size in bytes as
163
+ * statoss.truncated, as intake cuts it and records it: one huge line must
164
+ * not take the batch it is sent in down with it.
165
+ */
166
+ function cutBody(body, attributes) {
167
+ if (body.length * 3 <= exports.MAX_BODY_BYTES)
168
+ return body;
169
+ const size = utf8Length(body);
170
+ if (size <= exports.MAX_BODY_BYTES)
171
+ return body;
172
+ attributes[exports.TRUNCATED] = { value: size, type: "integer" };
173
+ return cutBytes(body, exports.MAX_BODY_BYTES);
174
+ }
175
+ /** The message, or a value in it, as text: objects as JSON. */
176
+ function bodyOf(message) {
177
+ if (typeof message === "string")
178
+ return message;
179
+ if (isLogMessage(message))
180
+ return message.text;
181
+ if (message instanceof String)
182
+ return String(message);
183
+ if ((0, utils_js_1.isObject)(message) && !(message instanceof Error)) {
184
+ const json = (0, utils_js_1.safely)(() => JSON.stringify((0, utils_js_1.normalize)(message, 4)));
185
+ if (json !== undefined)
186
+ return json;
187
+ }
188
+ return (0, utils_js_1.safeString)(message);
189
+ }
190
+ /**
191
+ * The level a line is logged at. One of the six (or a Sentry level) is
192
+ * itself; an app's own level is its range's, with its severity and its
193
+ * name for statoss.level; a name nobody declared ranks as error, as an
194
+ * event's does. Anything else is logged as info.
195
+ */
196
+ function chooseLogLevel(level, own, logger) {
197
+ if (isLogLevel(level))
198
+ return { level, severity: exports.LOG_SEVERITY[level] };
199
+ if ((0, levels_js_1.isSentryLevel)(level)) {
200
+ const as = logLevelOf(level) ?? "error";
201
+ return { level: as, severity: exports.LOG_SEVERITY[as] };
202
+ }
203
+ if ((0, levels_js_1.isLevelName)(level)) {
204
+ const found = own[level];
205
+ if (found)
206
+ return { ...found, custom: level };
207
+ logger.warn(`Level "${level}" is not in the schema or \`levels\`, so the line is logged as error.`);
208
+ return { level: "error", severity: exports.LOG_SEVERITY.error, custom: level };
209
+ }
210
+ logger.warn(`Logged "${(0, utils_js_1.safeString)(level)}" as info: use trace, debug, info, warn, error, fatal or a level name (${levels_js_1.LEVEL_RULE}).`);
211
+ return { level: "info", severity: exports.LOG_SEVERITY.info };
212
+ }
213
+ /** The ranges of init's `levels`: a Sentry level each. */
214
+ function ownLevelsFromSentry(levels) {
215
+ const out = {};
216
+ for (const [name, sentAs] of Object.entries(levels)) {
217
+ const level = logLevelOf(sentAs) ?? "error";
218
+ out[name] = { level, severity: exports.LOG_SEVERITY[level] };
219
+ }
220
+ return out;
221
+ }
222
+ /** A logger that hands each line to `capture`. */
223
+ function makeAppLogger(capture) {
224
+ const at = (level) => (message, attributes) => capture(level, message, attributes);
225
+ return Object.freeze({
226
+ trace: at("trace"),
227
+ debug: at("debug"),
228
+ info: at("info"),
229
+ warn: at("warn"),
230
+ error: at("error"),
231
+ fatal: at("fatal"),
232
+ log: (level, message, attributes) => capture(level, message, attributes),
233
+ fmt,
234
+ });
235
+ }