@statoss/sdk 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +151 -23
- package/dist/cjs/client.d.ts +29 -2
- package/dist/cjs/client.js +211 -4
- package/dist/cjs/envelope.d.ts +20 -1
- package/dist/cjs/envelope.js +41 -1
- package/dist/cjs/index.d.ts +19 -2
- package/dist/cjs/index.js +19 -6
- package/dist/cjs/levels.d.ts +4 -1
- package/dist/cjs/levels.js +23 -12
- package/dist/cjs/logs.d.ts +133 -0
- package/dist/cjs/logs.js +235 -0
- package/dist/cjs/schema.d.ts +211 -0
- package/dist/cjs/schema.js +159 -0
- package/dist/cjs/transport.d.ts +6 -1
- package/dist/cjs/transport.js +23 -7
- package/dist/cjs/types.d.ts +9 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client.d.ts +29 -2
- package/dist/esm/client.js +212 -5
- package/dist/esm/envelope.d.ts +20 -1
- package/dist/esm/envelope.js +37 -1
- package/dist/esm/index.d.ts +19 -2
- package/dist/esm/index.js +17 -5
- package/dist/esm/levels.d.ts +4 -1
- package/dist/esm/levels.js +20 -10
- package/dist/esm/logs.d.ts +133 -0
- package/dist/esm/logs.js +219 -0
- package/dist/esm/schema.d.ts +211 -0
- package/dist/esm/schema.js +153 -0
- package/dist/esm/transport.d.ts +6 -1
- package/dist/esm/transport.js +23 -7
- package/dist/esm/types.d.ts +9 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,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;
|
package/dist/esm/logs.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { isLevelName, isSentryLevel, LEVEL_RULE } from "./levels.js";
|
|
2
|
+
import { isObject, normalize, safely, safeString } from "./utils.js";
|
|
3
|
+
export const LOG_LEVELS = [
|
|
4
|
+
"trace",
|
|
5
|
+
"debug",
|
|
6
|
+
"info",
|
|
7
|
+
"warn",
|
|
8
|
+
"error",
|
|
9
|
+
"fatal",
|
|
10
|
+
];
|
|
11
|
+
/** What intake keeps of a line (docs/logs.md, In), in UTF-8 bytes: a body, and an attribute's value. */
|
|
12
|
+
export const MAX_BODY_BYTES = 64 * 1024;
|
|
13
|
+
export const MAX_ATTRIBUTE_BYTES = 8 * 1024;
|
|
14
|
+
/** A cut body's size in bytes before the cut, as intake records it. */
|
|
15
|
+
export const TRUNCATED = "statoss.truncated";
|
|
16
|
+
/** Each level's severity number: the start of its OpenTelemetry range. */
|
|
17
|
+
export const LOG_SEVERITY = {
|
|
18
|
+
trace: 1,
|
|
19
|
+
debug: 5,
|
|
20
|
+
info: 9,
|
|
21
|
+
warn: 13,
|
|
22
|
+
error: 17,
|
|
23
|
+
fatal: 21,
|
|
24
|
+
};
|
|
25
|
+
export function isLogLevel(value) {
|
|
26
|
+
return (typeof value === "string" &&
|
|
27
|
+
LOG_LEVELS.includes(value));
|
|
28
|
+
}
|
|
29
|
+
/** The level a range (a schema's `like`) or a Sentry level is logged at. */
|
|
30
|
+
export function logLevelOf(range) {
|
|
31
|
+
if (range === "warning")
|
|
32
|
+
return "warn";
|
|
33
|
+
return isLogLevel(range) ? range : undefined;
|
|
34
|
+
}
|
|
35
|
+
/** The range a severity number falls in. */
|
|
36
|
+
export function rangeOfSeverity(severity) {
|
|
37
|
+
if (severity >= 21)
|
|
38
|
+
return "fatal";
|
|
39
|
+
if (severity >= 17)
|
|
40
|
+
return "error";
|
|
41
|
+
if (severity >= 13)
|
|
42
|
+
return "warn";
|
|
43
|
+
if (severity >= 9)
|
|
44
|
+
return "info";
|
|
45
|
+
if (severity >= 5)
|
|
46
|
+
return "debug";
|
|
47
|
+
return "trace";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A message with parameters: logger.info(logger.fmt`Paid ${amount} for ${order}`).
|
|
51
|
+
* The line's body is the text; the template and each value go along as
|
|
52
|
+
* the attributes sentry.message.template and sentry.message.parameter.<n>.
|
|
53
|
+
*/
|
|
54
|
+
export function fmt(strings, ...values) {
|
|
55
|
+
const parts = Array.from(strings, (s, i) => s ?? strings.raw[i]);
|
|
56
|
+
let text = parts[0];
|
|
57
|
+
for (let i = 1; i < parts.length; i++)
|
|
58
|
+
text += `${bodyOf(values[i - 1])}${parts[i]}`;
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
text,
|
|
61
|
+
template: parts.map((s) => s.replace(/%/g, "%%")).join("%s"),
|
|
62
|
+
values: Object.freeze([...values]),
|
|
63
|
+
toString: () => text,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function isLogMessage(value) {
|
|
67
|
+
return (isObject(value) &&
|
|
68
|
+
typeof value.text === "string" &&
|
|
69
|
+
typeof value.template === "string" &&
|
|
70
|
+
Array.isArray(value.values));
|
|
71
|
+
}
|
|
72
|
+
/** An attribute's value as Sentry types it; undefined and null are left out. */
|
|
73
|
+
export function serializeAttribute(value) {
|
|
74
|
+
if (value === undefined || value === null)
|
|
75
|
+
return undefined;
|
|
76
|
+
if (typeof value === "string")
|
|
77
|
+
return { value: cutBytes(value, MAX_ATTRIBUTE_BYTES), type: "string" };
|
|
78
|
+
if (typeof value === "boolean")
|
|
79
|
+
return { value, type: "boolean" };
|
|
80
|
+
if (typeof value === "number") {
|
|
81
|
+
if (!Number.isFinite(value))
|
|
82
|
+
return { value: safeString(value), type: "string" };
|
|
83
|
+
return Number.isInteger(value)
|
|
84
|
+
? { value, type: "integer" }
|
|
85
|
+
: { value, type: "double" };
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === "bigint") {
|
|
88
|
+
const n = Number(value);
|
|
89
|
+
return Number.isSafeInteger(n)
|
|
90
|
+
? { value: n, type: "integer" }
|
|
91
|
+
: { value: value.toString(), type: "string" };
|
|
92
|
+
}
|
|
93
|
+
const text = value instanceof String
|
|
94
|
+
? String(value)
|
|
95
|
+
: (safely(() => JSON.stringify(normalize(value, 4, 1024, 100))) ??
|
|
96
|
+
safeString(value));
|
|
97
|
+
return { value: cutBytes(text, MAX_ATTRIBUTE_BYTES), type: "string" };
|
|
98
|
+
}
|
|
99
|
+
/** UTF-8 bytes of one UTF-16 unit at i, and how many units it takes. */
|
|
100
|
+
function utf8At(s, i) {
|
|
101
|
+
const c = s.charCodeAt(i);
|
|
102
|
+
if (c < 0x80)
|
|
103
|
+
return [1, 1];
|
|
104
|
+
if (c < 0x800)
|
|
105
|
+
return [2, 1];
|
|
106
|
+
if (c >= 0xd800 &&
|
|
107
|
+
c <= 0xdbff &&
|
|
108
|
+
i + 1 < s.length &&
|
|
109
|
+
(s.charCodeAt(i + 1) & 0xfc00) === 0xdc00)
|
|
110
|
+
return [4, 2];
|
|
111
|
+
// A lone surrogate is written as U+FFFD, three bytes.
|
|
112
|
+
return [3, 1];
|
|
113
|
+
}
|
|
114
|
+
/** A string's length in UTF-8 bytes. */
|
|
115
|
+
export function utf8Length(s) {
|
|
116
|
+
let n = 0;
|
|
117
|
+
for (let i = 0; i < s.length;) {
|
|
118
|
+
const [bytes, units] = utf8At(s, i);
|
|
119
|
+
n += bytes;
|
|
120
|
+
i += units;
|
|
121
|
+
}
|
|
122
|
+
return n;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A string cut to at most `max` UTF-8 bytes, the cut marked "...", as
|
|
126
|
+
* intake's cut writes it (intake/normalize.ts), never inside a character.
|
|
127
|
+
*/
|
|
128
|
+
export function cutBytes(s, max) {
|
|
129
|
+
if (s.length * 3 <= max || utf8Length(s) <= max)
|
|
130
|
+
return s;
|
|
131
|
+
let n = 0;
|
|
132
|
+
let i = 0;
|
|
133
|
+
while (i < s.length) {
|
|
134
|
+
const [bytes, units] = utf8At(s, i);
|
|
135
|
+
if (n + bytes > max - 3)
|
|
136
|
+
break;
|
|
137
|
+
n += bytes;
|
|
138
|
+
i += units;
|
|
139
|
+
}
|
|
140
|
+
// As intake: a lone high surrogate does not end a cut.
|
|
141
|
+
if (i > 0 && /[\ud800-\udbff]/.test(s[i - 1]))
|
|
142
|
+
i--;
|
|
143
|
+
return `${s.slice(0, i)}...`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A body past 64 KiB is cut there, with its size in bytes as
|
|
147
|
+
* statoss.truncated, as intake cuts it and records it: one huge line must
|
|
148
|
+
* not take the batch it is sent in down with it.
|
|
149
|
+
*/
|
|
150
|
+
export function cutBody(body, attributes) {
|
|
151
|
+
if (body.length * 3 <= MAX_BODY_BYTES)
|
|
152
|
+
return body;
|
|
153
|
+
const size = utf8Length(body);
|
|
154
|
+
if (size <= MAX_BODY_BYTES)
|
|
155
|
+
return body;
|
|
156
|
+
attributes[TRUNCATED] = { value: size, type: "integer" };
|
|
157
|
+
return cutBytes(body, MAX_BODY_BYTES);
|
|
158
|
+
}
|
|
159
|
+
/** The message, or a value in it, as text: objects as JSON. */
|
|
160
|
+
export function bodyOf(message) {
|
|
161
|
+
if (typeof message === "string")
|
|
162
|
+
return message;
|
|
163
|
+
if (isLogMessage(message))
|
|
164
|
+
return message.text;
|
|
165
|
+
if (message instanceof String)
|
|
166
|
+
return String(message);
|
|
167
|
+
if (isObject(message) && !(message instanceof Error)) {
|
|
168
|
+
const json = safely(() => JSON.stringify(normalize(message, 4)));
|
|
169
|
+
if (json !== undefined)
|
|
170
|
+
return json;
|
|
171
|
+
}
|
|
172
|
+
return safeString(message);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The level a line is logged at. One of the six (or a Sentry level) is
|
|
176
|
+
* itself; an app's own level is its range's, with its severity and its
|
|
177
|
+
* name for statoss.level; a name nobody declared ranks as error, as an
|
|
178
|
+
* event's does. Anything else is logged as info.
|
|
179
|
+
*/
|
|
180
|
+
export function chooseLogLevel(level, own, logger) {
|
|
181
|
+
if (isLogLevel(level))
|
|
182
|
+
return { level, severity: LOG_SEVERITY[level] };
|
|
183
|
+
if (isSentryLevel(level)) {
|
|
184
|
+
const as = logLevelOf(level) ?? "error";
|
|
185
|
+
return { level: as, severity: LOG_SEVERITY[as] };
|
|
186
|
+
}
|
|
187
|
+
if (isLevelName(level)) {
|
|
188
|
+
const found = own[level];
|
|
189
|
+
if (found)
|
|
190
|
+
return { ...found, custom: level };
|
|
191
|
+
logger.warn(`Level "${level}" is not in the schema or \`levels\`, so the line is logged as error.`);
|
|
192
|
+
return { level: "error", severity: LOG_SEVERITY.error, custom: level };
|
|
193
|
+
}
|
|
194
|
+
logger.warn(`Logged "${safeString(level)}" as info: use trace, debug, info, warn, error, fatal or a level name (${LEVEL_RULE}).`);
|
|
195
|
+
return { level: "info", severity: LOG_SEVERITY.info };
|
|
196
|
+
}
|
|
197
|
+
/** The ranges of init's `levels`: a Sentry level each. */
|
|
198
|
+
export function ownLevelsFromSentry(levels) {
|
|
199
|
+
const out = {};
|
|
200
|
+
for (const [name, sentAs] of Object.entries(levels)) {
|
|
201
|
+
const level = logLevelOf(sentAs) ?? "error";
|
|
202
|
+
out[name] = { level, severity: LOG_SEVERITY[level] };
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
/** A logger that hands each line to `capture`. */
|
|
207
|
+
export function makeAppLogger(capture) {
|
|
208
|
+
const at = (level) => (message, attributes) => capture(level, message, attributes);
|
|
209
|
+
return Object.freeze({
|
|
210
|
+
trace: at("trace"),
|
|
211
|
+
debug: at("debug"),
|
|
212
|
+
info: at("info"),
|
|
213
|
+
warn: at("warn"),
|
|
214
|
+
error: at("error"),
|
|
215
|
+
fatal: at("fatal"),
|
|
216
|
+
log: (level, message, attributes) => capture(level, message, attributes),
|
|
217
|
+
fmt,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { Logger } from "./logger.js";
|
|
2
|
+
import { type AppLogger, type OwnLogLevel } from "./logs.js";
|
|
3
|
+
import type { CaptureContext, Event, Level, SentryLevel } from "./types.js";
|
|
4
|
+
/** The ranges a level ranks in: Sentry's five and OpenTelemetry's trace. */
|
|
5
|
+
export declare const LIKES: readonly ["fatal", "error", "warning", "info", "debug", "trace"];
|
|
6
|
+
export type Like = (typeof LIKES)[number];
|
|
7
|
+
/** The colours a level or a category takes, by name. */
|
|
8
|
+
export declare const SCHEMA_COLORS: readonly ["crimson", "red", "orange", "amber", "yellow", "green", "aqua", "teal", "blue", "violet", "indigo", "magenta", "plum", "grey"];
|
|
9
|
+
export type SchemaColor = (typeof SCHEMA_COLORS)[number];
|
|
10
|
+
/** What a rule tests: a tag by key, the logger, or one of the event's own fields. */
|
|
11
|
+
export declare const RULE_SUBJECTS: readonly ["tag", "logger", "exception", "culprit", "title", "message"];
|
|
12
|
+
export type RuleSubject = (typeof RULE_SUBJECTS)[number];
|
|
13
|
+
export declare const RULE_MATCHES: readonly ["is", "starts", "contains"];
|
|
14
|
+
export type RuleMatch = (typeof RULE_MATCHES)[number];
|
|
15
|
+
export declare const FIELD_TYPES: readonly ["text", "number"];
|
|
16
|
+
export type FieldType = (typeof FIELD_TYPES)[number];
|
|
17
|
+
/** Each range's severities, as OpenTelemetry numbers them. */
|
|
18
|
+
interface Severities {
|
|
19
|
+
fatal: 21 | 22 | 23 | 24;
|
|
20
|
+
error: 17 | 18 | 19 | 20;
|
|
21
|
+
warning: 13 | 14 | 15 | 16;
|
|
22
|
+
info: 9 | 10 | 11 | 12;
|
|
23
|
+
debug: 5 | 6 | 7 | 8;
|
|
24
|
+
trace: 1 | 2 | 3 | 4;
|
|
25
|
+
}
|
|
26
|
+
export type Severity = Severities[Like];
|
|
27
|
+
/** A level of your own. Null clears a setting. */
|
|
28
|
+
export interface LevelSettings {
|
|
29
|
+
/** The range it ranks in. Without it (and without severity) it ranks as error. */
|
|
30
|
+
like?: Like;
|
|
31
|
+
/** 1 to 24, inside the range of `like`. */
|
|
32
|
+
severity?: Severity;
|
|
33
|
+
label?: string | null;
|
|
34
|
+
color?: SchemaColor | null;
|
|
35
|
+
}
|
|
36
|
+
/** A category. Null clears a setting. */
|
|
37
|
+
export interface CategorySettings {
|
|
38
|
+
label?: string | null;
|
|
39
|
+
color?: SchemaColor | null;
|
|
40
|
+
/** Left out of filters and lists; its events still carry it. */
|
|
41
|
+
hidden?: boolean;
|
|
42
|
+
/** A team member's email. */
|
|
43
|
+
owner?: string | null;
|
|
44
|
+
}
|
|
45
|
+
/** A field: a tag key. Null clears a setting. */
|
|
46
|
+
export interface FieldSettings {
|
|
47
|
+
label?: string | null;
|
|
48
|
+
type?: FieldType;
|
|
49
|
+
unit?: string | null;
|
|
50
|
+
/** Gets a picker in Explore's filters. */
|
|
51
|
+
filterable?: boolean;
|
|
52
|
+
}
|
|
53
|
+
type RuleTests = {
|
|
54
|
+
subject: "tag";
|
|
55
|
+
tagKey: string;
|
|
56
|
+
} | {
|
|
57
|
+
subject: Exclude<RuleSubject, "tag">;
|
|
58
|
+
tagKey?: undefined;
|
|
59
|
+
};
|
|
60
|
+
type RuleSets<L extends string, C extends string> = {
|
|
61
|
+
level: L;
|
|
62
|
+
category?: C;
|
|
63
|
+
} | {
|
|
64
|
+
level?: L;
|
|
65
|
+
category: C;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Sets a level, a category or both for events that match: the first rule
|
|
69
|
+
* that matches wins. `match` defaults to "is".
|
|
70
|
+
*/
|
|
71
|
+
export type SchemaRule<L extends string = string, C extends string = string> = RuleTests & {
|
|
72
|
+
match?: RuleMatch;
|
|
73
|
+
value: string;
|
|
74
|
+
} & RuleSets<L, C>;
|
|
75
|
+
/** An app's levels, categories, fields and rules, as the server takes them. */
|
|
76
|
+
export interface SchemaSpec {
|
|
77
|
+
/** Each name with the range it ranks in, or with its settings. */
|
|
78
|
+
levels?: {
|
|
79
|
+
readonly [name: string]: Like | LevelSettings;
|
|
80
|
+
};
|
|
81
|
+
categories?: readonly string[] | {
|
|
82
|
+
readonly [name: string]: CategorySettings;
|
|
83
|
+
};
|
|
84
|
+
fields?: readonly string[] | {
|
|
85
|
+
readonly [name: string]: FieldSettings;
|
|
86
|
+
};
|
|
87
|
+
rules?: readonly SchemaRule[];
|
|
88
|
+
}
|
|
89
|
+
type NamesIn<T> = T extends readonly (infer N)[] ? N & string : T extends object ? keyof T & string : never;
|
|
90
|
+
type LevelNames<S> = S extends {
|
|
91
|
+
levels?: infer L;
|
|
92
|
+
} ? NamesIn<NonNullable<L>> : never;
|
|
93
|
+
type CategoryNames<S> = S extends {
|
|
94
|
+
categories?: infer C;
|
|
95
|
+
} ? NamesIn<NonNullable<C>> : never;
|
|
96
|
+
type FieldNames<S> = S extends {
|
|
97
|
+
fields?: infer F;
|
|
98
|
+
} ? NamesIn<NonNullable<F>> : never;
|
|
99
|
+
/** A Sentry level or a level of the schema. */
|
|
100
|
+
export type LevelOf<S> = string extends LevelNames<S> ? Level : SentryLevel | LevelNames<S>;
|
|
101
|
+
/** A category of the schema. */
|
|
102
|
+
export type CategoryOf<S> = CategoryNames<S>;
|
|
103
|
+
/** A field of the schema. */
|
|
104
|
+
export type FieldOf<S> = FieldNames<S>;
|
|
105
|
+
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" | "." | "-" | "_";
|
|
106
|
+
/** True for 1 to Max of NameChar. A name only known as string passes. */
|
|
107
|
+
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;
|
|
108
|
+
type LevelRule = "Level names are 1 to 32 lowercase letters, digits, dots, dashes or underscores";
|
|
109
|
+
type CategoryRule = "Category names are 1 to 64 lowercase letters, digits, dots, dashes or underscores";
|
|
110
|
+
type FieldRule = "Field names are not empty and do not start statoss.";
|
|
111
|
+
type Only<V, Keys extends PropertyKey, Message extends string> = {
|
|
112
|
+
[P in Exclude<keyof V, Keys>]: {
|
|
113
|
+
[M in Message]: true;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
type LevelCheck<V> = V extends string ? Like : {
|
|
117
|
+
like?: Like;
|
|
118
|
+
severity?: V extends {
|
|
119
|
+
like: infer K extends Like;
|
|
120
|
+
} ? Severities[K] : Severity;
|
|
121
|
+
label?: string | null;
|
|
122
|
+
color?: SchemaColor | null;
|
|
123
|
+
} & Only<V, keyof LevelSettings, "A level takes like, severity, label and color">;
|
|
124
|
+
/** Sentry's own levels rank as themselves: a label and a colour only. */
|
|
125
|
+
type BuiltinLevelCheck<K extends SentryLevel, V> = V extends string ? K : {
|
|
126
|
+
like?: K;
|
|
127
|
+
label?: string | null;
|
|
128
|
+
color?: SchemaColor | null;
|
|
129
|
+
} & Only<V, "like" | "label" | "color", "A built-in level takes label and color, and ranks as itself">;
|
|
130
|
+
type CheckLevels<T> = {
|
|
131
|
+
[K in keyof T]: K extends SentryLevel ? BuiltinLevelCheck<K, T[K]> : K extends string ? IsName<K, 32> extends true ? LevelCheck<T[K]> : {
|
|
132
|
+
[M in LevelRule]: true;
|
|
133
|
+
} : never;
|
|
134
|
+
};
|
|
135
|
+
type CheckCategories<T> = T extends readonly unknown[] ? {
|
|
136
|
+
[I in keyof T]: T[I] extends string ? IsName<T[I], 64> extends true ? T[I] : {
|
|
137
|
+
[M in CategoryRule]: true;
|
|
138
|
+
} : string;
|
|
139
|
+
} : {
|
|
140
|
+
[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"> : {
|
|
141
|
+
[M in CategoryRule]: true;
|
|
142
|
+
} : never;
|
|
143
|
+
};
|
|
144
|
+
type IsField<K extends string> = K extends "" | `statoss.${string}` ? false : true;
|
|
145
|
+
type CheckFields<T> = T extends readonly unknown[] ? {
|
|
146
|
+
[I in keyof T]: T[I] extends string ? IsField<T[I]> extends true ? T[I] : {
|
|
147
|
+
[M in FieldRule]: true;
|
|
148
|
+
} : string;
|
|
149
|
+
} : {
|
|
150
|
+
[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"> : {
|
|
151
|
+
[M in FieldRule]: true;
|
|
152
|
+
} : never;
|
|
153
|
+
};
|
|
154
|
+
type CheckRules<T, L extends string, C extends string> = {
|
|
155
|
+
[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">;
|
|
156
|
+
};
|
|
157
|
+
/** What defineSchema holds a schema to, read from the schema itself. */
|
|
158
|
+
export type CheckSchema<S> = {
|
|
159
|
+
[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>> : {
|
|
160
|
+
"A schema takes levels, categories, fields and rules": true;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Declares an app's levels, categories, fields and rules. Returns the schema
|
|
165
|
+
* as given: plain data in the shape the server takes, so it can be sent as
|
|
166
|
+
* it is. Its names become types: pass it to init as `schema` and the capture
|
|
167
|
+
* functions init returns take only these names.
|
|
168
|
+
*/
|
|
169
|
+
export declare function defineSchema<const S extends SchemaSpec & CheckSchema<S>>(schema: S): S;
|
|
170
|
+
/** A field's value; sent as a tag. */
|
|
171
|
+
export type FieldValue = string | number | boolean;
|
|
172
|
+
/** What the capture functions of init's handle take, typed by the schema. */
|
|
173
|
+
export interface SchemaContext<S = SchemaSpec> extends Omit<CaptureContext, "level" | "category"> {
|
|
174
|
+
/** A Sentry level or a level of the schema. */
|
|
175
|
+
level?: LevelOf<S>;
|
|
176
|
+
/** A category of the schema, sent as the `statoss.category` tag. */
|
|
177
|
+
category?: CategoryOf<S>;
|
|
178
|
+
/**
|
|
179
|
+
* Fields of the schema, sent as tags; a schema that declares none takes
|
|
180
|
+
* none here.
|
|
181
|
+
*/
|
|
182
|
+
fields?: [FieldOf<S>] extends [never] ? never : {
|
|
183
|
+
[K in FieldOf<S>]?: FieldValue;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/** The capture functions, typed by the schema given to init. */
|
|
187
|
+
export interface Handle<S = SchemaSpec> {
|
|
188
|
+
/** Sends an error (or anything thrown). Returns the event id. */
|
|
189
|
+
captureException(error: unknown, context?: SchemaContext<S>): string;
|
|
190
|
+
/** Sends a message, with a level or a context. Returns the event id. */
|
|
191
|
+
captureMessage(message: string, levelOrContext?: LevelOf<S> | SchemaContext<S>): string;
|
|
192
|
+
/** Sends an event you built in Sentry's schema. Returns the event id. */
|
|
193
|
+
captureEvent(event: Event, context?: SchemaContext<S>): string;
|
|
194
|
+
/**
|
|
195
|
+
* Log lines, sent when init has `logs: true`; `log` takes the schema's
|
|
196
|
+
* own levels.
|
|
197
|
+
*/
|
|
198
|
+
logger: AppLogger<S>;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* How a line at each level of init's `schema` is logged: at its range's
|
|
202
|
+
* level (trace stays trace), with its severity when it gives one inside
|
|
203
|
+
* that range. Names readSchema refuses are left out.
|
|
204
|
+
*/
|
|
205
|
+
export declare function schemaLogLevels(schema: unknown): Record<string, OwnLogLevel>;
|
|
206
|
+
/**
|
|
207
|
+
* The Sentry level each level of init's `schema` is sent as. Built-in
|
|
208
|
+
* levels are left out; names the SDK cannot send are logged in debug mode.
|
|
209
|
+
*/
|
|
210
|
+
export declare function readSchema(schema: unknown, logger: Logger): Record<string, SentryLevel>;
|
|
211
|
+
export {};
|