logixia 1.13.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -44
- package/dist/async-context-B05ArHgM.mjs +134 -0
- package/dist/async-context-B05ArHgM.mjs.map +1 -0
- package/dist/async-context-DTQgi8f7.js +150 -0
- package/dist/async-context-DTQgi8f7.js.map +1 -0
- package/dist/{azure-monitor.transport-CHnjVRF-.mjs → azure-monitor.transport-BPbgjATu.mjs} +27 -15
- package/dist/azure-monitor.transport-BPbgjATu.mjs.map +1 -0
- package/dist/{azure-monitor.transport-BDFO4F-i.d.ts → azure-monitor.transport-C7kYyZUW.d.mts} +3 -6
- package/dist/azure-monitor.transport-C7kYyZUW.d.mts.map +1 -0
- package/dist/{azure-monitor.transport-CrvfHd9C.js → azure-monitor.transport-CwbOLZHB.js} +32 -20
- package/dist/azure-monitor.transport-CwbOLZHB.js.map +1 -0
- package/dist/{azure-monitor.transport-D5wQEH04.d.mts → azure-monitor.transport-DV03Z5P4.d.ts} +3 -6
- package/dist/azure-monitor.transport-DV03Z5P4.d.ts.map +1 -0
- package/dist/azure.d.mts +1 -2
- package/dist/azure.d.ts +1 -2
- package/dist/azure.js +3 -6
- package/dist/azure.mjs +1 -6
- package/dist/{batch-buffer-PGvSgbmd.mjs → batch-buffer-0fgLmIPn.mjs} +21 -7
- package/dist/batch-buffer-0fgLmIPn.mjs.map +1 -0
- package/dist/{batch-buffer-BY-mRWIK.js → batch-buffer-DT4gdXmf.js} +53 -40
- package/dist/batch-buffer-DT4gdXmf.js.map +1 -0
- package/dist/browser.d.mts +52 -23
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.d.ts +52 -23
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +460 -62
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +461 -65
- package/dist/browser.mjs.map +1 -1
- package/dist/{nestjs-extras-ImbuDhs1.mjs → builder-D70z6kjj.mjs} +279 -965
- package/dist/builder-D70z6kjj.mjs.map +1 -0
- package/dist/{nestjs-extras-DnJ6vrIC.js → builder-M7NoaugM.js} +421 -1177
- package/dist/builder-M7NoaugM.js.map +1 -0
- package/dist/cli/index.js +86 -67
- package/dist/cloudwatch.d.mts +1 -2
- package/dist/cloudwatch.d.ts +1 -2
- package/dist/cloudwatch.js +3 -7
- package/dist/cloudwatch.mjs +1 -7
- package/dist/{cloudwatch.transport-BO1ahxw-.js → cloudwatch.transport-B2wKQFF7.js} +61 -36
- package/dist/cloudwatch.transport-B2wKQFF7.js.map +1 -0
- package/dist/{cloudwatch.transport-DZ6ies7g.mjs → cloudwatch.transport-BTvYfLs9.mjs} +54 -28
- package/dist/cloudwatch.transport-BTvYfLs9.mjs.map +1 -0
- package/dist/{cloudwatch.transport-B00st6Xv.d.ts → cloudwatch.transport-Bf7L4I59.d.ts} +2 -6
- package/dist/cloudwatch.transport-Bf7L4I59.d.ts.map +1 -0
- package/dist/{cloudwatch.transport-Blc7Gtxt.d.mts → cloudwatch.transport-Rj4yTpRn.d.mts} +2 -6
- package/dist/cloudwatch.transport-Rj4yTpRn.d.mts.map +1 -0
- package/dist/{coerce.utils-Cu6IUTgX.mjs → coerce.utils-B_rEudCn.mjs} +3 -4
- package/dist/{coerce.utils-Cu6IUTgX.mjs.map → coerce.utils-B_rEudCn.mjs.map} +1 -1
- package/dist/{coerce.utils-B-LilukN.js → coerce.utils-C1C5sIX0.js} +8 -10
- package/dist/{coerce.utils-B-LilukN.js.map → coerce.utils-C1C5sIX0.js.map} +1 -1
- package/dist/correlation.d.mts +13 -16
- package/dist/correlation.d.mts.map +1 -1
- package/dist/correlation.d.ts +13 -16
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +58 -9
- package/dist/correlation.js.map +1 -1
- package/dist/correlation.mjs +56 -6
- package/dist/correlation.mjs.map +1 -1
- package/dist/{entry-fields-BJkuPnIg.mjs → entry-fields-CGuvT2R0.mjs} +5 -6
- package/dist/entry-fields-CGuvT2R0.mjs.map +1 -0
- package/dist/{entry-fields-BvOLFlJ4.js → entry-fields-RLd1jCoR.js} +10 -12
- package/dist/entry-fields-RLd1jCoR.js.map +1 -0
- package/dist/gcp.d.mts +1 -2
- package/dist/gcp.d.ts +1 -2
- package/dist/gcp.js +3 -7
- package/dist/gcp.mjs +1 -7
- package/dist/{gcp.transport-m4fVw0b1.d.ts → gcp.transport-C5D72b1g.d.mts} +3 -6
- package/dist/gcp.transport-C5D72b1g.d.mts.map +1 -0
- package/dist/{gcp.transport-zzNyIoih.d.mts → gcp.transport-CFQfdAOK.d.ts} +3 -6
- package/dist/gcp.transport-CFQfdAOK.d.ts.map +1 -0
- package/dist/{gcp.transport-DBX5TPIy.mjs → gcp.transport-CGKopAhQ.mjs} +55 -16
- package/dist/gcp.transport-CGKopAhQ.mjs.map +1 -0
- package/dist/{gcp.transport-CHV8MGgz.js → gcp.transport-KZe9hCAR.js} +60 -22
- package/dist/gcp.transport-KZe9hCAR.js.map +1 -0
- package/dist/{index-nZEeCr3X.d.ts → index-DwozbmB4.d.ts} +3 -8
- package/dist/index-DwozbmB4.d.ts.map +1 -0
- package/dist/{index-BPwEisy4.d.mts → index-xqaJtAZn.d.mts} +3 -8
- package/dist/index-xqaJtAZn.d.mts.map +1 -0
- package/dist/index.d.mts +141 -203
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +140 -202
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +196 -381
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +145 -294
- package/dist/index.mjs.map +1 -1
- package/dist/internal-log-b0Vp4qrB.js +183 -0
- package/dist/internal-log-b0Vp4qrB.js.map +1 -0
- package/dist/internal-log-uCSd1PMc.mjs +142 -0
- package/dist/internal-log-uCSd1PMc.mjs.map +1 -0
- package/dist/logitron-logger-D2B75Oc7.d.ts +365 -0
- package/dist/logitron-logger-D2B75Oc7.d.ts.map +1 -0
- package/dist/logitron-logger-oEd6wNhf.d.mts +365 -0
- package/dist/logitron-logger-oEd6wNhf.d.mts.map +1 -0
- package/dist/middleware.d.mts +10 -12
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.d.ts +10 -12
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +16 -47
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +13 -45
- package/dist/middleware.mjs.map +1 -1
- package/dist/nest.d.mts +467 -10
- package/dist/nest.d.mts.map +1 -1
- package/dist/nest.d.ts +467 -10
- package/dist/nest.d.ts.map +1 -1
- package/dist/nest.js +889 -43
- package/dist/nest.js.map +1 -0
- package/dist/nest.mjs +850 -9
- package/dist/nest.mjs.map +1 -0
- package/dist/{trace.utils-CvXgaiQT.mjs → request-trace-C_fvIDv4.mjs} +44 -9
- package/dist/request-trace-C_fvIDv4.mjs.map +1 -0
- package/dist/{trace.utils-DpBOLDtq.js → request-trace-wTJdKp6-.js} +121 -77
- package/dist/request-trace-wTJdKp6-.js.map +1 -0
- package/dist/rolldown-runtime-D3PmRqjj.mjs +4 -0
- package/dist/{safe-stringify-DJ66tW5I.mjs → safe-stringify-BMRl3EX3.mjs} +3 -4
- package/dist/safe-stringify-BMRl3EX3.mjs.map +1 -0
- package/dist/{safe-stringify-DWo3aZp_.js → safe-stringify-DLz1Kz3o.js} +23 -25
- package/dist/safe-stringify-DLz1Kz3o.js.map +1 -0
- package/dist/{search-CHUZPl0h.d.ts → search-BH33XhuU.d.mts} +2 -22
- package/dist/search-BH33XhuU.d.mts.map +1 -0
- package/dist/{search-DjXSn6v5.mjs → search-CWMVYC-R.mjs} +34 -41
- package/dist/search-CWMVYC-R.mjs.map +1 -0
- package/dist/{search-BfBzDIT9.d.mts → search-CazwyX5Z.d.ts} +2 -22
- package/dist/search-CazwyX5Z.d.ts.map +1 -0
- package/dist/{search-B0MH1SJJ.js → search-DUfWuzFP.js} +64 -71
- package/dist/search-DUfWuzFP.js.map +1 -0
- package/dist/search.d.mts +2 -4
- package/dist/search.d.ts +2 -4
- package/dist/search.js +3 -4
- package/dist/search.mjs +1 -4
- package/dist/secret-config-BegMLkYl.mjs +209 -0
- package/dist/secret-config-BegMLkYl.mjs.map +1 -0
- package/dist/secret-config-CBZBs0p1.js +271 -0
- package/dist/secret-config-CBZBs0p1.js.map +1 -0
- package/dist/slack.transport-Ce96JBDk.mjs +824 -0
- package/dist/slack.transport-Ce96JBDk.mjs.map +1 -0
- package/dist/slack.transport-Dlx3xvb7.js +847 -0
- package/dist/slack.transport-Dlx3xvb7.js.map +1 -0
- package/dist/slack.transport-DrirgDNy.d.ts +159 -0
- package/dist/slack.transport-DrirgDNy.d.ts.map +1 -0
- package/dist/slack.transport-DxHOXlLu.d.mts +159 -0
- package/dist/slack.transport-DxHOXlLu.d.mts.map +1 -0
- package/dist/testing.d.mts +3 -8
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.d.ts +3 -8
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +3 -4
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +2 -3
- package/dist/testing.mjs.map +1 -1
- package/dist/{transport.manager-Crau3Rle.js → transport.manager-DkcEOlqV.js} +331 -234
- package/dist/transport.manager-DkcEOlqV.js.map +1 -0
- package/dist/{transport.manager-CpdVHEGP.mjs → transport.manager-eZ_EeC00.mjs} +274 -178
- package/dist/transport.manager-eZ_EeC00.mjs.map +1 -0
- package/dist/{transport.types-CtNr3D7S.d.mts → transport.types-BJVL1QQl.d.mts} +5 -4
- package/dist/transport.types-BJVL1QQl.d.mts.map +1 -0
- package/dist/{transport.types-DqVLZTyf.d.ts → transport.types-BJVL1QQl.d.ts} +5 -4
- package/dist/transport.types-BJVL1QQl.d.ts.map +1 -0
- package/dist/transports.d.mts +32 -41
- package/dist/transports.d.mts.map +1 -1
- package/dist/transports.d.ts +32 -41
- package/dist/transports.d.ts.map +1 -1
- package/dist/transports.js +12 -12
- package/dist/transports.mjs +7 -12
- package/dist/worker-entry.js +337 -87
- package/dist/worker.d.mts +1 -2
- package/dist/worker.d.ts +1 -2
- package/dist/worker.js +3 -4
- package/dist/worker.mjs +1 -4
- package/dist/{worker.transport-Bj-i0P9V.d.ts → worker.transport-BuPwBi3d.d.ts} +2 -6
- package/dist/worker.transport-BuPwBi3d.d.ts.map +1 -0
- package/dist/{worker.transport-JP8YHBuU.js → worker.transport-C6GNl_r2.js} +44 -14
- package/dist/worker.transport-C6GNl_r2.js.map +1 -0
- package/dist/{worker.transport-otwhJ_Et.mjs → worker.transport-DLVA4y2e.mjs} +39 -9
- package/dist/worker.transport-DLVA4y2e.mjs.map +1 -0
- package/dist/{worker.transport-Bgv9heDI.d.mts → worker.transport-hJay1wxA.d.mts} +2 -6
- package/dist/worker.transport-hJay1wxA.d.mts.map +1 -0
- package/package.json +36 -19
- package/dist/async-context-BzJrbfQs.mjs +0 -72
- package/dist/async-context-BzJrbfQs.mjs.map +0 -1
- package/dist/async-context-DyQQYq3I.js +0 -92
- package/dist/async-context-DyQQYq3I.js.map +0 -1
- package/dist/azure-monitor.transport-BDFO4F-i.d.ts.map +0 -1
- package/dist/azure-monitor.transport-CHnjVRF-.mjs.map +0 -1
- package/dist/azure-monitor.transport-CrvfHd9C.js.map +0 -1
- package/dist/azure-monitor.transport-D5wQEH04.d.mts.map +0 -1
- package/dist/batch-buffer-BY-mRWIK.js.map +0 -1
- package/dist/batch-buffer-PGvSgbmd.mjs.map +0 -1
- package/dist/chunk-DLSkYcPk.mjs +0 -7
- package/dist/chunk-nOFOJqeH.js +0 -30
- package/dist/cloudwatch.transport-B00st6Xv.d.ts.map +0 -1
- package/dist/cloudwatch.transport-BO1ahxw-.js.map +0 -1
- package/dist/cloudwatch.transport-Blc7Gtxt.d.mts.map +0 -1
- package/dist/cloudwatch.transport-DZ6ies7g.mjs.map +0 -1
- package/dist/entry-fields-BJkuPnIg.mjs.map +0 -1
- package/dist/entry-fields-BvOLFlJ4.js.map +0 -1
- package/dist/gcp.transport-CHV8MGgz.js.map +0 -1
- package/dist/gcp.transport-DBX5TPIy.mjs.map +0 -1
- package/dist/gcp.transport-m4fVw0b1.d.ts.map +0 -1
- package/dist/gcp.transport-zzNyIoih.d.mts.map +0 -1
- package/dist/index-BPwEisy4.d.mts.map +0 -1
- package/dist/index-nZEeCr3X.d.ts.map +0 -1
- package/dist/internal-log-BUJYMZbE.js +0 -70
- package/dist/internal-log-BUJYMZbE.js.map +0 -1
- package/dist/internal-log-Cm8SOWSV.mjs +0 -52
- package/dist/internal-log-Cm8SOWSV.mjs.map +0 -1
- package/dist/nestjs-extras-DnJ6vrIC.js.map +0 -1
- package/dist/nestjs-extras-ImbuDhs1.mjs.map +0 -1
- package/dist/nestjs-extras-OkC796py.d.mts +0 -725
- package/dist/nestjs-extras-OkC796py.d.mts.map +0 -1
- package/dist/nestjs-extras-mKrHE9S6.d.ts +0 -725
- package/dist/nestjs-extras-mKrHE9S6.d.ts.map +0 -1
- package/dist/safe-stringify-DJ66tW5I.mjs.map +0 -1
- package/dist/safe-stringify-DWo3aZp_.js.map +0 -1
- package/dist/search-B0MH1SJJ.js.map +0 -1
- package/dist/search-BfBzDIT9.d.mts.map +0 -1
- package/dist/search-CHUZPl0h.d.ts.map +0 -1
- package/dist/search-DjXSn6v5.mjs.map +0 -1
- package/dist/trace.utils-CvXgaiQT.mjs.map +0 -1
- package/dist/trace.utils-DpBOLDtq.js.map +0 -1
- package/dist/transport.manager-CpdVHEGP.mjs.map +0 -1
- package/dist/transport.manager-Crau3Rle.js.map +0 -1
- package/dist/transport.types-CtNr3D7S.d.mts.map +0 -1
- package/dist/transport.types-DqVLZTyf.d.ts.map +0 -1
- package/dist/worker.transport-Bgv9heDI.d.mts.map +0 -1
- package/dist/worker.transport-Bj-i0P9V.d.ts.map +0 -1
- package/dist/worker.transport-JP8YHBuU.js.map +0 -1
- package/dist/worker.transport-otwhJ_Et.mjs.map +0 -1
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
const require_secret_config = require("./secret-config-CBZBs0p1.js");
|
|
2
|
+
const require_internal_log = require("./internal-log-b0Vp4qrB.js");
|
|
3
|
+
const require_safe_stringify = require("./safe-stringify-DLz1Kz3o.js");
|
|
4
|
+
const require_transport_manager = require("./transport.manager-DkcEOlqV.js");
|
|
5
|
+
const require_batch_buffer = require("./batch-buffer-DT4gdXmf.js");
|
|
6
|
+
// src/transports/otlp.transport.ts
|
|
7
|
+
const HEX_32 = /^[0-9a-f]{32}$/;
|
|
8
|
+
/**
|
|
9
|
+
* OTLP JSON wants a 32-char lowercase hex trace id. Dashed UUIDs are
|
|
10
|
+
* normalised; anything else (or all zeros) returns undefined.
|
|
11
|
+
*/
|
|
12
|
+
function toOtlpTraceId(id) {
|
|
13
|
+
const hex = id.replace(/-/g, "").toLowerCase();
|
|
14
|
+
return HEX_32.test(hex) && hex !== "0".repeat(32) ? hex : void 0;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Map a logixia level name to an OTel SeverityNumber (1–24) and text.
|
|
18
|
+
* Per the OTel logs SDK spec: TRACE=1, DEBUG=5, INFO=9, WARN=13, ERROR=17,
|
|
19
|
+
* FATAL=21. Custom/unknown levels fall back to INFO (9).
|
|
20
|
+
*/
|
|
21
|
+
function toOtelSeverity(level) {
|
|
22
|
+
switch (level.toLowerCase()) {
|
|
23
|
+
case "trace": return {
|
|
24
|
+
number: 1,
|
|
25
|
+
text: "TRACE"
|
|
26
|
+
};
|
|
27
|
+
case "verbose": return {
|
|
28
|
+
number: 5,
|
|
29
|
+
text: "DEBUG"
|
|
30
|
+
};
|
|
31
|
+
case "debug": return {
|
|
32
|
+
number: 5,
|
|
33
|
+
text: "DEBUG"
|
|
34
|
+
};
|
|
35
|
+
case "info": return {
|
|
36
|
+
number: 9,
|
|
37
|
+
text: "INFO"
|
|
38
|
+
};
|
|
39
|
+
case "warn":
|
|
40
|
+
case "warning": return {
|
|
41
|
+
number: 13,
|
|
42
|
+
text: "WARN"
|
|
43
|
+
};
|
|
44
|
+
case "error": return {
|
|
45
|
+
number: 17,
|
|
46
|
+
text: "ERROR"
|
|
47
|
+
};
|
|
48
|
+
case "fatal": return {
|
|
49
|
+
number: 21,
|
|
50
|
+
text: "FATAL"
|
|
51
|
+
};
|
|
52
|
+
default: return {
|
|
53
|
+
number: 9,
|
|
54
|
+
text: "INFO"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Coerce a JS value into an OTLP AnyValue. */
|
|
59
|
+
function toAnyValue(value) {
|
|
60
|
+
if (typeof value === "string") return { stringValue: value };
|
|
61
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
62
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };
|
|
63
|
+
if (typeof value === "bigint") return { stringValue: value.toString() };
|
|
64
|
+
if (value === null || value === void 0) return { stringValue: "" };
|
|
65
|
+
try {
|
|
66
|
+
return { stringValue: require_safe_stringify.stringifyForLog(value) };
|
|
67
|
+
} catch {
|
|
68
|
+
return { stringValue: String(value) };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Build the OTLP KeyValue attribute list from a flat record. */
|
|
72
|
+
function toAttributes(rec) {
|
|
73
|
+
return Object.entries(rec).map(([key, value]) => ({
|
|
74
|
+
key,
|
|
75
|
+
value: toAnyValue(value)
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
var OtlpLogTransport = class {
|
|
79
|
+
constructor(config) {
|
|
80
|
+
this.name = "otlp";
|
|
81
|
+
this.flushTimer = null;
|
|
82
|
+
this.url = config.url;
|
|
83
|
+
this.headers = config.headers ?? {};
|
|
84
|
+
this.batchSize = config.batchSize ?? 100;
|
|
85
|
+
this.flushIntervalMs = config.flushIntervalMs ?? 5e3;
|
|
86
|
+
this.requestTimeoutMs = config.requestTimeoutMs ?? 1e4;
|
|
87
|
+
this.level = config.level;
|
|
88
|
+
this.buffer = new require_batch_buffer.BatchBuffer({
|
|
89
|
+
name: "OtlpLogTransport",
|
|
90
|
+
batchSize: this.batchSize,
|
|
91
|
+
maxBufferSize: config.maxBufferSize ?? 1e4,
|
|
92
|
+
send: (entries) => this.send(entries),
|
|
93
|
+
secrets: () => this.secretValues()
|
|
94
|
+
});
|
|
95
|
+
const resource = {
|
|
96
|
+
"service.name": config.serviceName ?? "logixia",
|
|
97
|
+
...config.serviceVersion ? { "service.version": config.serviceVersion } : {},
|
|
98
|
+
...config.environment ? { "deployment.environment": config.environment } : {},
|
|
99
|
+
...config.resourceAttributes ?? {}
|
|
100
|
+
};
|
|
101
|
+
this.resourceAttrs = toAttributes(resource);
|
|
102
|
+
require_secret_config.protectSecrets(this, ["headers", "url"]);
|
|
103
|
+
this.flushTimer = setInterval(() => {
|
|
104
|
+
this.flush().catch(() => {});
|
|
105
|
+
}, this.flushIntervalMs);
|
|
106
|
+
if (this.flushTimer.unref) this.flushTimer.unref();
|
|
107
|
+
}
|
|
108
|
+
write(entry) {
|
|
109
|
+
this.buffer.push(entry);
|
|
110
|
+
this.buffer.flushIfFull();
|
|
111
|
+
}
|
|
112
|
+
/** Convert one entry into an OTLP LogRecord. */
|
|
113
|
+
toLogRecord(entry) {
|
|
114
|
+
const sev = toOtelSeverity(entry.level);
|
|
115
|
+
const tsNanos = String(entry.timestamp.getTime() * 1e6);
|
|
116
|
+
const attrs = { ...entry.data ?? {} };
|
|
117
|
+
if (entry.context !== void 0) attrs["context"] = entry.context;
|
|
118
|
+
if (entry.appName !== void 0) attrs["app.name"] = entry.appName;
|
|
119
|
+
if (entry.environment !== void 0) attrs["deployment.environment"] = entry.environment;
|
|
120
|
+
const record = {
|
|
121
|
+
timeUnixNano: tsNanos,
|
|
122
|
+
observedTimeUnixNano: tsNanos,
|
|
123
|
+
severityNumber: sev.number,
|
|
124
|
+
severityText: sev.text,
|
|
125
|
+
body: { stringValue: entry.message },
|
|
126
|
+
attributes: toAttributes(attrs)
|
|
127
|
+
};
|
|
128
|
+
if (entry.traceId) {
|
|
129
|
+
const traceId = toOtlpTraceId(entry.traceId);
|
|
130
|
+
if (traceId) record["traceId"] = traceId;
|
|
131
|
+
else record["attributes"].push({
|
|
132
|
+
key: "trace_id",
|
|
133
|
+
value: { stringValue: entry.traceId }
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return record;
|
|
137
|
+
}
|
|
138
|
+
buildPayload(entries) {
|
|
139
|
+
return require_safe_stringify.stringifyForLog({ resourceLogs: [{
|
|
140
|
+
resource: { attributes: this.resourceAttrs },
|
|
141
|
+
scopeLogs: [{
|
|
142
|
+
scope: { name: "logixia" },
|
|
143
|
+
logRecords: entries.map((e) => this.toLogRecord(e))
|
|
144
|
+
}]
|
|
145
|
+
}] });
|
|
146
|
+
}
|
|
147
|
+
flush() {
|
|
148
|
+
return this.buffer.flush();
|
|
149
|
+
}
|
|
150
|
+
secretValues() {
|
|
151
|
+
return require_secret_config.collectSecrets({
|
|
152
|
+
headers: this.headers,
|
|
153
|
+
url: this.url
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async send(entries) {
|
|
157
|
+
if (typeof fetch !== "function") {
|
|
158
|
+
require_internal_log.internalWarn("OtlpLogTransport: global fetch unavailable — cannot export logs");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const controller = new AbortController();
|
|
162
|
+
const timer = setTimeout(() => controller.abort(), this.requestTimeoutMs);
|
|
163
|
+
try {
|
|
164
|
+
const res = await fetch(this.url, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: {
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
...this.headers
|
|
169
|
+
},
|
|
170
|
+
body: this.buildPayload(entries),
|
|
171
|
+
signal: controller.signal
|
|
172
|
+
});
|
|
173
|
+
if (!res.ok) throw new require_batch_buffer.HttpError(res.status, "", "OTLP export");
|
|
174
|
+
} catch (err) {
|
|
175
|
+
require_secret_config.scrubError(err, this.secretValues());
|
|
176
|
+
if (controller.signal.aborted) throw new Error(`OTLP export timed out after ${this.requestTimeoutMs}ms`, { cause: err });
|
|
177
|
+
throw err;
|
|
178
|
+
} finally {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async close() {
|
|
183
|
+
if (this.flushTimer) {
|
|
184
|
+
clearInterval(this.flushTimer);
|
|
185
|
+
this.flushTimer = null;
|
|
186
|
+
}
|
|
187
|
+
await this.buffer.close();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
// src/transports/webhook-queue.ts
|
|
191
|
+
const MAX_BACKOFF_MS = 3e4;
|
|
192
|
+
const DEFAULT_QUEUE_CAP = 1e3;
|
|
193
|
+
const CLOSE_TIMEOUT_MS = 3e3;
|
|
194
|
+
const OVERFLOW_WARN_MS = 5e3;
|
|
195
|
+
const DEFAULT_WEBHOOK_TIMEOUT_MS = 1e4;
|
|
196
|
+
const ZWSP = "";
|
|
197
|
+
const LEVEL_RANK = {
|
|
198
|
+
fatal: 0,
|
|
199
|
+
error: 1,
|
|
200
|
+
warn: 2,
|
|
201
|
+
warning: 2,
|
|
202
|
+
info: 3,
|
|
203
|
+
debug: 4,
|
|
204
|
+
verbose: 5,
|
|
205
|
+
trace: 6
|
|
206
|
+
};
|
|
207
|
+
function resolveMinLevel(level, label) {
|
|
208
|
+
const resolved = (level ?? "error").toLowerCase();
|
|
209
|
+
if (!(resolved in LEVEL_RANK)) throw new TypeError(`${label}: unknown level "${String(level).slice(0, 40)}"`);
|
|
210
|
+
return resolved;
|
|
211
|
+
}
|
|
212
|
+
/** Unknown (custom) entry levels pass, like the TransportManager does. */
|
|
213
|
+
function meetsLevel(entryLevel, minLevel) {
|
|
214
|
+
const entryRank = LEVEL_RANK[entryLevel.toLowerCase()];
|
|
215
|
+
return entryRank === void 0 || entryRank <= (LEVEL_RANK[minLevel] ?? 1);
|
|
216
|
+
}
|
|
217
|
+
/** Validates the secret URL. Error messages never contain it. */
|
|
218
|
+
function parseWebhookUrl(raw, rules) {
|
|
219
|
+
const { label, allowCustomUrl, hostMatches, pathMatches, expected } = rules;
|
|
220
|
+
if (typeof raw !== "string" || raw.length === 0) throw new TypeError(`${label}: webhookUrl is required`);
|
|
221
|
+
let url;
|
|
222
|
+
try {
|
|
223
|
+
url = new URL(raw);
|
|
224
|
+
} catch {
|
|
225
|
+
throw new TypeError(`${label}: webhookUrl is not a valid URL`);
|
|
226
|
+
}
|
|
227
|
+
if (url.protocol !== "https:") throw new TypeError(`${label}: webhookUrl must use https:`);
|
|
228
|
+
if (url.username || url.password) throw new TypeError(`${label}: webhookUrl must not contain credentials`);
|
|
229
|
+
if (!allowCustomUrl) {
|
|
230
|
+
if (!hostMatches(url.hostname.toLowerCase()) || !pathMatches(url.pathname) || url.port) throw new TypeError(`${label}: webhookUrl must be a ${expected} webhook (set allowCustomUrl: true to use another https endpoint)`);
|
|
231
|
+
}
|
|
232
|
+
return url;
|
|
233
|
+
}
|
|
234
|
+
function retryAfterHeaderMs(res) {
|
|
235
|
+
var _res$headers;
|
|
236
|
+
const raw = (_res$headers = res.headers) === null || _res$headers === void 0 ? void 0 : _res$headers.get("retry-after");
|
|
237
|
+
if (raw === null || raw === void 0 || raw === "") return void 0;
|
|
238
|
+
const seconds = Number(raw);
|
|
239
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1e3 : void 0;
|
|
240
|
+
}
|
|
241
|
+
function nonNegative(value, fallback) {
|
|
242
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
243
|
+
}
|
|
244
|
+
function failureCode(err) {
|
|
245
|
+
const cause = err === null || err === void 0 ? void 0 : err.cause;
|
|
246
|
+
const code = (cause === null || cause === void 0 ? void 0 : cause.code) ?? (err === null || err === void 0 ? void 0 : err.code);
|
|
247
|
+
if (typeof code === "string") return code.slice(0, 40);
|
|
248
|
+
return err instanceof Error ? err.name : "error";
|
|
249
|
+
}
|
|
250
|
+
function sleep(ms, signal) {
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
var _timer$unref;
|
|
253
|
+
if (signal.aborted) return resolve();
|
|
254
|
+
const done = () => {
|
|
255
|
+
clearTimeout(timer);
|
|
256
|
+
signal.removeEventListener("abort", done);
|
|
257
|
+
resolve();
|
|
258
|
+
};
|
|
259
|
+
const timer = setTimeout(done, ms);
|
|
260
|
+
(_timer$unref = timer.unref) === null || _timer$unref === void 0 || _timer$unref.call(timer);
|
|
261
|
+
signal.addEventListener("abort", done, { once: true });
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async function cancelBody(res) {
|
|
265
|
+
try {
|
|
266
|
+
var _res$body;
|
|
267
|
+
await ((_res$body = res.body) === null || _res$body === void 0 ? void 0 : _res$body.cancel());
|
|
268
|
+
} catch {}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Throttled queue for chat webhooks. It is not built on BatchBuffer because the
|
|
272
|
+
* semantics differ: sends are spaced by `minIntervalMs` (leading edge, then one
|
|
273
|
+
* message per window), a window sends one summarized message and drops the
|
|
274
|
+
* overflow, 429 waits are cancellable by close(), and flush() surfaces send
|
|
275
|
+
* errors. HttpError / isPermanentHttpError are shared with BatchBuffer.
|
|
276
|
+
*
|
|
277
|
+
* Entries past `maxPerMessage` in one window are not sent: the message carries a
|
|
278
|
+
* "… and N more" line and an internal warning reports the count.
|
|
279
|
+
*/
|
|
280
|
+
var WebhookQueue = class {
|
|
281
|
+
constructor(opts) {
|
|
282
|
+
this.opts = opts;
|
|
283
|
+
this.items = [];
|
|
284
|
+
this.timer = null;
|
|
285
|
+
this.nextAllowed = 0;
|
|
286
|
+
this.failures = 0;
|
|
287
|
+
this.closed = false;
|
|
288
|
+
this.shutdown = new AbortController();
|
|
289
|
+
this.overflowPending = 0;
|
|
290
|
+
this.lastOverflowWarn = 0;
|
|
291
|
+
this.fetchWarned = false;
|
|
292
|
+
this.host = opts.url.hostname;
|
|
293
|
+
this.httpLabel = `${opts.label} (${this.host})`;
|
|
294
|
+
this.minIntervalMs = nonNegative(opts.minIntervalMs, 1e3);
|
|
295
|
+
this.maxRetries = Math.floor(nonNegative(opts.maxRetries, 1));
|
|
296
|
+
this.timeoutMs = nonNegative(opts.requestTimeoutMs, DEFAULT_WEBHOOK_TIMEOUT_MS);
|
|
297
|
+
this.cap = new require_transport_manager.BufferCap(opts.label, opts.maxQueueSize, opts.maxPerMessage, DEFAULT_QUEUE_CAP);
|
|
298
|
+
}
|
|
299
|
+
get length() {
|
|
300
|
+
return this.items.length;
|
|
301
|
+
}
|
|
302
|
+
push(entry) {
|
|
303
|
+
if (this.closed) return;
|
|
304
|
+
this.items.push(entry);
|
|
305
|
+
this.cap.enforce(this.items);
|
|
306
|
+
this.schedule();
|
|
307
|
+
}
|
|
308
|
+
flush() {
|
|
309
|
+
this.inFlight ??= this.drain().finally(() => {
|
|
310
|
+
this.inFlight = void 0;
|
|
311
|
+
});
|
|
312
|
+
return this.inFlight;
|
|
313
|
+
}
|
|
314
|
+
/** Cancels rate-limit waits, then makes one best-effort send with a short timeout. */
|
|
315
|
+
async close() {
|
|
316
|
+
this.closed = true;
|
|
317
|
+
this.clearTimer();
|
|
318
|
+
this.shutdown.abort();
|
|
319
|
+
try {
|
|
320
|
+
await this.inFlight;
|
|
321
|
+
} catch {}
|
|
322
|
+
try {
|
|
323
|
+
await this.flush();
|
|
324
|
+
} catch (err) {
|
|
325
|
+
require_internal_log.internalError(`${this.opts.label} flush on close failed`, err);
|
|
326
|
+
}
|
|
327
|
+
this.reportOverflow(true);
|
|
328
|
+
if (this.items.length > 0) {
|
|
329
|
+
require_internal_log.internalError(`${this.opts.label} closing with ${this.items.length} undelivered entries`);
|
|
330
|
+
this.items = [];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
clearTimer() {
|
|
334
|
+
if (this.timer) clearTimeout(this.timer);
|
|
335
|
+
this.timer = null;
|
|
336
|
+
}
|
|
337
|
+
schedule() {
|
|
338
|
+
var _this$timer$unref, _this$timer;
|
|
339
|
+
if (this.timer || this.closed || this.items.length === 0) return;
|
|
340
|
+
const delay = Math.max(0, this.nextAllowed - Date.now());
|
|
341
|
+
this.timer = setTimeout(() => {
|
|
342
|
+
this.timer = null;
|
|
343
|
+
this.flush().catch((err) => require_internal_log.internalError(`${this.opts.label} send failed`, err));
|
|
344
|
+
}, delay);
|
|
345
|
+
(_this$timer$unref = (_this$timer = this.timer).unref) === null || _this$timer$unref === void 0 || _this$timer$unref.call(_this$timer);
|
|
346
|
+
}
|
|
347
|
+
reportOverflow(force = false) {
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
if (this.overflowPending === 0 || !force && now - this.lastOverflowWarn < OVERFLOW_WARN_MS) return;
|
|
350
|
+
const n = this.overflowPending;
|
|
351
|
+
require_internal_log.internalWarn(`${this.opts.label}: ${n} entr${n === 1 ? "y" : "ies"} over the per-message limit (${this.opts.maxPerMessage}) summarized in a "... and N more" line and not sent`);
|
|
352
|
+
this.overflowPending = 0;
|
|
353
|
+
this.lastOverflowWarn = now;
|
|
354
|
+
}
|
|
355
|
+
render(batch) {
|
|
356
|
+
const rendered = [];
|
|
357
|
+
let bad = 0;
|
|
358
|
+
for (const entry of batch) try {
|
|
359
|
+
const value = this.opts.render(entry);
|
|
360
|
+
JSON.stringify(value);
|
|
361
|
+
rendered.push(value);
|
|
362
|
+
} catch (err) {
|
|
363
|
+
bad += 1;
|
|
364
|
+
if (bad === 1) require_internal_log.internalError(`${this.opts.label}: dropping an entry that cannot be rendered`, err);
|
|
365
|
+
}
|
|
366
|
+
if (bad > 1) require_internal_log.internalError(`${this.opts.label}: dropped ${bad} entries that cannot be rendered`);
|
|
367
|
+
return rendered;
|
|
368
|
+
}
|
|
369
|
+
async drain() {
|
|
370
|
+
if (this.items.length === 0) return;
|
|
371
|
+
if (typeof fetch !== "function") {
|
|
372
|
+
if (!this.fetchWarned) {
|
|
373
|
+
this.fetchWarned = true;
|
|
374
|
+
require_internal_log.internalWarn(`${this.opts.label}: global fetch is unavailable (Node 18+ required); dropping entries`);
|
|
375
|
+
}
|
|
376
|
+
this.items = [];
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const all = this.items;
|
|
380
|
+
this.items = [];
|
|
381
|
+
const batch = all.slice(0, this.opts.maxPerMessage);
|
|
382
|
+
const overflow = all.length - batch.length;
|
|
383
|
+
if (overflow > 0) {
|
|
384
|
+
this.overflowPending += overflow;
|
|
385
|
+
this.reportOverflow();
|
|
386
|
+
}
|
|
387
|
+
this.nextAllowed = Date.now() + this.minIntervalMs;
|
|
388
|
+
try {
|
|
389
|
+
const rendered = this.render(batch);
|
|
390
|
+
if (rendered.length === 0 && overflow === 0) return;
|
|
391
|
+
let body;
|
|
392
|
+
try {
|
|
393
|
+
body = JSON.stringify(this.opts.assemble(rendered, overflow));
|
|
394
|
+
} catch (err) {
|
|
395
|
+
require_internal_log.internalError(`${this.opts.label}: dropping a batch that cannot be serialized`, err);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
await this.sendWithRetry(body);
|
|
400
|
+
this.failures = 0;
|
|
401
|
+
} catch (err) {
|
|
402
|
+
if (!require_batch_buffer.isPermanentHttpError(err)) {
|
|
403
|
+
this.items.unshift(...batch);
|
|
404
|
+
this.cap.enforce(this.items);
|
|
405
|
+
this.failures += 1;
|
|
406
|
+
const backoff = Math.min(MAX_BACKOFF_MS, Math.max(1e3, this.minIntervalMs) * 2 ** this.failures);
|
|
407
|
+
this.nextAllowed = Math.max(this.nextAllowed, Date.now() + backoff);
|
|
408
|
+
}
|
|
409
|
+
throw err;
|
|
410
|
+
}
|
|
411
|
+
} finally {
|
|
412
|
+
this.schedule();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async sendWithRetry(body) {
|
|
416
|
+
for (let attempt = 0;; attempt += 1) {
|
|
417
|
+
const res = await this.post(body);
|
|
418
|
+
if (res.ok) return;
|
|
419
|
+
if (res.status !== 429) throw new require_batch_buffer.HttpError(res.status, "", this.httpLabel);
|
|
420
|
+
const waitMs = Math.min(MAX_BACKOFF_MS, Math.max(0, res.retryAfterMs ?? Math.max(1e3, this.minIntervalMs)));
|
|
421
|
+
this.nextAllowed = Math.max(this.nextAllowed, Date.now() + waitMs);
|
|
422
|
+
if (attempt >= this.maxRetries || this.closed) throw new require_batch_buffer.HttpError(429, "", this.httpLabel);
|
|
423
|
+
await sleep(waitMs, this.shutdown.signal);
|
|
424
|
+
if (this.shutdown.signal.aborted) throw new Error(`${this.opts.label}: rate-limit wait cancelled by close()`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async readRetryAfter(res) {
|
|
428
|
+
try {
|
|
429
|
+
return this.opts.retryAfterMs ? await this.opts.retryAfterMs(res) : retryAfterHeaderMs(res);
|
|
430
|
+
} catch {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async post(body) {
|
|
435
|
+
const controller = new AbortController();
|
|
436
|
+
const timeoutMs = this.closed ? Math.min(this.timeoutMs, CLOSE_TIMEOUT_MS) : this.timeoutMs;
|
|
437
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
438
|
+
try {
|
|
439
|
+
const res = await fetch(this.opts.url, {
|
|
440
|
+
method: "POST",
|
|
441
|
+
headers: { "Content-Type": "application/json" },
|
|
442
|
+
body,
|
|
443
|
+
redirect: "error",
|
|
444
|
+
signal: controller.signal
|
|
445
|
+
});
|
|
446
|
+
let retryAfterMs;
|
|
447
|
+
try {
|
|
448
|
+
if (res.status === 429) retryAfterMs = await this.readRetryAfter(res);
|
|
449
|
+
} finally {
|
|
450
|
+
await cancelBody(res);
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
ok: res.ok,
|
|
454
|
+
status: res.status,
|
|
455
|
+
retryAfterMs
|
|
456
|
+
};
|
|
457
|
+
} catch (err) {
|
|
458
|
+
throw new Error(`${this.opts.label}: request to ${this.host} failed (${controller.signal.aborted ? "timeout" : failureCode(err)})`);
|
|
459
|
+
} finally {
|
|
460
|
+
clearTimeout(timer);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
/** Cuts `text` to `max` chars without splitting a surrogate pair or a `&`-style entity. */
|
|
465
|
+
function truncateText(text, max, ellipsis = "…") {
|
|
466
|
+
if (text.length <= max) return text;
|
|
467
|
+
let cut = text.slice(0, Math.max(0, max - ellipsis.length));
|
|
468
|
+
cut = cut.replace(/&[a-z]{0,3}$/, "");
|
|
469
|
+
if (/[\uD800-\uDBFF]$/.test(cut)) cut = cut.slice(0, -1);
|
|
470
|
+
return cut + ellipsis;
|
|
471
|
+
}
|
|
472
|
+
/** Breaks code-fence and broadcast-mention sequences with a zero-width space. */
|
|
473
|
+
function defuseText(text) {
|
|
474
|
+
return text.replace(/`{3,}/g, (m) => m.split("").join(ZWSP)).replace(/@(everyone|here|channel)/gi, `@${ZWSP}$1`);
|
|
475
|
+
}
|
|
476
|
+
/** Collapses control characters and newlines for single-line fields. */
|
|
477
|
+
function singleLine(text) {
|
|
478
|
+
return text.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
|
|
479
|
+
}
|
|
480
|
+
/** ISO string, or undefined for a missing or invalid timestamp. */
|
|
481
|
+
function safeIso(date) {
|
|
482
|
+
return date instanceof Date && !Number.isNaN(date.getTime()) ? date.toISOString() : void 0;
|
|
483
|
+
}
|
|
484
|
+
const DISCORD_DESCRIPTION_MAX = 4096;
|
|
485
|
+
const DISCORD_FIELD_VALUE_MAX = 1024;
|
|
486
|
+
const DISCORD_EMBED_TOTAL_MAX = 6e3;
|
|
487
|
+
const DISCORD_CONTENT_MAX = 2e3;
|
|
488
|
+
const FIELD_VALUE_CAP = 64;
|
|
489
|
+
const DISCORD_FOOTER_MAX = 2048;
|
|
490
|
+
const FENCE_OVERHEAD = 12;
|
|
491
|
+
const MIN_USEFUL_BODY = 20;
|
|
492
|
+
const DISCORD_LEVEL_COLORS = {
|
|
493
|
+
fatal: 10038562,
|
|
494
|
+
error: 15548997,
|
|
495
|
+
warn: 16705372,
|
|
496
|
+
warning: 16705372,
|
|
497
|
+
info: 3447003,
|
|
498
|
+
debug: 9807270,
|
|
499
|
+
verbose: 9807270,
|
|
500
|
+
trace: 9807270
|
|
501
|
+
};
|
|
502
|
+
const DEFAULT_COLOR = 9807270;
|
|
503
|
+
/** Escapes Discord markdown and defuses @everyone / @here and code fences. */
|
|
504
|
+
function escapeDiscord(text) {
|
|
505
|
+
return defuseText(text.replace(/[\\*_~`|>#[\]()]/g, "\\$&"));
|
|
506
|
+
}
|
|
507
|
+
function textLength(value, key) {
|
|
508
|
+
const text = value === null || value === void 0 ? void 0 : value[key];
|
|
509
|
+
return typeof text === "string" ? text.length : 0;
|
|
510
|
+
}
|
|
511
|
+
/** Counts every text field Discord counts toward the 6000-character limit. */
|
|
512
|
+
function embedChars(embed) {
|
|
513
|
+
var _embed$title, _embed$description;
|
|
514
|
+
return (((_embed$title = embed.title) === null || _embed$title === void 0 ? void 0 : _embed$title.length) ?? 0) + (((_embed$description = embed.description) === null || _embed$description === void 0 ? void 0 : _embed$description.length) ?? 0) + (embed.fields ?? []).reduce((n, f) => n + f.name.length + f.value.length, 0) + textLength(embed["footer"], "text") + textLength(embed["author"], "name");
|
|
515
|
+
}
|
|
516
|
+
/** Shrinks one embed to `limit` chars: description first, then fields, footer/author, title. */
|
|
517
|
+
function shrinkToFit(embed, limit) {
|
|
518
|
+
var _embed$fields;
|
|
519
|
+
const excess = () => embedChars(embed) - limit;
|
|
520
|
+
if (excess() > 0 && embed.description) {
|
|
521
|
+
const keep = embed.description.length - excess();
|
|
522
|
+
if (keep > 0) embed.description = truncateText(embed.description, keep);
|
|
523
|
+
else delete embed.description;
|
|
524
|
+
}
|
|
525
|
+
while (excess() > 0 && ((_embed$fields = embed.fields) === null || _embed$fields === void 0 ? void 0 : _embed$fields.length)) embed.fields.pop();
|
|
526
|
+
if (excess() > 0) {
|
|
527
|
+
delete embed["footer"];
|
|
528
|
+
delete embed["author"];
|
|
529
|
+
}
|
|
530
|
+
if (excess() > 0 && embed.title) embed.title = truncateText(embed.title, Math.max(1, embed.title.length - excess()));
|
|
531
|
+
}
|
|
532
|
+
function codeBlock(json, max) {
|
|
533
|
+
return `\`\`\`json\n${truncateText(json.replace(/`{3,}/g, (m) => m.split("").join("")), max)}\n\`\`\``;
|
|
534
|
+
}
|
|
535
|
+
function clampCustomEmbed(embed) {
|
|
536
|
+
const out = { ...embed };
|
|
537
|
+
if (typeof out.title === "string") out.title = truncateText(out.title, 256);
|
|
538
|
+
if (typeof out.description === "string") out.description = truncateText(out.description, DISCORD_DESCRIPTION_MAX);
|
|
539
|
+
if (Array.isArray(out.fields)) out.fields = out.fields.slice(0, 25).map((f) => ({
|
|
540
|
+
...f,
|
|
541
|
+
name: truncateText(String(f.name), 256),
|
|
542
|
+
value: truncateText(String(f.value), DISCORD_FIELD_VALUE_MAX)
|
|
543
|
+
}));
|
|
544
|
+
const footer = out["footer"];
|
|
545
|
+
if (footer && typeof footer.text === "string") out["footer"] = {
|
|
546
|
+
...footer,
|
|
547
|
+
text: truncateText(footer.text, DISCORD_FOOTER_MAX)
|
|
548
|
+
};
|
|
549
|
+
const author = out["author"];
|
|
550
|
+
if (author && typeof author.name === "string") out["author"] = {
|
|
551
|
+
...author,
|
|
552
|
+
name: truncateText(author.name, 256)
|
|
553
|
+
};
|
|
554
|
+
return out;
|
|
555
|
+
}
|
|
556
|
+
var DiscordTransport = class {
|
|
557
|
+
constructor(config) {
|
|
558
|
+
this.name = "discord";
|
|
559
|
+
const url = parseWebhookUrl(config.webhookUrl, {
|
|
560
|
+
label: "DiscordTransport",
|
|
561
|
+
hostMatches: (h) => [
|
|
562
|
+
"discord.com",
|
|
563
|
+
"discordapp.com",
|
|
564
|
+
"ptb.discord.com",
|
|
565
|
+
"canary.discord.com"
|
|
566
|
+
].includes(h),
|
|
567
|
+
pathMatches: (p) => /^\/api(\/v\d+)?\/webhooks\//.test(p),
|
|
568
|
+
expected: "discord.com",
|
|
569
|
+
allowCustomUrl: config.allowCustomUrl
|
|
570
|
+
});
|
|
571
|
+
if (config.avatarUrl !== void 0 && !/^https:\/\//i.test(config.avatarUrl)) throw new TypeError("DiscordTransport: avatarUrl must use https:");
|
|
572
|
+
this.level = resolveMinLevel(config.level, "DiscordTransport");
|
|
573
|
+
if (config.filter) this.filter = config.filter;
|
|
574
|
+
this.username = config.username;
|
|
575
|
+
this.avatarUrl = config.avatarUrl;
|
|
576
|
+
this.formatter = config.formatter;
|
|
577
|
+
const queue = new WebhookQueue({
|
|
578
|
+
label: "DiscordTransport",
|
|
579
|
+
url,
|
|
580
|
+
minIntervalMs: config.minIntervalMs,
|
|
581
|
+
maxPerMessage: 10,
|
|
582
|
+
maxQueueSize: config.maxBufferSize,
|
|
583
|
+
maxRetries: config.maxRetries,
|
|
584
|
+
requestTimeoutMs: config.requestTimeoutMs ?? 1e4,
|
|
585
|
+
render: (entry) => this.draftFor(entry),
|
|
586
|
+
assemble: (drafts, overflow) => this.buildPayload(drafts, overflow),
|
|
587
|
+
retryAfterMs: readRetryAfter
|
|
588
|
+
});
|
|
589
|
+
Object.defineProperty(this, "queue", {
|
|
590
|
+
value: queue,
|
|
591
|
+
enumerable: false
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
write(entry) {
|
|
595
|
+
if (!meetsLevel(entry.level, this.level)) return;
|
|
596
|
+
this.queue.push(entry);
|
|
597
|
+
}
|
|
598
|
+
flush() {
|
|
599
|
+
return this.queue.flush();
|
|
600
|
+
}
|
|
601
|
+
close() {
|
|
602
|
+
return this.queue.close();
|
|
603
|
+
}
|
|
604
|
+
/** Never serialise the webhook URL (JSON.stringify, util.inspect). */
|
|
605
|
+
toJSON() {
|
|
606
|
+
return {
|
|
607
|
+
name: this.name,
|
|
608
|
+
level: this.level
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/** `showHidden` would otherwise print the non-enumerable queue (and its URL). */
|
|
612
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
613
|
+
return this.toJSON();
|
|
614
|
+
}
|
|
615
|
+
buildPayload(drafts, overflow) {
|
|
616
|
+
const kept = this.fitToBudget(drafts);
|
|
617
|
+
const dropped = overflow + (drafts.length - kept.length);
|
|
618
|
+
const payload = {
|
|
619
|
+
embeds: kept.map((d) => d.embed),
|
|
620
|
+
allowed_mentions: { parse: [] }
|
|
621
|
+
};
|
|
622
|
+
if (dropped > 0) payload["content"] = truncateText(`… and ${dropped} more entr${dropped === 1 ? "y" : "ies"} not shown`, DISCORD_CONTENT_MAX);
|
|
623
|
+
if (this.username) payload["username"] = truncateText(this.username, 80);
|
|
624
|
+
if (this.avatarUrl) payload["avatar_url"] = this.avatarUrl;
|
|
625
|
+
return payload;
|
|
626
|
+
}
|
|
627
|
+
/** Enforces the 6000-char total: drops trailing embeds if needed, then shares the rest among descriptions. */
|
|
628
|
+
fitToBudget(drafts) {
|
|
629
|
+
const kept = [...drafts];
|
|
630
|
+
const base = () => kept.reduce((n, d) => n + embedChars(d.embed), 0);
|
|
631
|
+
while (kept.length > 1 && base() > 6e3) kept.pop();
|
|
632
|
+
const only = kept[0];
|
|
633
|
+
if (only && base() > 6e3) shrinkToFit(only.embed, DISCORD_EMBED_TOTAL_MAX);
|
|
634
|
+
const withData = kept.filter((d) => d.json !== void 0);
|
|
635
|
+
if (withData.length === 0) return kept;
|
|
636
|
+
const share = Math.floor((DISCORD_EMBED_TOTAL_MAX - base()) / withData.length);
|
|
637
|
+
const body = Math.min(DISCORD_DESCRIPTION_MAX, share) - FENCE_OVERHEAD;
|
|
638
|
+
for (const d of withData) if (body >= MIN_USEFUL_BODY) d.embed.description = codeBlock(d.json, body);
|
|
639
|
+
return kept;
|
|
640
|
+
}
|
|
641
|
+
draftFor(entry) {
|
|
642
|
+
if (this.formatter) try {
|
|
643
|
+
return {
|
|
644
|
+
embed: clampCustomEmbed(this.formatter(entry)),
|
|
645
|
+
json: void 0
|
|
646
|
+
};
|
|
647
|
+
} catch {
|
|
648
|
+
require_internal_log.internalWarn("DiscordTransport: formatter threw; using the default embed");
|
|
649
|
+
}
|
|
650
|
+
return defaultDraft(entry);
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
function defaultDraft(entry) {
|
|
654
|
+
const title = `[${singleLine(entry.level).toUpperCase()}] ${escapeDiscord(singleLine(entry.message))}`;
|
|
655
|
+
const fields = [
|
|
656
|
+
["appName", entry.appName],
|
|
657
|
+
["environment", entry.environment],
|
|
658
|
+
["context", entry.context],
|
|
659
|
+
["traceId", entry.traceId]
|
|
660
|
+
].map(([name, value]) => [name, typeof value === "string" ? singleLine(value) : ""]).filter(([, value]) => value !== "").map(([name, value]) => ({
|
|
661
|
+
name,
|
|
662
|
+
value: truncateText(escapeDiscord(value), FIELD_VALUE_CAP),
|
|
663
|
+
inline: true
|
|
664
|
+
}));
|
|
665
|
+
const embed = {
|
|
666
|
+
title: truncateText(title, 256),
|
|
667
|
+
color: DISCORD_LEVEL_COLORS[entry.level.toLowerCase()] ?? DEFAULT_COLOR,
|
|
668
|
+
fields
|
|
669
|
+
};
|
|
670
|
+
const timestamp = safeIso(entry.timestamp);
|
|
671
|
+
if (timestamp) embed.timestamp = timestamp;
|
|
672
|
+
return {
|
|
673
|
+
embed,
|
|
674
|
+
json: entry.data && Object.keys(entry.data).length > 0 ? require_safe_stringify.stringifyForLog(entry.data) : void 0
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
async function readRetryAfter(res) {
|
|
678
|
+
try {
|
|
679
|
+
var _res$json;
|
|
680
|
+
const body = await ((_res$json = res.json) === null || _res$json === void 0 ? void 0 : _res$json.call(res));
|
|
681
|
+
const seconds = Number(body === null || body === void 0 ? void 0 : body.retry_after);
|
|
682
|
+
if ((body === null || body === void 0 ? void 0 : body.retry_after) !== void 0 && Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
683
|
+
} catch {}
|
|
684
|
+
return retryAfterHeaderMs(res);
|
|
685
|
+
}
|
|
686
|
+
const SLACK_DATA_MAX = 2500;
|
|
687
|
+
const SLACK_FIELD_MAX = 300;
|
|
688
|
+
const SLACK_TEXT_MAX = 1e3;
|
|
689
|
+
const SLACK_MAX_BLOCKS = 50;
|
|
690
|
+
const SLACK_MAX_PER_MESSAGE = 10;
|
|
691
|
+
/** Escapes the three characters Slack treats specially and defuses broadcast mentions. */
|
|
692
|
+
function escapeSlack(text) {
|
|
693
|
+
return defuseText(text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"));
|
|
694
|
+
}
|
|
695
|
+
const LEVEL_EMOJI = {
|
|
696
|
+
fatal: ":rotating_light:",
|
|
697
|
+
error: ":red_circle:",
|
|
698
|
+
warn: ":warning:",
|
|
699
|
+
warning: ":warning:",
|
|
700
|
+
info: ":information_source:"
|
|
701
|
+
};
|
|
702
|
+
var SlackTransport = class {
|
|
703
|
+
constructor(config) {
|
|
704
|
+
this.name = "slack";
|
|
705
|
+
const url = parseWebhookUrl(config.webhookUrl, {
|
|
706
|
+
label: "SlackTransport",
|
|
707
|
+
hostMatches: (h) => h === "hooks.slack.com" || h === "hooks.slack-gov.com",
|
|
708
|
+
pathMatches: (p) => p.startsWith("/services/") || p.startsWith("/triggers/"),
|
|
709
|
+
expected: "hooks.slack.com",
|
|
710
|
+
allowCustomUrl: config.allowCustomUrl
|
|
711
|
+
});
|
|
712
|
+
this.level = resolveMinLevel(config.level, "SlackTransport");
|
|
713
|
+
if (config.filter) this.filter = config.filter;
|
|
714
|
+
this.username = config.username;
|
|
715
|
+
this.iconEmoji = config.iconEmoji;
|
|
716
|
+
this.formatter = config.formatter;
|
|
717
|
+
const queue = new WebhookQueue({
|
|
718
|
+
label: "SlackTransport",
|
|
719
|
+
url,
|
|
720
|
+
minIntervalMs: config.minIntervalMs,
|
|
721
|
+
maxPerMessage: SLACK_MAX_PER_MESSAGE,
|
|
722
|
+
maxQueueSize: config.maxBufferSize,
|
|
723
|
+
maxRetries: config.maxRetries,
|
|
724
|
+
requestTimeoutMs: config.requestTimeoutMs ?? 1e4,
|
|
725
|
+
render: (entry) => this.messageFor(entry),
|
|
726
|
+
assemble: (messages, overflow) => this.buildPayload(messages, overflow)
|
|
727
|
+
});
|
|
728
|
+
Object.defineProperty(this, "queue", {
|
|
729
|
+
value: queue,
|
|
730
|
+
enumerable: false
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
write(entry) {
|
|
734
|
+
if (!meetsLevel(entry.level, this.level)) return;
|
|
735
|
+
this.queue.push(entry);
|
|
736
|
+
}
|
|
737
|
+
flush() {
|
|
738
|
+
return this.queue.flush();
|
|
739
|
+
}
|
|
740
|
+
close() {
|
|
741
|
+
return this.queue.close();
|
|
742
|
+
}
|
|
743
|
+
/** Never serialise the webhook URL (JSON.stringify, util.inspect). */
|
|
744
|
+
toJSON() {
|
|
745
|
+
return {
|
|
746
|
+
name: this.name,
|
|
747
|
+
level: this.level
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/** `showHidden` would otherwise print the non-enumerable queue (and its URL). */
|
|
751
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
752
|
+
return this.toJSON();
|
|
753
|
+
}
|
|
754
|
+
buildPayload(messages, overflow) {
|
|
755
|
+
const blocks = messages.flatMap((m) => m.blocks ?? []);
|
|
756
|
+
const counter = overflow > 0 ? `… and ${overflow} more entr${overflow === 1 ? "y" : "ies"} not shown` : "";
|
|
757
|
+
if (counter) blocks.push({
|
|
758
|
+
type: "context",
|
|
759
|
+
elements: [{
|
|
760
|
+
type: "mrkdwn",
|
|
761
|
+
text: counter
|
|
762
|
+
}]
|
|
763
|
+
});
|
|
764
|
+
const payload = {
|
|
765
|
+
text: truncateText([...messages.map((m) => m.text ?? ""), counter].filter(Boolean).join("\n"), SLACK_TEXT_MAX),
|
|
766
|
+
blocks: blocks.slice(0, SLACK_MAX_BLOCKS)
|
|
767
|
+
};
|
|
768
|
+
if (this.username) payload["username"] = truncateText(this.username, 80);
|
|
769
|
+
if (this.iconEmoji) payload["icon_emoji"] = this.iconEmoji;
|
|
770
|
+
return payload;
|
|
771
|
+
}
|
|
772
|
+
messageFor(entry) {
|
|
773
|
+
if (this.formatter) try {
|
|
774
|
+
return this.formatter(entry);
|
|
775
|
+
} catch {
|
|
776
|
+
require_internal_log.internalWarn("SlackTransport: formatter threw; using the default message");
|
|
777
|
+
}
|
|
778
|
+
return defaultMessage(entry);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
function defaultMessage(entry) {
|
|
782
|
+
const level = singleLine(entry.level).toUpperCase();
|
|
783
|
+
const emoji = LEVEL_EMOJI[entry.level.toLowerCase()] ?? ":white_circle:";
|
|
784
|
+
const headline = `[${level}] ${singleLine(entry.message)}`;
|
|
785
|
+
const blocks = [{
|
|
786
|
+
type: "header",
|
|
787
|
+
text: {
|
|
788
|
+
type: "plain_text",
|
|
789
|
+
text: truncateText(escapeSlack(headline), 150),
|
|
790
|
+
emoji: false
|
|
791
|
+
}
|
|
792
|
+
}];
|
|
793
|
+
const context = [
|
|
794
|
+
["app", entry.appName],
|
|
795
|
+
["env", entry.environment],
|
|
796
|
+
["context", entry.context],
|
|
797
|
+
["trace", entry.traceId],
|
|
798
|
+
["time", safeIso(entry.timestamp)]
|
|
799
|
+
].map(([label, value]) => [label, typeof value === "string" ? singleLine(value) : ""]).filter(([, value]) => value !== "").map(([label, value]) => ({
|
|
800
|
+
type: "mrkdwn",
|
|
801
|
+
text: `*${label}:* ${truncateText(escapeSlack(value), SLACK_FIELD_MAX)}`
|
|
802
|
+
}));
|
|
803
|
+
if (context.length > 0) blocks.push({
|
|
804
|
+
type: "context",
|
|
805
|
+
elements: context
|
|
806
|
+
});
|
|
807
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
808
|
+
const json = truncateText(escapeSlack(require_safe_stringify.stringifyForLog(entry.data)), SLACK_DATA_MAX);
|
|
809
|
+
blocks.push({
|
|
810
|
+
type: "section",
|
|
811
|
+
text: {
|
|
812
|
+
type: "mrkdwn",
|
|
813
|
+
text: `\`\`\`${json}\`\`\``
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
text: `${emoji} ${escapeSlack(truncateText(headline, 150))}`,
|
|
819
|
+
blocks
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
Object.defineProperty(exports, "DiscordTransport", {
|
|
823
|
+
enumerable: true,
|
|
824
|
+
get: function() {
|
|
825
|
+
return DiscordTransport;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
Object.defineProperty(exports, "OtlpLogTransport", {
|
|
829
|
+
enumerable: true,
|
|
830
|
+
get: function() {
|
|
831
|
+
return OtlpLogTransport;
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
Object.defineProperty(exports, "SlackTransport", {
|
|
835
|
+
enumerable: true,
|
|
836
|
+
get: function() {
|
|
837
|
+
return SlackTransport;
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
Object.defineProperty(exports, "toOtelSeverity", {
|
|
841
|
+
enumerable: true,
|
|
842
|
+
get: function() {
|
|
843
|
+
return toOtelSeverity;
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
//# sourceMappingURL=slack.transport-Dlx3xvb7.js.map
|