@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
package/dist/esm/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { addDebugImages } from "./debug-ids.js";
|
|
2
2
|
import { authHeader, envelopeEndpoint, envelopeUrlWithQueryAuth, parseDsn, } from "./dsn.js";
|
|
3
|
-
import { clientReportEnvelope, eventEnvelope, } from "./envelope.js";
|
|
3
|
+
import { clientReportEnvelope, eventEnvelope, logEnvelope, } from "./envelope.js";
|
|
4
4
|
import { addInitialFrame, eventFromUnknown } from "./event.js";
|
|
5
5
|
import { getCurrentScope } from "./hub.js";
|
|
6
6
|
import { installClicks, installNavigation } from "./integrations/browser.js";
|
|
@@ -10,13 +10,20 @@ import { installFetch } from "./integrations/fetch.js";
|
|
|
10
10
|
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
|
+
import { bodyOf, chooseLogLevel, cutBody, isLogLevel, isLogMessage, LOG_SEVERITY, logLevelOf, ownLevelsFromSentry, serializeAttribute, } from "./logs.js";
|
|
13
14
|
import { describeRuntime } from "./runtime.js";
|
|
15
|
+
import { readSchema, schemaLogLevels } from "./schema.js";
|
|
14
16
|
import { MAX_BREADCRUMBS } from "./scope.js";
|
|
15
17
|
import { fetchTransport, Sender } from "./transport.js";
|
|
16
18
|
import { isObject, isThenable, normalize, now, safely, safeString, scrubUrl, truncate, unref, uuid4, } from "./utils.js";
|
|
17
19
|
import { SDK_NAME, SDK_PACKAGE, SDK_VERSION } from "./version.js";
|
|
18
20
|
const REPORT_INTERVAL_MS = 30000;
|
|
19
21
|
const DEFAULT_FLUSH_MS = 2000;
|
|
22
|
+
// Log lines go out 100 at a time, or 5 seconds after the first waits, as
|
|
23
|
+
// Sentry's logger sends them, and well under an envelope item's 1 MiB.
|
|
24
|
+
const LOG_BATCH = 100;
|
|
25
|
+
const LOG_WAIT_MS = 5000;
|
|
26
|
+
const LOG_BATCH_CHARS = 800000;
|
|
20
27
|
// The server drops event items over 1 MiB.
|
|
21
28
|
const MAX_EVENT_CHARS = 1000000;
|
|
22
29
|
const MAX_TAG_VALUE = 200;
|
|
@@ -51,6 +58,67 @@ function cleanTags(tags) {
|
|
|
51
58
|
}
|
|
52
59
|
return out;
|
|
53
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* A line as a log item holds it, made valid again after beforeSendLog: one
|
|
63
|
+
* of the six levels, a severity inside its range, typed attributes.
|
|
64
|
+
*/
|
|
65
|
+
function serializeLog(log, traceId) {
|
|
66
|
+
const level = isLogLevel(log.level)
|
|
67
|
+
? log.level
|
|
68
|
+
: (logLevelOf(safeString(log.level)) ?? "info");
|
|
69
|
+
const n = log.severityNumber;
|
|
70
|
+
const severity = typeof n === "number" &&
|
|
71
|
+
Number.isInteger(n) &&
|
|
72
|
+
n >= LOG_SEVERITY[level] &&
|
|
73
|
+
n <= LOG_SEVERITY[level] + 3
|
|
74
|
+
? n
|
|
75
|
+
: LOG_SEVERITY[level];
|
|
76
|
+
const attributes = {};
|
|
77
|
+
if (isObject(log.attributes)) {
|
|
78
|
+
for (const [key, value] of Object.entries(log.attributes)) {
|
|
79
|
+
const typed = serializeAttribute(value);
|
|
80
|
+
if (key !== "" && typed)
|
|
81
|
+
attributes[key] = typed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const body = cutBody(bodyOf(log.message), attributes);
|
|
85
|
+
return {
|
|
86
|
+
timestamp: typeof log.timestamp === "number" && Number.isFinite(log.timestamp)
|
|
87
|
+
? log.timestamp
|
|
88
|
+
: now(),
|
|
89
|
+
level,
|
|
90
|
+
body,
|
|
91
|
+
trace_id: traceId,
|
|
92
|
+
severity_number: severity,
|
|
93
|
+
attributes,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** A capture's fields, as tags. Keys starting statoss. are not fields. */
|
|
97
|
+
function fieldTags(fields, logger) {
|
|
98
|
+
const out = {};
|
|
99
|
+
if (fields === undefined || fields === null)
|
|
100
|
+
return out;
|
|
101
|
+
if (!isObject(fields) || Array.isArray(fields)) {
|
|
102
|
+
logger.warn('Ignored `fields`: it should be an object such as { route: "/pay" }.');
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
106
|
+
if (value === undefined || value === null)
|
|
107
|
+
continue;
|
|
108
|
+
if (key === "" || key.startsWith("statoss.")) {
|
|
109
|
+
logger.warn(`Ignored field "${key}": fields are not empty and do not start statoss.`);
|
|
110
|
+
}
|
|
111
|
+
else if (typeof value === "string" ||
|
|
112
|
+
typeof value === "number" ||
|
|
113
|
+
typeof value === "boolean") {
|
|
114
|
+
out[key] = value;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
logger.warn(`Ignored field "${key}": its value should be a string, number or boolean.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
54
122
|
function cleanContexts(contexts) {
|
|
55
123
|
const out = {};
|
|
56
124
|
for (const [name, value] of Object.entries(contexts)) {
|
|
@@ -97,10 +165,20 @@ export class Client {
|
|
|
97
165
|
this.pending = new Set();
|
|
98
166
|
this.teardowns = [];
|
|
99
167
|
this.closed = false;
|
|
168
|
+
this.logBuffer = [];
|
|
169
|
+
this.logChars = 0;
|
|
170
|
+
this.saidLogsOff = false;
|
|
100
171
|
this.options = options;
|
|
101
172
|
this.logger = makeLogger(options.debug === true);
|
|
102
173
|
this.runtime = describeRuntime();
|
|
103
|
-
|
|
174
|
+
// What `levels` says wins over the schema.
|
|
175
|
+
const given = readLevels(options.levels, this.logger);
|
|
176
|
+
this.levels = { ...readSchema(options.schema, this.logger), ...given };
|
|
177
|
+
this.logLevels = {
|
|
178
|
+
...schemaLogLevels(options.schema),
|
|
179
|
+
...ownLevelsFromSentry(given),
|
|
180
|
+
};
|
|
181
|
+
this.logsOn = options.logs === true;
|
|
104
182
|
const rate = options.sampleRate;
|
|
105
183
|
if (rate !== undefined &&
|
|
106
184
|
!(typeof rate === "number" && rate >= 0 && rate <= 1)) {
|
|
@@ -179,8 +257,90 @@ export class Client {
|
|
|
179
257
|
this.integrations.push("Dedupe");
|
|
180
258
|
if (browser)
|
|
181
259
|
this.flushReportsWhenHidden();
|
|
260
|
+
if (this.logsOn)
|
|
261
|
+
this.sendLogsBeforeExit();
|
|
182
262
|
this.logger.log(`Integrations: ${this.integrations.join(", ") || "none"}.`);
|
|
183
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Keeps a log line for the next envelope of lines. The level is one of
|
|
266
|
+
* the six, a Sentry level, or the app's own (sent at its range's level
|
|
267
|
+
* with statoss.level); attributes go along typed, as Sentry's logger
|
|
268
|
+
* sends them.
|
|
269
|
+
*/
|
|
270
|
+
captureLog(level, message, attributes, scope) {
|
|
271
|
+
if (!this.enabled || this.closed)
|
|
272
|
+
return;
|
|
273
|
+
if (!this.logsOn) {
|
|
274
|
+
if (!this.saidLogsOff) {
|
|
275
|
+
this.saidLogsOff = true;
|
|
276
|
+
this.logger.log("Log lines are not sent: init has no logs: true.");
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const choice = chooseLogLevel(level, this.logLevels, this.logger);
|
|
282
|
+
const attrs = {};
|
|
283
|
+
if (isObject(attributes) && !Array.isArray(attributes)) {
|
|
284
|
+
for (const [key, value] of Object.entries(attributes))
|
|
285
|
+
if (key !== "")
|
|
286
|
+
attrs[key] = value;
|
|
287
|
+
}
|
|
288
|
+
else if (attributes !== undefined && attributes !== null) {
|
|
289
|
+
this.logger.warn("Ignored a log line's attributes: they should be an object such as { orderId: 7 }.");
|
|
290
|
+
}
|
|
291
|
+
// What the line says wins over what the SDK adds.
|
|
292
|
+
const add = (key, value) => {
|
|
293
|
+
if (value !== undefined && value !== null && !(key in attrs))
|
|
294
|
+
attrs[key] = value;
|
|
295
|
+
};
|
|
296
|
+
add("sentry.environment", this.options.environment ?? "production");
|
|
297
|
+
add("sentry.release", this.options.release);
|
|
298
|
+
add("sentry.sdk.name", SDK_NAME);
|
|
299
|
+
add("sentry.sdk.version", SDK_VERSION);
|
|
300
|
+
add("server.address", this.options.serverName ?? this.runtime.serverName);
|
|
301
|
+
// The user goes on a line only with sendDefaultPii.
|
|
302
|
+
const user = scope._user;
|
|
303
|
+
if (user && this.options.sendDefaultPii) {
|
|
304
|
+
add("user.id", user.id);
|
|
305
|
+
add("user.email", user.email);
|
|
306
|
+
add("user.name", user.username);
|
|
307
|
+
}
|
|
308
|
+
if (isLogMessage(message) && message.values.length > 0) {
|
|
309
|
+
attrs["sentry.message.template"] = message.template;
|
|
310
|
+
message.values.forEach((value, i) => {
|
|
311
|
+
attrs[`sentry.message.parameter.${i}`] = value;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (choice.custom)
|
|
315
|
+
attrs[LEVEL_TAG] = choice.custom;
|
|
316
|
+
let log = {
|
|
317
|
+
level: choice.level,
|
|
318
|
+
message: bodyOf(message),
|
|
319
|
+
attributes: attrs,
|
|
320
|
+
severityNumber: choice.severity,
|
|
321
|
+
timestamp: now(),
|
|
322
|
+
};
|
|
323
|
+
if (this.options.beforeSendLog) {
|
|
324
|
+
try {
|
|
325
|
+
log = this.options.beforeSendLog(log);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
this.logger.error("beforeSendLog threw, so the line was dropped:", error);
|
|
329
|
+
this.recordLoss("internal_sdk_error", "log_item");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!isObject(log)) {
|
|
333
|
+
this.recordLoss("before_send", "log_item");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
this.logTraceId ?? (this.logTraceId = uuid4());
|
|
338
|
+
this.keepLog(serializeLog(log, this.logTraceId));
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
this.logger.error("Could not capture a log line:", error);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
184
344
|
captureException(error, context, scope) {
|
|
185
345
|
return this.capture(() => eventFromUnknown(error, { type: "generic", handled: true }), context, scope, { originalException: error });
|
|
186
346
|
}
|
|
@@ -263,6 +423,7 @@ export class Client {
|
|
|
263
423
|
async flush(timeoutMs = DEFAULT_FLUSH_MS) {
|
|
264
424
|
if (!this.sender)
|
|
265
425
|
return true;
|
|
426
|
+
this.sendLogs();
|
|
266
427
|
const deadline = Date.now() + timeoutMs;
|
|
267
428
|
const left = () => Math.max(0, deadline - Date.now());
|
|
268
429
|
const processed = this.pending.size
|
|
@@ -283,18 +444,63 @@ export class Client {
|
|
|
283
444
|
this.teardown();
|
|
284
445
|
return this.flush(timeoutMs);
|
|
285
446
|
}
|
|
286
|
-
/** Removes the integrations at once, without waiting. */
|
|
447
|
+
/** Removes the integrations at once, without waiting; log lines kept go out. */
|
|
287
448
|
teardown() {
|
|
288
449
|
for (const teardown of this.teardowns.splice(0))
|
|
289
450
|
safely(teardown);
|
|
451
|
+
this.sendLogs();
|
|
452
|
+
}
|
|
453
|
+
keepLog(log) {
|
|
454
|
+
const size = safely(() => JSON.stringify(log).length, 0) ?? 0;
|
|
455
|
+
if (this.logBuffer.length > 0 && this.logChars + size > LOG_BATCH_CHARS)
|
|
456
|
+
this.sendLogs();
|
|
457
|
+
this.logBuffer.push(log);
|
|
458
|
+
this.logChars += size;
|
|
459
|
+
if (this.logBuffer.length >= LOG_BATCH) {
|
|
460
|
+
this.sendLogs();
|
|
461
|
+
}
|
|
462
|
+
else if (this.logTimer === undefined) {
|
|
463
|
+
this.logTimer = safely(() => setTimeout(() => {
|
|
464
|
+
this.logTimer = undefined;
|
|
465
|
+
this.sendLogs();
|
|
466
|
+
}, LOG_WAIT_MS));
|
|
467
|
+
unref(this.logTimer);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/** Sends the log lines kept, as one envelope. */
|
|
471
|
+
sendLogs() {
|
|
472
|
+
if (this.logTimer !== undefined) {
|
|
473
|
+
clearTimeout(this.logTimer);
|
|
474
|
+
this.logTimer = undefined;
|
|
475
|
+
}
|
|
476
|
+
if (this.logBuffer.length === 0 || !this.sender || !this.dsn)
|
|
477
|
+
return;
|
|
478
|
+
const logs = this.logBuffer;
|
|
479
|
+
this.logBuffer = [];
|
|
480
|
+
this.logChars = 0;
|
|
481
|
+
this.sender.send(logEnvelope(logs, this.dsn));
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* In Node, Bun and Deno: sends the lines kept when the process runs out
|
|
485
|
+
* of work, since the timer that would send them does not keep it alive.
|
|
486
|
+
*/
|
|
487
|
+
sendLogsBeforeExit() {
|
|
488
|
+
const proc = globalThis.process;
|
|
489
|
+
if (!proc || typeof proc.on !== "function")
|
|
490
|
+
return;
|
|
491
|
+
const onBeforeExit = () => safely(() => this.sendLogs());
|
|
492
|
+
safely(() => proc.on("beforeExit", onBeforeExit));
|
|
493
|
+
this.teardowns.push(() => proc.removeListener?.("beforeExit", onBeforeExit));
|
|
290
494
|
}
|
|
291
495
|
flushReportsWhenHidden() {
|
|
292
496
|
const doc = globalThis.document;
|
|
293
497
|
if (!doc || typeof doc.addEventListener !== "function")
|
|
294
498
|
return;
|
|
295
499
|
const onChange = () => {
|
|
296
|
-
if (doc.visibilityState
|
|
297
|
-
|
|
500
|
+
if (doc.visibilityState !== "hidden")
|
|
501
|
+
return;
|
|
502
|
+
safely(() => this.sendLogs());
|
|
503
|
+
safely(() => this.sendClientReport());
|
|
298
504
|
};
|
|
299
505
|
doc.addEventListener("visibilitychange", onChange);
|
|
300
506
|
this.teardowns.push(() => doc.removeEventListener?.("visibilitychange", onChange));
|
|
@@ -326,6 +532,7 @@ export class Client {
|
|
|
326
532
|
const tags = {
|
|
327
533
|
...scope._tags,
|
|
328
534
|
...ctx.tags,
|
|
535
|
+
...fieldTags(ctx.fields, this.logger),
|
|
329
536
|
...base.tags,
|
|
330
537
|
};
|
|
331
538
|
if (choice.custom)
|
package/dist/esm/envelope.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Dsn } from "./dsn.js";
|
|
2
|
+
import type { SerializedLog } from "./logs.js";
|
|
2
3
|
import type { Event } from "./types.js";
|
|
3
4
|
export interface DiscardedEvent {
|
|
4
5
|
reason: string;
|
|
@@ -26,11 +27,29 @@ export type ClientReportItem = [
|
|
|
26
27
|
discarded_events: DiscardedEvent[];
|
|
27
28
|
}
|
|
28
29
|
];
|
|
29
|
-
export
|
|
30
|
+
export declare const LOG_CONTENT_TYPE = "application/vnd.sentry.items.log+json";
|
|
31
|
+
export type LogItem = [
|
|
32
|
+
{
|
|
33
|
+
type: "log";
|
|
34
|
+
item_count: number;
|
|
35
|
+
content_type: typeof LOG_CONTENT_TYPE;
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
version: 2;
|
|
39
|
+
items: SerializedLog[];
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
export type EnvelopeItem = EventItem | ClientReportItem | LogItem;
|
|
30
43
|
export type Envelope = [EnvelopeHeader, EnvelopeItem[]];
|
|
31
44
|
export declare function eventEnvelope(event: Event, dsn: Dsn): Envelope;
|
|
32
45
|
export declare function clientReportEnvelope(discarded: DiscardedEvent[], dsn: Dsn, timestamp?: number): Envelope;
|
|
46
|
+
/** Log lines in one envelope, as Sentry's logger sends them. */
|
|
47
|
+
export declare function logEnvelope(logs: SerializedLog[], dsn: Dsn): Envelope;
|
|
33
48
|
/** Sentry's rate-limit category for an envelope item. */
|
|
34
49
|
export declare function itemCategory(item: EnvelopeItem): string;
|
|
50
|
+
/** How many things an item holds, as client reports count them. */
|
|
51
|
+
export declare function itemQuantity(item: EnvelopeItem): number;
|
|
52
|
+
/** True for an envelope of log lines. */
|
|
53
|
+
export declare function isLogEnvelope(envelope: Envelope): boolean;
|
|
35
54
|
/** Byte for byte what Sentry's serializeEnvelope produces for string payloads. */
|
|
36
55
|
export declare function serializeEnvelope(envelope: Envelope): string;
|
package/dist/esm/envelope.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dsnToString } from "./dsn.js";
|
|
2
2
|
import { normalize } from "./utils.js";
|
|
3
3
|
import { SDK_NAME, SDK_VERSION } from "./version.js";
|
|
4
|
+
export const LOG_CONTENT_TYPE = "application/vnd.sentry.items.log+json";
|
|
4
5
|
function sdkHeader() {
|
|
5
6
|
return { name: SDK_NAME, version: SDK_VERSION };
|
|
6
7
|
}
|
|
@@ -25,9 +26,44 @@ export function clientReportEnvelope(discarded, dsn, timestamp = Date.now() / 10
|
|
|
25
26
|
[[{ type: "client_report" }, { timestamp, discarded_events: discarded }]],
|
|
26
27
|
];
|
|
27
28
|
}
|
|
29
|
+
/** Log lines in one envelope, as Sentry's logger sends them. */
|
|
30
|
+
export function logEnvelope(logs, dsn) {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
sent_at: new Date().toISOString(),
|
|
34
|
+
dsn: dsnToString(dsn),
|
|
35
|
+
sdk: sdkHeader(),
|
|
36
|
+
},
|
|
37
|
+
[
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
type: "log",
|
|
41
|
+
item_count: logs.length,
|
|
42
|
+
content_type: LOG_CONTENT_TYPE,
|
|
43
|
+
},
|
|
44
|
+
{ version: 2, items: logs },
|
|
45
|
+
],
|
|
46
|
+
],
|
|
47
|
+
];
|
|
48
|
+
}
|
|
28
49
|
/** Sentry's rate-limit category for an envelope item. */
|
|
29
50
|
export function itemCategory(item) {
|
|
30
|
-
|
|
51
|
+
switch (item[0].type) {
|
|
52
|
+
case "event":
|
|
53
|
+
return "error";
|
|
54
|
+
case "log":
|
|
55
|
+
return "log_item";
|
|
56
|
+
default:
|
|
57
|
+
return "internal";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** How many things an item holds, as client reports count them. */
|
|
61
|
+
export function itemQuantity(item) {
|
|
62
|
+
return item[0].type === "log" ? item[0].item_count : 1;
|
|
63
|
+
}
|
|
64
|
+
/** True for an envelope of log lines. */
|
|
65
|
+
export function isLogEnvelope(envelope) {
|
|
66
|
+
return envelope[1].some((item) => item[0].type === "log");
|
|
31
67
|
}
|
|
32
68
|
function stringify(payload) {
|
|
33
69
|
try {
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -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
|
|
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/esm/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { Client } from "./client.js";
|
|
2
2
|
import { captureWith, getCarrier, getCurrentScope, messageContext, } from "./hub.js";
|
|
3
|
+
import { makeAppLogger } from "./logs.js";
|
|
3
4
|
import { isObject, safely } from "./utils.js";
|
|
4
5
|
export { withScope } from "./hub.js";
|
|
6
|
+
export { defineSchema } from "./schema.js";
|
|
5
7
|
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
8
|
export function init(options = {}) {
|
|
12
9
|
try {
|
|
13
10
|
const carrier = getCarrier();
|
|
@@ -26,6 +23,7 @@ export function init(options = {}) {
|
|
|
26
23
|
safely(() => console.error("[StatOSS] init failed:", error));
|
|
27
24
|
}
|
|
28
25
|
}
|
|
26
|
+
return HANDLE;
|
|
29
27
|
}
|
|
30
28
|
/** Sends an error (or anything thrown). Returns the event id. */
|
|
31
29
|
export function captureException(error, context) {
|
|
@@ -42,6 +40,20 @@ export function captureMessage(message, levelOrContext) {
|
|
|
42
40
|
export function captureEvent(event, context) {
|
|
43
41
|
return captureWith(undefined, (client, scope) => client.captureEvent(event, context, scope));
|
|
44
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Log lines, sent when init has `logs: true`: logger.info("Paid", { orderId: 7 }),
|
|
45
|
+
* logger.warn(logger.fmt`Took ${ms} ms`), logger.log("payment-failed", "Card declined").
|
|
46
|
+
* Lines go out in batches; flush() and close() send what waits.
|
|
47
|
+
*/
|
|
48
|
+
export const logger = makeAppLogger((level, message, attributes) => safely(() => getCarrier().client?.captureLog(level, message, attributes, getCurrentScope())));
|
|
49
|
+
// What init returns: the same functions, typed by the schema. The client
|
|
50
|
+
// reads a context's fields whichever function it came through.
|
|
51
|
+
const HANDLE = Object.freeze({
|
|
52
|
+
captureException,
|
|
53
|
+
captureMessage,
|
|
54
|
+
captureEvent,
|
|
55
|
+
logger,
|
|
56
|
+
});
|
|
45
57
|
/** Pass null to forget the user. */
|
|
46
58
|
export function setUser(user) {
|
|
47
59
|
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
|
}
|