diff --git a/src/logging/console.ts b/src/logging/console.ts index 85c7dd38ce7..205f0cc848b 100644 --- a/src/logging/console.ts +++ b/src/logging/console.ts @@ -130,12 +130,27 @@ export function setConsoleTimestampPrefix(enabled: boolean): void { loggingState.consoleTimestampPrefix = enabled; } -export function shouldLogSubsystemToConsole(subsystem: string): boolean { +function normalizeConsoleSubsystem(subsystem?: string | null): string | null { + if (typeof subsystem !== "string") { + return null; + } + const normalized = subsystem.trim(); + return normalized.length > 0 ? normalized : null; +} + +export function shouldLogSubsystemToConsole(subsystem?: string | null): boolean { const filter = loggingState.consoleSubsystemFilter; if (!filter || filter.length === 0) { return true; } - return filter.some((prefix) => subsystem === prefix || subsystem.startsWith(`${prefix}/`)); + const normalizedSubsystem = normalizeConsoleSubsystem(subsystem); + if (!normalizedSubsystem) { + return false; + } + return filter.some( + (prefix) => + normalizedSubsystem === prefix || normalizedSubsystem.startsWith(`${prefix}/`), + ); } const SUPPRESSED_CONSOLE_PREFIXES = [ diff --git a/src/logging/subsystem.test.ts b/src/logging/subsystem.test.ts index 871d46d3ffb..92c40f35334 100644 --- a/src/logging/subsystem.test.ts +++ b/src/logging/subsystem.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { setConsoleSubsystemFilter } from "./console.js"; +import { setConsoleSubsystemFilter, shouldLogSubsystemToConsole } from "./console.js"; import { resetLogger, setLoggerOverride } from "./logger.js"; import { loggingState } from "./state.js"; import { createSubsystemLogger } from "./subsystem.js"; @@ -87,6 +87,32 @@ describe("createSubsystemLogger().isEnabled", () => { expect(log.isEnabled("info")).toBe(true); }); + it("treats missing subsystem labels as non-matches when filters are active", () => { + setConsoleSubsystemFilter(["gateway"]); + + expect(() => shouldLogSubsystemToConsole(undefined as unknown as string)).not.toThrow(); + expect(shouldLogSubsystemToConsole(undefined as unknown as string)).toBe(false); + }); + + it("does not throw when a malformed subsystem logger checks console enablement", () => { + setLoggerOverride({ level: "silent", consoleLevel: "info" }); + setConsoleSubsystemFilter(["gateway"]); + const log = createSubsystemLogger(undefined as unknown as string); + + expect(() => log.isEnabled("info", "console")).not.toThrow(); + expect(log.isEnabled("info", "console")).toBe(false); + }); + + it("falls back to an unknown subsystem label when a malformed logger emits", () => { + setLoggerOverride({ level: "silent", consoleLevel: "warn" }); + const warn = installConsoleMethodSpy("warn"); + const log = createSubsystemLogger(undefined as unknown as string); + + expect(() => log.warn("missing subsystem label")).not.toThrow(); + expect(warn).toHaveBeenCalledTimes(1); + expect(String(warn.mock.calls[0]?.[0] ?? "")).toContain("[unknown]"); + }); + it("suppresses probe warnings for embedded subsystems based on structured run metadata", () => { setLoggerOverride({ level: "silent", consoleLevel: "warn" }); const warn = installConsoleMethodSpy("warn"); diff --git a/src/logging/subsystem.ts b/src/logging/subsystem.ts index 30bc38e7d68..5d35ad73930 100644 --- a/src/logging/subsystem.ts +++ b/src/logging/subsystem.ts @@ -29,6 +29,14 @@ export type SubsystemLogger = { child: (name: string) => SubsystemLogger; }; +function normalizeSubsystemLabel(subsystem?: string | null): string { + if (typeof subsystem !== "string") { + return "unknown"; + } + const normalized = subsystem.trim(); + return normalized.length > 0 ? normalized : "unknown"; +} + function shouldLogToConsole(level: LogLevel, settings: { level: LogLevel }): boolean { if (level === "silent") { return false; @@ -259,8 +267,8 @@ function writeConsoleLine(level: LogLevel, line: string) { function shouldSuppressProbeConsoleLine(params: { level: LogLevel; - subsystem: string; - message: string; + subsystem?: string | null; + message?: string | null; meta?: Record; }): boolean { if (isVerbose()) { @@ -269,11 +277,13 @@ function shouldSuppressProbeConsoleLine(params: { if (params.level === "error" || params.level === "fatal") { return false; } + const subsystem = normalizeSubsystemLabel(params.subsystem); + const message = typeof params.message === "string" ? params.message : ""; const isProbeSuppressedSubsystem = - params.subsystem === "agent/embedded" || - params.subsystem.startsWith("agent/embedded/") || - params.subsystem === "model-fallback" || - params.subsystem.startsWith("model-fallback/"); + subsystem === "agent/embedded" || + subsystem.startsWith("agent/embedded/") || + subsystem === "model-fallback" || + subsystem.startsWith("model-fallback/"); if (!isProbeSuppressedSubsystem) { return false; } @@ -286,7 +296,7 @@ function shouldSuppressProbeConsoleLine(params: { if (runLikeId?.startsWith("probe-")) { return true; } - return /(sessionId|runId)=probe-/.test(params.message); + return /(sessionId|runId)=probe-/.test(message); } function logToFile( @@ -313,13 +323,14 @@ function logToFile( } export function createSubsystemLogger(subsystem: string): SubsystemLogger { + const resolvedSubsystem = normalizeSubsystemLabel(subsystem); let fileLogger: TsLogger | null = null; const emitLog = (level: LogLevel, message: string, meta?: Record) => { const consoleSettings = getConsoleSettings(); const consoleEnabled = shouldLogToConsole(level, { level: consoleSettings.level }) && - shouldLogSubsystemToConsole(subsystem); + shouldLogSubsystemToConsole(resolvedSubsystem); const fileEnabled = isFileLogLevelEnabled(level); if (!consoleEnabled && !fileEnabled) { return; @@ -337,7 +348,7 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { } if (fileEnabled) { if (!fileLogger) { - fileLogger = getChildLogger({ subsystem }); + fileLogger = getChildLogger({ subsystem: resolvedSubsystem }); } logToFile(fileLogger, level, message, fileMeta); } @@ -348,7 +359,7 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { if ( shouldSuppressProbeConsoleLine({ level, - subsystem, + subsystem: resolvedSubsystem, message: consoleMessage, meta: fileMeta, }) @@ -359,7 +370,7 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { level, formatConsoleLine({ level, - subsystem, + subsystem: resolvedSubsystem, message: consoleSettings.style === "json" ? message : consoleMessage, style: consoleSettings.style, meta: fileMeta, @@ -368,11 +379,11 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { }; const logger: SubsystemLogger = { - subsystem, + subsystem: resolvedSubsystem, isEnabled(level, target = "any") { const isConsoleEnabled = shouldLogToConsole(level, { level: getConsoleSettings().level }) && - shouldLogSubsystemToConsole(subsystem); + shouldLogSubsystemToConsole(resolvedSubsystem); const isFileEnabled = isFileLogLevelEnabled(level); if (target === "console") { return isConsoleEnabled; @@ -403,22 +414,28 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { raw(message) { if (isFileLogLevelEnabled("info")) { if (!fileLogger) { - fileLogger = getChildLogger({ subsystem }); + fileLogger = getChildLogger({ subsystem: resolvedSubsystem }); } logToFile(fileLogger, "info", message, { raw: true }); } if ( shouldLogToConsole("info", { level: getConsoleSettings().level }) && - shouldLogSubsystemToConsole(subsystem) + shouldLogSubsystemToConsole(resolvedSubsystem) ) { - if (shouldSuppressProbeConsoleLine({ level: "info", subsystem, message })) { + if ( + shouldSuppressProbeConsoleLine({ + level: "info", + subsystem: resolvedSubsystem, + message, + }) + ) { return; } writeConsoleLine("info", message); } }, child(name) { - return createSubsystemLogger(`${subsystem}/${name}`); + return createSubsystemLogger(`${resolvedSubsystem}/${name}`); }, }; return logger; diff --git a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts index 7f30425ebca..8a3b6cad8d1 100644 --- a/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts +++ b/src/plugins/contracts/plugin-sdk-package-contract-guardrails.test.ts @@ -104,6 +104,7 @@ function collectExtensionCoreImportLeaks(): Array<{ file: string; specifier: str if ( /(?:^|\/)(?:__tests__|tests|test-support)(?:\/|$)/.test(repoRelativePath) || /(?:^|\/)test-support\.[cm]?tsx?$/.test(repoRelativePath) || + /\.test-support\.[cm]?tsx?$/.test(repoRelativePath) || /\.test\.[cm]?tsx?$/.test(repoRelativePath) ) { continue;