@statoss/sdk 0.3.0 → 0.5.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 +46 -0
- package/README.md +68 -10
- package/dist/cjs/client.d.ts +24 -1
- package/dist/cjs/client.js +181 -6
- package/dist/cjs/envelope.d.ts +20 -1
- package/dist/cjs/envelope.js +41 -1
- package/dist/cjs/index.d.ts +11 -3
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/logs.d.ts +133 -0
- package/dist/cjs/logs.js +235 -0
- package/dist/cjs/schema.d.ts +13 -1
- package/dist/cjs/schema.js +42 -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 +24 -1
- package/dist/esm/client.js +183 -8
- package/dist/esm/envelope.d.ts +20 -1
- package/dist/esm/envelope.js +37 -1
- package/dist/esm/index.d.ts +11 -3
- package/dist/esm/index.js +8 -0
- package/dist/esm/logs.d.ts +133 -0
- package/dist/esm/logs.js +219 -0
- package/dist/esm/schema.d.ts +13 -1
- package/dist/esm/schema.js +41 -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,14 +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";
|
|
14
|
-
import { readSchema } from "./schema.js";
|
|
15
|
+
import { readSchema, schemaLogLevels } from "./schema.js";
|
|
15
16
|
import { MAX_BREADCRUMBS } from "./scope.js";
|
|
16
17
|
import { fetchTransport, Sender } from "./transport.js";
|
|
17
18
|
import { isObject, isThenable, normalize, now, safely, safeString, scrubUrl, truncate, unref, uuid4, } from "./utils.js";
|
|
18
19
|
import { SDK_NAME, SDK_PACKAGE, SDK_VERSION } from "./version.js";
|
|
19
20
|
const REPORT_INTERVAL_MS = 30000;
|
|
20
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;
|
|
21
27
|
// The server drops event items over 1 MiB.
|
|
22
28
|
const MAX_EVENT_CHARS = 1000000;
|
|
23
29
|
const MAX_TAG_VALUE = 200;
|
|
@@ -52,6 +58,41 @@ function cleanTags(tags) {
|
|
|
52
58
|
}
|
|
53
59
|
return out;
|
|
54
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
|
+
}
|
|
55
96
|
/** A capture's fields, as tags. Keys starting statoss. are not fields. */
|
|
56
97
|
function fieldTags(fields, logger) {
|
|
57
98
|
const out = {};
|
|
@@ -124,14 +165,20 @@ export class Client {
|
|
|
124
165
|
this.pending = new Set();
|
|
125
166
|
this.teardowns = [];
|
|
126
167
|
this.closed = false;
|
|
168
|
+
this.logBuffer = [];
|
|
169
|
+
this.logChars = 0;
|
|
170
|
+
this.saidLogsOff = false;
|
|
127
171
|
this.options = options;
|
|
128
172
|
this.logger = makeLogger(options.debug === true);
|
|
129
173
|
this.runtime = describeRuntime();
|
|
130
174
|
// What `levels` says wins over the schema.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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),
|
|
134
180
|
};
|
|
181
|
+
this.logsOn = options.logs === true;
|
|
135
182
|
const rate = options.sampleRate;
|
|
136
183
|
if (rate !== undefined &&
|
|
137
184
|
!(typeof rate === "number" && rate >= 0 && rate <= 1)) {
|
|
@@ -210,8 +257,90 @@ export class Client {
|
|
|
210
257
|
this.integrations.push("Dedupe");
|
|
211
258
|
if (browser)
|
|
212
259
|
this.flushReportsWhenHidden();
|
|
260
|
+
if (this.logsOn)
|
|
261
|
+
this.sendLogsBeforeExit();
|
|
213
262
|
this.logger.log(`Integrations: ${this.integrations.join(", ") || "none"}.`);
|
|
214
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
|
+
}
|
|
215
344
|
captureException(error, context, scope) {
|
|
216
345
|
return this.capture(() => eventFromUnknown(error, { type: "generic", handled: true }), context, scope, { originalException: error });
|
|
217
346
|
}
|
|
@@ -294,6 +423,7 @@ export class Client {
|
|
|
294
423
|
async flush(timeoutMs = DEFAULT_FLUSH_MS) {
|
|
295
424
|
if (!this.sender)
|
|
296
425
|
return true;
|
|
426
|
+
this.sendLogs();
|
|
297
427
|
const deadline = Date.now() + timeoutMs;
|
|
298
428
|
const left = () => Math.max(0, deadline - Date.now());
|
|
299
429
|
const processed = this.pending.size
|
|
@@ -314,18 +444,63 @@ export class Client {
|
|
|
314
444
|
this.teardown();
|
|
315
445
|
return this.flush(timeoutMs);
|
|
316
446
|
}
|
|
317
|
-
/** Removes the integrations at once, without waiting. */
|
|
447
|
+
/** Removes the integrations at once, without waiting; log lines kept go out. */
|
|
318
448
|
teardown() {
|
|
319
449
|
for (const teardown of this.teardowns.splice(0))
|
|
320
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));
|
|
321
494
|
}
|
|
322
495
|
flushReportsWhenHidden() {
|
|
323
496
|
const doc = globalThis.document;
|
|
324
497
|
if (!doc || typeof doc.addEventListener !== "function")
|
|
325
498
|
return;
|
|
326
499
|
const onChange = () => {
|
|
327
|
-
if (doc.visibilityState
|
|
328
|
-
|
|
500
|
+
if (doc.visibilityState !== "hidden")
|
|
501
|
+
return;
|
|
502
|
+
safely(() => this.sendLogs());
|
|
503
|
+
safely(() => this.sendClientReport());
|
|
329
504
|
};
|
|
330
505
|
doc.addEventListener("visibilitychange", onChange);
|
|
331
506
|
this.teardowns.push(() => doc.removeEventListener?.("visibilitychange", onChange));
|
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,6 +1,8 @@
|
|
|
1
|
+
import { type AppLogger } from "./logs.js";
|
|
1
2
|
import type { CheckSchema, Handle, SchemaSpec } from "./schema.js";
|
|
2
3
|
import type { Breadcrumb, CaptureContext, Event, InitOptions, Level, Primitive, User } from "./types.js";
|
|
3
4
|
export { withScope } from "./hub.js";
|
|
5
|
+
export type { AppLogger, Log, LogAttributes, LogAttributeValue, LogLevel, LogLevelOf, LogMessage, SerializedLog, } from "./logs.js";
|
|
4
6
|
export { defineSchema } from "./schema.js";
|
|
5
7
|
export type { CategoryOf, CategorySettings, CheckSchema, FieldOf, FieldSettings, FieldType, FieldValue, Handle, LevelOf, LevelSettings, Like, RuleMatch, RuleSubject, SchemaColor, SchemaContext, SchemaRule, SchemaSpec, Severity, } from "./schema.js";
|
|
6
8
|
export type { Scope } from "./scope.js";
|
|
@@ -9,11 +11,11 @@ export type { Breadcrumb, BreadcrumbHint, CaptureContext, DebugImage, Event, Eve
|
|
|
9
11
|
/**
|
|
10
12
|
* Starts the SDK. Call it once, as early as you can. Calling it again replaces
|
|
11
13
|
* the earlier setup. Without a valid DSN, or with enabled: false, nothing is
|
|
12
|
-
* sent and nothing is patched.
|
|
14
|
+
* sent and nothing is patched. With `logs: true`, `logger`'s lines are sent.
|
|
13
15
|
*
|
|
14
16
|
* With a `schema` from defineSchema, its levels are sent as the Sentry level
|
|
15
|
-
* each ranks like, and the capture functions it returns take
|
|
16
|
-
* schema's levels, categories and fields.
|
|
17
|
+
* each ranks like, and the capture functions and logger it returns take
|
|
18
|
+
* only the schema's levels, categories and fields.
|
|
17
19
|
*/
|
|
18
20
|
export declare function init<const S extends SchemaSpec & CheckSchema<S> = SchemaSpec>(options?: InitOptions & {
|
|
19
21
|
schema?: S;
|
|
@@ -27,6 +29,12 @@ export declare function captureException(error: unknown, context?: CaptureContex
|
|
|
27
29
|
export declare function captureMessage(message: string, levelOrContext?: Level | CaptureContext): string;
|
|
28
30
|
/** Sends an event you built in Sentry's schema. Returns the event id. */
|
|
29
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;
|
|
30
38
|
/** Pass null to forget the user. */
|
|
31
39
|
export declare function setUser(user: User | null): void;
|
|
32
40
|
export declare function setTag(key: string, value: Primitive): void;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
5
6
|
export { defineSchema } from "./schema.js";
|
|
@@ -39,12 +40,19 @@ export function captureMessage(message, levelOrContext) {
|
|
|
39
40
|
export function captureEvent(event, context) {
|
|
40
41
|
return captureWith(undefined, (client, scope) => client.captureEvent(event, context, scope));
|
|
41
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())));
|
|
42
49
|
// What init returns: the same functions, typed by the schema. The client
|
|
43
50
|
// reads a context's fields whichever function it came through.
|
|
44
51
|
const HANDLE = Object.freeze({
|
|
45
52
|
captureException,
|
|
46
53
|
captureMessage,
|
|
47
54
|
captureEvent,
|
|
55
|
+
logger,
|
|
48
56
|
});
|
|
49
57
|
/** Pass null to forget the user. */
|
|
50
58
|
export function setUser(user) {
|
|
@@ -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;
|