@camstack/system 1.2.13 → 1.2.15
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/dist/addon-runner.js +2 -2
- package/dist/addon-runner.mjs +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.d.ts +4 -0
- package/dist/builtins/loki-logging/index.js +445 -0
- package/dist/builtins/loki-logging/index.mjs +431 -0
- package/dist/builtins/loki-logging/loki-destination.d.ts +88 -0
- package/dist/builtins/loki-logging/loki-logging.addon.d.ts +36 -0
- package/dist/builtins/loki-logging/loki-payload.d.ts +46 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-DpKeSZel.mjs → dist-eOaraBhT.mjs} +21 -1
- package/dist/{dist-BQOCTD5b.js → dist-yttC1aUI.js} +26 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +128 -15
- package/dist/index.mjs +127 -16
- package/dist/kernel/config-schema.d.ts +38 -1
- package/dist/logging/log-manager.d.ts +14 -4
- package/dist/logging/log-ring-buffer.d.ts +15 -0
- package/dist/logging/partitioned-log-buffer.d.ts +49 -6
- package/dist/{manifest-python-deps-1I7WdPDD.js → manifest-python-deps-DSrmih6E.js} +1 -1
- package/dist/{manifest-python-deps-I2MGvDvo.mjs → manifest-python-deps-bn3qrsxR.mjs} +1 -1
- package/package.json +13 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_dist = require("./dist-
|
|
3
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
2
|
+
const require_dist = require("./dist-yttC1aUI.js");
|
|
3
|
+
const require_manifest_python_deps = require("./manifest-python-deps-DSrmih6E.js");
|
|
4
4
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
node_fs = require_chunk.__toESM(node_fs);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { N as isCollectionArrayMethod, t as ALL_CAPABILITY_DEFINITIONS } from "./dist-
|
|
2
|
-
import { Ct as resolveAddonClass, I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps } from "./manifest-python-deps-
|
|
1
|
+
import { N as isCollectionArrayMethod, t as ALL_CAPABILITY_DEFINITIONS } from "./dist-eOaraBhT.mjs";
|
|
2
|
+
import { Ct as resolveAddonClass, I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps } from "./manifest-python-deps-bn3qrsxR.mjs";
|
|
3
3
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
4
4
|
import { register } from "node:module";
|
|
5
5
|
import * as fs from "node:fs";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as EventCategory, ot as errMsg, p as addonPagesCapability, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
|
|
8
8
|
let node_fs = require("node:fs");
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as EventCategory, h as addonWidgetsCapability, ot as errMsg, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
//#region src/builtins/alerts/alerts.addon.ts
|
|
8
8
|
var CATEGORY_MAPPINGS = {
|
|
9
9
|
"addon.crashed": {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as EventCategory, bt as createEvent, g as alertsCapability, n as AlertSchema, ot as errMsg, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
//#region src/builtins/alerts/alerts.addon.ts
|
|
3
3
|
var CATEGORY_MAPPINGS = {
|
|
4
4
|
"addon.crashed": {
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
let node_fs = require("node:fs");
|
|
8
8
|
let node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
9
9
|
node_fs = require_chunk.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as EventCategory, st as BaseAddon, y as backupCapability } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import Kt from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/console-logging/console-destination.ts
|
|
9
9
|
var LEVEL_RANK = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { st as BaseAddon, z as logDestinationCapability } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/console-logging/console-destination.ts
|
|
4
4
|
var LEVEL_RANK = {
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
let _camstack_types_node = require("@camstack/types/node");
|
|
8
8
|
let zod = require("zod");
|
|
9
9
|
let node_crypto = require("node:crypto");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as deviceStatusCapability, D as enumerateSchemaFields, E as enumerateItemArrayFields, K as parseStreamParamsFormPatch, Mt as EventCategory, O as evaluateLinkExpression, S as deviceStateCapability, Tt as isDeviceConfigCap, W as normalizeUnit, Z as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as validateExpressionSource, b as buildStreamParamsConfigSchema, dt as DeviceFeature, ft as DeviceRole, gt as WELL_KNOWN_TAB_MAP, j as getByPath, jt as sleep, l as STREAM_PROFILE_META, o as DeviceStatusSchema, ot as errMsg, pt as DeviceType, rt as toExpressionValue, st as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, x as deviceManagerCapability } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import { canonicalDeviceFingerprint } from "@camstack/types/node";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
8
8
|
/**
|
|
9
9
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as EventCategory, bt as createEvent, ot as errMsg, pt as DeviceType, st as BaseAddon, w as doorbellCapability } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
//#region src/builtins/doorbell/trigger-engine.ts
|
|
3
3
|
/**
|
|
4
4
|
* Pure trigger logic for the virtual-doorbell builtin — edge detection over
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
9
9
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { st as BaseAddon, z as logDestinationCapability } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
//#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
|
|
4
4
|
var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
9
9
|
let crypto$1 = require("crypto");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as UserRecordSchema,
|
|
1
|
+
import { f as UserRecordSchema, it as userManagementCapability, r as ApiKeyRecordSchema, st as BaseAddon, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as crypto$1 from "node:crypto";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
3
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
4
4
|
let node_os = require("node:os");
|
|
5
5
|
node_os = require_chunk.__toESM(node_os);
|
|
6
6
|
//#region src/builtins/local-network/local-network.addon.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Mt as EventCategory, R as localNetworkCapability, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
//#region src/builtins/local-network/local-network.addon.ts
|
|
4
4
|
/**
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { LokiLoggingAddon } from './loki-logging.addon.js';
|
|
2
|
+
export { LokiDestination, type LokiConfig, type LokiStatus } from './loki-destination.js';
|
|
3
|
+
export { buildPushBody, formatLokiLine, parseExtraLabels, sanitizeLabelName, toUnixNano, type LokiPushBody, type LokiStream, } from './loki-payload.js';
|
|
4
|
+
export { LokiLoggingAddon as default } from './loki-logging.addon.js';
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
require("../../chunk-Cek0wNdY.js");
|
|
6
|
+
const require_dist = require("../../dist-yttC1aUI.js");
|
|
7
|
+
//#region src/builtins/loki-logging/loki-payload.ts
|
|
8
|
+
/**
|
|
9
|
+
* Loki rejects a label name that is not a valid Prometheus label
|
|
10
|
+
* (`[a-zA-Z_][a-zA-Z0-9_]*`), so operator-supplied keys are sanitised rather
|
|
11
|
+
* than passed through — a 400 from the push endpoint on every batch is a much
|
|
12
|
+
* worse outcome than a renamed label.
|
|
13
|
+
*/
|
|
14
|
+
function sanitizeLabelName(raw) {
|
|
15
|
+
const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
|
|
16
|
+
return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
|
|
17
|
+
}
|
|
18
|
+
/** Parse an operator's `key=value,key2=value2` label string. */
|
|
19
|
+
function parseExtraLabels(raw) {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const pair of raw.split(",")) {
|
|
22
|
+
const trimmed = pair.trim();
|
|
23
|
+
if (trimmed.length === 0) continue;
|
|
24
|
+
const eq = trimmed.indexOf("=");
|
|
25
|
+
if (eq <= 0) continue;
|
|
26
|
+
const name = sanitizeLabelName(trimmed.slice(0, eq).trim());
|
|
27
|
+
const value = trimmed.slice(eq + 1).trim();
|
|
28
|
+
if (value.length > 0) out[name] = value;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/** `Date` → Loki's nanosecond-precision string timestamp. */
|
|
33
|
+
function toUnixNano(timestamp) {
|
|
34
|
+
const ms = timestamp instanceof Date ? timestamp.getTime() : typeof timestamp === "number" ? timestamp : Date.parse(timestamp);
|
|
35
|
+
return `${String(Math.trunc(Number.isFinite(ms) ? ms : 0))}000000`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The line body: JSON carrying everything that must NOT be a label. Loki can
|
|
39
|
+
* parse this with `| json` at query time, so `scope` and `tags.deviceId` stay
|
|
40
|
+
* filterable without ever being indexed.
|
|
41
|
+
*/
|
|
42
|
+
function formatLokiLine(entry) {
|
|
43
|
+
const line = { message: entry.message };
|
|
44
|
+
if (entry.scope !== void 0 && entry.scope.length > 0) line["scope"] = entry.scope;
|
|
45
|
+
if (entry.tags !== void 0 && Object.keys(entry.tags).length > 0) line["tags"] = entry.tags;
|
|
46
|
+
if (entry.meta !== void 0 && Object.keys(entry.meta).length > 0) line["meta"] = entry.meta;
|
|
47
|
+
return JSON.stringify(line);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Group a batch into one stream per distinct label set, and sort each stream's
|
|
51
|
+
* values by timestamp.
|
|
52
|
+
*
|
|
53
|
+
* The sort is not cosmetic. Loki historically rejected a stream whose entries
|
|
54
|
+
* were not in ascending time order, and even with out-of-order ingestion enabled
|
|
55
|
+
* it is bounded by a window. Entries reach a destination from several concurrent
|
|
56
|
+
* producers, so a batch is NOT naturally ordered — without this, some pushes
|
|
57
|
+
* fail with `entry out of order` and those log lines are simply gone.
|
|
58
|
+
*/
|
|
59
|
+
function buildPushBody(entries, labels) {
|
|
60
|
+
if (entries.length === 0) return null;
|
|
61
|
+
const base = {
|
|
62
|
+
job: labels.job,
|
|
63
|
+
node: labels.node
|
|
64
|
+
};
|
|
65
|
+
for (const [key, value] of Object.entries(labels.extra ?? {})) base[key] = value;
|
|
66
|
+
const byLevel = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const values = byLevel.get(entry.level) ?? [];
|
|
69
|
+
values.push([toUnixNano(entry.timestamp), formatLokiLine(entry)]);
|
|
70
|
+
byLevel.set(entry.level, values);
|
|
71
|
+
}
|
|
72
|
+
const streams = [];
|
|
73
|
+
for (const [level, values] of byLevel) streams.push({
|
|
74
|
+
stream: {
|
|
75
|
+
...base,
|
|
76
|
+
level
|
|
77
|
+
},
|
|
78
|
+
values: values.toSorted((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
79
|
+
});
|
|
80
|
+
return { streams };
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/builtins/loki-logging/loki-destination.ts
|
|
84
|
+
var LEVEL_ORDER = {
|
|
85
|
+
debug: 0,
|
|
86
|
+
info: 1,
|
|
87
|
+
warn: 2,
|
|
88
|
+
error: 3
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Bounded so an unreachable Loki cannot grow the heap without limit. 10k entries
|
|
92
|
+
* of a few hundred bytes is a handful of MB — enough to ride out a restart of the
|
|
93
|
+
* Loki container, not enough to matter if it never comes back.
|
|
94
|
+
*/
|
|
95
|
+
var MAX_QUEUE = 1e4;
|
|
96
|
+
var LokiDestination = class {
|
|
97
|
+
config = null;
|
|
98
|
+
queue = [];
|
|
99
|
+
timer;
|
|
100
|
+
flushing = false;
|
|
101
|
+
pushed = 0;
|
|
102
|
+
dropped = 0;
|
|
103
|
+
failures = 0;
|
|
104
|
+
lastError = null;
|
|
105
|
+
lastPushAtMs = null;
|
|
106
|
+
fetchFn;
|
|
107
|
+
setIntervalFn;
|
|
108
|
+
clearIntervalFn;
|
|
109
|
+
constructor(deps) {
|
|
110
|
+
this.fetchFn = deps?.fetchFn ?? ((url, init) => globalThis.fetch(url, {
|
|
111
|
+
method: init.method,
|
|
112
|
+
headers: { ...init.headers },
|
|
113
|
+
body: init.body,
|
|
114
|
+
...init.signal ? { signal: init.signal } : {}
|
|
115
|
+
}));
|
|
116
|
+
this.setIntervalFn = deps?.setIntervalFn ?? setInterval;
|
|
117
|
+
this.clearIntervalFn = deps?.clearIntervalFn ?? clearInterval;
|
|
118
|
+
}
|
|
119
|
+
async initialize(config) {
|
|
120
|
+
this.config = config ?? null;
|
|
121
|
+
if (!this.isActive()) return;
|
|
122
|
+
this.timer = this.setIntervalFn(() => {
|
|
123
|
+
this.flush();
|
|
124
|
+
}, Math.max(250, config?.batchIntervalMs ?? 5e3));
|
|
125
|
+
const timer = this.timer;
|
|
126
|
+
if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
|
|
127
|
+
}
|
|
128
|
+
/** Enabled AND pointed somewhere. An enabled destination with no URL is inert. */
|
|
129
|
+
isActive() {
|
|
130
|
+
return this.config !== null && this.config.enabled && this.config.url.trim().length > 0;
|
|
131
|
+
}
|
|
132
|
+
write(entry) {
|
|
133
|
+
if (!this.isActive() || this.config === null) return;
|
|
134
|
+
if (LEVEL_ORDER[entry.level] < LEVEL_ORDER[this.config.level]) return;
|
|
135
|
+
this.queue.push(entry);
|
|
136
|
+
if (this.queue.length > MAX_QUEUE) {
|
|
137
|
+
const overflow = this.queue.length - MAX_QUEUE;
|
|
138
|
+
this.queue.splice(0, overflow);
|
|
139
|
+
this.dropped += overflow;
|
|
140
|
+
}
|
|
141
|
+
if (this.queue.length >= this.config.batchSize) this.flush();
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Push whatever is queued. Never throws and never logs.
|
|
145
|
+
*
|
|
146
|
+
* On failure the batch is put BACK at the front of the queue so a transient
|
|
147
|
+
* Loki outage does not lose lines; `MAX_QUEUE` is what stops that from growing
|
|
148
|
+
* without bound.
|
|
149
|
+
*/
|
|
150
|
+
async flush() {
|
|
151
|
+
if (this.flushing || !this.isActive() || this.config === null) return;
|
|
152
|
+
if (this.queue.length === 0) return;
|
|
153
|
+
this.flushing = true;
|
|
154
|
+
const batch = this.queue;
|
|
155
|
+
this.queue = [];
|
|
156
|
+
try {
|
|
157
|
+
const body = buildPushBody(batch, {
|
|
158
|
+
job: this.config.job,
|
|
159
|
+
node: this.config.node,
|
|
160
|
+
extra: parseExtraLabels(this.config.extraLabels)
|
|
161
|
+
});
|
|
162
|
+
if (body !== null) {
|
|
163
|
+
await this.push(body, this.config);
|
|
164
|
+
this.pushed += batch.length;
|
|
165
|
+
this.lastPushAtMs = Date.now();
|
|
166
|
+
this.lastError = null;
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
this.failures += 1;
|
|
170
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
171
|
+
this.queue = [...batch, ...this.queue];
|
|
172
|
+
if (this.queue.length > MAX_QUEUE) {
|
|
173
|
+
const overflow = this.queue.length - MAX_QUEUE;
|
|
174
|
+
this.queue.splice(0, overflow);
|
|
175
|
+
this.dropped += overflow;
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
this.flushing = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async push(body, config) {
|
|
182
|
+
const endpoint = `${config.url.replace(/\/+$/, "")}/loki/api/v1/push`;
|
|
183
|
+
const headers = { "content-type": "application/json" };
|
|
184
|
+
if (config.tenantId.trim().length > 0) headers["X-Scope-OrgID"] = config.tenantId.trim();
|
|
185
|
+
if (config.username.length > 0) headers["authorization"] = `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`;
|
|
186
|
+
const controller = new AbortController();
|
|
187
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1e3, config.timeoutMs));
|
|
188
|
+
try {
|
|
189
|
+
const response = await this.fetchFn(endpoint, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers,
|
|
192
|
+
body: JSON.stringify(body),
|
|
193
|
+
signal: controller.signal
|
|
194
|
+
});
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
const detail = await response.text().catch(() => "");
|
|
197
|
+
throw new Error(`Loki push failed: ${String(response.status)}${detail.length > 0 ? ` — ${detail.slice(0, 200)}` : ""}`);
|
|
198
|
+
}
|
|
199
|
+
} finally {
|
|
200
|
+
clearTimeout(timeout);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
getStatus() {
|
|
204
|
+
return {
|
|
205
|
+
enabled: this.config?.enabled === true,
|
|
206
|
+
configured: this.isActive(),
|
|
207
|
+
queued: this.queue.length,
|
|
208
|
+
pushed: this.pushed,
|
|
209
|
+
dropped: this.dropped,
|
|
210
|
+
failures: this.failures,
|
|
211
|
+
lastError: this.lastError,
|
|
212
|
+
lastPushAtMs: this.lastPushAtMs
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* One-shot reachability probe for the Admin UI's "Test connection" button.
|
|
217
|
+
* Pushes a single synthetic entry, which validates the URL, the auth, the
|
|
218
|
+
* tenant header AND the label policy in one go — a bare GET would validate
|
|
219
|
+
* none of them.
|
|
220
|
+
*/
|
|
221
|
+
async testConnection(config) {
|
|
222
|
+
if (config.url.trim().length === 0) return {
|
|
223
|
+
ok: false,
|
|
224
|
+
error: "No Loki URL configured"
|
|
225
|
+
};
|
|
226
|
+
const body = buildPushBody([{
|
|
227
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
228
|
+
level: "info",
|
|
229
|
+
scope: "loki-logging",
|
|
230
|
+
message: "CamStack connection test"
|
|
231
|
+
}], {
|
|
232
|
+
job: config.job,
|
|
233
|
+
node: config.node,
|
|
234
|
+
extra: parseExtraLabels(config.extraLabels)
|
|
235
|
+
});
|
|
236
|
+
if (body === null) return {
|
|
237
|
+
ok: false,
|
|
238
|
+
error: "Nothing to send"
|
|
239
|
+
};
|
|
240
|
+
try {
|
|
241
|
+
await this.push(body, config);
|
|
242
|
+
return {
|
|
243
|
+
ok: true,
|
|
244
|
+
error: null
|
|
245
|
+
};
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return {
|
|
248
|
+
ok: false,
|
|
249
|
+
error: error instanceof Error ? error.message : String(error)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async query(_filter) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
async shutdown() {
|
|
257
|
+
if (this.timer !== void 0) {
|
|
258
|
+
this.clearIntervalFn(this.timer);
|
|
259
|
+
this.timer = void 0;
|
|
260
|
+
}
|
|
261
|
+
await this.flush();
|
|
262
|
+
this.config = null;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/builtins/loki-logging/loki-logging.addon.ts
|
|
267
|
+
var DEFAULTS = {
|
|
268
|
+
enabled: false,
|
|
269
|
+
url: "",
|
|
270
|
+
job: "camstack",
|
|
271
|
+
level: "info",
|
|
272
|
+
batchIntervalMs: 5e3,
|
|
273
|
+
batchSize: 200,
|
|
274
|
+
extraLabels: "",
|
|
275
|
+
tenantId: "",
|
|
276
|
+
username: "",
|
|
277
|
+
password: "",
|
|
278
|
+
timeoutMs: 1e4
|
|
279
|
+
};
|
|
280
|
+
var LokiLoggingAddon = class extends require_dist.BaseAddon {
|
|
281
|
+
destination = null;
|
|
282
|
+
constructor() {
|
|
283
|
+
super({ ...DEFAULTS });
|
|
284
|
+
}
|
|
285
|
+
async onInitialize() {
|
|
286
|
+
this.destination = new LokiDestination();
|
|
287
|
+
await this.destination.initialize(this.destinationConfig());
|
|
288
|
+
this.ctx.logger.info(this.config.enabled && this.config.url.length > 0 ? `Loki logging initialized → ${this.config.url}` : "Loki logging registered but inactive (set a URL and enable it)");
|
|
289
|
+
return [{
|
|
290
|
+
capability: require_dist.logDestinationCapability,
|
|
291
|
+
provider: this.destination
|
|
292
|
+
}];
|
|
293
|
+
}
|
|
294
|
+
async onShutdown() {
|
|
295
|
+
await this.destination?.shutdown();
|
|
296
|
+
this.destination = null;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* `node` is a Loki LABEL, so it must be the node's real identity and must not
|
|
300
|
+
* be operator-editable — a typo would fork the stream and split a node's history
|
|
301
|
+
* in two. `kernel.localNodeId` is the Moleculer node id (`'hub'` on the hub, the
|
|
302
|
+
* agent's own name elsewhere), which is exactly the bounded value wanted here.
|
|
303
|
+
*
|
|
304
|
+
* NOT named `resolveConfig`: `BaseAddon` already has a protected
|
|
305
|
+
* `resolveConfig()` that reloads persisted settings, and shadowing it with a
|
|
306
|
+
* different signature is a type error — usefully, since silently overriding it
|
|
307
|
+
* would have broken this addon's settings reload.
|
|
308
|
+
*/
|
|
309
|
+
destinationConfig() {
|
|
310
|
+
return {
|
|
311
|
+
...this.config,
|
|
312
|
+
node: this.ctx.kernel.localNodeId ?? "hub"
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
getDestination() {
|
|
316
|
+
if (!this.destination) throw new Error("Loki logging not initialized");
|
|
317
|
+
return this.destination;
|
|
318
|
+
}
|
|
319
|
+
globalSettingsSchema() {
|
|
320
|
+
return this.schema({ sections: [{
|
|
321
|
+
id: "loki-logging",
|
|
322
|
+
title: "Loki",
|
|
323
|
+
description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations — enabling this does not disable those.",
|
|
324
|
+
columns: 2,
|
|
325
|
+
fields: [
|
|
326
|
+
this.field({
|
|
327
|
+
type: "boolean",
|
|
328
|
+
key: "enabled",
|
|
329
|
+
label: "Enabled",
|
|
330
|
+
description: "Off by default. Needs a URL below to do anything.",
|
|
331
|
+
default: false
|
|
332
|
+
}),
|
|
333
|
+
this.field({
|
|
334
|
+
type: "text",
|
|
335
|
+
key: "url",
|
|
336
|
+
label: "Loki URL",
|
|
337
|
+
description: "Base URL, e.g. http://loki:3100 — the /loki/api/v1/push path is added.",
|
|
338
|
+
placeholder: "http://loki:3100",
|
|
339
|
+
default: ""
|
|
340
|
+
}),
|
|
341
|
+
this.field({
|
|
342
|
+
type: "select",
|
|
343
|
+
key: "level",
|
|
344
|
+
label: "Minimum level",
|
|
345
|
+
default: "info",
|
|
346
|
+
options: [
|
|
347
|
+
{
|
|
348
|
+
value: "debug",
|
|
349
|
+
label: "Debug"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
value: "info",
|
|
353
|
+
label: "Info"
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
value: "warn",
|
|
357
|
+
label: "Warn"
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
value: "error",
|
|
361
|
+
label: "Error"
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
}),
|
|
365
|
+
this.field({
|
|
366
|
+
type: "text",
|
|
367
|
+
key: "job",
|
|
368
|
+
label: "Job label",
|
|
369
|
+
description: "The Loki `job` label. Keep it fixed — labels are indexed.",
|
|
370
|
+
default: "camstack"
|
|
371
|
+
}),
|
|
372
|
+
this.field({
|
|
373
|
+
type: "text",
|
|
374
|
+
key: "extraLabels",
|
|
375
|
+
label: "Extra labels",
|
|
376
|
+
description: "Static `key=value,key2=value2`. Low-cardinality only: per-entry values belong in the line body, and a high-cardinality label will bring Loki down.",
|
|
377
|
+
placeholder: "env=home,site=garage",
|
|
378
|
+
default: ""
|
|
379
|
+
}),
|
|
380
|
+
this.field({
|
|
381
|
+
type: "text",
|
|
382
|
+
key: "tenantId",
|
|
383
|
+
label: "Tenant ID",
|
|
384
|
+
description: "Sent as X-Scope-OrgID. Required by Grafana Cloud and multi-tenant Loki.",
|
|
385
|
+
default: ""
|
|
386
|
+
}),
|
|
387
|
+
this.field({
|
|
388
|
+
type: "text",
|
|
389
|
+
key: "username",
|
|
390
|
+
label: "Username",
|
|
391
|
+
description: "HTTP basic auth. Leave empty for none.",
|
|
392
|
+
default: ""
|
|
393
|
+
}),
|
|
394
|
+
this.field({
|
|
395
|
+
type: "password",
|
|
396
|
+
key: "password",
|
|
397
|
+
label: "Password",
|
|
398
|
+
default: ""
|
|
399
|
+
}),
|
|
400
|
+
this.field({
|
|
401
|
+
type: "number",
|
|
402
|
+
key: "batchIntervalMs",
|
|
403
|
+
label: "Flush interval",
|
|
404
|
+
description: "How often a partial batch is pushed.",
|
|
405
|
+
min: 250,
|
|
406
|
+
max: 6e4,
|
|
407
|
+
step: 250,
|
|
408
|
+
default: 5e3,
|
|
409
|
+
unit: "ms"
|
|
410
|
+
}),
|
|
411
|
+
this.field({
|
|
412
|
+
type: "number",
|
|
413
|
+
key: "batchSize",
|
|
414
|
+
label: "Batch size",
|
|
415
|
+
description: "Push early once this many entries are queued.",
|
|
416
|
+
min: 1,
|
|
417
|
+
max: 5e3,
|
|
418
|
+
step: 10,
|
|
419
|
+
default: 200
|
|
420
|
+
}),
|
|
421
|
+
this.field({
|
|
422
|
+
type: "number",
|
|
423
|
+
key: "timeoutMs",
|
|
424
|
+
label: "Request timeout",
|
|
425
|
+
min: 1e3,
|
|
426
|
+
max: 6e4,
|
|
427
|
+
step: 500,
|
|
428
|
+
default: 1e4,
|
|
429
|
+
unit: "ms"
|
|
430
|
+
})
|
|
431
|
+
]
|
|
432
|
+
}] });
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
//#endregion
|
|
436
|
+
exports.LokiDestination = LokiDestination;
|
|
437
|
+
exports.LokiDestination$1 = LokiDestination;
|
|
438
|
+
exports.LokiLoggingAddon = LokiLoggingAddon;
|
|
439
|
+
exports.LokiLoggingAddon$1 = LokiLoggingAddon;
|
|
440
|
+
exports.buildPushBody = buildPushBody;
|
|
441
|
+
exports.default = LokiLoggingAddon;
|
|
442
|
+
exports.formatLokiLine = formatLokiLine;
|
|
443
|
+
exports.parseExtraLabels = parseExtraLabels;
|
|
444
|
+
exports.sanitizeLabelName = sanitizeLabelName;
|
|
445
|
+
exports.toUnixNano = toUnixNano;
|