mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 14:45:46 +00:00
perf(cli): trim gateway status startup imports
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
buildPlatformRuntimeLogHints,
|
||||
buildPlatformServiceStartHints,
|
||||
} from "../../daemon/runtime-hints.js";
|
||||
import { getResolvedLoggerSettings } from "../../logging.js";
|
||||
import { colorize, isRich, theme } from "../../terminal/theme.js";
|
||||
import { formatCliCommand } from "../command-format.js";
|
||||
import { parsePort } from "../shared/parse-port.js";
|
||||
@@ -147,18 +146,13 @@ export function normalizeListenerAddress(raw: string): string {
|
||||
export function renderRuntimeHints(
|
||||
runtime: { missingUnit?: boolean; status?: string } | undefined,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
logFile?: string | null,
|
||||
): string[] {
|
||||
if (!runtime) {
|
||||
return [];
|
||||
}
|
||||
const hints: string[] = [];
|
||||
const fileLog = (() => {
|
||||
try {
|
||||
return getResolvedLoggerSettings().file;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const fileLog = logFile ?? null;
|
||||
if (runtime.missingUnit) {
|
||||
hints.push(`Service not installed. Run: ${formatCliCommand("openclaw gateway install", env)}`);
|
||||
if (fileLog) {
|
||||
|
||||
@@ -58,6 +58,8 @@ const resolveStateDir = vi.fn(
|
||||
const resolveConfigPath = vi.fn((env: NodeJS.ProcessEnv, stateDir: string) => {
|
||||
return env.OPENCLAW_CONFIG_PATH ?? `${stateDir}/openclaw.json`;
|
||||
});
|
||||
const readConfigFileSnapshotCalls = vi.fn((configPath: string) => configPath);
|
||||
const loadConfigCalls = vi.fn((configPath: string) => configPath);
|
||||
let daemonLoadedConfig: Record<string, unknown> = {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
@@ -74,14 +76,23 @@ let cliLoadedConfig: Record<string, unknown> = {
|
||||
vi.mock("../../config/config.js", () => ({
|
||||
createConfigIO: ({ configPath }: { configPath: string }) => {
|
||||
const isDaemon = configPath.includes("/openclaw-daemon/");
|
||||
const runtimeConfig = isDaemon ? daemonLoadedConfig : cliLoadedConfig;
|
||||
return {
|
||||
readConfigFileSnapshot: async () => ({
|
||||
path: configPath,
|
||||
exists: true,
|
||||
valid: true,
|
||||
issues: [],
|
||||
}),
|
||||
loadConfig: () => (isDaemon ? daemonLoadedConfig : cliLoadedConfig),
|
||||
readConfigFileSnapshot: async () => {
|
||||
readConfigFileSnapshotCalls(configPath);
|
||||
return {
|
||||
path: configPath,
|
||||
exists: true,
|
||||
valid: true,
|
||||
issues: [],
|
||||
runtimeConfig,
|
||||
config: runtimeConfig,
|
||||
};
|
||||
},
|
||||
loadConfig: () => {
|
||||
loadConfigCalls(configPath);
|
||||
return runtimeConfig;
|
||||
},
|
||||
};
|
||||
},
|
||||
loadConfig: () => cliLoadedConfig,
|
||||
@@ -158,6 +169,8 @@ describe("gatherDaemonStatus", () => {
|
||||
callGatewayStatusProbe.mockClear();
|
||||
loadGatewayTlsRuntime.mockClear();
|
||||
inspectGatewayRestart.mockClear();
|
||||
readConfigFileSnapshotCalls.mockClear();
|
||||
loadConfigCalls.mockClear();
|
||||
daemonLoadedConfig = {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
@@ -212,6 +225,22 @@ describe("gatherDaemonStatus", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reuses the shared CLI config snapshot when the daemon uses the same config path", async () => {
|
||||
serviceReadCommand.mockResolvedValueOnce({
|
||||
programArguments: ["/bin/node", "cli", "gateway", "--port", "19001"],
|
||||
});
|
||||
|
||||
await gatherDaemonStatus({
|
||||
rpc: {},
|
||||
probe: true,
|
||||
deep: false,
|
||||
});
|
||||
|
||||
expect(readConfigFileSnapshotCalls).toHaveBeenCalledTimes(1);
|
||||
expect(readConfigFileSnapshotCalls).toHaveBeenCalledWith("/tmp/openclaw-cli/openclaw.json");
|
||||
expect(loadConfigCalls).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not force local TLS fingerprint when probe URL is explicitly overridden", async () => {
|
||||
const status = await gatherDaemonStatus({
|
||||
rpc: { url: "wss://override.example:18790" },
|
||||
|
||||
@@ -6,18 +6,20 @@ import {
|
||||
} from "../../config/config.js";
|
||||
import type {
|
||||
OpenClawConfig,
|
||||
ConfigFileSnapshot,
|
||||
GatewayBindMode,
|
||||
GatewayControlUiConfig,
|
||||
} from "../../config/types.js";
|
||||
import { readLastGatewayErrorLine } from "../../daemon/diagnostics.js";
|
||||
import type { FindExtraGatewayServicesOptions } from "../../daemon/inspect.js";
|
||||
import { findExtraGatewayServices } from "../../daemon/inspect.js";
|
||||
import type { ServiceConfigAudit } from "../../daemon/service-audit.js";
|
||||
import { auditGatewayServiceConfig } from "../../daemon/service-audit.js";
|
||||
import type { GatewayServiceRuntime } from "../../daemon/service-runtime.js";
|
||||
import { resolveGatewayService } from "../../daemon/service.js";
|
||||
import { isGatewaySecretRefUnavailableError, trimToUndefined } from "../../gateway/credentials.js";
|
||||
import { resolveGatewayProbeAuthWithSecretInputs } from "../../gateway/probe-auth.js";
|
||||
import {
|
||||
isGatewaySecretRefUnavailableError,
|
||||
resolveGatewayProbeCredentialsFromConfig,
|
||||
trimToUndefined,
|
||||
} from "../../gateway/credentials.js";
|
||||
import {
|
||||
inspectBestEffortPrimaryTailnetIPv4,
|
||||
resolveBestEffortGatewayBindHostForDisplay,
|
||||
@@ -29,9 +31,7 @@ import {
|
||||
type PortListener,
|
||||
type PortUsageStatus,
|
||||
} from "../../infra/ports.js";
|
||||
import { loadGatewayTlsRuntime } from "../../infra/tls/gateway.js";
|
||||
import { probeGatewayStatus } from "./probe.js";
|
||||
import { inspectGatewayRestart } from "./restart-health.js";
|
||||
import { resolveConfiguredLogFilePath } from "../../logging/log-file-path.js";
|
||||
import { normalizeListenerAddress, parsePortFromArgs, pickProbeHostForBind } from "./shared.js";
|
||||
import type { GatewayRpcOpts } from "./types.js";
|
||||
|
||||
@@ -76,6 +76,52 @@ type ResolvedGatewayStatus = {
|
||||
probeUrlOverride: string | null;
|
||||
};
|
||||
|
||||
let gatewayProbeAuthModulePromise:
|
||||
| Promise<typeof import("../../gateway/probe-auth.js")>
|
||||
| undefined;
|
||||
let daemonInspectModulePromise: Promise<typeof import("../../daemon/inspect.js")> | undefined;
|
||||
let serviceAuditModulePromise: Promise<typeof import("../../daemon/service-audit.js")> | undefined;
|
||||
let gatewayTlsModulePromise: Promise<typeof import("../../infra/tls/gateway.js")> | undefined;
|
||||
let daemonProbeModulePromise: Promise<typeof import("./probe.js")> | undefined;
|
||||
let restartHealthModulePromise: Promise<typeof import("./restart-health.js")> | undefined;
|
||||
|
||||
function loadGatewayProbeAuthModule() {
|
||||
gatewayProbeAuthModulePromise ??= import("../../gateway/probe-auth.js");
|
||||
return gatewayProbeAuthModulePromise;
|
||||
}
|
||||
|
||||
function loadDaemonInspectModule() {
|
||||
daemonInspectModulePromise ??= import("../../daemon/inspect.js");
|
||||
return daemonInspectModulePromise;
|
||||
}
|
||||
|
||||
function loadServiceAuditModule() {
|
||||
serviceAuditModulePromise ??= import("../../daemon/service-audit.js");
|
||||
return serviceAuditModulePromise;
|
||||
}
|
||||
|
||||
function loadGatewayTlsModule() {
|
||||
gatewayTlsModulePromise ??= import("../../infra/tls/gateway.js");
|
||||
return gatewayTlsModulePromise;
|
||||
}
|
||||
|
||||
function loadDaemonProbeModule() {
|
||||
daemonProbeModulePromise ??= import("./probe.js");
|
||||
return daemonProbeModulePromise;
|
||||
}
|
||||
|
||||
function loadRestartHealthModule() {
|
||||
restartHealthModulePromise ??= import("./restart-health.js");
|
||||
return restartHealthModulePromise;
|
||||
}
|
||||
|
||||
function resolveSnapshotRuntimeConfig(snapshot: ConfigFileSnapshot | null): OpenClawConfig | null {
|
||||
if (!snapshot?.valid || !snapshot.runtimeConfig) {
|
||||
return null;
|
||||
}
|
||||
return snapshot.runtimeConfig;
|
||||
}
|
||||
|
||||
function appendProbeNote(
|
||||
existing: string | undefined,
|
||||
extra: string | undefined,
|
||||
@@ -87,6 +133,7 @@ function appendProbeNote(
|
||||
return [...new Set(values)].join(" ");
|
||||
}
|
||||
export type DaemonStatus = {
|
||||
logFile?: string;
|
||||
service: {
|
||||
label: string;
|
||||
loaded: boolean;
|
||||
@@ -162,17 +209,27 @@ async function loadDaemonConfigContext(
|
||||
);
|
||||
|
||||
const cliIO = createConfigIO({ env: process.env, configPath: cliConfigPath });
|
||||
const daemonIO = createConfigIO({
|
||||
env: mergedDaemonEnv,
|
||||
configPath: daemonConfigPath,
|
||||
});
|
||||
const sharesDaemonConfigContext = !serviceEnv && cliConfigPath === daemonConfigPath;
|
||||
const daemonIO = sharesDaemonConfigContext
|
||||
? cliIO
|
||||
: createConfigIO({
|
||||
env: mergedDaemonEnv,
|
||||
configPath: daemonConfigPath,
|
||||
});
|
||||
|
||||
const cliSnapshotPromise = cliIO.readConfigFileSnapshot().catch(() => null);
|
||||
const daemonSnapshotPromise = sharesDaemonConfigContext
|
||||
? cliSnapshotPromise
|
||||
: daemonIO.readConfigFileSnapshot().catch(() => null);
|
||||
const [cliSnapshot, daemonSnapshot] = await Promise.all([
|
||||
cliIO.readConfigFileSnapshot().catch(() => null),
|
||||
daemonIO.readConfigFileSnapshot().catch(() => null),
|
||||
cliSnapshotPromise,
|
||||
daemonSnapshotPromise,
|
||||
]);
|
||||
const cliCfg = cliIO.loadConfig();
|
||||
const daemonCfg = daemonIO.loadConfig();
|
||||
const cliCfg = resolveSnapshotRuntimeConfig(cliSnapshot) ?? cliIO.loadConfig();
|
||||
const daemonCfg =
|
||||
sharesDaemonConfigContext && cliSnapshot === daemonSnapshot
|
||||
? cliCfg
|
||||
: (resolveSnapshotRuntimeConfig(daemonSnapshot) ?? daemonIO.loadConfig());
|
||||
|
||||
const cliConfigSummary: ConfigSummary = {
|
||||
path: cliSnapshot?.path ?? cliConfigPath,
|
||||
@@ -300,10 +357,14 @@ export async function gatherDaemonStatus(
|
||||
service.isLoaded({ env: serviceEnv }).catch(() => false),
|
||||
service.readRuntime(serviceEnv).catch((err) => ({ status: "unknown", detail: String(err) })),
|
||||
]);
|
||||
const configAudit = await auditGatewayServiceConfig({
|
||||
env: process.env,
|
||||
command,
|
||||
});
|
||||
const configAudit = command
|
||||
? await loadServiceAuditModule().then(({ auditGatewayServiceConfig }) =>
|
||||
auditGatewayServiceConfig({
|
||||
env: process.env,
|
||||
command,
|
||||
}),
|
||||
)
|
||||
: { ok: true, issues: [] satisfies ServiceConfigAudit["issues"] };
|
||||
const {
|
||||
mergedDaemonEnv,
|
||||
cliCfg,
|
||||
@@ -324,25 +385,33 @@ export async function gatherDaemonStatus(
|
||||
cliPort,
|
||||
});
|
||||
|
||||
const extraServices = await findExtraGatewayServices(
|
||||
process.env as Record<string, string | undefined>,
|
||||
{ deep: Boolean(opts.deep) },
|
||||
).catch(() => []);
|
||||
const extraServices = opts.deep
|
||||
? await loadDaemonInspectModule()
|
||||
.then(({ findExtraGatewayServices }) =>
|
||||
findExtraGatewayServices(process.env as Record<string, string | undefined>, {
|
||||
deep: true,
|
||||
}),
|
||||
)
|
||||
.catch(() => [])
|
||||
: [];
|
||||
|
||||
const timeoutMs = parseStrictPositiveInteger(opts.rpc.timeout ?? "10000") ?? 10_000;
|
||||
|
||||
const tlsEnabled = daemonCfg.gateway?.tls?.enabled === true;
|
||||
const shouldUseLocalTlsRuntime = opts.probe && !probeUrlOverride && tlsEnabled;
|
||||
const tlsRuntime = shouldUseLocalTlsRuntime
|
||||
? await loadGatewayTlsRuntime(daemonCfg.gateway?.tls)
|
||||
? await loadGatewayTlsModule().then(({ loadGatewayTlsRuntime }) =>
|
||||
loadGatewayTlsRuntime(daemonCfg.gateway?.tls),
|
||||
)
|
||||
: undefined;
|
||||
let daemonProbeAuth: { token?: string; password?: string } | undefined;
|
||||
let rpcAuthWarning: string | undefined;
|
||||
if (opts.probe) {
|
||||
const probeMode = daemonCfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||
try {
|
||||
daemonProbeAuth = await resolveGatewayProbeAuthWithSecretInputs({
|
||||
daemonProbeAuth = resolveGatewayProbeCredentialsFromConfig({
|
||||
cfg: daemonCfg,
|
||||
mode: daemonCfg.gateway?.mode === "remote" ? "remote" : "local",
|
||||
mode: probeMode,
|
||||
env: mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
explicitAuth: {
|
||||
token: opts.rpc.token,
|
||||
@@ -354,36 +423,61 @@ export async function gatherDaemonStatus(
|
||||
if (!refPath) {
|
||||
throw error;
|
||||
}
|
||||
daemonProbeAuth = undefined;
|
||||
rpcAuthWarning = `${refPath} SecretRef is unavailable in this command path; probing without configured auth credentials.`;
|
||||
try {
|
||||
daemonProbeAuth = await loadGatewayProbeAuthModule().then(
|
||||
({ resolveGatewayProbeAuthWithSecretInputs }) =>
|
||||
resolveGatewayProbeAuthWithSecretInputs({
|
||||
cfg: daemonCfg,
|
||||
mode: probeMode,
|
||||
env: mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
explicitAuth: {
|
||||
token: opts.rpc.token,
|
||||
password: opts.rpc.password,
|
||||
},
|
||||
}),
|
||||
);
|
||||
} catch (fallbackError) {
|
||||
const fallbackRefPath = parseGatewaySecretRefPathFromError(fallbackError);
|
||||
if (!fallbackRefPath) {
|
||||
throw fallbackError;
|
||||
}
|
||||
daemonProbeAuth = undefined;
|
||||
rpcAuthWarning = `${fallbackRefPath} SecretRef is unavailable in this command path; probing without configured auth credentials.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rpc = opts.probe
|
||||
? await probeGatewayStatus({
|
||||
url: gateway.probeUrl,
|
||||
token: daemonProbeAuth?.token,
|
||||
password: daemonProbeAuth?.password,
|
||||
tlsFingerprint:
|
||||
shouldUseLocalTlsRuntime && tlsRuntime?.enabled
|
||||
? tlsRuntime.fingerprintSha256
|
||||
: undefined,
|
||||
timeoutMs,
|
||||
json: opts.rpc.json,
|
||||
requireRpc: opts.requireRpc,
|
||||
configPath: daemonConfigSummary.path,
|
||||
})
|
||||
? await loadDaemonProbeModule().then(({ probeGatewayStatus }) =>
|
||||
probeGatewayStatus({
|
||||
url: gateway.probeUrl,
|
||||
token: daemonProbeAuth?.token,
|
||||
password: daemonProbeAuth?.password,
|
||||
tlsFingerprint:
|
||||
shouldUseLocalTlsRuntime && tlsRuntime?.enabled
|
||||
? tlsRuntime.fingerprintSha256
|
||||
: undefined,
|
||||
timeoutMs,
|
||||
json: opts.rpc.json,
|
||||
requireRpc: opts.requireRpc,
|
||||
configPath: daemonConfigSummary.path,
|
||||
}),
|
||||
)
|
||||
: undefined;
|
||||
if (rpc?.ok) {
|
||||
rpcAuthWarning = undefined;
|
||||
}
|
||||
const health =
|
||||
opts.probe && loaded
|
||||
? await inspectGatewayRestart({
|
||||
service,
|
||||
port: daemonPort,
|
||||
env: serviceEnv,
|
||||
}).catch(() => undefined)
|
||||
? await loadRestartHealthModule()
|
||||
.then(({ inspectGatewayRestart }) =>
|
||||
inspectGatewayRestart({
|
||||
service,
|
||||
port: daemonPort,
|
||||
env: serviceEnv,
|
||||
}),
|
||||
)
|
||||
.catch(() => undefined)
|
||||
: undefined;
|
||||
|
||||
let lastError: string | undefined;
|
||||
@@ -392,6 +486,7 @@ export async function gatherDaemonStatus(
|
||||
}
|
||||
|
||||
return {
|
||||
logFile: resolveConfiguredLogFilePath(cliCfg),
|
||||
service: {
|
||||
label: service.label,
|
||||
loaded,
|
||||
|
||||
@@ -20,7 +20,7 @@ vi.mock("../../terminal/theme.js", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../commands/onboard-helpers.js", () => ({
|
||||
vi.mock("../../gateway/control-ui-links.js", () => ({
|
||||
resolveControlUiLinks: () => ({ httpUrl: "http://127.0.0.1:18789" }),
|
||||
}));
|
||||
|
||||
@@ -44,10 +44,6 @@ vi.mock("../../infra/wsl.js", () => ({
|
||||
isWSLEnv: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("../../logging.js", () => ({
|
||||
getResolvedLoggerSettings: () => ({ file: "/tmp/openclaw.log" }),
|
||||
}));
|
||||
|
||||
vi.mock("./shared.js", () => ({
|
||||
createCliStatusTextStyles: () => ({
|
||||
rich: false,
|
||||
@@ -87,6 +83,7 @@ describe("printDaemonStatus", () => {
|
||||
notLoadedText: "not loaded",
|
||||
runtime: { status: "running", pid: 8000 },
|
||||
},
|
||||
logFile: "/tmp/openclaw.log",
|
||||
gateway: {
|
||||
bindMode: "loopback",
|
||||
bindHost: "127.0.0.1",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { resolveControlUiLinks } from "../../commands/onboard-helpers.js";
|
||||
import { formatConfigIssueLine } from "../../config/issue-format.js";
|
||||
import {
|
||||
resolveGatewayLaunchAgentLabel,
|
||||
@@ -11,8 +10,8 @@ import {
|
||||
renderSystemdUnavailableHints,
|
||||
} from "../../daemon/systemd-hints.js";
|
||||
import { classifySystemdUnavailableDetail } from "../../daemon/systemd-unavailable.js";
|
||||
import { resolveControlUiLinks } from "../../gateway/control-ui-links.js";
|
||||
import { isWSLEnv } from "../../infra/wsl.js";
|
||||
import { getResolvedLoggerSettings } from "../../logging.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { colorize } from "../../terminal/theme.js";
|
||||
import { shortenHomePath } from "../../utils.js";
|
||||
@@ -67,11 +66,8 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
? okText(service.loadedText)
|
||||
: warnText(service.notLoadedText);
|
||||
defaultRuntime.log(`${label("Service:")} ${accent(service.label)} (${serviceStatus})`);
|
||||
try {
|
||||
const logFile = getResolvedLoggerSettings().file;
|
||||
defaultRuntime.log(`${label("File logs:")} ${infoText(shortenHomePath(logFile))}`);
|
||||
} catch {
|
||||
// ignore missing config/log resolution
|
||||
if (status.logFile) {
|
||||
defaultRuntime.log(`${label("File logs:")} ${infoText(shortenHomePath(status.logFile))}`);
|
||||
}
|
||||
if (service.command?.programArguments?.length) {
|
||||
defaultRuntime.log(
|
||||
@@ -237,7 +233,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
|
||||
if (service.runtime?.missingUnit) {
|
||||
defaultRuntime.error(errorText("Service unit not found."));
|
||||
for (const hint of renderRuntimeHints(service.runtime)) {
|
||||
for (const hint of renderRuntimeHints(service.runtime, process.env, status.logFile)) {
|
||||
defaultRuntime.error(errorText(hint));
|
||||
}
|
||||
} else if (service.loaded && service.runtime?.status === "stopped") {
|
||||
@@ -247,6 +243,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
for (const hint of renderRuntimeHints(
|
||||
service.runtime,
|
||||
service.command?.environment ?? process.env,
|
||||
status.logFile,
|
||||
)) {
|
||||
defaultRuntime.error(errorText(hint));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ async function prepareRoutedCommand(params: {
|
||||
loadPlugins?: boolean | ((argv: string[]) => boolean);
|
||||
}) {
|
||||
const suppressDoctorStdout = hasFlag(params.argv, "--json");
|
||||
const skipConfigGuard = params.commandPath[0] === "status" && suppressDoctorStdout;
|
||||
const skipConfigGuard =
|
||||
(params.commandPath[0] === "status" && suppressDoctorStdout) ||
|
||||
(params.commandPath[0] === "gateway" && params.commandPath[1] === "status");
|
||||
if (!suppressDoctorStdout && process.stdout.isTTY) {
|
||||
const [{ emitCliBanner }, { VERSION }] = await Promise.all([
|
||||
import("./banner.js"),
|
||||
|
||||
@@ -1889,7 +1889,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
||||
const exists = deps.fs.existsSync(configPath);
|
||||
if (!exists) {
|
||||
const hash = hashConfigRaw(null);
|
||||
const config = materializeRuntimeConfig({}, "missing");
|
||||
const config = {};
|
||||
const legacyIssues: LegacyConfigIssue[] = [];
|
||||
return await finalizeReadConfigSnapshotInternalResult(deps, {
|
||||
snapshot: createConfigFileSnapshot({
|
||||
|
||||
41
src/logging/log-file-path.ts
Normal file
41
src/logging/log-file-path.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import {
|
||||
POSIX_OPENCLAW_TMP_DIR,
|
||||
resolvePreferredOpenClawTmpDir,
|
||||
} from "../infra/tmp-openclaw-dir.js";
|
||||
|
||||
const LOG_PREFIX = "openclaw";
|
||||
const LOG_SUFFIX = ".log";
|
||||
|
||||
function canUseNodeFs(): boolean {
|
||||
const getBuiltinModule = (
|
||||
process as NodeJS.Process & {
|
||||
getBuiltinModule?: (id: string) => unknown;
|
||||
}
|
||||
).getBuiltinModule;
|
||||
if (typeof getBuiltinModule !== "function") {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return getBuiltinModule("fs") !== undefined;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatLocalDate(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function resolveDefaultRollingLogFile(date = new Date()): string {
|
||||
const logDir = canUseNodeFs() ? resolvePreferredOpenClawTmpDir() : POSIX_OPENCLAW_TMP_DIR;
|
||||
return path.join(logDir, `${LOG_PREFIX}-${formatLocalDate(date)}${LOG_SUFFIX}`);
|
||||
}
|
||||
|
||||
export function resolveConfiguredLogFilePath(config?: OpenClawConfig | null): string {
|
||||
return config?.logging?.file ?? resolveDefaultRollingLogFile();
|
||||
}
|
||||
Reference in New Issue
Block a user