@camstack/core 0.1.13 → 0.1.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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +220 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -0
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +9 -0
- package/dist/builtins/addon-pages-aggregator/index.js +222 -0
- package/dist/builtins/addon-pages-aggregator/index.js.map +1 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs +9 -0
- package/dist/builtins/addon-pages-aggregator/index.mjs.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +200 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +9 -0
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.js +202 -0
- package/dist/builtins/addon-widgets-aggregator/index.js.map +1 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs +9 -0
- package/dist/builtins/addon-widgets-aggregator/index.mjs.map +1 -0
- package/dist/builtins/alerts/alerts.addon.js +443 -0
- package/dist/builtins/alerts/alerts.addon.js.map +1 -0
- package/dist/builtins/alerts/alerts.addon.mjs +9 -0
- package/dist/builtins/alerts/alerts.addon.mjs.map +1 -0
- package/dist/builtins/alerts/index.js +443 -0
- package/dist/builtins/alerts/index.js.map +1 -0
- package/dist/builtins/alerts/index.mjs +8 -0
- package/dist/builtins/alerts/index.mjs.map +1 -0
- package/dist/builtins/console-logging/index.js +242 -0
- package/dist/builtins/console-logging/index.js.map +1 -0
- package/dist/builtins/console-logging/index.mjs +11 -0
- package/dist/builtins/console-logging/index.mjs.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.js +2155 -0
- package/dist/builtins/device-manager/device-manager.addon.js.map +1 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs +9 -0
- package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -0
- package/dist/builtins/device-manager/index.js +2157 -0
- package/dist/builtins/device-manager/index.js.map +1 -0
- package/dist/builtins/device-manager/index.mjs +10 -0
- package/dist/builtins/device-manager/index.mjs.map +1 -0
- package/dist/builtins/hub-forwarder/index.js +297 -0
- package/dist/builtins/hub-forwarder/index.js.map +1 -0
- package/dist/builtins/hub-forwarder/index.mjs +11 -0
- package/dist/builtins/hub-forwarder/index.mjs.map +1 -0
- package/dist/builtins/local-auth/index.js +623 -0
- package/dist/builtins/local-auth/index.js.map +1 -0
- package/dist/builtins/local-auth/index.mjs +8 -0
- package/dist/builtins/local-auth/index.mjs.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.js +623 -0
- package/dist/builtins/local-auth/local-auth.addon.js.map +1 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs +9 -0
- package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -0
- package/dist/builtins/local-backup/index.js +53 -68
- package/dist/builtins/local-backup/index.js.map +1 -1
- package/dist/builtins/local-backup/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +898 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +7 -0
- package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -0
- package/dist/builtins/snapshot/index.js +504 -0
- package/dist/builtins/snapshot/index.js.map +1 -0
- package/dist/builtins/snapshot/index.mjs +477 -0
- package/dist/builtins/snapshot/index.mjs.map +1 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +16 -166
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +554 -621
- package/dist/builtins/sqlite-storage/index.js.map +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +9 -11
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +368 -130
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/system-config/index.js +189 -0
- package/dist/builtins/system-config/index.js.map +1 -0
- package/dist/builtins/system-config/index.mjs +10 -0
- package/dist/builtins/system-config/index.mjs.map +1 -0
- package/dist/builtins/system-config/system-config.addon.js +187 -0
- package/dist/builtins/system-config/system-config.addon.js.map +1 -0
- package/dist/builtins/system-config/system-config.addon.mjs +9 -0
- package/dist/builtins/system-config/system-config.addon.mjs.map +1 -0
- package/dist/builtins/winston-logging/index.js +185 -65
- package/dist/builtins/winston-logging/index.js.map +1 -1
- package/dist/builtins/winston-logging/index.mjs +2 -1
- package/dist/chunk-2CIYKDRN.mjs +1 -0
- package/dist/chunk-2CIYKDRN.mjs.map +1 -0
- package/dist/chunk-2F76X6NL.mjs +136 -0
- package/dist/chunk-2F76X6NL.mjs.map +1 -0
- package/dist/chunk-2QUFBZ7M.mjs +1 -0
- package/dist/chunk-2QUFBZ7M.mjs.map +1 -0
- package/dist/chunk-3BK2Y7GY.mjs +593 -0
- package/dist/chunk-3BK2Y7GY.mjs.map +1 -0
- package/dist/chunk-4OOHFJHT.mjs +421 -0
- package/dist/chunk-4OOHFJHT.mjs.map +1 -0
- package/dist/chunk-4XHB7IHT.mjs +809 -0
- package/dist/chunk-4XHB7IHT.mjs.map +1 -0
- package/dist/{chunk-2F3XZYRW.mjs → chunk-6M2HSSTQ.mjs} +16 -7
- package/dist/chunk-6M2HSSTQ.mjs.map +1 -0
- package/dist/{chunk-SO4LROOT.mjs → chunk-7FI7SQS7.mjs} +54 -69
- package/dist/chunk-7FI7SQS7.mjs.map +1 -0
- package/dist/chunk-ED57RCQE.mjs +171 -0
- package/dist/chunk-ED57RCQE.mjs.map +1 -0
- package/dist/chunk-FZN56HGQ.mjs +626 -0
- package/dist/chunk-FZN56HGQ.mjs.map +1 -0
- package/dist/chunk-GL4OOB25.mjs +51 -0
- package/dist/chunk-GL4OOB25.mjs.map +1 -0
- package/dist/chunk-KDG2NTDB.mjs +137 -0
- package/dist/chunk-KDG2NTDB.mjs.map +1 -0
- package/dist/chunk-NRBQWBDM.mjs +191 -0
- package/dist/chunk-NRBQWBDM.mjs.map +1 -0
- package/dist/chunk-O4V246GG.mjs +2137 -0
- package/dist/chunk-O4V246GG.mjs.map +1 -0
- package/dist/chunk-QT57H266.mjs +163 -0
- package/dist/chunk-QT57H266.mjs.map +1 -0
- package/dist/chunk-QX4RH25I.mjs +141 -0
- package/dist/chunk-QX4RH25I.mjs.map +1 -0
- package/dist/chunk-TB562PZX.mjs +86 -0
- package/dist/chunk-TB562PZX.mjs.map +1 -0
- package/dist/chunk-TDYPZXK5.mjs +1 -0
- package/dist/chunk-TDYPZXK5.mjs.map +1 -0
- package/dist/chunk-UJI4LN5P.mjs +36 -0
- package/dist/chunk-UJI4LN5P.mjs.map +1 -0
- package/dist/chunk-W6RTHQGP.mjs +1 -0
- package/dist/chunk-W6RTHQGP.mjs.map +1 -0
- package/dist/chunk-ZELBCPDC.mjs +369 -0
- package/dist/chunk-ZELBCPDC.mjs.map +1 -0
- package/dist/index.d.mts +1103 -544
- package/dist/index.d.ts +1103 -544
- package/dist/index.js +7032 -6033
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +568 -2226
- package/dist/index.mjs.map +1 -1
- package/dist/resource-monitor-UZUGPIAU.mjs +9 -0
- package/dist/resource-monitor-UZUGPIAU.mjs.map +1 -0
- package/dist/storage-location-manager-HFNB3PCS.mjs +7 -0
- package/dist/storage-location-manager-HFNB3PCS.mjs.map +1 -0
- package/package.json +123 -2
- package/dist/builtins/local-backup/index.d.mts +0 -42
- package/dist/builtins/local-backup/index.d.ts +0 -42
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.mts +0 -2
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +0 -2
- package/dist/builtins/sqlite-storage/index.d.mts +0 -4
- package/dist/builtins/sqlite-storage/index.d.ts +0 -4
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.mts +0 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +0 -2
- package/dist/builtins/winston-logging/index.d.mts +0 -30
- package/dist/builtins/winston-logging/index.d.ts +0 -30
- package/dist/chunk-2F3XZYRW.mjs.map +0 -1
- package/dist/chunk-LQFPAEQF.mjs +0 -147
- package/dist/chunk-LQFPAEQF.mjs.map +0 -1
- package/dist/chunk-R3DIIBBX.mjs +0 -532
- package/dist/chunk-R3DIIBBX.mjs.map +0 -1
- package/dist/chunk-SMNR44VG.mjs +0 -386
- package/dist/chunk-SMNR44VG.mjs.map +0 -1
- package/dist/chunk-SO4LROOT.mjs.map +0 -1
- package/dist/chunk-SPA4JBKN.mjs +0 -175
- package/dist/chunk-SPA4JBKN.mjs.map +0 -1
- package/dist/dist-3BY63UQ5.mjs +0 -2151
- package/dist/dist-3BY63UQ5.mjs.map +0 -1
- package/dist/filesystem-storage.addon-C42r589X.d.mts +0 -57
- package/dist/filesystem-storage.addon-C42r589X.d.ts +0 -57
- package/dist/sql-schema-CKz78rId.d.mts +0 -97
- package/dist/sql-schema-CKz78rId.d.ts +0 -97
- package/dist/sqlite-settings.addon-KwG-uKMP.d.mts +0 -79
- package/dist/sqlite-settings.addon-KwG-uKMP.d.ts +0 -79
- package/dist/storage-location-manager-KKDQNAKA.mjs +0 -7
- /package/dist/{storage-location-manager-KKDQNAKA.mjs.map → builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/builtins/system-config/system-config.addon.ts"],"sourcesContent":["import type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n} from '@camstack/types'\nimport { BaseAddon, hydrateSchema , errMsg } from '@camstack/types'\n\n/**\n * Built-in `system-config` addon — Phase 4 of the settings redesign.\n *\n * Exposes the cluster-wide yml-backed sections that the bootstrap\n * loader reads before any addon is instantiated — things that cannot\n * live inside an addon because changing them requires a server\n * restart (port, host, dataPath) or because they own no runtime\n * state of their own (auth token expiry, global ffmpeg binary path).\n *\n * Sections exposed:\n * - server (port, host, dataPath — READ-ONLY, loaded from config.yaml at bootstrap)\n * - auth (tokenExpiry)\n * - ffmpeg (binaryPath, hwAccel, threadCount)\n *\n * Sections NOT exposed (each owned by the addon that implements them):\n * - logging → `winston-logging` addon (level + retention)\n * - recording → `recording` addon (segment duration, retention)\n * - retention → analytics / detection addons (per-table retention)\n *\n * Implementation notes:\n * - Runs only on the hub process (yml sections are hub-side state).\n * On agent nodes this addon would still instantiate, but every\n * `ctx.settings.getSection(section)` call would hit an empty\n * ConfigManager section so the UI would show the schema defaults.\n * In practice the built-in loader can skip it on agents; for now we\n * just let it run everywhere because it's harmless.\n * - Reads via `ctx.settings.getSection(section)` and writes via\n * `ctx.settings.setSection(section, patch)` — the explicit\n * yml-section pair on `AddonSettingsApi`.\n * - Does NOT register as a capability provider. It's a pure \"settings\n * surface\" addon — its sole purpose is to expose the yml sections\n * through the new three-level settings API.\n *\n * This addon is declared in `@camstack/core`'s package.json `camstack.addons[]`\n * and loaded automatically via AddonLoader like any other core built-in.\n */\n\n/** Section key → user-facing section title. */\nconst SECTION_TITLES: Readonly<Record<string, string>> = {\n server: 'Server',\n auth: 'Authentication',\n ffmpeg: 'FFmpeg',\n}\n\n/** Keys that map 1:1 to a given yml section — used by `updateGlobalSettings`\n * to figure out which section owns each key in the incoming patch. */\nconst KEY_TO_SECTION: Readonly<Record<string, string>> = {\n // server (read-only)\n port: 'server',\n host: 'server',\n dataPath: 'server',\n // auth\n tokenExpiry: 'auth',\n // ffmpeg\n binaryPath: 'ffmpeg',\n hwAccel: 'ffmpeg',\n threadCount: 'ffmpeg',\n}\n\nexport class SystemConfigAddon extends BaseAddon {\n readonly id = 'system-config'\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<void> {\n this.ctx.logger.info('Initialized — exposes yml-backed sections via getGlobalSettings')\n }\n\n // ── Three-level settings API (only `getGlobalSettings` is implemented) ──\n\n private buildGlobalSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'system-config-server',\n title: SECTION_TITLES['server']!,\n description: 'Core server connection settings — read-only, loaded from config.yaml at bootstrap.',\n columns: 2,\n fields: [\n {\n type: 'info',\n key: 'server-restart-note',\n label: 'Restart required',\n content: 'Server settings are read-only. Change them in config.yaml and restart.',\n variant: 'warning',\n },\n { type: 'text', key: 'port', label: 'Port', description: 'Listening port', disabled: true },\n { type: 'text', key: 'host', label: 'Host', description: 'Bind address', disabled: true },\n { type: 'text', key: 'dataPath', label: 'Data Path', description: 'Root data directory', disabled: true, span: 2 },\n ],\n },\n {\n id: 'system-config-auth',\n title: SECTION_TITLES['auth']!,\n description: 'Token and session settings.',\n columns: 1,\n fields: [\n {\n type: 'text',\n key: 'tokenExpiry',\n label: 'Token Expiry',\n description: 'JWT token lifetime (e.g. 24h, 7d, 1h)',\n placeholder: '24h',\n default: '24h',\n },\n ],\n },\n {\n id: 'system-config-ffmpeg',\n title: SECTION_TITLES['ffmpeg']!,\n description: 'FFmpeg binary and hardware acceleration settings.',\n columns: 2,\n fields: [\n {\n type: 'text',\n key: 'binaryPath',\n label: 'Binary Path',\n description: 'Path to ffmpeg executable',\n placeholder: 'ffmpeg',\n default: 'ffmpeg',\n span: 2,\n },\n {\n type: 'select',\n key: 'hwAccel',\n label: 'Hardware Acceleration',\n description: 'GPU decoding/encoding backend',\n default: 'auto',\n options: [\n { value: 'auto', label: 'Auto-detect' },\n { value: 'none', label: 'None (CPU only)' },\n { value: 'videotoolbox', label: 'VideoToolbox (macOS)' },\n { value: 'vaapi', label: 'VA-API (Linux Intel/AMD)' },\n { value: 'qsv', label: 'QSV (Intel Quick Sync)' },\n { value: 'cuda', label: 'CUDA (NVIDIA)' },\n ],\n },\n {\n type: 'number',\n key: 'threadCount',\n label: 'Thread Count',\n description: '0 = auto (let FFmpeg decide)',\n min: 0,\n max: 16,\n step: 1,\n default: 0,\n },\n ],\n },\n ],\n }\n }\n\n async getGlobalSettings(): Promise<ConfigUISchemaWithValues> {\n const schema = this.buildGlobalSchema()\n if (!this.ctx.settings) {\n return hydrateSchema(schema, {})\n }\n\n // Read all yml sections in parallel and merge their values into a\n // single flat record keyed by field name. The `KEY_TO_SECTION` map\n // guarantees uniqueness (each key belongs to exactly one section).\n const sectionNames = Object.keys(SECTION_TITLES)\n const merged: Record<string, unknown> = {}\n for (const section of sectionNames) {\n try {\n const values = await this.ctx.settings!.getSection(section)\n Object.assign(merged, values)\n } catch (err: unknown) {\n const msg = errMsg(err)\n this.ctx.logger.debug('Failed to read section', { meta: { section, error: msg } })\n }\n }\n\n return hydrateSchema(schema, merged)\n }\n\n async updateGlobalSettings(patch: Record<string, unknown>): Promise<void> {\n if (!this.ctx.settings) {\n throw new Error('system-config: ctx.settings not available — cannot write sections')\n }\n\n // Group the patch keys by the section they belong to, then write\n // each section in one shot via ctx.settings.setSection(). The\n // server section is read-only, so any keys targeting it are skipped\n // with a warning.\n const perSection = new Map<string, Record<string, unknown>>()\n for (const [key, value] of Object.entries(patch)) {\n const section = KEY_TO_SECTION[key]\n if (section === undefined) {\n this.ctx.logger.warn('update ignored unknown key (not mapped to any section)', { meta: { key } })\n continue\n }\n if (section === 'server') {\n this.ctx.logger.warn('update ignored read-only server-section key', { meta: { key } })\n continue\n }\n let bucket = perSection.get(section)\n if (!bucket) {\n bucket = {}\n perSection.set(section, bucket)\n }\n bucket[key] = value\n }\n\n for (const [section, sectionPatch] of perSection) {\n await this.ctx.settings!.setSection(section, sectionPatch)\n this.ctx.logger.info('Wrote keys to section', { meta: { section, keyCount: Object.keys(sectionPatch).length } })\n }\n }\n}\n\nexport default SystemConfigAddon\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -36,16 +36,147 @@ __export(winston_logging_exports, {
|
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(winston_logging_exports);
|
|
38
38
|
|
|
39
|
+
// src/builtins/winston-logging/winston-logging.addon.ts
|
|
40
|
+
var import_types = require("@camstack/types");
|
|
41
|
+
|
|
39
42
|
// src/builtins/winston-logging/winston-destination.ts
|
|
40
43
|
var winston = __toESM(require("winston"));
|
|
41
44
|
var import_winston_daily_rotate_file = __toESM(require("winston-daily-rotate-file"));
|
|
42
45
|
var path = __toESM(require("path"));
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
|
|
47
|
+
// src/logging/formatter.ts
|
|
48
|
+
var USE_COLOR = (() => {
|
|
49
|
+
try {
|
|
50
|
+
if (typeof process === "undefined") return false;
|
|
51
|
+
const env = process.env ?? {};
|
|
52
|
+
if (env["NO_COLOR"]) return false;
|
|
53
|
+
const force = env["FORCE_COLOR"];
|
|
54
|
+
if (force !== void 0) return force !== "" && force !== "0" && force !== "false";
|
|
55
|
+
return Boolean(process.stdout?.isTTY);
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
var NO_COLOR = (() => {
|
|
61
|
+
try {
|
|
62
|
+
return Boolean(process?.env?.["NO_COLOR"]);
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
var RESET = "\x1B[0m";
|
|
68
|
+
var ANSI = {
|
|
69
|
+
green: "\x1B[32m",
|
|
70
|
+
yellow: "\x1B[33m",
|
|
71
|
+
red: "\x1B[31m",
|
|
72
|
+
cyan: "\x1B[36m",
|
|
73
|
+
gray: "\x1B[90m"
|
|
74
|
+
};
|
|
75
|
+
var COLOR_BY_LEVEL = {
|
|
76
|
+
debug: ANSI.gray,
|
|
77
|
+
info: ANSI.green,
|
|
78
|
+
warn: ANSI.yellow,
|
|
79
|
+
error: ANSI.red
|
|
80
|
+
};
|
|
81
|
+
function paint(color, text, enabled) {
|
|
82
|
+
return enabled ? `${color}${text}${RESET}` : text;
|
|
83
|
+
}
|
|
84
|
+
function colorizeLevel(level, text, enabled) {
|
|
85
|
+
if (!enabled) return text;
|
|
86
|
+
const code = COLOR_BY_LEVEL[level];
|
|
87
|
+
return code ? `${code}${text}${RESET}` : text;
|
|
88
|
+
}
|
|
89
|
+
function padRight(value, width) {
|
|
90
|
+
if (value.length >= width) return value.slice(0, width);
|
|
91
|
+
return value + " ".repeat(width - value.length);
|
|
48
92
|
}
|
|
93
|
+
function resolveAgent(tags) {
|
|
94
|
+
const agentId = tags?.agentId;
|
|
95
|
+
if (typeof agentId === "string" && agentId.length > 0) return agentId;
|
|
96
|
+
const nodeId = tags?.nodeId;
|
|
97
|
+
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
98
|
+
return nodeId.includes("/") ? nodeId.split("/")[0] : nodeId;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function resolveAddon(entry) {
|
|
103
|
+
const addonId = entry.tags?.addonId;
|
|
104
|
+
if (typeof addonId === "string" && addonId.length > 0) return addonId;
|
|
105
|
+
return "system";
|
|
106
|
+
}
|
|
107
|
+
function resolveDevice(tags) {
|
|
108
|
+
const name = tags?.deviceName;
|
|
109
|
+
if (typeof name === "string" && name.length > 0) return name;
|
|
110
|
+
const id = tags?.deviceId;
|
|
111
|
+
if (typeof id === "number" && Number.isFinite(id)) return `#${id}`;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
function buildBrand(entry) {
|
|
115
|
+
const agent = resolveAgent(entry.tags) ?? "?";
|
|
116
|
+
const addon = resolveAddon(entry);
|
|
117
|
+
return `${agent}/${addon}`;
|
|
118
|
+
}
|
|
119
|
+
function buildDeviceContext(entry) {
|
|
120
|
+
return resolveDevice(entry.tags);
|
|
121
|
+
}
|
|
122
|
+
function formatTimestamp(value) {
|
|
123
|
+
const date = value instanceof Date ? value : new Date(typeof value === "string" ? value : Date.now());
|
|
124
|
+
return date.toLocaleString("en-US", {
|
|
125
|
+
month: "numeric",
|
|
126
|
+
day: "numeric",
|
|
127
|
+
year: "numeric",
|
|
128
|
+
hour: "numeric",
|
|
129
|
+
minute: "2-digit",
|
|
130
|
+
second: "2-digit",
|
|
131
|
+
hour12: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function formatMeta(meta) {
|
|
135
|
+
if (!meta) return "";
|
|
136
|
+
const keys = Object.keys(meta);
|
|
137
|
+
if (keys.length === 0) return "";
|
|
138
|
+
const parts = [];
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
const value = meta[key];
|
|
141
|
+
parts.push(`${key}=${formatMetaValue(value)}`);
|
|
142
|
+
}
|
|
143
|
+
return `{${parts.join(" ")}}`;
|
|
144
|
+
}
|
|
145
|
+
function formatMetaValue(value) {
|
|
146
|
+
if (value === null) return "null";
|
|
147
|
+
if (value === void 0) return "undefined";
|
|
148
|
+
if (typeof value === "string") {
|
|
149
|
+
return /\s|"/.test(value) ? JSON.stringify(value) : value;
|
|
150
|
+
}
|
|
151
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
152
|
+
if (value instanceof Error) return JSON.stringify(value.message);
|
|
153
|
+
try {
|
|
154
|
+
return JSON.stringify(value);
|
|
155
|
+
} catch {
|
|
156
|
+
return String(value);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
var PID = typeof process !== "undefined" && typeof process.pid === "number" ? String(process.pid) : "?";
|
|
160
|
+
function formatLogLine(entry, options) {
|
|
161
|
+
const wantsColor = options?.colorize ?? USE_COLOR;
|
|
162
|
+
const colorize = wantsColor && !NO_COLOR;
|
|
163
|
+
const levelColor = COLOR_BY_LEVEL[entry.level];
|
|
164
|
+
const brand = paint(ANSI.green, `[${buildBrand(entry)}]`, colorize);
|
|
165
|
+
const entryPid = entry.tags?.pid;
|
|
166
|
+
const pid = typeof entryPid === "number" ? String(entryPid) : PID;
|
|
167
|
+
const timestamp = formatTimestamp(entry.timestamp);
|
|
168
|
+
const level = colorizeLevel(entry.level, padRight(entry.level.toUpperCase(), 5), colorize);
|
|
169
|
+
const device = buildDeviceContext(entry);
|
|
170
|
+
const ctxPart = device ? " " + paint(ANSI.yellow, `[${device}]`, colorize) : "";
|
|
171
|
+
const scope = typeof entry.scope === "string" && entry.scope.length > 0 ? entry.scope : null;
|
|
172
|
+
const scopePart = scope ? " " + paint(ANSI.cyan, `(${scope})`, colorize) : "";
|
|
173
|
+
const metaText = formatMeta(entry.meta);
|
|
174
|
+
const metaPart = metaText ? " " + paint(ANSI.gray, metaText, colorize) : "";
|
|
175
|
+
const message = colorize && levelColor ? `${levelColor}${entry.message}${RESET}` : entry.message;
|
|
176
|
+
return `${brand} ${pid} - ${timestamp} ${level}${ctxPart}${scopePart} ${message}${metaPart}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/builtins/winston-logging/winston-destination.ts
|
|
49
180
|
var WinstonDestination = class {
|
|
50
181
|
logger = null;
|
|
51
182
|
async initialize(config) {
|
|
@@ -54,24 +185,23 @@ var WinstonDestination = class {
|
|
|
54
185
|
retentionDays = 30,
|
|
55
186
|
logsDir = path.join("camstack-data", "logs")
|
|
56
187
|
} = config ?? {};
|
|
57
|
-
const consoleFormat = winston.format.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
);
|
|
188
|
+
const consoleFormat = winston.format.printf((info) => {
|
|
189
|
+
const rendered = info._rendered;
|
|
190
|
+
return typeof rendered === "string" ? rendered : String(info.message);
|
|
191
|
+
});
|
|
192
|
+
const stripRendered = winston.format((info) => {
|
|
193
|
+
delete info["_rendered"];
|
|
194
|
+
return info;
|
|
195
|
+
});
|
|
65
196
|
const fileFormat = winston.format.combine(
|
|
66
197
|
winston.format.timestamp(),
|
|
198
|
+
stripRendered(),
|
|
67
199
|
winston.format.json()
|
|
68
200
|
);
|
|
201
|
+
void consoleFormat;
|
|
69
202
|
this.logger = winston.createLogger({
|
|
70
203
|
level,
|
|
71
204
|
transports: [
|
|
72
|
-
new winston.transports.Console({
|
|
73
|
-
format: consoleFormat
|
|
74
|
-
}),
|
|
75
205
|
new import_winston_daily_rotate_file.default({
|
|
76
206
|
dirname: logsDir,
|
|
77
207
|
filename: "camstack-%DATE%.log",
|
|
@@ -84,14 +214,16 @@ var WinstonDestination = class {
|
|
|
84
214
|
}
|
|
85
215
|
write(entry) {
|
|
86
216
|
if (!this.logger) return;
|
|
87
|
-
const
|
|
88
|
-
const
|
|
217
|
+
const ts = entry.timestamp instanceof Date ? entry.timestamp.toISOString() : String(entry.timestamp);
|
|
218
|
+
const rendered = formatLogLine(entry, { colorize: true });
|
|
89
219
|
this.logger.log({
|
|
90
220
|
level: entry.level,
|
|
91
221
|
message: entry.message,
|
|
92
|
-
|
|
93
|
-
timestamp:
|
|
94
|
-
|
|
222
|
+
_rendered: rendered,
|
|
223
|
+
timestamp: ts,
|
|
224
|
+
scope: entry.scope,
|
|
225
|
+
...entry.tags ? { tags: entry.tags } : {},
|
|
226
|
+
...entry.meta ? { meta: entry.meta } : {}
|
|
95
227
|
});
|
|
96
228
|
}
|
|
97
229
|
async query(_filter) {
|
|
@@ -108,51 +240,47 @@ var WinstonDestination = class {
|
|
|
108
240
|
};
|
|
109
241
|
|
|
110
242
|
// src/builtins/winston-logging/winston-logging.addon.ts
|
|
111
|
-
var WinstonLoggingAddon = class {
|
|
112
|
-
manifest = {
|
|
113
|
-
id: "winston-logging",
|
|
114
|
-
name: "Winston Logging",
|
|
115
|
-
version: "1.0.0",
|
|
116
|
-
capabilities: ["log-destination"]
|
|
117
|
-
};
|
|
243
|
+
var WinstonLoggingAddon = class extends import_types.BaseAddon {
|
|
118
244
|
destination = null;
|
|
119
|
-
|
|
120
|
-
level: "info",
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.currentConfig = {
|
|
125
|
-
level: context.addonConfig.level ?? this.currentConfig.level,
|
|
126
|
-
retentionDays: context.addonConfig.retentionDays ?? this.currentConfig.retentionDays
|
|
127
|
-
};
|
|
128
|
-
const logsDir = context.locationPaths.logs;
|
|
245
|
+
constructor() {
|
|
246
|
+
super({ level: "info", retentionDays: 30 });
|
|
247
|
+
}
|
|
248
|
+
async onInitialize() {
|
|
249
|
+
const logsDir = await this.ctx.api.storage.resolve.query({ location: "logs", relativePath: "" }).catch(() => "camstack-data/logs");
|
|
129
250
|
this.destination = new WinstonDestination();
|
|
130
|
-
await this.destination.initialize({ ...this.
|
|
131
|
-
|
|
251
|
+
await this.destination.initialize({ ...this.config, logsDir });
|
|
252
|
+
this.ctx.logger.info("Winston logging initialized");
|
|
253
|
+
return [{ capability: import_types.logDestinationCapability, provider: this.destination }];
|
|
132
254
|
}
|
|
133
|
-
async
|
|
255
|
+
async onShutdown() {
|
|
134
256
|
await this.destination?.shutdown();
|
|
135
257
|
}
|
|
136
258
|
getDestination() {
|
|
137
259
|
if (!this.destination) throw new Error("Winston not initialized");
|
|
138
260
|
return this.destination;
|
|
139
261
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return this.destination;
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
getConfigSchema() {
|
|
147
|
-
return {
|
|
262
|
+
globalSettingsSchema() {
|
|
263
|
+
return this.schema({
|
|
148
264
|
sections: [
|
|
149
265
|
{
|
|
150
|
-
id: "winston-
|
|
151
|
-
title: "
|
|
152
|
-
description: "
|
|
153
|
-
columns:
|
|
266
|
+
id: "winston-logging",
|
|
267
|
+
title: "Logging",
|
|
268
|
+
description: "Global log verbosity + retention for rotated log files on this node.",
|
|
269
|
+
columns: 2,
|
|
154
270
|
fields: [
|
|
155
|
-
{
|
|
271
|
+
this.field({
|
|
272
|
+
type: "select",
|
|
273
|
+
key: "level",
|
|
274
|
+
label: "Log Level",
|
|
275
|
+
default: "info",
|
|
276
|
+
options: [
|
|
277
|
+
{ value: "debug", label: "Debug" },
|
|
278
|
+
{ value: "info", label: "Info" },
|
|
279
|
+
{ value: "warn", label: "Warn" },
|
|
280
|
+
{ value: "error", label: "Error" }
|
|
281
|
+
]
|
|
282
|
+
}),
|
|
283
|
+
this.field({
|
|
156
284
|
type: "number",
|
|
157
285
|
key: "retentionDays",
|
|
158
286
|
label: "Retention (days)",
|
|
@@ -160,21 +288,13 @@ var WinstonLoggingAddon = class {
|
|
|
160
288
|
min: 1,
|
|
161
289
|
max: 365,
|
|
162
290
|
step: 1,
|
|
291
|
+
default: 30,
|
|
163
292
|
unit: "days"
|
|
164
|
-
}
|
|
293
|
+
})
|
|
165
294
|
]
|
|
166
295
|
}
|
|
167
296
|
]
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
getConfig() {
|
|
171
|
-
return { ...this.currentConfig };
|
|
172
|
-
}
|
|
173
|
-
async onConfigChange(config) {
|
|
174
|
-
this.currentConfig = {
|
|
175
|
-
level: config.level ?? this.currentConfig.level,
|
|
176
|
-
retentionDays: config.retentionDays ?? this.currentConfig.retentionDays
|
|
177
|
-
};
|
|
297
|
+
});
|
|
178
298
|
}
|
|
179
299
|
};
|
|
180
300
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/builtins/winston-logging/index.ts","../../../src/builtins/winston-logging/winston-destination.ts","../../../src/builtins/winston-logging/winston-logging.addon.ts"],"sourcesContent":["export { WinstonLoggingAddon } from './winston-logging.addon'\nexport { WinstonDestination } from './winston-destination'\nexport { WinstonLoggingAddon as default } from './winston-logging.addon'\n","import * as winston from 'winston'\nimport DailyRotateFile from 'winston-daily-rotate-file'\nimport * as path from 'node:path'\nimport type { ILogDestination, LogEntry, LogFilter } from '@camstack/types'\n\ninterface WinstonConfig {\n readonly level: string\n readonly retentionDays: number\n /** Resolved absolute path to the logs directory (replaces the old dataPath field). */\n readonly logsDir: string\n}\n\nfunction formatScope(scope: readonly string[]): string {\n if (scope.length === 0) return ''\n const [first, ...rest] = scope\n const restFormatted = rest.map((s) => `[${s}]`).join('')\n return `(${first})${restFormatted}`\n}\n\nexport class WinstonDestination implements ILogDestination {\n private logger: winston.Logger | null = null\n\n async initialize(config?: WinstonConfig): Promise<void> {\n const {\n level = 'info',\n retentionDays = 30,\n logsDir = path.join('camstack-data', 'logs'),\n } = config ?? {}\n\n const consoleFormat = winston.format.combine(\n winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n winston.format.colorize(),\n winston.format.printf(({ timestamp, level: lvl, message, scope }) => {\n const scopeStr = scope ? ` ${scope}` : ''\n return `${timestamp} [${lvl}]${scopeStr} - ${message}`\n }),\n )\n\n const fileFormat = winston.format.combine(\n winston.format.timestamp(),\n winston.format.json(),\n )\n\n this.logger = winston.createLogger({\n level,\n transports: [\n new winston.transports.Console({\n format: consoleFormat,\n }),\n new DailyRotateFile({\n dirname: logsDir,\n filename: 'camstack-%DATE%.log',\n datePattern: 'YYYY-MM-DD',\n maxFiles: `${retentionDays}d`,\n format: fileFormat,\n }),\n ],\n })\n }\n\n write(entry: LogEntry): void {\n if (!this.logger) return\n\n const scope = formatScope(entry.scope)\n const meta = entry.meta ?? {}\n\n this.logger.log({\n level: entry.level,\n message: entry.message,\n scope,\n timestamp: entry.timestamp.toISOString(),\n ...meta,\n })\n }\n\n async query(_filter: LogFilter): Promise<readonly LogEntry[]> {\n // File-based log querying is not implemented; use structured storage for log queries\n return []\n }\n\n async shutdown(): Promise<void> {\n if (!this.logger) return\n\n await new Promise<void>((resolve) => {\n this.logger!.on('finish', resolve)\n this.logger!.end()\n })\n this.logger = null\n }\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n IConfigurable,\n ConfigUISchema,\n CapabilityProviderMap,\n} from '@camstack/types'\nimport { WinstonDestination } from './winston-destination'\n\nexport class WinstonLoggingAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'winston-logging',\n name: 'Winston Logging',\n version: '1.0.0',\n capabilities: ['log-destination'],\n }\n\n private destination: WinstonDestination | null = null\n private currentConfig = {\n level: 'info',\n retentionDays: 30,\n }\n\n async initialize(context: AddonContext): Promise<void> {\n this.currentConfig = {\n level: (context.addonConfig.level as string) ?? this.currentConfig.level,\n retentionDays: (context.addonConfig.retentionDays as number) ?? this.currentConfig.retentionDays,\n }\n const logsDir = context.locationPaths.logs\n this.destination = new WinstonDestination()\n await this.destination.initialize({ ...this.currentConfig, logsDir })\n context.logger.info('Winston logging initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.destination?.shutdown()\n }\n\n getDestination(): WinstonDestination {\n if (!this.destination) throw new Error('Winston not initialized')\n return this.destination\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'log-destination' && this.destination) {\n return this.destination as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'winston-retention',\n title: 'Log Retention',\n description: 'How long Winston keeps rotated log files on disk.',\n columns: 1,\n fields: [\n {\n type: 'number',\n key: 'retentionDays',\n label: 'Retention (days)',\n description: 'Number of days to keep rotated log files before deletion',\n min: 1,\n max: 365,\n step: 1,\n unit: 'days',\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig = {\n level: (config.level as string) ?? this.currentConfig.level,\n retentionDays: (config.retentionDays as number) ?? this.currentConfig.retentionDays,\n }\n }\n}\n"],"mappings":"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","names":["DailyRotateFile"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/builtins/winston-logging/index.ts","../../../src/builtins/winston-logging/winston-logging.addon.ts","../../../src/builtins/winston-logging/winston-destination.ts","../../../src/logging/formatter.ts"],"sourcesContent":["export { WinstonLoggingAddon } from './winston-logging.addon.js'\nexport { WinstonDestination } from './winston-destination.js'\nexport { WinstonLoggingAddon as default } from './winston-logging.addon.js'\n","import type { ProviderRegistration } from '@camstack/types'\nimport { BaseAddon, logDestinationCapability } from '@camstack/types'\nimport { WinstonDestination } from './winston-destination.js'\n\ninterface WinstonConfig {\n readonly level: string\n readonly retentionDays: number\n}\n\n/**\n * Winston logging addon — rotated log files + console output.\n * Settings appear under Cluster → NodeDetail → Settings.\n */\nexport class WinstonLoggingAddon extends BaseAddon<WinstonConfig> {\n private destination: WinstonDestination | null = null\n\n constructor() {\n super({ level: 'info', retentionDays: 30 })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const logsDir = await this.ctx.api.storage.resolve.query({ location: 'logs', relativePath: '' })\n .catch(() => 'camstack-data/logs')\n this.destination = new WinstonDestination()\n await this.destination.initialize({ ...this.config, logsDir })\n this.ctx.logger.info('Winston logging initialized')\n return [{ capability: logDestinationCapability, provider: this.destination }]\n }\n\n protected async onShutdown(): Promise<void> {\n await this.destination?.shutdown()\n }\n\n getDestination(): WinstonDestination {\n if (!this.destination) throw new Error('Winston not initialized')\n return this.destination\n }\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'winston-logging',\n title: 'Logging',\n description: 'Global log verbosity + retention for rotated log files on this node.',\n columns: 2,\n fields: [\n this.field({\n type: 'select',\n key: 'level',\n label: 'Log Level',\n default: 'info',\n options: [\n { value: 'debug', label: 'Debug' },\n { value: 'info', label: 'Info' },\n { value: 'warn', label: 'Warn' },\n { value: 'error', label: 'Error' },\n ],\n }),\n this.field({\n type: 'number',\n key: 'retentionDays',\n label: 'Retention (days)',\n description: 'Number of days to keep rotated log files before deletion',\n min: 1,\n max: 365,\n step: 1,\n default: 30,\n unit: 'days',\n }),\n ],\n },\n ],\n })\n }\n}\n","import * as winston from 'winston'\nimport DailyRotateFile from 'winston-daily-rotate-file'\nimport * as path from 'node:path'\nimport type { ILogDestination, LogEntry, LogFilter } from '@camstack/types'\nimport { formatLogLine } from '../../logging/formatter.js'\n\ninterface WinstonConfig {\n readonly level: string\n readonly retentionDays: number\n /** Resolved absolute path to the logs directory (replaces the old dataPath field). */\n readonly logsDir: string\n}\n\n/**\n * Rotated-file log destination. Console output goes through the shared\n * `formatLogLine` so every console sink (Winston, ConsoleDestination,\n * HubForwarderDestination) emits identical lines. File output keeps a\n * structured JSON shape so log tooling can query tags/meta.\n */\nexport class WinstonDestination implements ILogDestination {\n private logger: winston.Logger | null = null\n\n async initialize(config?: WinstonConfig): Promise<void> {\n const {\n level = 'info',\n retentionDays = 30,\n logsDir = path.join('camstack-data', 'logs'),\n } = config ?? {}\n\n // Console: canonical single-line formatter — `_rendered` already\n // carries level-based ANSI colour codes (see `formatLogLine` in\n // `@camstack/core/logging/formatter.ts`). Using Winston's colorize\n // here would just decorate unused `info.level` and leave the\n // rendered line uncoloured.\n const consoleFormat = winston.format.printf((info) => {\n const rendered = (info as unknown as { _rendered?: string })._rendered\n return typeof rendered === 'string' ? rendered : String(info.message)\n })\n\n // File: structured JSON. Strip `_rendered` first — it carries ANSI\n // escape codes intended for the terminal and has no place in a\n // rotated-file store used for ops queries.\n const stripRendered = winston.format((info) => {\n delete (info as Record<string, unknown>)['_rendered']\n return info\n })\n const fileFormat = winston.format.combine(\n winston.format.timestamp(),\n stripRendered(),\n winston.format.json(),\n )\n\n // File-only winston. Stdout is owned by `ConsoleDestination`\n // (the `console-logging` builtin addon) — adding a Winston console\n // transport here would re-render every log line a second time on\n // the hub terminal, which is the historic cause of the duplicate\n // output operators have been seeing. The `consoleFormat` printf\n // above stays wired in so a future opt-in transport can use it\n // without re-deriving the layout.\n void consoleFormat\n this.logger = winston.createLogger({\n level,\n transports: [\n new DailyRotateFile({\n dirname: logsDir,\n filename: 'camstack-%DATE%.log',\n datePattern: 'YYYY-MM-DD',\n maxFiles: `${retentionDays}d`,\n format: fileFormat,\n }),\n ],\n })\n }\n\n write(entry: LogEntry): void {\n if (!this.logger) return\n\n // Moleculer JSON serialization converts Date → string; normalize back\n const ts = entry.timestamp instanceof Date\n ? entry.timestamp.toISOString()\n : String(entry.timestamp)\n // Render once with colours for the console transport; file transport\n // strips `_rendered` via the format chain above so disk stays plain.\n const rendered = formatLogLine(entry, { colorize: true })\n\n this.logger.log({\n level: entry.level,\n message: entry.message,\n _rendered: rendered,\n timestamp: ts,\n scope: entry.scope,\n ...(entry.tags ? { tags: entry.tags } : {}),\n ...(entry.meta ? { meta: entry.meta } : {}),\n })\n }\n\n async query(_filter: LogFilter): Promise<readonly LogEntry[]> {\n // File-based log querying is not implemented; use structured storage for log queries\n return []\n }\n\n async shutdown(): Promise<void> {\n if (!this.logger) return\n\n await new Promise<void>((resolve) => {\n this.logger!.on('finish', resolve)\n this.logger!.end()\n })\n this.logger = null\n }\n}\n","/**\n * Canonical single-line formatter for every `LogEntry` that reaches a\n * console transport — hub-side `WinstonDestination` +\n * `ConsoleDestination`, agent-side `HubForwarderDestination`. One\n * definition so the three paths can't drift.\n *\n * Layout mirrors NestJS `ConsoleLogger` output:\n *\n * [Nest] 54994 - 04/20/2026, 12:45:53 AM LOG [InstanceLoader] TrpcModule dependencies initialized +0ms\n *\n * Mapping for camstack — cluster-aware:\n * - `[Nest]` (brand bracket) → `[<agent>/<addonId>]` so operators see\n * at a glance which node emitted the line\n * (matches the cluster topology)\n * - `54994` (pid) → `process.pid`\n * - `<timestamp>` → `M/D/YYYY, H:MM:SS AM`\n * - `LOG` / `WARN` / `ERROR` → `entry.level` upper-cased, colour per level\n * - `[InstanceLoader]` (ctx) → `[<device>]` — shown only when a device\n * tag is present; omitted otherwise\n * - `<message>` → `entry.message`\n * - trailing `+Nms` → we don't compute timing yet; omitted\n *\n * Rendered examples:\n * [hub/winston-logging] 71184 - 04/20/2026, 12:45:53 AM INFO Winston logging initialized\n * [hub/provider-rtsp] 71184 - 04/20/2026, 12:46:01 AM WARN [salone] probe failed {err=\"timeout\"}\n * [dev-agent-0/detection] 12345 - 04/20/2026, 12:47:15 AM ERROR [cortile] no detector {codec=av1}\n */\nimport type { LogEntry, LogTags } from '@camstack/types'\n\n// Emit ANSI colour codes when the runtime supports them. Heuristic\n// follows npm ecosystem convention so log-consumers can override:\n// - `NO_COLOR` env present → OFF (https://no-color.org/)\n// - `FORCE_COLOR=0` → OFF\n// - `FORCE_COLOR` truthy (1/2/3/true) → ON (forces even when piped)\n// - stdout.isTTY → ON\n// - else → OFF\n// Rationale: running via `concurrently` / `npm run dev` often pipes\n// stdout and strips `isTTY`, making colours disappear. Dev sets\n// `FORCE_COLOR=1` in the shell to opt back in; CI / file-piping\n// pipelines set `NO_COLOR=1` to strip.\n//\n// Destinations that always render for console (ConsoleDestination,\n// HubForwarderDestination) pass `colorize: true` explicitly — still\n// gated by NO_COLOR. File sinks (Winston JSON) pass `colorize: false`\n// so log files stay plain text regardless of the shell environment.\nconst USE_COLOR = (() => {\n try {\n if (typeof process === 'undefined') return false\n const env = process.env ?? {}\n if (env['NO_COLOR']) return false\n const force = env['FORCE_COLOR']\n if (force !== undefined) return force !== '' && force !== '0' && force !== 'false'\n return Boolean((process.stdout as { isTTY?: boolean })?.isTTY)\n } catch { return false }\n})()\n\n/** True when `NO_COLOR` is set — destinations that force colour on still honour this. */\nconst NO_COLOR = (() => {\n try { return Boolean(process?.env?.['NO_COLOR']) } catch { return false }\n})()\n\nconst RESET = '\\x1b[0m'\nconst ANSI = {\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n} as const\n\nconst COLOR_BY_LEVEL: Readonly<Record<string, string>> = {\n debug: ANSI.gray,\n info: ANSI.green,\n warn: ANSI.yellow,\n error: ANSI.red,\n}\n\nfunction paint(color: string, text: string, enabled: boolean): string {\n return enabled ? `${color}${text}${RESET}` : text\n}\n\nfunction colorizeLevel(level: string, text: string, enabled: boolean): string {\n if (!enabled) return text\n const code = COLOR_BY_LEVEL[level]\n return code ? `${code}${text}${RESET}` : text\n}\n\n/** Right-pad `value` to exactly `width` characters. */\nfunction padRight(value: string, width: number): string {\n if (value.length >= width) return value.slice(0, width)\n return value + ' '.repeat(width - value.length)\n}\n\n/** Derive the agent — the top-level cluster node, never the forked-worker suffix. */\nfunction resolveAgent(tags: LogTags | undefined): string | null {\n const agentId = tags?.agentId\n if (typeof agentId === 'string' && agentId.length > 0) return agentId\n const nodeId = tags?.nodeId\n if (typeof nodeId === 'string' && nodeId.length > 0) {\n return nodeId.includes('/') ? nodeId.split('/')[0]! : nodeId\n }\n return null\n}\n\n/** Derive the addon — `tags.addonId` first; `system` when the tag is missing\n * (scope is no longer a fallback — it's a sub-component label, not identity). */\nfunction resolveAddon(entry: Pick<LogEntry, 'tags'>): string {\n const addonId = entry.tags?.addonId\n if (typeof addonId === 'string' && addonId.length > 0) return addonId\n return 'system'\n}\n\n/** Device name (or `#id` fallback) — used inside the context bracket when present. */\nfunction resolveDevice(tags: LogTags | undefined): string | null {\n const name = tags?.deviceName\n if (typeof name === 'string' && name.length > 0) return name\n const id = tags?.deviceId\n if (typeof id === 'number' && Number.isFinite(id)) return `#${id}`\n return null\n}\n\n/**\n * Build the brand bracket content — `<agent>/<addonId>`. Agent is\n * always shown (even `hub`) so operators never have to guess which\n * node emitted a line. Falls back to `?/<addon>` when tags don't\n * carry an agent id (pre-boot logs, test harnesses).\n */\nfunction buildBrand(entry: Pick<LogEntry, 'tags'>): string {\n const agent = resolveAgent(entry.tags) ?? '?'\n const addon = resolveAddon(entry)\n return `${agent}/${addon}`\n}\n\n/**\n * Build the optional device-context bracket. Returns `null` when the\n * line doesn't carry a device tag — renders as just the brand + message\n * without the second bracket. With a device tag: `salone` / `#42`.\n */\nfunction buildDeviceContext(entry: Pick<LogEntry, 'tags'>): string | null {\n return resolveDevice(entry.tags)\n}\n\n/**\n * NestJS-style locale timestamp: `M/D/YYYY, H:MM:SS AM`. Built with\n * `toLocaleString('en-US')` to match the default NestJS format that\n * operators are used to from Nest boot output.\n */\nfunction formatTimestamp(value: unknown): string {\n const date = value instanceof Date ? value : new Date(typeof value === 'string' ? value : Date.now())\n return date.toLocaleString('en-US', {\n month: 'numeric',\n day: 'numeric',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n second: '2-digit',\n hour12: true,\n })\n}\n\n/** Compact `{key=value key=\"with space\"}` rendering of an arbitrary object. Empty → ''. */\nfunction formatMeta(meta: Record<string, unknown> | undefined): string {\n if (!meta) return ''\n const keys = Object.keys(meta)\n if (keys.length === 0) return ''\n const parts: string[] = []\n for (const key of keys) {\n const value = meta[key]\n parts.push(`${key}=${formatMetaValue(value)}`)\n }\n return `{${parts.join(' ')}}`\n}\n\nfunction formatMetaValue(value: unknown): string {\n if (value === null) return 'null'\n if (value === undefined) return 'undefined'\n if (typeof value === 'string') {\n return /\\s|\"/.test(value) ? JSON.stringify(value) : value\n }\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Error) return JSON.stringify(value.message)\n try { return JSON.stringify(value) } catch { return String(value) }\n}\n\n/** Cached renderer PID — fallback when the entry doesn't carry its own `tags.pid`. */\nconst PID = typeof process !== 'undefined' && typeof process.pid === 'number' ? String(process.pid) : '?'\n\n/** Options for `formatLogLine`. */\nexport interface FormatLogLineOptions {\n /**\n * Emit ANSI colour codes. When omitted, falls back to the shell-env\n * heuristic (`NO_COLOR`/`FORCE_COLOR`/`isTTY`). Destinations that\n * always target a console (ConsoleDestination, HubForwarderDestination)\n * pass `true`; file sinks (Winston JSON) pass `false` so logs on disk\n * stay plain text regardless of the shell env.\n *\n * `NO_COLOR=1` in the environment still wins — consumer opt-in beats\n * producer opt-in, matching the https://no-color.org/ convention.\n */\n readonly colorize?: boolean\n}\n\n/**\n * Render a `LogEntry` as one complete console line — no trailing newline.\n * Caller appends `\\n` (file sinks) or relies on `console.log` (stdout sink).\n *\n * PID column prefers `entry.tags.pid` when set (so forked-worker entries\n * carry the worker's OS pid even when the hub renders them), falling back\n * to the renderer's own `process.pid`.\n *\n * Colours (when enabled) follow the NestJS convention: the level token\n * AND the message body share the level colour (green/yellow/red/gray for\n * info/warn/error/debug). Brand, device context, scope and meta keep\n * their fixed hues so the structural fields stay visually distinct from\n * the message content.\n */\nexport function formatLogLine(entry: LogEntry, options?: FormatLogLineOptions): string {\n const wantsColor = options?.colorize ?? USE_COLOR\n const colorize = wantsColor && !NO_COLOR\n const levelColor = COLOR_BY_LEVEL[entry.level]\n\n const brand = paint(ANSI.green, `[${buildBrand(entry)}]`, colorize)\n const entryPid = entry.tags?.pid\n const pid = typeof entryPid === 'number' ? String(entryPid) : PID\n const timestamp = formatTimestamp(entry.timestamp)\n // Right-pad level to 5 chars (matches NestJS) so the context/message\n // column aligns across different levels without fixed-width hacks\n // on the surrounding fields.\n const level = colorizeLevel(entry.level, padRight(entry.level.toUpperCase(), 5), colorize)\n const device = buildDeviceContext(entry)\n const ctxPart = device ? ' ' + paint(ANSI.yellow, `[${device}]`, colorize) : ''\n const scope = typeof entry.scope === 'string' && entry.scope.length > 0 ? entry.scope : null\n const scopePart = scope ? ' ' + paint(ANSI.cyan, `(${scope})`, colorize) : ''\n const metaText = formatMeta(entry.meta)\n const metaPart = metaText ? ' ' + paint(ANSI.gray, metaText, colorize) : ''\n // NestJS layout: `[<agent>/<addon>] <pid> - <timestamp> <LEVEL> [<device>] (<scope>) <message>`\n // Message body is tinted by level colour so `error`/`warn` stand out at a glance.\n const message = colorize && levelColor\n ? `${levelColor}${entry.message}${RESET}`\n : entry.message\n return `${brand} ${pid} - ${timestamp} ${level}${ctxPart}${scopePart} ${message}${metaPart}`\n}\n"],"mappings":"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","names":["DailyRotateFile"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-2CIYKDRN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// src/logging/formatter.ts
|
|
2
|
+
var USE_COLOR = (() => {
|
|
3
|
+
try {
|
|
4
|
+
if (typeof process === "undefined") return false;
|
|
5
|
+
const env = process.env ?? {};
|
|
6
|
+
if (env["NO_COLOR"]) return false;
|
|
7
|
+
const force = env["FORCE_COLOR"];
|
|
8
|
+
if (force !== void 0) return force !== "" && force !== "0" && force !== "false";
|
|
9
|
+
return Boolean(process.stdout?.isTTY);
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
})();
|
|
14
|
+
var NO_COLOR = (() => {
|
|
15
|
+
try {
|
|
16
|
+
return Boolean(process?.env?.["NO_COLOR"]);
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
})();
|
|
21
|
+
var RESET = "\x1B[0m";
|
|
22
|
+
var ANSI = {
|
|
23
|
+
green: "\x1B[32m",
|
|
24
|
+
yellow: "\x1B[33m",
|
|
25
|
+
red: "\x1B[31m",
|
|
26
|
+
cyan: "\x1B[36m",
|
|
27
|
+
gray: "\x1B[90m"
|
|
28
|
+
};
|
|
29
|
+
var COLOR_BY_LEVEL = {
|
|
30
|
+
debug: ANSI.gray,
|
|
31
|
+
info: ANSI.green,
|
|
32
|
+
warn: ANSI.yellow,
|
|
33
|
+
error: ANSI.red
|
|
34
|
+
};
|
|
35
|
+
function paint(color, text, enabled) {
|
|
36
|
+
return enabled ? `${color}${text}${RESET}` : text;
|
|
37
|
+
}
|
|
38
|
+
function colorizeLevel(level, text, enabled) {
|
|
39
|
+
if (!enabled) return text;
|
|
40
|
+
const code = COLOR_BY_LEVEL[level];
|
|
41
|
+
return code ? `${code}${text}${RESET}` : text;
|
|
42
|
+
}
|
|
43
|
+
function padRight(value, width) {
|
|
44
|
+
if (value.length >= width) return value.slice(0, width);
|
|
45
|
+
return value + " ".repeat(width - value.length);
|
|
46
|
+
}
|
|
47
|
+
function resolveAgent(tags) {
|
|
48
|
+
const agentId = tags?.agentId;
|
|
49
|
+
if (typeof agentId === "string" && agentId.length > 0) return agentId;
|
|
50
|
+
const nodeId = tags?.nodeId;
|
|
51
|
+
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
52
|
+
return nodeId.includes("/") ? nodeId.split("/")[0] : nodeId;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function resolveAddon(entry) {
|
|
57
|
+
const addonId = entry.tags?.addonId;
|
|
58
|
+
if (typeof addonId === "string" && addonId.length > 0) return addonId;
|
|
59
|
+
return "system";
|
|
60
|
+
}
|
|
61
|
+
function resolveDevice(tags) {
|
|
62
|
+
const name = tags?.deviceName;
|
|
63
|
+
if (typeof name === "string" && name.length > 0) return name;
|
|
64
|
+
const id = tags?.deviceId;
|
|
65
|
+
if (typeof id === "number" && Number.isFinite(id)) return `#${id}`;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function buildBrand(entry) {
|
|
69
|
+
const agent = resolveAgent(entry.tags) ?? "?";
|
|
70
|
+
const addon = resolveAddon(entry);
|
|
71
|
+
return `${agent}/${addon}`;
|
|
72
|
+
}
|
|
73
|
+
function buildDeviceContext(entry) {
|
|
74
|
+
return resolveDevice(entry.tags);
|
|
75
|
+
}
|
|
76
|
+
function formatTimestamp(value) {
|
|
77
|
+
const date = value instanceof Date ? value : new Date(typeof value === "string" ? value : Date.now());
|
|
78
|
+
return date.toLocaleString("en-US", {
|
|
79
|
+
month: "numeric",
|
|
80
|
+
day: "numeric",
|
|
81
|
+
year: "numeric",
|
|
82
|
+
hour: "numeric",
|
|
83
|
+
minute: "2-digit",
|
|
84
|
+
second: "2-digit",
|
|
85
|
+
hour12: true
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function formatMeta(meta) {
|
|
89
|
+
if (!meta) return "";
|
|
90
|
+
const keys = Object.keys(meta);
|
|
91
|
+
if (keys.length === 0) return "";
|
|
92
|
+
const parts = [];
|
|
93
|
+
for (const key of keys) {
|
|
94
|
+
const value = meta[key];
|
|
95
|
+
parts.push(`${key}=${formatMetaValue(value)}`);
|
|
96
|
+
}
|
|
97
|
+
return `{${parts.join(" ")}}`;
|
|
98
|
+
}
|
|
99
|
+
function formatMetaValue(value) {
|
|
100
|
+
if (value === null) return "null";
|
|
101
|
+
if (value === void 0) return "undefined";
|
|
102
|
+
if (typeof value === "string") {
|
|
103
|
+
return /\s|"/.test(value) ? JSON.stringify(value) : value;
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
106
|
+
if (value instanceof Error) return JSON.stringify(value.message);
|
|
107
|
+
try {
|
|
108
|
+
return JSON.stringify(value);
|
|
109
|
+
} catch {
|
|
110
|
+
return String(value);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
var PID = typeof process !== "undefined" && typeof process.pid === "number" ? String(process.pid) : "?";
|
|
114
|
+
function formatLogLine(entry, options) {
|
|
115
|
+
const wantsColor = options?.colorize ?? USE_COLOR;
|
|
116
|
+
const colorize = wantsColor && !NO_COLOR;
|
|
117
|
+
const levelColor = COLOR_BY_LEVEL[entry.level];
|
|
118
|
+
const brand = paint(ANSI.green, `[${buildBrand(entry)}]`, colorize);
|
|
119
|
+
const entryPid = entry.tags?.pid;
|
|
120
|
+
const pid = typeof entryPid === "number" ? String(entryPid) : PID;
|
|
121
|
+
const timestamp = formatTimestamp(entry.timestamp);
|
|
122
|
+
const level = colorizeLevel(entry.level, padRight(entry.level.toUpperCase(), 5), colorize);
|
|
123
|
+
const device = buildDeviceContext(entry);
|
|
124
|
+
const ctxPart = device ? " " + paint(ANSI.yellow, `[${device}]`, colorize) : "";
|
|
125
|
+
const scope = typeof entry.scope === "string" && entry.scope.length > 0 ? entry.scope : null;
|
|
126
|
+
const scopePart = scope ? " " + paint(ANSI.cyan, `(${scope})`, colorize) : "";
|
|
127
|
+
const metaText = formatMeta(entry.meta);
|
|
128
|
+
const metaPart = metaText ? " " + paint(ANSI.gray, metaText, colorize) : "";
|
|
129
|
+
const message = colorize && levelColor ? `${levelColor}${entry.message}${RESET}` : entry.message;
|
|
130
|
+
return `${brand} ${pid} - ${timestamp} ${level}${ctxPart}${scopePart} ${message}${metaPart}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
formatLogLine
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=chunk-2F76X6NL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logging/formatter.ts"],"sourcesContent":["/**\n * Canonical single-line formatter for every `LogEntry` that reaches a\n * console transport — hub-side `WinstonDestination` +\n * `ConsoleDestination`, agent-side `HubForwarderDestination`. One\n * definition so the three paths can't drift.\n *\n * Layout mirrors NestJS `ConsoleLogger` output:\n *\n * [Nest] 54994 - 04/20/2026, 12:45:53 AM LOG [InstanceLoader] TrpcModule dependencies initialized +0ms\n *\n * Mapping for camstack — cluster-aware:\n * - `[Nest]` (brand bracket) → `[<agent>/<addonId>]` so operators see\n * at a glance which node emitted the line\n * (matches the cluster topology)\n * - `54994` (pid) → `process.pid`\n * - `<timestamp>` → `M/D/YYYY, H:MM:SS AM`\n * - `LOG` / `WARN` / `ERROR` → `entry.level` upper-cased, colour per level\n * - `[InstanceLoader]` (ctx) → `[<device>]` — shown only when a device\n * tag is present; omitted otherwise\n * - `<message>` → `entry.message`\n * - trailing `+Nms` → we don't compute timing yet; omitted\n *\n * Rendered examples:\n * [hub/winston-logging] 71184 - 04/20/2026, 12:45:53 AM INFO Winston logging initialized\n * [hub/provider-rtsp] 71184 - 04/20/2026, 12:46:01 AM WARN [salone] probe failed {err=\"timeout\"}\n * [dev-agent-0/detection] 12345 - 04/20/2026, 12:47:15 AM ERROR [cortile] no detector {codec=av1}\n */\nimport type { LogEntry, LogTags } from '@camstack/types'\n\n// Emit ANSI colour codes when the runtime supports them. Heuristic\n// follows npm ecosystem convention so log-consumers can override:\n// - `NO_COLOR` env present → OFF (https://no-color.org/)\n// - `FORCE_COLOR=0` → OFF\n// - `FORCE_COLOR` truthy (1/2/3/true) → ON (forces even when piped)\n// - stdout.isTTY → ON\n// - else → OFF\n// Rationale: running via `concurrently` / `npm run dev` often pipes\n// stdout and strips `isTTY`, making colours disappear. Dev sets\n// `FORCE_COLOR=1` in the shell to opt back in; CI / file-piping\n// pipelines set `NO_COLOR=1` to strip.\n//\n// Destinations that always render for console (ConsoleDestination,\n// HubForwarderDestination) pass `colorize: true` explicitly — still\n// gated by NO_COLOR. File sinks (Winston JSON) pass `colorize: false`\n// so log files stay plain text regardless of the shell environment.\nconst USE_COLOR = (() => {\n try {\n if (typeof process === 'undefined') return false\n const env = process.env ?? {}\n if (env['NO_COLOR']) return false\n const force = env['FORCE_COLOR']\n if (force !== undefined) return force !== '' && force !== '0' && force !== 'false'\n return Boolean((process.stdout as { isTTY?: boolean })?.isTTY)\n } catch { return false }\n})()\n\n/** True when `NO_COLOR` is set — destinations that force colour on still honour this. */\nconst NO_COLOR = (() => {\n try { return Boolean(process?.env?.['NO_COLOR']) } catch { return false }\n})()\n\nconst RESET = '\\x1b[0m'\nconst ANSI = {\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n red: '\\x1b[31m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n} as const\n\nconst COLOR_BY_LEVEL: Readonly<Record<string, string>> = {\n debug: ANSI.gray,\n info: ANSI.green,\n warn: ANSI.yellow,\n error: ANSI.red,\n}\n\nfunction paint(color: string, text: string, enabled: boolean): string {\n return enabled ? `${color}${text}${RESET}` : text\n}\n\nfunction colorizeLevel(level: string, text: string, enabled: boolean): string {\n if (!enabled) return text\n const code = COLOR_BY_LEVEL[level]\n return code ? `${code}${text}${RESET}` : text\n}\n\n/** Right-pad `value` to exactly `width` characters. */\nfunction padRight(value: string, width: number): string {\n if (value.length >= width) return value.slice(0, width)\n return value + ' '.repeat(width - value.length)\n}\n\n/** Derive the agent — the top-level cluster node, never the forked-worker suffix. */\nfunction resolveAgent(tags: LogTags | undefined): string | null {\n const agentId = tags?.agentId\n if (typeof agentId === 'string' && agentId.length > 0) return agentId\n const nodeId = tags?.nodeId\n if (typeof nodeId === 'string' && nodeId.length > 0) {\n return nodeId.includes('/') ? nodeId.split('/')[0]! : nodeId\n }\n return null\n}\n\n/** Derive the addon — `tags.addonId` first; `system` when the tag is missing\n * (scope is no longer a fallback — it's a sub-component label, not identity). */\nfunction resolveAddon(entry: Pick<LogEntry, 'tags'>): string {\n const addonId = entry.tags?.addonId\n if (typeof addonId === 'string' && addonId.length > 0) return addonId\n return 'system'\n}\n\n/** Device name (or `#id` fallback) — used inside the context bracket when present. */\nfunction resolveDevice(tags: LogTags | undefined): string | null {\n const name = tags?.deviceName\n if (typeof name === 'string' && name.length > 0) return name\n const id = tags?.deviceId\n if (typeof id === 'number' && Number.isFinite(id)) return `#${id}`\n return null\n}\n\n/**\n * Build the brand bracket content — `<agent>/<addonId>`. Agent is\n * always shown (even `hub`) so operators never have to guess which\n * node emitted a line. Falls back to `?/<addon>` when tags don't\n * carry an agent id (pre-boot logs, test harnesses).\n */\nfunction buildBrand(entry: Pick<LogEntry, 'tags'>): string {\n const agent = resolveAgent(entry.tags) ?? '?'\n const addon = resolveAddon(entry)\n return `${agent}/${addon}`\n}\n\n/**\n * Build the optional device-context bracket. Returns `null` when the\n * line doesn't carry a device tag — renders as just the brand + message\n * without the second bracket. With a device tag: `salone` / `#42`.\n */\nfunction buildDeviceContext(entry: Pick<LogEntry, 'tags'>): string | null {\n return resolveDevice(entry.tags)\n}\n\n/**\n * NestJS-style locale timestamp: `M/D/YYYY, H:MM:SS AM`. Built with\n * `toLocaleString('en-US')` to match the default NestJS format that\n * operators are used to from Nest boot output.\n */\nfunction formatTimestamp(value: unknown): string {\n const date = value instanceof Date ? value : new Date(typeof value === 'string' ? value : Date.now())\n return date.toLocaleString('en-US', {\n month: 'numeric',\n day: 'numeric',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n second: '2-digit',\n hour12: true,\n })\n}\n\n/** Compact `{key=value key=\"with space\"}` rendering of an arbitrary object. Empty → ''. */\nfunction formatMeta(meta: Record<string, unknown> | undefined): string {\n if (!meta) return ''\n const keys = Object.keys(meta)\n if (keys.length === 0) return ''\n const parts: string[] = []\n for (const key of keys) {\n const value = meta[key]\n parts.push(`${key}=${formatMetaValue(value)}`)\n }\n return `{${parts.join(' ')}}`\n}\n\nfunction formatMetaValue(value: unknown): string {\n if (value === null) return 'null'\n if (value === undefined) return 'undefined'\n if (typeof value === 'string') {\n return /\\s|\"/.test(value) ? JSON.stringify(value) : value\n }\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Error) return JSON.stringify(value.message)\n try { return JSON.stringify(value) } catch { return String(value) }\n}\n\n/** Cached renderer PID — fallback when the entry doesn't carry its own `tags.pid`. */\nconst PID = typeof process !== 'undefined' && typeof process.pid === 'number' ? String(process.pid) : '?'\n\n/** Options for `formatLogLine`. */\nexport interface FormatLogLineOptions {\n /**\n * Emit ANSI colour codes. When omitted, falls back to the shell-env\n * heuristic (`NO_COLOR`/`FORCE_COLOR`/`isTTY`). Destinations that\n * always target a console (ConsoleDestination, HubForwarderDestination)\n * pass `true`; file sinks (Winston JSON) pass `false` so logs on disk\n * stay plain text regardless of the shell env.\n *\n * `NO_COLOR=1` in the environment still wins — consumer opt-in beats\n * producer opt-in, matching the https://no-color.org/ convention.\n */\n readonly colorize?: boolean\n}\n\n/**\n * Render a `LogEntry` as one complete console line — no trailing newline.\n * Caller appends `\\n` (file sinks) or relies on `console.log` (stdout sink).\n *\n * PID column prefers `entry.tags.pid` when set (so forked-worker entries\n * carry the worker's OS pid even when the hub renders them), falling back\n * to the renderer's own `process.pid`.\n *\n * Colours (when enabled) follow the NestJS convention: the level token\n * AND the message body share the level colour (green/yellow/red/gray for\n * info/warn/error/debug). Brand, device context, scope and meta keep\n * their fixed hues so the structural fields stay visually distinct from\n * the message content.\n */\nexport function formatLogLine(entry: LogEntry, options?: FormatLogLineOptions): string {\n const wantsColor = options?.colorize ?? USE_COLOR\n const colorize = wantsColor && !NO_COLOR\n const levelColor = COLOR_BY_LEVEL[entry.level]\n\n const brand = paint(ANSI.green, `[${buildBrand(entry)}]`, colorize)\n const entryPid = entry.tags?.pid\n const pid = typeof entryPid === 'number' ? String(entryPid) : PID\n const timestamp = formatTimestamp(entry.timestamp)\n // Right-pad level to 5 chars (matches NestJS) so the context/message\n // column aligns across different levels without fixed-width hacks\n // on the surrounding fields.\n const level = colorizeLevel(entry.level, padRight(entry.level.toUpperCase(), 5), colorize)\n const device = buildDeviceContext(entry)\n const ctxPart = device ? ' ' + paint(ANSI.yellow, `[${device}]`, colorize) : ''\n const scope = typeof entry.scope === 'string' && entry.scope.length > 0 ? entry.scope : null\n const scopePart = scope ? ' ' + paint(ANSI.cyan, `(${scope})`, colorize) : ''\n const metaText = formatMeta(entry.meta)\n const metaPart = metaText ? ' ' + paint(ANSI.gray, metaText, colorize) : ''\n // NestJS layout: `[<agent>/<addon>] <pid> - <timestamp> <LEVEL> [<device>] (<scope>) <message>`\n // Message body is tinted by level colour so `error`/`warn` stand out at a glance.\n const message = colorize && levelColor\n ? `${levelColor}${entry.message}${RESET}`\n : entry.message\n return `${brand} ${pid} - ${timestamp} ${level}${ctxPart}${scopePart} ${message}${metaPart}`\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-2QUFBZ7M.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|