File size: 4,998 Bytes
76289e7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | // Logging config helpers read and normalize logger configuration.
import fs from "node:fs";
import { isRecord as isObjectRecord } from "@openclaw/normalization-core/record-coerce";
import { resolveConfigEnvVars } from "../config/env-substitution.js";
import { resolveConfigIncludes, resolveConfigIncludesForTopLevelKey } from "../config/includes.js";
import { resolveConfigPath, resolveIncludeRoots } from "../config/paths.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { tryProcessCwd } from "../infra/safe-cwd.js";
import { parseJsonWithJson5Fallback } from "../utils/parse-json-compat.js";
import { APPLIED_LOGGING_CONFIG_UNOWNED, loggingState } from "./state.js";
// Lightweight logging-config reader used before the full config runtime is safe to load.
type LoggingConfig = NonNullable<OpenClawConfig["logging"]>;
let cachedLoggingConfig:
| {
selector: string;
logging: LoggingConfig | undefined;
}
| undefined;
export function invalidateLoggingConfigCache(): void {
cachedLoggingConfig = undefined;
}
function resolveLoggingConfigSelector(): string {
const env = process.env;
return [
env.OPENCLAW_CONFIG_PATH,
env.OPENCLAW_STATE_DIR,
env.OPENCLAW_HOME,
env.OPENCLAW_PROFILE,
env.HOME,
env.USERPROFILE,
env.HOMEDRIVE,
env.HOMEPATH,
env.PREFIX,
env.ANDROID_DATA,
env.OPENCLAW_TEST_FAST,
tryProcessCwd() ?? "",
]
.map((value) => value ?? "")
.join("\0");
}
function resolvePartialDiagnosticLoggingConfig(logging: unknown): LoggingConfig | undefined {
if (!isObjectRecord(logging)) {
return undefined;
}
const partial: Record<string, unknown> = {};
if (typeof logging.consoleStyle === "string") {
try {
const resolved = resolveConfigEnvVars({ consoleStyle: logging.consoleStyle });
if (
isObjectRecord(resolved) &&
(resolved.consoleStyle === "pretty" ||
resolved.consoleStyle === "compact" ||
resolved.consoleStyle === "json")
) {
partial.consoleStyle = resolved.consoleStyle;
}
} catch {
// Keep resolving independent diagnostic fields.
}
}
if (Array.isArray(logging.redactPatterns)) {
try {
const resolved = resolveConfigEnvVars({ redactPatterns: logging.redactPatterns });
if (
isObjectRecord(resolved) &&
Array.isArray(resolved.redactPatterns) &&
resolved.redactPatterns.every((entry) => typeof entry === "string")
) {
partial.redactPatterns = resolved.redactPatterns;
}
} catch {
// A missing variable in one pattern must not hide a separately resolvable style.
}
}
return Object.keys(partial).length > 0 ? (partial as LoggingConfig) : undefined;
}
/** Reads the logging block from config, caching by resolved config path. */
export function readLoggingConfig(): LoggingConfig | undefined {
try {
if (loggingState.appliedConfig !== APPLIED_LOGGING_CONFIG_UNOWNED) {
return loggingState.appliedConfig;
}
const selector = resolveLoggingConfigSelector();
if (cachedLoggingConfig?.selector === selector) {
return cachedLoggingConfig.logging;
}
const configPath = resolveConfigPath();
if (!fs.existsSync(configPath)) {
cachedLoggingConfig = { selector, logging: undefined };
return undefined;
}
const parsed = parseJsonWithJson5Fallback(fs.readFileSync(configPath, "utf8"));
const allowedRoots = resolveIncludeRoots();
let includedConfig: unknown;
try {
includedConfig = resolveConfigIncludesForTopLevelKey(
parsed,
configPath,
"logging",
undefined,
{ allowedRoots },
);
} catch {
// Validation commands still need a directly-authored logging style when an unrelated
// root include is malformed. Resolve only that subtree through the same canonical rules.
const directLogging = isObjectRecord(parsed) ? parsed.logging : undefined;
if (directLogging === undefined) {
return undefined;
}
try {
includedConfig = resolveConfigIncludes({ logging: directLogging }, configPath, undefined, {
allowedRoots,
});
} catch {
const logging = resolvePartialDiagnosticLoggingConfig(directLogging);
return logging;
}
}
let resolvedConfig: unknown;
try {
resolvedConfig = resolveConfigEnvVars(includedConfig);
} catch {
const includedLogging = isObjectRecord(includedConfig) ? includedConfig.logging : undefined;
const logging = resolvePartialDiagnosticLoggingConfig(includedLogging);
return logging;
}
const logging = isObjectRecord(resolvedConfig) ? resolvedConfig.logging : undefined;
const resolvedLogging = isObjectRecord(logging) ? (logging as LoggingConfig) : undefined;
cachedLoggingConfig = {
selector,
logging: resolvedLogging,
};
return resolvedLogging;
} catch {
return undefined;
}
}
|