target-supabase-sdk 0.2.12 → 0.2.13
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/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1 -1
- package/dist/chunks/enable-DqnfLPsQ.js +874 -0
- package/dist/chunks/enable-DqnfLPsQ.js.map +1 -0
- package/dist/chunks/list.api-BzbTJBsM.js.map +1 -1
- package/dist/chunks/{task-queue-H4mpSW8R.js → task-queue-6X3aglJh.js} +108 -3
- package/dist/chunks/task-queue-6X3aglJh.js.map +1 -0
- package/dist/node/preload/run-service-preload.d.ts.map +1 -1
- package/dist/node/preload/service-preload.interface.d.ts +3 -1
- package/dist/node/preload/service-preload.interface.d.ts.map +1 -1
- package/dist/node/process/managed-child-processes.d.ts.map +1 -1
- package/dist/node/service-guard/service-guard-node.d.ts.map +1 -1
- package/dist/node/service-host/index.d.ts +1 -0
- package/dist/node/service-host/index.d.ts.map +1 -1
- package/dist/node/service-host/service-host.d.ts +5 -1
- package/dist/node/service-host/service-host.d.ts.map +1 -1
- package/dist/node.d.ts +10 -5
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +492 -47
- package/dist/node.js.map +1 -1
- package/dist/preload.js +9 -6
- package/dist/preload.js.map +1 -1
- package/dist/service/index.d.ts +2 -2
- package/dist/service/index.d.ts.map +1 -1
- package/dist/service/registry.service.d.ts +19 -0
- package/dist/service/registry.service.d.ts.map +1 -1
- package/dist/shared/log/core/create-logger.d.ts.map +1 -0
- package/dist/shared/log/{log-batch.d.ts → core/log-batch.d.ts} +4 -4
- package/dist/shared/log/core/log-batch.d.ts.map +1 -0
- package/dist/shared/log/core/log-manager.d.ts.map +1 -0
- package/dist/shared/log/core/log-min-level.d.ts.map +1 -0
- package/dist/shared/log/core/log-scope.d.ts.map +1 -0
- package/dist/shared/log/index.d.ts +13 -13
- package/dist/shared/log/index.d.ts.map +1 -1
- package/dist/shared/log/spool/collector.d.ts +21 -0
- package/dist/shared/log/spool/collector.d.ts.map +1 -0
- package/dist/shared/log/spool/config.d.ts +7 -0
- package/dist/shared/log/spool/config.d.ts.map +1 -0
- package/dist/shared/log/spool/coordinator.d.ts +18 -0
- package/dist/shared/log/spool/coordinator.d.ts.map +1 -0
- package/dist/shared/log/spool/enable.d.ts +23 -0
- package/dist/shared/log/spool/enable.d.ts.map +1 -0
- package/dist/shared/log/spool/file.d.ts +21 -0
- package/dist/shared/log/spool/file.d.ts.map +1 -0
- package/dist/shared/log/spool/interface.d.ts +55 -0
- package/dist/shared/log/spool/interface.d.ts.map +1 -0
- package/dist/shared/log/spool/paths.d.ts +6 -0
- package/dist/shared/log/spool/paths.d.ts.map +1 -0
- package/dist/shared/log/spool/process-roles.d.ts +17 -0
- package/dist/shared/log/spool/process-roles.d.ts.map +1 -0
- package/dist/shared/log/spool/register-collect-log-runner.d.ts +15 -0
- package/dist/shared/log/spool/register-collect-log-runner.d.ts.map +1 -0
- package/dist/shared/log/spool/service-lifecycle.d.ts +13 -0
- package/dist/shared/log/spool/service-lifecycle.d.ts.map +1 -0
- package/dist/shared/log/spool/writer.d.ts +32 -0
- package/dist/shared/log/spool/writer.d.ts.map +1 -0
- package/dist/shared/log/{log-persist-batch-id.d.ts → upload/batch-id.d.ts} +3 -3
- package/dist/shared/log/upload/batch-id.d.ts.map +1 -0
- package/dist/shared/log/upload/env.d.ts +7 -0
- package/dist/shared/log/upload/env.d.ts.map +1 -0
- package/dist/shared/log/{log-persist-flush.d.ts → upload/flush.d.ts} +3 -3
- package/dist/shared/log/upload/flush.d.ts.map +1 -0
- package/dist/shared/log/{log-persist-hook.d.ts → upload/hook.d.ts} +2 -2
- package/dist/shared/log/upload/hook.d.ts.map +1 -0
- package/dist/shared/log/upload/interface.d.ts +33 -0
- package/dist/shared/log/upload/interface.d.ts.map +1 -0
- package/dist/shared/log/upload/logger.d.ts +22 -0
- package/dist/shared/log/upload/logger.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/browser.ts +1 -0
- package/src/node/preload/run-service-preload.ts +11 -6
- package/src/node/preload/service-preload.interface.ts +3 -1
- package/src/node/process/create-managed-child-processes.ts +1 -1
- package/src/node/process/managed-child-processes.ts +8 -1
- package/src/node/service-guard/service-guard-node.ts +5 -0
- package/src/node/service-host/index.ts +4 -0
- package/src/node/service-host/service-host.ts +22 -3
- package/src/node.ts +26 -19
- package/src/service/index.ts +3 -0
- package/src/service/registry.service.ts +142 -0
- package/src/shared/log/README.md +206 -0
- package/src/shared/log/{log-batch.ts → core/log-batch.ts} +4 -4
- package/src/shared/log/{log-manager.ts → core/log-manager.ts} +3 -3
- package/src/shared/log/{log-min-level.ts → core/log-min-level.ts} +1 -1
- package/src/shared/log/{log-scope.ts → core/log-scope.ts} +1 -1
- package/src/shared/log/index.ts +13 -18
- package/src/shared/log/spool/collector.ts +330 -0
- package/src/shared/log/spool/config.ts +56 -0
- package/src/shared/log/spool/coordinator.ts +55 -0
- package/src/shared/log/spool/enable.ts +136 -0
- package/src/shared/log/spool/file.ts +75 -0
- package/src/shared/log/spool/interface.ts +62 -0
- package/src/shared/log/spool/paths.ts +30 -0
- package/src/shared/log/spool/process-roles.ts +75 -0
- package/src/shared/log/spool/register-collect-log-runner.ts +63 -0
- package/src/shared/log/spool/service-lifecycle.ts +35 -0
- package/src/shared/log/spool/writer.ts +273 -0
- package/src/shared/log/{log-persist-batch-id.ts → upload/batch-id.ts} +2 -2
- package/src/shared/log/upload/env.ts +26 -0
- package/src/shared/log/{log-persist-flush.ts → upload/flush.ts} +8 -7
- package/src/shared/log/{log-persist-hook.ts → upload/hook.ts} +1 -1
- package/src/shared/log/upload/interface.ts +38 -0
- package/src/shared/log/upload/logger.ts +37 -0
- package/dist/chunks/enable-log-persist-Dl55_mv2.js +0 -1674
- package/dist/chunks/enable-log-persist-Dl55_mv2.js.map +0 -1
- package/dist/chunks/task-queue-H4mpSW8R.js.map +0 -1
- package/dist/shared/log/create-logger.d.ts.map +0 -1
- package/dist/shared/log/enable-log-persist.d.ts +0 -28
- package/dist/shared/log/enable-log-persist.d.ts.map +0 -1
- package/dist/shared/log/log-batch.d.ts.map +0 -1
- package/dist/shared/log/log-manager.d.ts.map +0 -1
- package/dist/shared/log/log-min-level.d.ts.map +0 -1
- package/dist/shared/log/log-persist-batch-id.d.ts.map +0 -1
- package/dist/shared/log/log-persist-coordinator.d.ts +0 -37
- package/dist/shared/log/log-persist-coordinator.d.ts.map +0 -1
- package/dist/shared/log/log-persist-errors.d.ts +0 -4
- package/dist/shared/log/log-persist-errors.d.ts.map +0 -1
- package/dist/shared/log/log-persist-flush.d.ts.map +0 -1
- package/dist/shared/log/log-persist-hook.d.ts.map +0 -1
- package/dist/shared/log/log-persist-logger.d.ts +0 -16
- package/dist/shared/log/log-persist-logger.d.ts.map +0 -1
- package/dist/shared/log/log-persist-registry.d.ts +0 -18
- package/dist/shared/log/log-persist-registry.d.ts.map +0 -1
- package/dist/shared/log/log-persist.config.d.ts +0 -9
- package/dist/shared/log/log-persist.config.d.ts.map +0 -1
- package/dist/shared/log/log-persist.d.ts +0 -62
- package/dist/shared/log/log-persist.d.ts.map +0 -1
- package/dist/shared/log/log-persist.interface.d.ts +0 -132
- package/dist/shared/log/log-persist.interface.d.ts.map +0 -1
- package/dist/shared/log/log-scope.d.ts.map +0 -1
- package/src/shared/log/enable-log-persist.ts +0 -225
- package/src/shared/log/log-persist-coordinator.ts +0 -107
- package/src/shared/log/log-persist-errors.ts +0 -16
- package/src/shared/log/log-persist-logger.ts +0 -61
- package/src/shared/log/log-persist-registry.ts +0 -252
- package/src/shared/log/log-persist.config.ts +0 -115
- package/src/shared/log/log-persist.interface.ts +0 -150
- package/src/shared/log/log-persist.ts +0 -822
- /package/dist/shared/log/{create-logger.d.ts → core/create-logger.d.ts} +0 -0
- /package/dist/shared/log/{log-manager.d.ts → core/log-manager.d.ts} +0 -0
- /package/dist/shared/log/{log-min-level.d.ts → core/log-min-level.d.ts} +0 -0
- /package/dist/shared/log/{log-scope.d.ts → core/log-scope.d.ts} +0 -0
- /package/src/shared/log/{create-logger.ts → core/create-logger.ts} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { envInt, envMs } from "../../../node/env/env-parsers";
|
|
2
|
+
import type { LogSpoolConfig } from "./interface";
|
|
3
|
+
|
|
4
|
+
export const LOG_SPOOL_SERVICE_ID_ENV = "LOG_SPOOL_SERVICE_ID";
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_LOG_SPOOL_CONFIG: LogSpoolConfig = {
|
|
7
|
+
writer: {
|
|
8
|
+
maxEntries: 2000,
|
|
9
|
+
maxBytes: 2 * 1024 * 1024,
|
|
10
|
+
flushDebounceMs: 3_000,
|
|
11
|
+
maxAgeMs: 60_000,
|
|
12
|
+
postTimeoutMs: 30_000,
|
|
13
|
+
},
|
|
14
|
+
gc: {
|
|
15
|
+
intervalMs: 24 * 60 * 60 * 1000,
|
|
16
|
+
syncedRetentionMs: 7 * 24 * 60 * 60 * 1000,
|
|
17
|
+
tmpRetentionMs: 7 * 24 * 60 * 60 * 1000,
|
|
18
|
+
},
|
|
19
|
+
collectIntervalMs: 60_000,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function resolveLogSpoolConfig(partial?: Partial<LogSpoolConfig>): LogSpoolConfig {
|
|
23
|
+
const defaults = DEFAULT_LOG_SPOOL_CONFIG;
|
|
24
|
+
return {
|
|
25
|
+
writer: { ...defaults.writer, ...partial?.writer },
|
|
26
|
+
gc: { ...defaults.gc, ...partial?.gc },
|
|
27
|
+
collectIntervalMs: partial?.collectIntervalMs ?? defaults.collectIntervalMs,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function logSpoolConfigFromEnv(env: NodeJS.ProcessEnv = process.env): LogSpoolConfig {
|
|
32
|
+
return resolveLogSpoolConfig({
|
|
33
|
+
writer: {
|
|
34
|
+
maxEntries: envInt("LOG_SPOOL_MAX_ENTRIES", DEFAULT_LOG_SPOOL_CONFIG.writer.maxEntries, { env, min: 1 }),
|
|
35
|
+
maxBytes: envInt("LOG_SPOOL_MAX_BYTES", DEFAULT_LOG_SPOOL_CONFIG.writer.maxBytes, { env, min: 1 }),
|
|
36
|
+
flushDebounceMs: envMs("LOG_SPOOL_FLUSH_DEBOUNCE_MS", DEFAULT_LOG_SPOOL_CONFIG.writer.flushDebounceMs, {
|
|
37
|
+
env,
|
|
38
|
+
}),
|
|
39
|
+
maxAgeMs: envMs("LOG_SPOOL_MAX_AGE_MS", DEFAULT_LOG_SPOOL_CONFIG.writer.maxAgeMs, { env }),
|
|
40
|
+
postTimeoutMs: envMs("LOG_PERSIST_POST_TIMEOUT_MS", DEFAULT_LOG_SPOOL_CONFIG.writer.postTimeoutMs, { env }),
|
|
41
|
+
},
|
|
42
|
+
gc: {
|
|
43
|
+
intervalMs: envMs("LOG_SPOOL_GC_INTERVAL_MS", DEFAULT_LOG_SPOOL_CONFIG.gc.intervalMs, { env }),
|
|
44
|
+
syncedRetentionMs: envMs("LOG_SPOOL_SYNCED_RETENTION_MS", DEFAULT_LOG_SPOOL_CONFIG.gc.syncedRetentionMs, {
|
|
45
|
+
env,
|
|
46
|
+
}),
|
|
47
|
+
tmpRetentionMs: envMs("LOG_SPOOL_TMP_RETENTION_MS", DEFAULT_LOG_SPOOL_CONFIG.gc.tmpRetentionMs, { env }),
|
|
48
|
+
},
|
|
49
|
+
collectIntervalMs: envMs("LOG_SPOOL_COLLECT_INTERVAL_MS", DEFAULT_LOG_SPOOL_CONFIG.collectIntervalMs, { env }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function logSpoolServiceIdFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
54
|
+
const raw = env[LOG_SPOOL_SERVICE_ID_ENV]?.trim();
|
|
55
|
+
return raw != null && raw !== "" ? raw : undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ensureLogSpoolFromEnv, getLogSpoolStats, logSpoolEnabledFromEnv, shutdownLogSpoolFromEnv } from "./enable";
|
|
2
|
+
import type { LogSpoolWriterStats } from "./interface";
|
|
3
|
+
|
|
4
|
+
export interface LogSpoolCoordinatorOptions {
|
|
5
|
+
/** Logical service value (e.g. `watch-service`). */
|
|
6
|
+
serviceValue: string;
|
|
7
|
+
/** Instance id after registry claim — passed to child spawn env. */
|
|
8
|
+
serviceId: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Service-bound facade for file log spool writer lifecycle. */
|
|
12
|
+
export interface LogSpoolCoordinator {
|
|
13
|
+
isEnabled(): boolean;
|
|
14
|
+
getServiceId(): string;
|
|
15
|
+
/** Enable spool writer in this process. */
|
|
16
|
+
registerProcess(process?: string): Promise<boolean>;
|
|
17
|
+
getWriterStats(): LogSpoolWriterStats | null;
|
|
18
|
+
shutdownLogSpool(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createLogSpoolCoordinator(options: LogSpoolCoordinatorOptions): LogSpoolCoordinator {
|
|
22
|
+
const { serviceValue, serviceId } = options;
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
isEnabled() {
|
|
26
|
+
return logSpoolEnabledFromEnv();
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
getServiceId() {
|
|
30
|
+
return serviceId;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
registerProcess(process?: string) {
|
|
34
|
+
return ensureLogSpoolFromEnv({
|
|
35
|
+
serviceValue,
|
|
36
|
+
serviceId,
|
|
37
|
+
processRole: process,
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
getWriterStats() {
|
|
42
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return getLogSpoolStats();
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async shutdownLogSpool() {
|
|
49
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await shutdownLogSpoolFromEnv();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createLogger } from "../core/create-logger";
|
|
2
|
+
import { logPersistProcessFromEnv, logPersistServiceFromEnv, logSpoolEnabledFromEnv } from "../upload/env";
|
|
3
|
+
import { registerLogPersistOffer } from "../upload/hook";
|
|
4
|
+
import { LOG_PERSIST_TOPIC } from "../upload/logger";
|
|
5
|
+
import { LOG_SPOOL_SERVICE_ID_ENV, logSpoolConfigFromEnv, logSpoolServiceIdFromEnv } from "./config";
|
|
6
|
+
import type { EnsureLogSpoolFromEnvOptions, LogSpoolProcessRole } from "./interface";
|
|
7
|
+
import { resolveLogSpoolRoot } from "./paths";
|
|
8
|
+
import {
|
|
9
|
+
assertValidLogSpoolExtraProcessRole,
|
|
10
|
+
isKnownLogSpoolProcessRole,
|
|
11
|
+
LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV,
|
|
12
|
+
} from "./process-roles";
|
|
13
|
+
import { enableLogSpoolWriter, getLogSpoolWriterStats, shutdownLogSpoolWriter } from "./writer";
|
|
14
|
+
|
|
15
|
+
export { logSpoolEnabledFromEnv };
|
|
16
|
+
|
|
17
|
+
export function resolveLogSpoolProcessRole(explicit?: string): LogSpoolProcessRole {
|
|
18
|
+
const raw = explicit ?? logPersistProcessFromEnv();
|
|
19
|
+
if (raw == null || raw === "") {
|
|
20
|
+
throw new Error("[LogSpool] LOG_PERSIST_PROCESS is required when LOG_PERSIST_ENABLED=true");
|
|
21
|
+
}
|
|
22
|
+
if (!isKnownLogSpoolProcessRole(raw)) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`[LogSpool] invalid LOG_PERSIST_PROCESS: ${raw} (not a core role and not listed in ${LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV})`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Env vars for child spawn after main claims registry slot.
|
|
32
|
+
* Optional when using {@link ManagedChildProcesses.spawn} — auto-injected from parent env.
|
|
33
|
+
*/
|
|
34
|
+
export function buildLogSpoolSpawnEnv(input: {
|
|
35
|
+
serviceId: string;
|
|
36
|
+
processRole: LogSpoolProcessRole;
|
|
37
|
+
}): Record<string, string> {
|
|
38
|
+
return {
|
|
39
|
+
[LOG_SPOOL_SERVICE_ID_ENV]: input.serviceId,
|
|
40
|
+
LOG_PERSIST_PROCESS: input.processRole,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* When persistence is enabled and parent has `LOG_SPOOL_SERVICE_ID`, map spawn `label`
|
|
46
|
+
* to spool env when `label` is a known core or extra process role.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveLogSpoolSpawnEnvForLabel(
|
|
49
|
+
label: string,
|
|
50
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
51
|
+
): Record<string, string> {
|
|
52
|
+
if (!logSpoolEnabledFromEnv(env)) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
const serviceId = env[LOG_SPOOL_SERVICE_ID_ENV]?.trim();
|
|
56
|
+
if (serviceId == null || serviceId === "") {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
if (!isKnownLogSpoolProcessRole(label, env)) {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
return buildLogSpoolSpawnEnv({ serviceId, processRole: label });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function ensureLogSpoolFromEnv(options: EnsureLogSpoolFromEnvOptions = {}): Promise<boolean> {
|
|
66
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
67
|
+
registerLogPersistOffer(null);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const serviceId = options.serviceId ?? logSpoolServiceIdFromEnv();
|
|
72
|
+
const serviceValue = options.serviceValue ?? logPersistServiceFromEnv();
|
|
73
|
+
const processRole = resolveLogSpoolProcessRole(options.processRole);
|
|
74
|
+
|
|
75
|
+
if (serviceId == null || serviceId === "") {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"[LogSpool] LOG_SPOOL_SERVICE_ID is required when LOG_PERSIST_ENABLED=true (set after registry claim or pass serviceId)",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (serviceValue == null || serviceValue === "") {
|
|
81
|
+
throw new Error("[LogSpool] LOG_PERSIST_SERVICE is required when LOG_PERSIST_ENABLED=true");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const spoolRoot = options.spoolRoot ?? resolveLogSpoolRoot();
|
|
85
|
+
|
|
86
|
+
await enableLogSpoolWriter({
|
|
87
|
+
serviceId,
|
|
88
|
+
processRole,
|
|
89
|
+
serviceValue,
|
|
90
|
+
spoolRoot,
|
|
91
|
+
config: options.config ?? logSpoolConfigFromEnv(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const lifecycleLogger = createLogger({ module: "log-spool" });
|
|
95
|
+
lifecycleLogger.info("log spool enabled from env", {
|
|
96
|
+
topic: LOG_PERSIST_TOPIC,
|
|
97
|
+
data: { serviceId, processRole, serviceValue, spoolRoot },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function shutdownLogSpoolFromEnv(): Promise<void> {
|
|
104
|
+
await shutdownLogSpoolWriter();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getLogSpoolStats(): ReturnType<typeof getLogSpoolWriterStats> {
|
|
108
|
+
return getLogSpoolWriterStats();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function validateLogSpoolPreloadEnv(env: NodeJS.ProcessEnv = process.env): void {
|
|
112
|
+
if (!logSpoolEnabledFromEnv(env)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const missing: string[] = [];
|
|
117
|
+
if (env.LOG_PERSIST_SERVICE?.trim() == null || env.LOG_PERSIST_SERVICE.trim() === "") {
|
|
118
|
+
missing.push("LOG_PERSIST_SERVICE");
|
|
119
|
+
}
|
|
120
|
+
const runtimeDir = env.LOG_PERSIST_REGISTRY_DIR?.trim() || env.RUNTIME_DATA_DIR?.trim();
|
|
121
|
+
if (runtimeDir == null || runtimeDir === "") {
|
|
122
|
+
missing.push("LOG_PERSIST_REGISTRY_DIR or RUNTIME_DATA_DIR");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (missing.length > 0) {
|
|
126
|
+
throw new Error(`[LogSpool] preload env incomplete: ${missing.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const role of env[LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV]?.split(",") ?? []) {
|
|
130
|
+
const trimmed = role.trim();
|
|
131
|
+
if (trimmed === "") {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
assertValidLogSpoolExtraProcessRole(trimmed);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { LogEntry } from "../core/log-manager";
|
|
2
|
+
import { computeLogBatchIdempotencyKey } from "../upload/batch-id";
|
|
3
|
+
import type { LogSpoolProcessRole } from "./interface";
|
|
4
|
+
|
|
5
|
+
/** `role.timestamp.hash.tmp|json` — role is lowercase alphanumeric + hyphen. */
|
|
6
|
+
const BATCH_FILE_PATTERN = /^([a-z][a-z0-9-]*)\.(\d{14})\.([a-f0-9]{8})\.(tmp|json)$/;
|
|
7
|
+
|
|
8
|
+
export function formatLogSpoolBatchTimestamp(ms: number): string {
|
|
9
|
+
const d = new Date(ms);
|
|
10
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
11
|
+
return (
|
|
12
|
+
`${d.getFullYear()}` +
|
|
13
|
+
`${pad(d.getMonth() + 1)}` +
|
|
14
|
+
`${pad(d.getDate())}` +
|
|
15
|
+
`${pad(d.getHours())}` +
|
|
16
|
+
`${pad(d.getMinutes())}` +
|
|
17
|
+
`${pad(d.getSeconds())}`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function computeLogSpoolShortHash(input: {
|
|
22
|
+
serviceValue: string;
|
|
23
|
+
serviceId: string;
|
|
24
|
+
processRole: LogSpoolProcessRole;
|
|
25
|
+
entries: LogEntry[];
|
|
26
|
+
}): string {
|
|
27
|
+
const processKey = `${input.serviceId}:${input.processRole}`;
|
|
28
|
+
const idempotencyKey = computeLogBatchIdempotencyKey({
|
|
29
|
+
service: input.serviceValue,
|
|
30
|
+
process: processKey,
|
|
31
|
+
lane: "slow",
|
|
32
|
+
entries: input.entries,
|
|
33
|
+
});
|
|
34
|
+
return idempotencyKey.slice(0, 8);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildLogSpoolBatchBaseName(input: {
|
|
38
|
+
processRole: LogSpoolProcessRole;
|
|
39
|
+
firstEntryTimestamp: number;
|
|
40
|
+
shortHash: string;
|
|
41
|
+
}): string {
|
|
42
|
+
const ts = formatLogSpoolBatchTimestamp(input.firstEntryTimestamp);
|
|
43
|
+
return `${input.processRole}.${ts}.${input.shortHash}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseLogSpoolBatchFilename(filename: string): {
|
|
47
|
+
processRole: LogSpoolProcessRole;
|
|
48
|
+
timestampMs: number;
|
|
49
|
+
shortHash: string;
|
|
50
|
+
extension: "tmp" | "json";
|
|
51
|
+
} | null {
|
|
52
|
+
const match = BATCH_FILE_PATTERN.exec(filename);
|
|
53
|
+
if (match == null) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const processRole = match[1] as LogSpoolProcessRole;
|
|
58
|
+
const tsRaw = match[2];
|
|
59
|
+
const shortHash = match[3];
|
|
60
|
+
const extension = match[4] as "tmp" | "json";
|
|
61
|
+
|
|
62
|
+
const year = Number(tsRaw.slice(0, 4));
|
|
63
|
+
const month = Number(tsRaw.slice(4, 6));
|
|
64
|
+
const day = Number(tsRaw.slice(6, 8));
|
|
65
|
+
const hour = Number(tsRaw.slice(8, 10));
|
|
66
|
+
const minute = Number(tsRaw.slice(10, 12));
|
|
67
|
+
const second = Number(tsRaw.slice(12, 14));
|
|
68
|
+
const timestampMs = Date.UTC(year, month - 1, day, hour, minute, second);
|
|
69
|
+
|
|
70
|
+
if (!Number.isFinite(timestampMs)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { processRole, timestampMs, shortHash, extension };
|
|
75
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { LogEntry } from "../core/log-manager";
|
|
2
|
+
import type { LogSpoolCoreProcessRole, LogSpoolProcessRole } from "./process-roles";
|
|
3
|
+
|
|
4
|
+
/** On-disk batch before guard upload — producer writes `.tmp`, guard renames to `.json` after post. */
|
|
5
|
+
export interface LogSpoolBatchFile {
|
|
6
|
+
entries: LogEntry[];
|
|
7
|
+
meta?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface LogSpoolWriterConfig {
|
|
11
|
+
maxEntries: number;
|
|
12
|
+
maxBytes: number;
|
|
13
|
+
flushDebounceMs: number;
|
|
14
|
+
/** Flush buffered entries after this age even when below count/byte thresholds. */
|
|
15
|
+
maxAgeMs: number;
|
|
16
|
+
postTimeoutMs: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LogSpoolGcConfig {
|
|
20
|
+
/** How often guard runs tmp/json GC (upload collect still runs every collect tick). */
|
|
21
|
+
intervalMs: number;
|
|
22
|
+
syncedRetentionMs: number;
|
|
23
|
+
tmpRetentionMs: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LogSpoolConfig {
|
|
27
|
+
writer: LogSpoolWriterConfig;
|
|
28
|
+
gc: LogSpoolGcConfig;
|
|
29
|
+
collectIntervalMs: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface EnableLogSpoolOptions {
|
|
33
|
+
/** Service instance id (`Service.id` from registry claim). */
|
|
34
|
+
serviceId: string;
|
|
35
|
+
/** Spool directory name — core or {@link LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV} extra. */
|
|
36
|
+
processRole: LogSpoolProcessRole;
|
|
37
|
+
/** Logical service value for List meta (`LOG_PERSIST_SERVICE`, e.g. `watch-service`). */
|
|
38
|
+
serviceValue: string;
|
|
39
|
+
spoolRoot?: string;
|
|
40
|
+
config?: Partial<LogSpoolConfig>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface EnsureLogSpoolFromEnvOptions {
|
|
44
|
+
serviceId?: string;
|
|
45
|
+
processRole?: LogSpoolProcessRole;
|
|
46
|
+
serviceValue?: string;
|
|
47
|
+
spoolRoot?: string;
|
|
48
|
+
config?: Partial<LogSpoolConfig>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LogSpoolWriterStats {
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
bufferedEntries: number;
|
|
54
|
+
bufferedBytes: number;
|
|
55
|
+
flushInProgress: boolean;
|
|
56
|
+
debouncePending: boolean;
|
|
57
|
+
ageFlushPending: boolean;
|
|
58
|
+
serviceId: string | null;
|
|
59
|
+
processRole: LogSpoolProcessRole | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type { LogSpoolCoreProcessRole, LogSpoolProcessRole };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { runtimeDataDirFromEnv } from "../upload/env";
|
|
3
|
+
import type { LogSpoolProcessRole } from "./interface";
|
|
4
|
+
|
|
5
|
+
export const LOG_SPOOL_DIR_NAME = "log-spool";
|
|
6
|
+
|
|
7
|
+
export function resolveLogSpoolRoot(explicit?: string): string {
|
|
8
|
+
if (explicit != null && explicit.trim() !== "") {
|
|
9
|
+
return explicit;
|
|
10
|
+
}
|
|
11
|
+
const configured = process.env.LOG_SPOOL_DIR?.trim();
|
|
12
|
+
if (configured != null && configured !== "") {
|
|
13
|
+
return configured;
|
|
14
|
+
}
|
|
15
|
+
const runtimeDir = runtimeDataDirFromEnv();
|
|
16
|
+
if (runtimeDir == null) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"[LogSpool] spool root required: set LOG_SPOOL_DIR or LOG_PERSIST_REGISTRY_DIR / RUNTIME_DATA_DIR",
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return join(runtimeDir, LOG_SPOOL_DIR_NAME);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function logSpoolServiceDir(spoolRoot: string, serviceId: string): string {
|
|
25
|
+
return join(spoolRoot, serviceId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function logSpoolProcessDir(spoolRoot: string, serviceId: string, processRole: LogSpoolProcessRole): string {
|
|
29
|
+
return join(spoolRoot, serviceId, processRole);
|
|
30
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Four-process L3 blueprint roles — always scanned by collect-log. */
|
|
2
|
+
export const LOG_SPOOL_CORE_PROCESS_ROLES = ["main", "guard", "scheduler", "worker"] as const;
|
|
3
|
+
|
|
4
|
+
export type LogSpoolCoreProcessRole = (typeof LOG_SPOOL_CORE_PROCESS_ROLES)[number];
|
|
5
|
+
|
|
6
|
+
/** Spool directory / `LOG_PERSIST_PROCESS` name — core or service-specific extra. */
|
|
7
|
+
export type LogSpoolProcessRole = LogSpoolCoreProcessRole | string;
|
|
8
|
+
|
|
9
|
+
export const LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV = "LOG_SPOOL_EXTRA_PROCESS_ROLES";
|
|
10
|
+
|
|
11
|
+
const EXTRA_ROLE_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
12
|
+
|
|
13
|
+
function parseRoleList(raw: string | undefined): string[] {
|
|
14
|
+
if (raw == null || raw.trim() === "") {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return raw
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((part) => part.trim())
|
|
20
|
+
.filter((part) => part !== "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Parse comma-separated extra roles from env (e.g. `chrome-sidecar`). */
|
|
24
|
+
export function logSpoolExtraProcessRolesFromEnv(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
25
|
+
return parseRoleList(env[LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Core + extras, deduped, stable order (core first). */
|
|
29
|
+
export function resolveAllLogSpoolProcessRoles(env: NodeJS.ProcessEnv = process.env): LogSpoolProcessRole[] {
|
|
30
|
+
const extras = logSpoolExtraProcessRolesFromEnv(env);
|
|
31
|
+
const seen = new Set<string>(LOG_SPOOL_CORE_PROCESS_ROLES);
|
|
32
|
+
const all: LogSpoolProcessRole[] = [...LOG_SPOOL_CORE_PROCESS_ROLES];
|
|
33
|
+
for (const role of extras) {
|
|
34
|
+
if (!seen.has(role)) {
|
|
35
|
+
seen.add(role);
|
|
36
|
+
all.push(role);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return all;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isLogSpoolCoreProcessRole(value: string): value is LogSpoolCoreProcessRole {
|
|
43
|
+
return (LOG_SPOOL_CORE_PROCESS_ROLES as readonly string[]).includes(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Whether `value` is a known spool role (core or configured extra). */
|
|
47
|
+
export function isKnownLogSpoolProcessRole(value: string, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
48
|
+
if (isLogSpoolCoreProcessRole(value)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return logSpoolExtraProcessRolesFromEnv(env).includes(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Validate extra role token — lowercase alphanumeric + hyphen, not a duplicate core name. */
|
|
55
|
+
export function assertValidLogSpoolExtraProcessRole(role: string): void {
|
|
56
|
+
if (role === "" || !EXTRA_ROLE_PATTERN.test(role)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`[LogSpool] invalid extra process role "${role}" — use lowercase letters, digits, hyphen (e.g. chrome-sidecar)`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (isLogSpoolCoreProcessRole(role)) {
|
|
62
|
+
throw new Error(`[LogSpool] extra process role "${role}" duplicates a core role`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function mergeLogSpoolExtraProcessRolesEnv(env: NodeJS.ProcessEnv, extraRoles: readonly string[]): void {
|
|
67
|
+
if (extraRoles.length === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const role of extraRoles) {
|
|
71
|
+
assertValidLogSpoolExtraProcessRole(role);
|
|
72
|
+
}
|
|
73
|
+
const merged = new Set([...logSpoolExtraProcessRolesFromEnv(env), ...extraRoles]);
|
|
74
|
+
env[LOG_SPOOL_EXTRA_PROCESS_ROLES_ENV] = [...merged].join(",");
|
|
75
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { LOG_TOPIC_TRIGGER } from "../../../trigger/trigger.constant";
|
|
2
|
+
import { TriggerManager } from "../../../trigger/trigger-manager";
|
|
3
|
+
import { logSpoolEnabledFromEnv } from "../upload/env";
|
|
4
|
+
import { spoolLogger } from "../upload/logger";
|
|
5
|
+
import { runCollectLogTick } from "./collector";
|
|
6
|
+
import { logSpoolConfigFromEnv } from "./config";
|
|
7
|
+
|
|
8
|
+
export const COLLECT_LOG_RUNNER_KEY = "collect-log";
|
|
9
|
+
|
|
10
|
+
export interface RegisterCollectLogRunnerOptions {
|
|
11
|
+
serviceValue: string;
|
|
12
|
+
getServiceId: () => Promise<string | null>;
|
|
13
|
+
runnerKey?: string;
|
|
14
|
+
intervalMs?: number;
|
|
15
|
+
initialDelayMs?: number;
|
|
16
|
+
spoolRoot?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Guard process only — uploads `.tmp` spool batches and runs GC.
|
|
21
|
+
* Register before {@link TriggerNode.start} (e.g. in {@link ServiceGuardNode.create}).
|
|
22
|
+
*/
|
|
23
|
+
export function registerCollectLogRunner(options: RegisterCollectLogRunnerOptions): void {
|
|
24
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const config = logSpoolConfigFromEnv();
|
|
29
|
+
const intervalMs = options.intervalMs ?? config.collectIntervalMs;
|
|
30
|
+
|
|
31
|
+
TriggerManager.registerRunner({
|
|
32
|
+
key: options.runnerKey ?? COLLECT_LOG_RUNNER_KEY,
|
|
33
|
+
intervalMs,
|
|
34
|
+
initialDelayMs: options.initialDelayMs ?? 0,
|
|
35
|
+
retryCount: 1,
|
|
36
|
+
fn: async (ctx) => {
|
|
37
|
+
const serviceId = await options.getServiceId();
|
|
38
|
+
if (serviceId == null) {
|
|
39
|
+
ctx.logger.warn("collect-log skipped — serviceId unavailable", { topic: LOG_TOPIC_TRIGGER });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = await runCollectLogTick({
|
|
44
|
+
serviceId,
|
|
45
|
+
serviceValue: options.serviceValue,
|
|
46
|
+
spoolRoot: options.spoolRoot,
|
|
47
|
+
config,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (result.uploaded > 0 || result.failed > 0) {
|
|
51
|
+
ctx.logger.info("collect-log runner finished", {
|
|
52
|
+
topic: LOG_TOPIC_TRIGGER,
|
|
53
|
+
data: result,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
spoolLogger.info("collect-log runner registered", {
|
|
60
|
+
intervalMs,
|
|
61
|
+
serviceValue: options.serviceValue,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { logSpoolEnabledFromEnv } from "../upload/env";
|
|
2
|
+
import { ensureLogSpoolFromEnv, getLogSpoolStats, shutdownLogSpoolFromEnv } from "./enable";
|
|
3
|
+
import type { LogSpoolWriterStats } from "./interface";
|
|
4
|
+
|
|
5
|
+
/** Whether file log spool is enabled (`LOG_PERSIST_ENABLED=true`). */
|
|
6
|
+
export function isLogSpoolEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
7
|
+
return logSpoolEnabledFromEnv(env);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Guard / scheduler / worker (and extra roles) — enable writer from spawn env.
|
|
12
|
+
* Main is enabled by `createServiceHost` after registry claim.
|
|
13
|
+
*/
|
|
14
|
+
export async function enableLogSpoolFromEnvInChild(): Promise<void> {
|
|
15
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
await ensureLogSpoolFromEnv();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Writer buffer stats when spool enabled; `null` when disabled. Typical use: main observability. */
|
|
22
|
+
export function getMainLogSpoolWriterStats(): LogSpoolWriterStats | null {
|
|
23
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return getLogSpoolStats();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Flush memory buffer and disable writer when spool enabled; no-op otherwise. */
|
|
30
|
+
export async function shutdownLogSpool(): Promise<void> {
|
|
31
|
+
if (!logSpoolEnabledFromEnv()) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
await shutdownLogSpoolFromEnv();
|
|
35
|
+
}
|